xref: /inferno-os/appl/lib/dhcpclient.b (revision e45fa0eb0763b57d6fb0649c064bc3b95ccdea6c)
1implement Dhcpclient;
2
3#
4# DHCP and BOOTP clients
5# Copyright © 2004-2006 Vita Nuova Holdings Limited
6#
7
8include "sys.m";
9	sys: Sys;
10
11include "ip.m";
12	ip: IP;
13	IPv4off, IPaddrlen, Udphdrlen, Udpraddr, Udpladdr, Udprport, Udplport: import IP;
14	IPaddr: import ip;
15	get2, get4, put2, put4: import ip;
16
17include "keyring.m";
18include "security.m";	# for Random
19
20include "dial.m";
21	dial: Dial;
22
23include "dhcp.m";
24
25debug := 0;
26
27xidgen: int;
28
29init()
30{
31	sys = load Sys Sys->PATH;
32	random := load Random Random->PATH;
33	if(random != nil)
34		xidgen = random->randomint(Random->NotQuiteRandom);
35	else
36		xidgen = sys->pctl(0, nil)*sys->millisec();
37	random = nil;
38	dial = load Dial Dial->PATH;
39	ip = load IP IP->PATH;
40	ip->init();
41}
42
43tracing(d: int)
44{
45	debug = d;
46}
47
48Bootconf.new(): ref Bootconf
49{
50	bc := ref Bootconf;
51	bc.lease = 0;
52	bc.options = array[256] of array of byte;
53	return bc;
54}
55
56Bootconf.get(c: self ref Bootconf, n: int): array of byte
57{
58	a := c.options;
59	if(n & Ovendor){
60		a = c.vendor;
61		n &= ~Ovendor;
62	}
63	if(n < 0 || n >= len a)
64		return nil;
65	return a[n];
66}
67
68Bootconf.getint(c: self ref Bootconf, n: int): int
69{
70	a := c.get(n);
71	v := 0;
72	for(i := 0; i < len a; i++)
73		v = (v<<8) | int a[i];
74	return v;
75}
76
77Bootconf.getip(c: self ref Bootconf, n: int): string
78{
79	l := c.getips(n);
80	if(l == nil)
81		return nil;
82	return hd l;
83}
84
85Bootconf.getips(c: self ref Bootconf, n: int): list of string
86{
87	a := c.get(n);
88	rl: list of string;
89	while(len a >= 4){
90		rl = v4text(a) :: rl;
91		a = a[4:];
92	}
93	l: list of string;
94	for(; rl != nil; rl = tl rl)
95		l = hd rl :: l;
96	return l;
97}
98
99Bootconf.gets(c: self ref Bootconf, n: int): string
100{
101	a := c.get(n);
102	if(a == nil)
103		return nil;
104	for(i:=0; i<len a; i++)
105		if(a[i] == byte 0)
106			break;
107	return string a[0:i];
108}
109
110Bootconf.put(c: self ref Bootconf, n: int, a: array of byte)
111{
112	if(n < 0 || n >= len c.options)
113		return;
114	ca := array[len a] of byte;
115	ca[0:] = a;
116	c.options[n] = ca;
117}
118
119Bootconf.putint(c: self ref Bootconf, n: int, v: int)
120{
121	if(n < 0 || n >= len c.options)
122		return;
123	a := array[4] of byte;
124	put4(a, 0, v);
125	c.options[n] = a;
126}
127
128Bootconf.putips(c: self ref Bootconf, n: int, ips: list of string)
129{
130	if(n < 0 || n >= len c.options)
131		return;
132	na := len ips;
133	a := array[na*4] of byte;
134	na = 0;
135	for(; ips != nil; ips = tl ips){
136		(nil, ipa) := IPaddr.parse(hd ips);
137		a[na++:] = ipa.v4();
138	}
139	c.options[n] = a;
140}
141
142Bootconf.puts(c: self ref Bootconf, n: int, s: string)
143{
144	if(n < 0 || n >= len c.options)
145		return;
146	c.options[n] = array of byte s;
147}
148
149#
150#
151# DHCP
152#
153#
154
155# BOOTP operations
156Bootprequest, Bootpreply: con 1+iota;
157
158# DHCP operations
159NotDHCP, Discover, Offer, Request, Decline, Ack, Nak, Release, Inform: con iota;
160
161Dhcp: adt {
162	udphdr:	array of byte;
163	op:		int;
164	htype:	int;
165	hops:	int;
166	xid:		int;
167	secs:		int;
168	flags:	int;
169	ciaddr:	IPaddr;
170	yiaddr:	IPaddr;
171	siaddr:	IPaddr;
172	giaddr:	IPaddr;
173	chaddr:	array of byte;
174	sname:	string;
175	file:		string;
176	options:	list of (int, array of byte);
177	dhcpop:	int;
178};
179
180opnames := array[] of {
181	Discover => "Discover",
182	Offer => "Offer",
183	Request => "Request",
184	Decline => "Decline",
185	Ack => "Ack",
186	Nak => "Nak",
187	Release => "Release",
188	Inform => "Inform"
189};
190
191opname(op: int): string
192{
193	if(op >= 0 && op < len opnames)
194		return opnames[op];
195	return sys->sprint("OP%d", op);
196}
197
198stringget(buf: array of byte): string
199{
200	for(x := 0; x < len buf; x++)
201		if(buf[x] == byte 0)
202			break;
203	if(x == 0)
204		return nil;
205	return string buf[0 : x];
206}
207
208eqbytes(b1: array of byte, b2: array of byte): int
209{
210	l := len b1;
211	if(l != len b2)
212		return 0;
213	for(i := 0; i < l; i++)
214		if(b1[i] != b2[i])
215			return 0;
216	return 1;
217}
218
219magic := array[] of {byte 99, byte 130, byte 83, byte 99};	# RFC2132 (replacing RFC1048)
220
221dhcpsend(fd: ref Sys->FD, xid: int, dhcp: ref Dhcp)
222{
223	dhcp.xid = xid;
224	abuf := array[576+Udphdrlen] of {* => byte 0};
225	abuf[0:] = dhcp.udphdr;
226	buf := abuf[Udphdrlen:];
227	buf[0] = byte dhcp.op;
228	buf[1] = byte dhcp.htype;
229	buf[2] = byte len dhcp.chaddr;
230	buf[3] = byte dhcp.hops;
231	put4(buf, 4, xid);
232	put2(buf, 8, dhcp.secs);
233	put2(buf, 10, dhcp.flags);
234	buf[12:] = dhcp.ciaddr.v4();
235	buf[16:] = dhcp.yiaddr.v4();
236	buf[20:] = dhcp.siaddr.v4();
237	buf[24:] = dhcp.giaddr.v4();
238	buf[28:] = dhcp.chaddr;
239	buf[44:] = array of byte dhcp.sname;	# [64]
240	buf[108:] = array of byte dhcp.file;	# [128]
241	o := 236;
242	# RFC1542 suggests including magic and Oend as a minimum, even in BOOTP
243	buf[o:] = magic;
244	o += 4;
245	if(dhcp.dhcpop != NotDHCP){
246		buf[o++] = byte Otype;
247		buf[o++] = byte 1;
248		buf[o++] = byte dhcp.dhcpop;
249	}
250	for(ol := dhcp.options; ol != nil; ol = tl ol){
251		(opt, val) := hd ol;
252		buf[o++] = byte opt;
253		buf[o++] = byte len val;
254		if(len val > 0){
255			buf[o:] = val;
256			o += len val;
257		}
258	}
259	buf[o++] = byte Oend;
260	if(debug)
261		dumpdhcp(dhcp, "->");
262	sys->write(fd, abuf, len abuf);
263}
264
265kill(pid: int, grp: string)
266{
267	fd := sys->open("#p/" + string pid + "/ctl", sys->OWRITE);
268	if(fd != nil)
269		sys->fprint(fd, "kill%s", grp);
270}
271
272v4text(a: array of byte): string
273{
274	return sys->sprint("%ud.%ud.%ud.%ud", int a[0], int a[1], int a[2], int a[3]);
275}
276
277parseopt(a: array of byte, isdhcp: int): (int, list of (int, array of byte))
278{
279	opts: list of (int, array of byte);
280	xop := NotDHCP;
281	for(i := 0; i < len a;){
282		op := int a[i++];
283		if(op == Opad)
284			continue;
285		if(op == Oend || i >= len a)
286			break;
287		l := int a[i++];
288		if(i+l > len a)
289			break;
290		if(isdhcp && op == Otype)
291			xop = int a[i];
292		else
293			opts = (op, a[i:i+l]) :: opts;
294		i += l;
295	}
296	rl := opts;
297	opts = nil;
298	for(; rl != nil; rl = tl rl)
299		opts = hd rl :: opts;
300	return (xop, opts);
301}
302
303dhcpreader(pidc: chan of int, srv: ref DhcpIO)
304{
305	pidc <-= sys->pctl(0, nil);
306	for(;;){
307		abuf := array [576+Udphdrlen] of byte;
308		n := sys->read(srv.fd, abuf, len abuf);
309		if(n < 0){
310			if(debug)
311				sys->print("read error: %r\n");
312			sys->sleep(1000);
313			continue;
314		}
315		if(n < Udphdrlen+236){
316			if(debug)
317				sys->print("short read: %d\n", n);
318			continue;
319		}
320		buf := abuf[Udphdrlen:n];
321		n -= Udphdrlen;
322		dhcp := ref Dhcp;
323		dhcp.op = int buf[0];
324		if(dhcp.op != Bootpreply){
325			if(debug)
326				sys->print("bootp: not reply, discarded\n");
327			continue;
328		}
329		dhcp.dhcpop = NotDHCP;
330		if(n >= 240 && eqbytes(buf[236:240], magic))	# otherwise it's something we won't understand
331			(dhcp.dhcpop, dhcp.options) = parseopt(buf[240:n], 1);
332		case dhcp.dhcpop {
333		NotDHCP or Ack or Nak or Offer =>
334			;
335		* =>
336			if(debug)
337				sys->print("dhcp: ignore dhcp op %d\n", dhcp.dhcpop);
338			continue;
339		}
340		dhcp.udphdr = abuf[0:Udphdrlen];
341		dhcp.htype = int buf[1];
342		hlen := int buf[2];
343		dhcp.hops = int buf[3];
344		dhcp.xid = get4(buf, 4);
345		dhcp.secs = get2(buf, 8);
346		dhcp.flags = get2(buf, 10);
347		dhcp.ciaddr = IPaddr.newv4(buf[12:]);
348		dhcp.yiaddr = IPaddr.newv4(buf[16:]);
349		dhcp.siaddr = IPaddr.newv4(buf[20:]);
350		dhcp.giaddr = IPaddr.newv4(buf[24:]);
351		dhcp.chaddr = buf[28 : 28 + hlen];
352		dhcp.sname = stringget(buf[44 : 108]);
353		dhcp.file = stringget(buf[108 : 236]);
354		srv.dc <-= dhcp;
355	}
356}
357
358timeoutstart(msecs: int): (int, chan of int)
359{
360	tc := chan of int;
361	spawn timeoutproc(tc, msecs);
362	return (<-tc, tc);
363}
364
365timeoutproc(c: chan of int, msecs: int)
366{
367	c <-= sys->pctl(0, nil);
368	sys->sleep(msecs);
369	c <-= 1;
370}
371
372hex(b: int): int
373{
374	if(b >= '0' && b <= '9')
375		return b-'0';
376	if(b >= 'A' && b <= 'F')
377		return b-'A' + 10;
378	if(b >= 'a' && b <= 'f')
379		return b-'a' + 10;
380	return -1;
381}
382
383gethaddr(device: string): (int, string, array of byte)
384{
385	fd := sys->open(device, Sys->OREAD);
386	if(fd == nil)
387		return (-1, sys->sprint("%r"), nil);
388	buf := array [100] of byte;
389	n := sys->read(fd, buf, len buf);
390	if(n < 0)
391		return (-1, sys->sprint("%r"), nil);
392	if(n == 0)
393		return (-1, "empty address file", nil);
394	addr := array [n/2] of byte;
395	for(i := 0; i < len addr; i++){
396		u := hex(int buf[2*i]);
397		l := hex(int buf[2*i+1]);
398		if(u < 0 || l < 0)
399			return (-1, "bad address syntax", nil);
400		addr[i] = byte ((u<<4)|l);
401	}
402	return (1, nil, addr);
403}
404
405newrequest(dest: IPaddr, bootfile: string, htype: int, haddr: array of byte, ipaddr: IPaddr, options: array of array of byte): ref Dhcp
406{
407	dhcp := ref Dhcp;
408	dhcp.op = Bootprequest;
409	hdr := array[Udphdrlen] of {* => byte 0};
410	hdr[Udpraddr:] = dest.v6();
411	put2(hdr, Udprport, 67);
412	dhcp.udphdr = hdr;
413	dhcp.htype = htype;
414	dhcp.chaddr = haddr;
415	dhcp.hops = 0;
416	dhcp.secs = 0;
417	dhcp.flags = 0;
418	dhcp.xid = 0;
419	dhcp.ciaddr = ipaddr;
420	dhcp.yiaddr = ip->v4noaddr;
421	dhcp.siaddr = ip->v4noaddr;
422	dhcp.giaddr = ip->v4noaddr;
423	dhcp.file = bootfile;
424	dhcp.dhcpop = NotDHCP;
425	if(options != nil){
426		for(i := 0; i < len options; i++)
427			if(options[i] != nil)
428				dhcp.options = (i, options[i]) :: dhcp.options;
429	}
430	clientid := array[len haddr + 1] of byte;
431	clientid[0] = byte htype;
432	clientid[1:] = haddr;
433	dhcp.options = (Oclientid, clientid) :: dhcp.options;
434	dhcp.options = (Ovendorclass, array of byte "plan9_386") :: dhcp.options;	# 386 will do because type doesn't matter
435	return dhcp;
436}
437
438udpannounce(net: string): (ref Sys->FD, string)
439{
440	if(net == nil)
441		net = "/net";
442	conn := dial->announce(net+"/udp!*!68");
443	if(conn == nil)
444		return (nil, sys->sprint("can't announce dhcp port: %r"));
445	if(sys->fprint(conn.cfd, "headers") < 0)
446		return (nil, sys->sprint("can't set headers mode on dhcp port: %r"));
447	conn.dfd = sys->open(conn.dir+"/data", Sys->ORDWR);
448	if(conn.dfd == nil)
449		return (nil, sys->sprint("can't open %s: %r", conn.dir+"/data"));
450	return (conn.dfd, nil);
451}
452
453ifcnoaddr(fd: ref Sys->FD, s: string)
454{
455	if(fd != nil && sys->fprint(fd, "%s %s %s", s, (ip->noaddr).text(), (ip->noaddr).text()) < 0){
456		if(debug)
457			sys->print("dhcp: ctl %s: %r\n", s);
458	}
459}
460
461setup(net: string, device: string, init: ref Bootconf): (ref Dhcp, ref DhcpIO, string)
462{
463	(htype, err, mac) := gethaddr(device);
464	if(htype < 0)
465		return (nil, nil, sys->sprint("can't get hardware MAC address: %s", err));
466	ciaddr := ip->v4noaddr;
467	if(init != nil && init.ip != nil){
468		valid: int;
469		(valid, ciaddr) = IPaddr.parse(init.ip);
470		if(valid < 0)
471			return (nil, nil, sys->sprint("invalid ip address: %s", init.ip));
472	}
473	(dfd, err2) := udpannounce(net);
474	if(err2 != nil)
475		return (nil, nil, err);
476	bootfile: string;
477	options: array of array of byte;
478	if(init != nil){
479		bootfile = init.bootf;
480		options = init.options;
481	}
482	return (newrequest(ip->v4bcast, bootfile, htype, mac, ciaddr, options), DhcpIO.new(dfd), nil);
483}
484
485#
486# BOOTP (RFC951) is used by Inferno only during net boots, to get initial IP address and TFTP address and parameters
487#
488bootp(net: string, ctlifc: ref Sys->FD, device: string, init: ref Bootconf): (ref Bootconf, string)
489{
490	(req, srv, err) := setup(net, device, init);
491	if(err != nil)
492		return (nil, err);
493	ifcnoaddr(ctlifc, "add");
494	rdhcp := exchange(srv, ++xidgen, req, 1<<NotDHCP);
495	srv.rstop();
496	ifcnoaddr(ctlifc, "remove");
497	if(rdhcp == nil)
498		return (nil, "no response to BOOTP request");
499	return (fillbootconf(init, rdhcp), nil);
500}
501
502defparams := array[] of {
503	byte Omask, byte Orouter, byte Odnsserver, byte Ohostname, byte Odomainname, byte Ontpserver,
504};
505
506#
507# DHCP (RFC2131)
508#
509dhcp(net: string, ctlifc: ref Sys->FD, device: string, init: ref Bootconf, needparam: array of int): (ref Bootconf, ref Lease, string)
510{
511	(req, srv, err) := setup(net, device, init);
512	if(err != nil)
513		return (nil, nil, err);
514	params := defparams;
515	if(needparam != nil){
516		n := len defparams;
517		params = array[n+len needparam] of byte;
518		params[0:] = defparams;
519		for(i := 0; i < len needparam; i++)
520			params[n+i] = byte needparam[i];
521	}
522	initopt := (Oparams, params) :: req.options;	# RFC2131 requires parameters to be repeated each time
523	lease := ref Lease(0, chan[1] of (ref Bootconf, string));
524	spawn dhcp1(srv, lease, net, ctlifc, req, init, initopt);
525	bc: ref Bootconf;
526	(bc, err) = <-lease.configs;
527	return (bc, lease, err);
528}
529
530dhcp1(srv: ref DhcpIO, lease: ref Lease, net: string, ctlifc: ref Sys->FD, req: ref Dhcp, init: ref Bootconf, initopt: list of (int, array of byte))
531{
532	cfd := -1;
533	if(ctlifc != nil)
534		cfd = ctlifc.fd;
535	lease.pid = sys->pctl(Sys->NEWPGRP|Sys->NEWFD, 1 :: srv.fd.fd :: cfd :: nil);
536	if(ctlifc != nil)
537		ctlifc = sys->fildes(ctlifc.fd);
538	srv.fd = sys->fildes(srv.fd.fd);
539	rep: ref Dhcp;
540	ifcnoaddr(ctlifc, "add");
541	if(req.ciaddr.isvalid())
542		rep = reacquire(srv, req, initopt, req.ciaddr);
543	if(rep == nil)
544		rep = askround(srv, req, initopt);
545	srv.rstop();
546	ifcnoaddr(ctlifc, "remove");
547	if(rep == nil){
548		lease.pid = 0;
549		lease.configs <-= (nil, "no response");
550		exit;
551	}
552	for(;;){
553		conf := fillbootconf(init, rep);
554		applycfg(net, ctlifc, conf);
555		if(conf.lease == 0){
556			srv.rstop();
557			lease.pid = 0;
558			flush(lease.configs);
559			lease.configs <-= (conf, nil);
560			exit;
561		}
562		flush(lease.configs);
563		lease.configs <-= (conf, nil);
564		req.ciaddr = rep.yiaddr;
565		while((rep = tenancy(srv, req, conf.lease)) != nil){
566			if(rep.dhcpop == Nak || !rep.ciaddr.eq(req.ciaddr))
567				break;
568			req.udphdr[Udpraddr:] = rep.udphdr[Udpraddr:Udpraddr+IPaddrlen];
569			conf = fillbootconf(init, rep);
570		}
571		removecfg(net, ctlifc, conf);
572		ifcnoaddr(ctlifc, "add");
573		while((rep = askround(srv, req, initopt)) == nil){
574			flush(lease.configs);
575			lease.configs <-= (nil, "no response");
576			srv.rstop();
577			sys->sleep(60*1000);
578		}
579		ifcnoaddr(ctlifc, "remove");
580	}
581}
582
583reacquire(srv: ref DhcpIO, req: ref Dhcp, initopt: list of (int, array of byte), addr: IPaddr): ref Dhcp
584{
585	# INIT-REBOOT: know an address; try requesting it (once)
586	# TO DO: could use Inform when our address is static but we need a few service parameters
587	req.ciaddr = ip->v4noaddr;
588	rep := request(srv, ++xidgen, req, (Oipaddr, addr.v4()) :: initopt);
589	if(rep != nil && rep.dhcpop == Ack && addr.eq(rep.yiaddr)){
590		if(debug)
591			sys->print("req: server accepted\n");
592		req.udphdr[Udpraddr:] = rep.udphdr[Udpraddr:Udpraddr+IPaddrlen];
593		return rep;
594	}
595	if(debug)
596		sys->print("req: cannot reclaim\n");
597	return nil;
598}
599
600askround(srv: ref DhcpIO, req: ref Dhcp, initopt: list of (int, array of byte)): ref Dhcp
601{
602	# INIT
603	req.ciaddr = ip->v4noaddr;
604	req.udphdr[Udpraddr:] = (ip->v4bcast).v6();
605	for(retries := 0; retries < 5; retries++){
606		# SELECTING
607		req.dhcpop = Discover;
608		req.options = initopt;
609		rep := exchange(srv, ++xidgen, req, 1<<Offer);
610		if(rep == nil)
611			break;
612		#
613		# could wait a little while and accumulate offers, but is it sensible?
614		# we do sometimes see arguments between DHCP servers that could
615		# only be resolved by user choice
616		#
617		if(!rep.yiaddr.isvalid())
618			continue;		# server has no idea either
619		serverid := getopt(rep.options, Oserverid, 4);
620		if(serverid == nil)
621			continue;	# broken server
622		# REQUESTING
623		options := (Oserverid, serverid) :: (Oipaddr, rep.yiaddr.v4()) :: initopt;
624		lease := getlease(rep);
625		if(lease != nil)
626			options = (Olease, lease) :: options;
627		rep = request(srv, rep.xid, req, options);
628		if(rep != nil){
629			# could probe with ARP here, and if found, Decline
630			if(debug)
631				sys->print("req: server accepted\n");
632			req.udphdr[Udpraddr:] = rep.udphdr[Udpraddr:Udpraddr+IPaddrlen];
633			return rep;
634		}
635	}
636	return nil;
637}
638
639request(srv: ref DhcpIO, xid: int, req: ref Dhcp, options: list of (int, array of byte)): ref Dhcp
640{
641	req.dhcpop = Request;	# Selecting
642	req.options = options;
643	rep := exchange(srv, xid, req, (1<<Ack)|(1<<Nak));
644	if(rep == nil || rep.dhcpop == Nak)
645		return nil;
646	return rep;
647}
648
649# renew
650#	direct to server from T1 to T2 [RENEW]
651#	Request must not include
652#		requested IP address, server identifier
653#	Request must include
654#		ciaddr set to client's address
655#	Request might include
656#		lease time
657#	similar, but broadcast, from T2 to T3 [REBIND]
658#	at T3, unbind, restart Discover
659
660tenancy(srv: ref DhcpIO, req: ref Dhcp, leasesec: int): ref Dhcp
661{
662	# configure address...
663	t3 := big leasesec * big 1000;	# lease expires; restart
664	t2 := (big 3 * t3)/big 4;	# broadcast renewal request at ¾time
665	t1 := t2/big 2;		# renew lease with original server at ½time
666	srv.rstop();
667	thebigsleep(t1);
668	# RENEW
669	rep := renewing(srv, req, t1, t2);
670	if(rep != nil)
671		return rep;
672	# REBIND
673	req.udphdr[Udpraddr:] = (ip->v4bcast).v6();	# now try broadcast
674	return renewing(srv, req, t2, t3);
675}
676
677renewing(srv: ref DhcpIO, req: ref Dhcp, a: big, b: big): ref Dhcp
678{
679	Minute: con big(60*1000);
680	while(a < b){
681		rep := exchange(srv, req.xid, req, (1<<Ack)|(1<<Nak));
682		if(rep != nil)
683			return rep;
684		delta := (b-a)/big 2;
685		if(delta < Minute)
686			delta = Minute;
687		thebigsleep(delta);
688		a += delta;
689	}
690	return nil;
691}
692
693thebigsleep(msec: big)
694{
695	Day: con big (24*3600*1000);	# 1 day in msec
696	while(msec > big 0){
697		n := msec;
698		if(n > Day)
699			n = Day;
700		sys->sleep(int n);
701		msec -= n;
702	}
703}
704
705getlease(m: ref Dhcp): array of byte
706{
707	lease := getopt(m.options, Olease, 4);
708	if(lease == nil)
709		return nil;
710	if(get4(lease, 0) == 0){
711		lease = array[4] of byte;
712		put4(lease, 0, 15*60);
713	}
714	return lease;
715}
716
717fillbootconf(init: ref Bootconf, pkt: ref Dhcp): ref Bootconf
718{
719	bc := ref Bootconf;
720	if(init != nil)
721		*bc = *init;
722	if(bc.options == nil)
723		bc.options = array[256] of array of byte;
724	for(l := pkt.options; l != nil; l = tl l){
725		(c, v) := hd l;
726		if(bc.options[c] == nil)
727			bc.options[c] = v;	# give priority to first occurring
728	}
729	if((a := bc.get(Ovendorinfo)) != nil){
730		if(bc.vendor == nil)
731			bc.vendor = array[256] of array of byte;
732		for(l = parseopt(a, 0).t1; l  != nil; l = tl l){
733			(c, v) := hd l;
734			if(bc.vendor[c] == nil)
735				bc.vendor[c] = v;
736		}
737	}
738	if(pkt.yiaddr.isvalid()){
739		bc.ip = pkt.yiaddr.text();
740		bc.ipmask = bc.getip(Omask);
741		if(bc.ipmask == nil)
742			bc.ipmask = pkt.yiaddr.classmask().masktext();
743	}
744	bc.bootf = pkt.file;
745	bc.dhcpip = IPaddr.newv6(pkt.udphdr[Udpraddr:]).text();
746	bc.siaddr = pkt.siaddr.text();
747	bc.lease = bc.getint(Olease);
748	if(bc.lease == Infinite)
749		bc.lease = 0;
750	else if(debug > 1)
751		bc.lease = 2*60;	# shorten time, for testing
752	bc.dom = bc.gets(Odomainname);
753	s := bc.gets(Ohostname);
754	for(i:=0; i<len s; i++)
755		if(s[i] == '.'){
756			if(bc.dom == nil)
757				bc.dom = s[i+1:];
758			s = s[0:i];
759			break;
760		}
761	bc.sys = s;
762	bc.ipgw = bc.getip(Orouter);
763	bc.bootip = bc.getip(Otftpserver);
764	bc.serverid = bc.getip(Oserverid);
765	return bc;
766}
767
768Lease.release(l: self ref Lease)
769{
770	# could send a Release message
771	# should unconfigure
772	if(l.pid){
773		kill(l.pid, "grp");
774		l.pid = 0;
775	}
776}
777
778flush(c: chan of (ref Bootconf, string))
779{
780	alt{
781	<-c =>	;
782	* =>	;
783	}
784}
785
786DhcpIO: adt {
787	fd:	ref Sys->FD;
788	pid:	int;
789	dc:	chan of ref Dhcp;
790	new:	fn(fd: ref Sys->FD): ref DhcpIO;
791	rstart:	fn(io: self ref DhcpIO);
792	rstop:	fn(io: self ref DhcpIO);
793};
794
795DhcpIO.new(fd: ref Sys->FD): ref DhcpIO
796{
797	return ref DhcpIO(fd, 0, chan of ref Dhcp);
798}
799
800DhcpIO.rstart(io: self ref DhcpIO)
801{
802	if(io.pid == 0){
803		pids := chan of int;
804		spawn dhcpreader(pids, io);
805		io.pid = <-pids;
806	}
807}
808
809DhcpIO.rstop(io: self ref DhcpIO)
810{
811	if(io.pid != 0){
812		kill(io.pid, "");
813		io.pid = 0;
814	}
815}
816
817getopt(options: list of (int, array of byte), op: int, minlen: int): array of byte
818{
819	for(; options != nil; options = tl options){
820		(opt, val) := hd options;
821		if(opt == op && len val >= minlen)
822			return val;
823	}
824	return nil;
825}
826
827exchange(srv: ref DhcpIO, xid: int, req: ref Dhcp, accept: int): ref Dhcp
828{
829	srv.rstart();
830	nsec := 3;
831	for(count := 0; count < 5; count++) {
832		(tpid, tc) := timeoutstart(nsec*1000);
833		dhcpsend(srv.fd, xid, req);
834	   Wait:
835		for(;;){
836			alt {
837			<-tc=>
838				break Wait;
839			rep := <-srv.dc=>
840				if(debug)
841					dumpdhcp(rep, "<-");
842				if(rep.op == Bootpreply &&
843				    rep.xid == req.xid &&
844				    rep.ciaddr.eq(req.ciaddr) &&
845				    eqbytes(rep.chaddr, req.chaddr)){
846					if((accept & (1<<rep.dhcpop)) == 0){
847						if(debug)
848							sys->print("req: unexpected reply %s to %s\n", opname(rep.dhcpop), opname(req.dhcpop));
849						continue;
850					}
851					kill(tpid, "");
852					return rep;
853				}
854				if(debug)
855					sys->print("req: mismatch\n");
856			}
857		}
858		req.secs += nsec;
859		nsec++;
860	}
861	return nil;
862}
863
864applycfg(net: string, ctlfd: ref Sys->FD, bc: ref Bootconf): string
865{
866	# write addresses to /net/...
867	# local address, mask[or default], remote address [mtu]
868	if(net == nil)
869		net = "/net";
870	if(bc.ip == nil)
871		return  "invalid address";
872	if(ctlfd != nil){
873		if(sys->fprint(ctlfd, "add %s %s", bc.ip, bc.ipmask) < 0)	# TO DO: [raddr [mtu]]
874			return sys->sprint("add interface: %r");
875		# could use "mtu n" request to set/change mtu
876	}
877	# if primary:
878	# 	add default route if gateway valid
879	# 	put ndb entries ip=, ipmask=, ipgw=; sys= dom=; fs=; auth=; dns=; ntp=; other options from bc.options
880	if(bc.ipgw != nil){
881		fd := sys->open(net+"/iproute", Sys->OWRITE);
882		if(fd != nil)
883			sys->fprint(fd, "add 0 0 %s", bc.ipgw);
884	}
885	s := sys->sprint("ip=%s ipmask=%s", bc.ip, bc.ipmask);
886	if(bc.ipgw != nil)
887		s += sys->sprint(" ipgw=%s", bc.ipgw);
888	s += "\n";
889	if(bc.sys != nil)
890		s += sys->sprint("	sys=%s\n", bc.sys);
891	if(bc.dom != nil)
892		s += sys->sprint("	dom=%s.%s\n", bc.sys, bc.dom);
893	if((addr := bc.getip(OP9auth)) != nil)
894		s += sys->sprint("	auth=%s\n", addr);	# TO DO: several addresses
895	if((addr = bc.getip(OP9fs)) != nil)
896		s += sys->sprint("	fs=%s\n", addr);
897	if((addr = bc.getip(Odnsserver)) != nil)
898		s += sys->sprint("	dns=%s\n", addr);
899	fd := sys->open(net+"/ndb", Sys->OWRITE | Sys->OTRUNC);
900	if(fd != nil){
901		a := array of byte s;
902		sys->write(fd, a, len a);
903	}
904	return nil;
905}
906
907removecfg(nil: string, ctlfd: ref Sys->FD, bc: ref Bootconf): string
908{
909	# remove localaddr, localmask[or default]
910	if(ctlfd != nil){
911		if(sys->fprint(ctlfd, "remove %s %s", bc.ip, bc.ipmask) < 0)
912			return sys->sprint("remove address: %r");
913	}
914	bc.ip = nil;
915	bc.ipgw = nil;
916	bc.ipmask = nil;
917	# remote address?
918	# clear net+"/ndb"?
919	return nil;
920}
921
922#
923# the following is just for debugging
924#
925
926dumpdhcp(m: ref Dhcp, dir: string)
927{
928	s := "";
929	sys->print("%s %s/%ud: ", dir, IPaddr.newv6(m.udphdr[Udpraddr:]).text(), get2(m.udphdr, Udprport));
930	if(m.dhcpop != NotDHCP)
931		s = " "+opname(m.dhcpop);
932	sys->print("op %d%s htype %d hops %d xid %ud\n", m.op, s, m.htype, m.hops, m.xid);
933	sys->print("\tsecs %d flags 0x%.4ux\n", m.secs, m.flags);
934	sys->print("\tciaddr %s\n", m.ciaddr.text());
935	sys->print("\tyiaddr %s\n", m.yiaddr.text());
936	sys->print("\tsiaddr %s\n", m.siaddr.text());
937	sys->print("\tgiaddr %s\n", m.giaddr.text());
938	sys->print("\tchaddr ");
939	for(x := 0; x < len m.chaddr; x++)
940		sys->print("%2.2ux", int m.chaddr[x]);
941	sys->print("\n");
942	if(m.sname != nil)
943		sys->print("\tsname %s\n", m.sname);
944	if(m.file != nil)
945		sys->print("\tfile %s\n", m.file);
946	if(m.options != nil){
947		sys->print("\t");
948		printopts(m.options, opts);
949		sys->print("\n");
950	}
951}
952
953Optbytes, Optaddr, Optmask, Optint, Optstr, Optopts, Opthex: con iota;
954
955Opt: adt
956{
957	code:	int;
958	name:	string;
959	otype:	int;
960};
961
962opts: array of Opt = array[] of {
963	(Omask, "ipmask", Optmask),
964	(Orouter, "ipgw", Optaddr),
965	(Odnsserver, "dns", Optaddr),
966	(Ohostname, "hostname", Optstr),
967	(Odomainname, "domain", Optstr),
968	(Ontpserver, "ntp", Optaddr),
969	(Oipaddr, "requestedip", Optaddr),
970	(Olease, "lease", Optint),
971	(Oserverid, "serverid", Optaddr),
972	(Otype, "dhcpop", Optint),
973	(Ovendorclass, "vendorclass", Optstr),
974	(Ovendorinfo, "vendorinfo", Optopts),
975	(Onetbiosns, "wins", Optaddr),
976	(Opop3server, "pop3", Optaddr),
977	(Osmtpserver, "smtp", Optaddr),
978	(Owwwserver, "www", Optaddr),
979	(Oparams, "params", Optbytes),
980	(Otftpserver, "tftp", Optaddr),
981	(Oclientid, "clientid", Opthex),
982};
983
984p9opts: array of Opt = array[] of {
985	(OP9fs, "fs", Optaddr),
986	(OP9auth, "auth", Optaddr),
987};
988
989lookopt(optab: array of Opt, code: int): (int, string, int)
990{
991	for(i:=0; i<len optab; i++)
992		if(opts[i].code == code)
993			return opts[i];
994	return (-1, nil, 0);
995}
996
997printopts(options: list of (int, array of byte), opts: array of Opt)
998{
999	for(; options != nil; options = tl options){
1000		(code, val) := hd options;
1001		sys->print("(%d %d", code, len val);
1002		(nil, name, otype) := lookopt(opts, code);
1003		if(name == nil){
1004			for(v := 0; v < len val; v++)
1005				sys->print(" %d", int val[v]);
1006		}else{
1007			sys->print(" %s", name);
1008			case otype {
1009			Optbytes =>
1010				for(v := 0; v < len val; v++)
1011					sys->print(" %d", int val[v]);
1012			Opthex =>
1013				for(v := 0; v < len val; v++)
1014					sys->print(" %#.2ux", int val[v]);
1015			Optaddr or Optmask =>
1016				while(len val >= 4){
1017					sys->print(" %s", v4text(val));
1018					val = val[4:];
1019				}
1020			Optstr =>
1021				sys->print(" \"%s\"", string val);
1022			Optint =>
1023				n := 0;
1024				for(v := 0; v < len val; v++)
1025					n = (n<<8) | int val[v];
1026				sys->print(" %d", n);
1027			Optopts =>
1028				printopts(parseopt(val, 0).t1, p9opts);
1029			}
1030		}
1031		sys->print(")");
1032	}
1033}
1034