xref: /plan9/sys/src/cmd/ip/dhcpclient.c (revision f27a9a5a0b699d2f44893d9491ecc2336a1fbc19)
1 #include <u.h>
2 #include <libc.h>
3 #include <ip.h>
4 #include "dhcp.h"
5 
6 void	bootpdump(uchar *p, int n);
7 void	dhcpinit(void);
8 void	dhcprecv(void);
9 void	dhcpsend(int);
10 void	myfatal(char *fmt, ...);
11 int	openlisten(char*);
12 uchar	*optaddaddr(uchar*, int, uchar*);
13 uchar	*optaddbyte(uchar*, int, int);
14 uchar	*optadd(uchar*, int, void*, int);
15 uchar	*optaddulong(uchar*, int, ulong);
16 uchar	*optget(Bootp*, int, int);
17 int	optgetaddr(Bootp*, int, uchar*);
18 int	optgetbyte(Bootp*, int);
19 ulong	optgetulong(Bootp*, int);
20 Bootp	*parse(uchar*, int);
21 void	stdinthread(void*);
22 ulong	thread(void(*f)(void*), void *a);
23 void	timerthread(void*);
24 void	usage(void);
25 
26 struct {
27 	QLock	lk;
28 	int	state;
29 	int	fd;
30 	ulong	xid;
31 	ulong	starttime;
32 	char	cid[100];
33 	char	sname[64];
34 	uchar	server[IPaddrlen];		/* server IP address */
35 	uchar	client[IPaddrlen];		/* client IP address */
36 	uchar	mask[IPaddrlen];		/* client mask */
37 	ulong	lease;		/* lease time */
38 	ulong	resend;		/* number of resends for current state */
39 	ulong	timeout;	/* time to timeout - seconds */
40 } dhcp;
41 
42 char	net[64];
43 
44 char optmagic[4] = { 0x63, 0x82, 0x53, 0x63 };
45 
46 void
main(int argc,char * argv[])47 main(int argc, char *argv[])
48 {
49 	char *p;
50 
51 	setnetmtpt(net, sizeof(net), nil);
52 
53 	ARGBEGIN{
54 	case 'x':
55 		p = ARGF();
56 		if(p == nil)
57 			usage();
58 		setnetmtpt(net, sizeof(net), p);
59 	}ARGEND;
60 
61 	fmtinstall('E', eipfmt);
62 	fmtinstall('I', eipfmt);
63 	fmtinstall('V', eipfmt);
64 
65 	dhcpinit();
66 
67 	rfork(RFNOTEG|RFREND);
68 
69 	thread(timerthread, 0);
70 	thread(stdinthread, 0);
71 
72 	qlock(&dhcp.lk);
73 	dhcp.starttime = time(0);
74 	dhcp.fd = openlisten(net);
75 	dhcpsend(Discover);
76 	dhcp.state = Sselecting;
77 	dhcp.resend = 0;
78 	dhcp.timeout = 4;
79 
80 	while(dhcp.state != Sbound)
81 		dhcprecv();
82 
83 	/* allows other clients on this machine */
84 	close(dhcp.fd);
85 	dhcp.fd = -1;
86 
87 	print("ip=%I\n", dhcp.client);
88 	print("mask=%I\n", dhcp.mask);
89 	print("end\n");
90 
91 	/* keep lease alive */
92 	for(;;) {
93 //fprint(2, "got lease for %d\n", dhcp.lease);
94 		qunlock(&dhcp.lk);
95 		sleep(dhcp.lease*500);	/* wait half of lease time */
96 		qlock(&dhcp.lk);
97 
98 //fprint(2, "try renue\n", dhcp.lease);
99 		dhcp.starttime = time(0);
100 		dhcp.fd = openlisten(net);
101 		dhcp.xid = time(0)*getpid();
102 		dhcpsend(Request);
103 		dhcp.state = Srenewing;
104 		dhcp.resend = 0;
105 		dhcp.timeout = 1;
106 
107 		while(dhcp.state != Sbound)
108 			dhcprecv();
109 
110 		/* allows other clients on this machine */
111 		close(dhcp.fd);
112 		dhcp.fd = -1;
113 	}
114 }
115 
116 void
usage(void)117 usage(void)
118 {
119 	fprint(2, "usage: %s [-x netextension]\n", argv0);
120 	exits("usage");
121 }
122 
123 void
timerthread(void *)124 timerthread(void*)
125 {
126 	for(;;) {
127 		sleep(1000);
128 		qlock(&dhcp.lk);
129 		if(--dhcp.timeout > 0) {
130 			qunlock(&dhcp.lk);
131 			continue;
132 		}
133 
134 		switch(dhcp.state) {
135 		default:
136 			myfatal("timerthread: unknown state %d", dhcp.state);
137 		case Sinit:
138 			break;
139 		case Sselecting:
140 			dhcpsend(Discover);
141 			dhcp.timeout = 4;
142 			dhcp.resend++;
143 			if(dhcp.resend>5)
144 				myfatal("dhcp: giving up: selecting");
145 			break;
146 		case Srequesting:
147 			dhcpsend(Request);
148 			dhcp.timeout = 4;
149 			dhcp.resend++;
150 			if(dhcp.resend>5)
151 				myfatal("dhcp: giving up: requesting");
152 			break;
153 		case Srenewing:
154 			dhcpsend(Request);
155 			dhcp.timeout = 1;
156 			dhcp.resend++;
157 			if(dhcp.resend>3) {
158 				dhcp.state = Srebinding;
159 				dhcp.resend = 0;
160 			}
161 			break;
162 		case Srebinding:
163 			dhcpsend(Request);
164 			dhcp.timeout = 4;
165 			dhcp.resend++;
166 			if(dhcp.resend>5)
167 				myfatal("dhcp: giving up: rebinding");
168 			break;
169 		case Sbound:
170 			break;
171 		}
172 		qunlock(&dhcp.lk);
173 	}
174 }
175 
176 void
stdinthread(void *)177 stdinthread(void*)
178 {
179 	uchar buf[100];
180 	int n;
181 
182 	for(;;) {
183 		n = read(0, buf, sizeof(buf));
184 		if(n <= 0)
185 			break;
186 	}
187 	/* shutdown cleanly */
188 	qlock(&dhcp.lk);
189 	if(dhcp.client) {
190 		if(dhcp.fd < 0)
191 			dhcp.fd = openlisten(net);
192 		dhcpsend(Release);
193 	}
194 	qunlock(&dhcp.lk);
195 
196 	postnote(PNGROUP, getpid(), "die");
197 	exits(0);
198 }
199 
200 void
dhcpinit(void)201 dhcpinit(void)
202 {
203 	int fd;
204 
205 	dhcp.state = Sinit;
206 	dhcp.timeout = 4;
207 
208 	fd = open("/dev/random", 0);
209 	if(fd >= 0) {
210 		read(fd, &dhcp.xid, sizeof(dhcp.xid));
211 		close(fd);
212 	} else
213 		dhcp.xid = time(0)*getpid();
214 	srand(dhcp.xid);
215 
216 	sprint(dhcp.cid, "%s.%d", getenv("sysname"), getpid());
217 }
218 
219 void
dhcpsend(int type)220 dhcpsend(int type)
221 {
222 	int n;
223 	uchar *p;
224 	Bootp bp;
225 	Udphdr *up;
226 
227 	memset(&bp, 0, sizeof bp);
228 	up = (Udphdr*)bp.udphdr;
229 
230 	hnputs(up->rport, 67);
231 	bp.op = Bootrequest;
232 	hnputl(bp.xid, dhcp.xid);
233 	hnputs(bp.secs, time(0) - dhcp.starttime);
234 	hnputs(bp.flags, Fbroadcast);		/* reply must be broadcast */
235 	memmove(bp.optmagic, optmagic, 4);
236 	p = bp.optdata;
237 	p = optaddbyte(p, ODtype, type);
238 	p = optadd(p, ODclientid, dhcp.cid, strlen(dhcp.cid));
239 	switch(type) {
240 	default:
241 		myfatal("dhcpsend: unknown message type: %d", type);
242 	case Discover:
243 		ipmove(up->raddr, IPv4bcast);	/* broadcast */
244 		break;
245 	case Request:
246 		if(dhcp.state == Sbound || dhcp.state == Srenewing)
247 			ipmove(up->raddr, dhcp.server);
248 		else
249 			ipmove(up->raddr, IPv4bcast);	/* broadcast */
250 		p = optaddulong(p, ODlease, dhcp.lease);
251 		if(dhcp.state == Sselecting || dhcp.state == Srequesting) {
252 			p = optaddaddr(p, ODipaddr, dhcp.client);	/* mistake?? */
253 			p = optaddaddr(p, ODserverid, dhcp.server);
254 		} else
255 			v6tov4(bp.ciaddr, dhcp.client);
256 		break;
257 	case Release:
258 		ipmove(up->raddr, dhcp.server);
259 		v6tov4(bp.ciaddr, dhcp.client);
260 		p = optaddaddr(p, ODipaddr, dhcp.client);
261 		p = optaddaddr(p, ODserverid, dhcp.server);
262 		break;
263 	}
264 
265 	*p++ = OBend;
266 
267 	n = p - (uchar*)&bp;
268 
269 	if(write(dhcp.fd, &bp, n) != n)
270 		myfatal("dhcpsend: write failed: %r");
271 }
272 
273 void
dhcprecv(void)274 dhcprecv(void)
275 {
276 	uchar buf[2000];
277 	Bootp *bp;
278 	int n, type;
279 	ulong lease;
280 	uchar mask[IPaddrlen];
281 
282 	qunlock(&dhcp.lk);
283 	n = read(dhcp.fd, buf, sizeof(buf));
284 	qlock(&dhcp.lk);
285 
286 	if(n <= 0)
287 		myfatal("dhcprecv: bad read: %r");
288 
289 	bp = parse(buf, n);
290 	if(bp == 0)
291 		return;
292 
293 if(1) {
294 fprint(2, "recved\n");
295 bootpdump(buf, n);
296 }
297 
298 	type = optgetbyte(bp, ODtype);
299 	switch(type) {
300 	default:
301 		fprint(2, "dhcprecv: unknown type: %d\n", type);
302 		break;
303 	case Offer:
304 		if(dhcp.state != Sselecting)
305 			break;
306 		lease = optgetulong(bp, ODlease);
307 		if(lease == 0)
308 			myfatal("bad lease");
309 		if(!optgetaddr(bp, OBmask, mask))
310 			memset(mask, 0xff, sizeof(mask));
311 		v4tov6(dhcp.client, bp->yiaddr);
312 		if(!optgetaddr(bp, ODserverid, dhcp.server)) {
313 			fprint(2, "dhcprecv: Offer from server with invalid serverid\n");
314 			break;
315 		}
316 
317 		dhcp.lease = lease;
318 		ipmove(dhcp.mask, mask);
319 		memmove(dhcp.sname, bp->sname, sizeof(dhcp.sname));
320 		dhcp.sname[sizeof(dhcp.sname)-1] = 0;
321 
322 		dhcpsend(Request);
323 		dhcp.state = Srequesting;
324 		dhcp.resend = 0;
325 		dhcp.timeout = 4;
326 		break;
327 	case Ack:
328 		if(dhcp.state != Srequesting)
329 		if(dhcp.state != Srenewing)
330 		if(dhcp.state != Srebinding)
331 			break;
332 		lease = optgetulong(bp, ODlease);
333 		if(lease == 0)
334 			myfatal("bad lease");
335 		if(!optgetaddr(bp, OBmask, mask))
336 			memset(mask, 0xff, sizeof(mask));
337 		v4tov6(dhcp.client, bp->yiaddr);
338 		dhcp.lease = lease;
339 		ipmove(dhcp.mask, mask);
340 		dhcp.state = Sbound;
341 		break;
342 	case Nak:
343 		myfatal("recved nak");
344 		break;
345 	}
346 
347 }
348 
349 int
openlisten(char * net)350 openlisten(char *net)
351 {
352 	int n, fd, cfd;
353 	char data[128], devdir[40];
354 
355 //	sprint(data, "%s/udp!*!bootpc", net);
356 	sprint(data, "%s/udp!*!68", net);
357 	for(n = 0; ; n++) {
358 		cfd = announce(data, devdir);
359 		if(cfd >= 0)
360 			break;
361 		/* might be another client - wait and try again */
362 		fprint(2, "dhcpclient: can't announce %s: %r", data);
363 		sleep(1000);
364 		if(n > 10)
365 			myfatal("can't announce: giving up: %r");
366 	}
367 
368 	if(fprint(cfd, "headers") < 0)
369 		myfatal("can't set header mode: %r");
370 
371 	sprint(data, "%s/data", devdir);
372 	fd = open(data, ORDWR);
373 	if(fd < 0)
374 		myfatal("open %s: %r", data);
375 	close(cfd);
376 	return fd;
377 }
378 
379 uchar*
optadd(uchar * p,int op,void * d,int n)380 optadd(uchar *p, int op, void *d, int n)
381 {
382 	p[0] = op;
383 	p[1] = n;
384 	memmove(p+2, d, n);
385 	return p+n+2;
386 }
387 
388 uchar*
optaddbyte(uchar * p,int op,int b)389 optaddbyte(uchar *p, int op, int b)
390 {
391 	p[0] = op;
392 	p[1] = 1;
393 	p[2] = b;
394 	return p+3;
395 }
396 
397 uchar*
optaddulong(uchar * p,int op,ulong x)398 optaddulong(uchar *p, int op, ulong x)
399 {
400 	p[0] = op;
401 	p[1] = 4;
402 	hnputl(p+2, x);
403 	return p+6;
404 }
405 
406 uchar *
optaddaddr(uchar * p,int op,uchar * ip)407 optaddaddr(uchar *p, int op, uchar *ip)
408 {
409 	p[0] = op;
410 	p[1] = 4;
411 	v6tov4(p+2, ip);
412 	return p+6;
413 }
414 
415 uchar*
optget(Bootp * bp,int op,int n)416 optget(Bootp *bp, int op, int n)
417 {
418 	int len, code;
419 	uchar *p;
420 
421 	p = bp->optdata;
422 	for(;;) {
423 		code = *p++;
424 		if(code == OBpad)
425 			continue;
426 		if(code == OBend)
427 			return 0;
428 		len = *p++;
429 		if(code != op) {
430 			p += len;
431 			continue;
432 		}
433 		if(n && n != len)
434 			return 0;
435 		return p;
436 	}
437 }
438 
439 
440 int
optgetbyte(Bootp * bp,int op)441 optgetbyte(Bootp *bp, int op)
442 {
443 	uchar *p;
444 
445 	p = optget(bp, op, 1);
446 	if(p == 0)
447 		return 0;
448 	return *p;
449 }
450 
451 ulong
optgetulong(Bootp * bp,int op)452 optgetulong(Bootp *bp, int op)
453 {
454 	uchar *p;
455 
456 	p = optget(bp, op, 4);
457 	if(p == 0)
458 		return 0;
459 	return nhgetl(p);
460 }
461 
462 int
optgetaddr(Bootp * bp,int op,uchar * ip)463 optgetaddr(Bootp *bp, int op, uchar *ip)
464 {
465 	uchar *p;
466 
467 	p = optget(bp, op, 4);
468 	if(p == 0)
469 		return 0;
470 	v4tov6(ip, p);
471 	return 1;
472 }
473 
474 /* make sure packet looks ok */
475 Bootp *
parse(uchar * p,int n)476 parse(uchar *p, int n)
477 {
478 	int len, code;
479 	Bootp *bp;
480 
481 	bp = (Bootp*)p;
482 	if(n < bp->optmagic - p) {
483 		fprint(2, "dhcpclient: parse: short bootp packet");
484 		return 0;
485 	}
486 
487 	if(dhcp.xid != nhgetl(bp->xid)) {
488 		fprint(2, "dhcpclient: parse: bad xid: got %ux expected %lux\n",
489 			nhgetl(bp->xid), dhcp.xid);
490 		return 0;
491 	}
492 
493 	if(bp->op != Bootreply) {
494 		fprint(2, "dhcpclient: parse: bad op\n");
495 		return 0;
496 	}
497 
498 	n -= bp->optmagic - p;
499 	p = bp->optmagic;
500 
501 	if(n < 4) {
502 		fprint(2, "dhcpclient: parse: not option data");
503 		return 0;
504 	}
505 	if(memcmp(optmagic, p, 4) != 0) {
506 		fprint(2, "dhcpclient: parse: bad opt magic %ux %ux %ux %ux\n",
507 			p[0], p[1], p[2], p[3]);
508 		return 0;
509 	}
510 	p += 4;
511 	n -= 4;
512 	while(n>0) {
513 		code = *p++;
514 		n--;
515 		if(code == OBpad)
516 			continue;
517 		if(code == OBend)
518 			return bp;
519 		if(n == 0) {
520 			fprint(2, "dhcpclient: parse: bad option: %d", code);
521 			return 0;
522 		}
523 		len = *p++;
524 		n--;
525 		if(len > n) {
526 			fprint(2, "dhcpclient: parse: bad option: %d", code);
527 			return 0;
528 		}
529 		p += len;
530 		n -= len;
531 	}
532 
533 	/* fix up nonstandard packets */
534 	/* assume there is space */
535 	*p = OBend;
536 
537 	return bp;
538 }
539 
540 void
bootpdump(uchar * p,int n)541 bootpdump(uchar *p, int n)
542 {
543 	int len, i, code;
544 	Bootp *bp;
545 	Udphdr *up;
546 
547 	bp = (Bootp*)p;
548 	up = (Udphdr*)bp->udphdr;
549 
550 	if(n < bp->optmagic - p) {
551 		fprint(2, "dhcpclient: short bootp packet");
552 		return;
553 	}
554 
555 	fprint(2, "laddr=%I lport=%d raddr=%I rport=%d\n", up->laddr,
556 		nhgets(up->lport), up->raddr, nhgets(up->rport));
557 	fprint(2, "op=%d htype=%d hlen=%d hops=%d\n", bp->op, bp->htype,
558 		bp->hlen, bp->hops);
559 	fprint(2, "xid=%ux secs=%d flags=%ux\n", nhgetl(bp->xid),
560 		nhgets(bp->secs), nhgets(bp->flags));
561 	fprint(2, "ciaddr=%V yiaddr=%V siaddr=%V giaddr=%V\n",
562 		bp->ciaddr, bp->yiaddr, bp->siaddr, bp->giaddr);
563 	fprint(2, "chaddr=");
564 	for(i=0; i<16; i++)
565 		fprint(2, "%ux ", bp->chaddr[i]);
566 	fprint(2, "\n");
567 	fprint(2, "sname=%s\n", bp->sname);
568 	fprint(2, "file = %s\n", bp->file);
569 
570 	n -= bp->optmagic - p;
571 	p = bp->optmagic;
572 
573 	if(n < 4)
574 		return;
575 	if(memcmp(optmagic, p, 4) != 0)
576 		fprint(2, "dhcpclient: bad opt magic %ux %ux %ux %ux\n",
577 			p[0], p[1], p[2], p[3]);
578 	p += 4;
579 	n -= 4;
580 
581 	while(n>0) {
582 		code = *p++;
583 		n--;
584 		if(code == OBpad)
585 			continue;
586 		if(code == OBend)
587 			break;
588 		if(n == 0) {
589 			fprint(2, " bad option: %d", code);
590 			return;
591 		}
592 		len = *p++;
593 		n--;
594 		if(len > n) {
595 			fprint(2, " bad option: %d", code);
596 			return;
597 		}
598 		switch(code) {
599 		default:
600 			fprint(2, "unknown option %d\n", code);
601 			for(i = 0; i<len; i++)
602 				fprint(2, "%ux ", p[i]);
603 		case ODtype:
604 			fprint(2, "DHCP type %d\n", p[0]);
605 			break;
606 		case ODclientid:
607 			fprint(2, "client id=");
608 			for(i = 0; i<len; i++)
609 				fprint(2, "%ux ", p[i]);
610 			fprint(2, "\n");
611 			break;
612 		case ODlease:
613 			fprint(2, "lease=%d\n", nhgetl(p));
614 			break;
615 		case ODserverid:
616 			fprint(2, "server id=%V\n", p);
617 			break;
618 		case OBmask:
619 			fprint(2, "mask=%V\n", p);
620 			break;
621 		case OBrouter:
622 			fprint(2, "router=%V\n", p);
623 			break;
624 		}
625 		p += len;
626 		n -= len;
627 	}
628 }
629 
630 ulong
thread(void (* f)(void *),void * a)631 thread(void(*f)(void*), void *a)
632 {
633 	int pid;
634 
635 	pid = rfork(RFNOWAIT|RFMEM|RFPROC);
636 	if(pid < 0)
637 		myfatal("rfork failed: %r");
638 	if(pid != 0)
639 		return pid;
640 	(*f)(a);
641 	return 0;	/* never reaches here */
642 }
643 
644 void
myfatal(char * fmt,...)645 myfatal(char *fmt, ...)
646 {
647 	char buf[1024];
648 	va_list arg;
649 
650 	va_start(arg, fmt);
651 	vseprint(buf, buf+sizeof(buf), fmt, arg);
652 	va_end(arg);
653 	fprint(2, "%s: %s\n", argv0, buf);
654 	postnote(PNGROUP, getpid(), "die");
655 	exits(buf);
656 }
657