xref: /plan9/sys/src/cmd/ip/dhcpclient.c (revision ff8c3af2f44d95267f67219afa20ba82ff6cf7e4)
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 	OUdphdr *up;
224 	uchar *p;
225 	int n;
226 
227 	memset(&bp, 0, sizeof(bp));
228 	up = (OUdphdr*)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 	fprint(cfd, "oldheaders");
373 
374 	sprint(data, "%s/data", devdir);
375 
376 	fd = open(data, ORDWR);
377 	if(fd < 0)
378 		myfatal("open udp data: %r");
379 	close(cfd);
380 
381 	return fd;
382 }
383 
384 uchar*
385 optadd(uchar *p, int op, void *d, int n)
386 {
387 	p[0] = op;
388 	p[1] = n;
389 	memmove(p+2, d, n);
390 	return p+n+2;
391 }
392 
393 uchar*
394 optaddbyte(uchar *p, int op, int b)
395 {
396 	p[0] = op;
397 	p[1] = 1;
398 	p[2] = b;
399 	return p+3;
400 }
401 
402 uchar*
403 optaddulong(uchar *p, int op, ulong x)
404 {
405 	p[0] = op;
406 	p[1] = 4;
407 	hnputl(p+2, x);
408 	return p+6;
409 }
410 
411 uchar *
412 optaddaddr(uchar *p, int op, uchar *ip)
413 {
414 	p[0] = op;
415 	p[1] = 4;
416 	v6tov4(p+2, ip);
417 	return p+6;
418 }
419 
420 uchar*
421 optget(Bootp *bp, int op, int n)
422 {
423 	int len, code;
424 	uchar *p;
425 
426 	p = bp->optdata;
427 	for(;;) {
428 		code = *p++;
429 		if(code == OBpad)
430 			continue;
431 		if(code == OBend)
432 			return 0;
433 		len = *p++;
434 		if(code != op) {
435 			p += len;
436 			continue;
437 		}
438 		if(n && n != len)
439 			return 0;
440 		return p;
441 	}
442 }
443 
444 
445 int
446 optgetbyte(Bootp *bp, int op)
447 {
448 	uchar *p;
449 
450 	p = optget(bp, op, 1);
451 	if(p == 0)
452 		return 0;
453 	return *p;
454 }
455 
456 ulong
457 optgetulong(Bootp *bp, int op)
458 {
459 	uchar *p;
460 
461 	p = optget(bp, op, 4);
462 	if(p == 0)
463 		return 0;
464 	return nhgetl(p);
465 }
466 
467 int
468 optgetaddr(Bootp *bp, int op, uchar *ip)
469 {
470 	uchar *p;
471 
472 	p = optget(bp, op, 4);
473 	if(p == 0)
474 		return 0;
475 	v4tov6(ip, p);
476 	return 1;
477 }
478 
479 /* make sure packet looks ok */
480 Bootp *
481 parse(uchar *p, int n)
482 {
483 	int len, code;
484 	Bootp *bp;
485 
486 	bp = (Bootp*)p;
487 	if(n < bp->optmagic - p) {
488 		fprint(2, "dhcpclient: parse: short bootp packet");
489 		return 0;
490 	}
491 
492 	if(dhcp.xid != nhgetl(bp->xid)) {
493 		fprint(2, "dhcpclient: parse: bad xid: got %ux expected %lux\n", nhgetl(bp->xid), dhcp.xid);
494 		return 0;
495 	}
496 
497 	if(bp->op != Bootreply) {
498 		fprint(2, "dhcpclient: parse: bad op\n");
499 		return 0;
500 	}
501 
502 	n -= bp->optmagic - p;
503 	p = bp->optmagic;
504 
505 	if(n < 4) {
506 		fprint(2, "dhcpclient: parse: not option data");
507 		return 0;
508 	}
509 	if(memcmp(optmagic, p, 4) != 0) {
510 		fprint(2, "dhcpclient: parse: bad opt magic %ux %ux %ux %ux\n", p[0], p[1], p[2], p[3]);
511 		return 0;
512 	}
513 	p += 4;
514 	n -= 4;
515 	while(n>0) {
516 		code = *p++;
517 		n--;
518 		if(code == OBpad)
519 			continue;
520 		if(code == OBend)
521 			return bp;
522 		if(n == 0) {
523 			fprint(2, "dhcpclient: parse: bad option: %d", code);
524 			return 0;
525 		}
526 		len = *p++;
527 		n--;
528 		if(len > n) {
529 			fprint(2, "dhcpclient: parse: bad option: %d", code);
530 			return 0;
531 		}
532 		p += len;
533 		n -= len;
534 	}
535 
536 	/* fix up nonstandard packets */
537 	/* assume there is space */
538 	*p = OBend;
539 
540 	return bp;
541 }
542 
543 void
544 bootpdump(uchar *p, int n)
545 {
546 	Bootp *bp;
547 	OUdphdr *up;
548 	int len, i, code;
549 
550 	bp = (Bootp*)p;
551 	up = (OUdphdr*)bp->udphdr;
552 
553 	if(n < bp->optmagic - p) {
554 		fprint(2, "dhcpclient: short bootp packet");
555 		return;
556 	}
557 
558 	fprint(2, "laddr=%I lport=%d raddr=%I rport=%d\n", up->laddr, nhgets(up->lport),
559 			up->raddr, nhgets(up->rport));
560 	fprint(2, "op=%d htype=%d hlen=%d hops=%d\n", bp->op, bp->htype, bp->hlen, bp->hops);
561 	fprint(2, "xid=%ux secs=%d flags=%ux\n", nhgetl(bp->xid), nhgets(bp->secs), nhgets(bp->flags));
562 	fprint(2, "ciaddr=%V yiaddr=%V siaddr=%V giaddr=%V\n",
563 		bp->ciaddr, bp->yiaddr, bp->siaddr, bp->giaddr);
564 	fprint(2, "chaddr=");
565 	for(i=0; i<16; i++)
566 		fprint(2, "%ux ", bp->chaddr[i]);
567 	fprint(2, "\n");
568 	fprint(2, "sname=%s\n", bp->sname);
569 	fprint(2, "file = %s\n", bp->file);
570 
571 	n -= bp->optmagic - p;
572 	p = bp->optmagic;
573 
574 	if(n < 4)
575 		return;
576 	if(memcmp(optmagic, p, 4) != 0)
577 		fprint(2, "dhcpclient: bad opt magic %ux %ux %ux %ux\n", 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
631 thread(void(*f)(void*), void *a)
632 {
633 	int pid;
634 	pid=rfork(RFNOWAIT|RFMEM|RFPROC);
635 	if(pid < 0)
636 		myfatal("rfork failed: %r");
637 	if(pid != 0)
638 		return pid;
639 	(*f)(a);
640 	return 0; // never reaches here
641 }
642 
643 void
644 myfatal(char *fmt, ...)
645 {
646 	char buf[1024];
647 	va_list arg;
648 
649 	va_start(arg, fmt);
650 	vseprint(buf, buf+sizeof(buf), fmt, arg);
651 	va_end(arg);
652 	fprint(2, "%s: %s\n", argv0, buf);
653 	postnote(PNGROUP, getpid(), "die");
654 	exits(buf);
655 }
656