xref: /plan9/sys/src/cmd/ip/dhcpclient.c (revision 9a747e4fd48b9f4522c70c07e8f882a15030f964)
1 #include <u.h>
2 #include <libc.h>
3 #include <ip.h>
4 #include "dhcp.h"
5 
6 void	usage(void);
7 void	bootpdump(uchar *p, int n);
8 int	openlisten(char*);
9 void	dhcpinit(void);
10 uchar	*optadd(uchar*, int, void*, int);
11 uchar	*optaddbyte(uchar*, int, int);
12 uchar	*optaddulong(uchar*, int, ulong);
13 uchar	*optaddaddr(uchar*, int, uchar*);
14 uchar	*optget(Bootp*, int, int);
15 int	optgetbyte(Bootp*, int);
16 ulong	optgetulong(Bootp*, int);
17 int	optgetaddr(Bootp*, int, uchar*);
18 void	dhcpsend(int);
19 void	dhcprecv(void);
20 void	timerthread(void*);
21 void	stdinthread(void*);
22 Bootp	*parse(uchar*, int);
23 ulong	thread(void(*f)(void*), void *a);
24 void	myfatal(char *fmt, ...);
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
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
117 usage(void)
118 {
119 	fprint(2, "usage: %s [-x netextension]\n", argv0);
120 	exits("usage");
121 }
122 
123 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
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
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
220 dhcpsend(int type)
221 {
222 	Bootp bp;
223 	Udphdr *up;
224 	uchar *p;
225 	int n;
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
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(0) {
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
350 openlisten(char *net)
351 {
352 	int fd, cfd;
353 	char data[128];
354 	char devdir[40];
355 	int n;
356 
357 //	sprint(data, "%s/udp!*!bootpc", net);
358 	sprint(data, "%s/udp!*!68", net);
359 	for(n=0;;n++) {
360 		cfd = announce(data, devdir);
361 		if(cfd >= 0)
362 			break;
363 		/* might be another client - wait and try again */
364 		fprint(2, "dhcpclient: can't announce: %r");
365 		sleep(1000);
366 		if(n>10)
367 			myfatal("can't announce: giving up: %r");
368 	}
369 
370 	if(fprint(cfd, "headers") < 0)
371 		myfatal("can't set header mode: %r");
372 
373 	sprint(data, "%s/data", devdir);
374 
375 	fd = open(data, ORDWR);
376 	if(fd < 0)
377 		myfatal("open udp data: %r");
378 	close(cfd);
379 
380 	return fd;
381 }
382 
383 uchar*
384 optadd(uchar *p, int op, void *d, int n)
385 {
386 	p[0] = op;
387 	p[1] = n;
388 	memmove(p+2, d, n);
389 	return p+n+2;
390 }
391 
392 uchar*
393 optaddbyte(uchar *p, int op, int b)
394 {
395 	p[0] = op;
396 	p[1] = 1;
397 	p[2] = b;
398 	return p+3;
399 }
400 
401 uchar*
402 optaddulong(uchar *p, int op, ulong x)
403 {
404 	p[0] = op;
405 	p[1] = 4;
406 	hnputl(p+2, x);
407 	return p+6;
408 }
409 
410 uchar *
411 optaddaddr(uchar *p, int op, uchar *ip)
412 {
413 	p[0] = op;
414 	p[1] = 4;
415 	v6tov4(p+2, ip);
416 	return p+6;
417 }
418 
419 uchar*
420 optget(Bootp *bp, int op, int n)
421 {
422 	int len, code;
423 	uchar *p;
424 
425 	p = bp->optdata;
426 	for(;;) {
427 		code = *p++;
428 		if(code == OBpad)
429 			continue;
430 		if(code == OBend)
431 			return 0;
432 		len = *p++;
433 		if(code != op) {
434 			p += len;
435 			continue;
436 		}
437 		if(n && n != len)
438 			return 0;
439 		return p;
440 	}
441 }
442 
443 
444 int
445 optgetbyte(Bootp *bp, int op)
446 {
447 	uchar *p;
448 
449 	p = optget(bp, op, 1);
450 	if(p == 0)
451 		return 0;
452 	return *p;
453 }
454 
455 ulong
456 optgetulong(Bootp *bp, int op)
457 {
458 	uchar *p;
459 
460 	p = optget(bp, op, 4);
461 	if(p == 0)
462 		return 0;
463 	return nhgetl(p);
464 }
465 
466 int
467 optgetaddr(Bootp *bp, int op, uchar *ip)
468 {
469 	uchar *p;
470 
471 	p = optget(bp, op, 4);
472 	if(p == 0)
473 		return 0;
474 	v4tov6(ip, p);
475 	return 1;
476 }
477 
478 /* make sure packet looks ok */
479 Bootp *
480 parse(uchar *p, int n)
481 {
482 	int len, code;
483 	Bootp *bp;
484 
485 	bp = (Bootp*)p;
486 	if(n < bp->optmagic - p) {
487 		fprint(2, "dhcpclient: parse: short bootp packet");
488 		return 0;
489 	}
490 
491 	if(dhcp.xid != nhgetl(bp->xid)) {
492 		fprint(2, "dhcpclient: parse: bad xid: got %ux expected %lux\n", nhgetl(bp->xid), dhcp.xid);
493 		return 0;
494 	}
495 
496 	if(bp->op != Bootreply) {
497 		fprint(2, "dhcpclient: parse: bad op\n");
498 		return 0;
499 	}
500 
501 	n -= bp->optmagic - p;
502 	p = bp->optmagic;
503 
504 	if(n < 4) {
505 		fprint(2, "dhcpclient: parse: not option data");
506 		return 0;
507 	}
508 	if(memcmp(optmagic, p, 4) != 0) {
509 		fprint(2, "dhcpclient: parse: bad opt magic %ux %ux %ux %ux\n", p[0], p[1], p[2], p[3]);
510 		return 0;
511 	}
512 	p += 4;
513 	n -= 4;
514 	while(n>0) {
515 		code = *p++;
516 		n--;
517 		if(code == OBpad)
518 			continue;
519 		if(code == OBend)
520 			return bp;
521 		if(n == 0) {
522 			fprint(2, "dhcpclient: parse: bad option: %d", code);
523 			return 0;
524 		}
525 		len = *p++;
526 		n--;
527 		if(len > n) {
528 			fprint(2, "dhcpclient: parse: bad option: %d", code);
529 			return 0;
530 		}
531 		p += len;
532 		n -= len;
533 	}
534 
535 	/* fix up nonstandard packets */
536 	/* assume there is space */
537 	*p = OBend;
538 
539 	return bp;
540 }
541 
542 void
543 bootpdump(uchar *p, int n)
544 {
545 	Bootp *bp;
546 	Udphdr *up;
547 	int len, i, code;
548 
549 	bp = (Bootp*)p;
550 	up = (Udphdr*)bp->udphdr;
551 
552 	if(n < bp->optmagic - p) {
553 		fprint(2, "dhcpclient: short bootp packet");
554 		return;
555 	}
556 
557 	fprint(2, "laddr=%I lport=%d raddr=%I rport=%d\n", up->laddr, nhgets(up->lport),
558 			up->raddr, nhgets(up->rport));
559 	fprint(2, "op=%d htype=%d hlen=%d hops=%d\n", bp->op, bp->htype, bp->hlen, bp->hops);
560 	fprint(2, "xid=%ux secs=%d flags=%ux\n", nhgetl(bp->xid), 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", p[0], p[1], p[2], p[3]);
577 	p += 4;
578 	n -= 4;
579 
580 	while(n>0) {
581 		code = *p++;
582 		n--;
583 		if(code == OBpad)
584 			continue;
585 		if(code == OBend)
586 			break;
587 		if(n == 0) {
588 			fprint(2, " bad option: %d", code);
589 			return;
590 		}
591 		len = *p++;
592 		n--;
593 		if(len > n) {
594 			fprint(2, " bad option: %d", code);
595 			return;
596 		}
597 		switch(code) {
598 		default:
599 			fprint(2, "unknown option %d\n", code);
600 			for(i = 0; i<len; i++)
601 				fprint(2, "%ux ", p[i]);
602 		case ODtype:
603 			fprint(2, "DHCP type %d\n", p[0]);
604 			break;
605 		case ODclientid:
606 			fprint(2, "client id=");
607 			for(i = 0; i<len; i++)
608 				fprint(2, "%ux ", p[i]);
609 			fprint(2, "\n");
610 			break;
611 		case ODlease:
612 			fprint(2, "lease=%d\n", nhgetl(p));
613 			break;
614 		case ODserverid:
615 			fprint(2, "server id=%V\n", p);
616 			break;
617 		case OBmask:
618 			fprint(2, "mask=%V\n", p);
619 			break;
620 		case OBrouter:
621 			fprint(2, "router=%V\n", p);
622 			break;
623 		}
624 		p += len;
625 		n -= len;
626 	}
627 }
628 
629 ulong
630 thread(void(*f)(void*), void *a)
631 {
632 	int pid;
633 	pid=rfork(RFNOWAIT|RFMEM|RFPROC);
634 	if(pid < 0)
635 		myfatal("rfork failed: %r");
636 	if(pid != 0)
637 		return pid;
638 	(*f)(a);
639 	return 0; // never reaches here
640 }
641 
642 void
643 myfatal(char *fmt, ...)
644 {
645 	char buf[1024];
646 	va_list arg;
647 
648 	va_start(arg, fmt);
649 	vseprint(buf, buf+sizeof(buf), fmt, arg);
650 	va_end(arg);
651 	fprint(2, "%s: %s\n", argv0, buf);
652 	postnote(PNGROUP, getpid(), "die");
653 	exits(buf);
654 }
655