master xplshn/aruu / cmd / net / sdhcp.c
  1/* sdhcp: simple DHCP client, ported from git.2f30.org/sdhcp */
  2/* See LICENSE file for copyright and license details */
  3#include "arg.h"
  4#include "util.h"
  5
  6#include <errno.h>
  7#include <fcntl.h>
  8#include <limits.h>
  9#include <net/if.h>
 10#include <net/route.h>
 11#include <netinet/in.h>
 12#include <poll.h>
 13#include <signal.h>
 14#include <stdint.h>
 15#include <stdio.h>
 16#include <stdlib.h>
 17#include <string.h>
 18#include <sys/ioctl.h>
 19#include <sys/socket.h>
 20#include <sys/timerfd.h>
 21#include <time.h>
 22#include <unistd.h>
 23
 24/* bootp packet layout: fields are byte arrays to avoid alignment/endian issues */
 25struct Bootp {
 26	unsigned char op      [1];
 27	unsigned char htype   [1];
 28	unsigned char hlen    [1];
 29	unsigned char hops    [1];
 30	unsigned char xid     [4];
 31	unsigned char secs    [2];
 32	unsigned char flags   [2];
 33	unsigned char ciaddr  [4];
 34	unsigned char yiaddr  [4];
 35	unsigned char siaddr  [4];
 36	unsigned char giaddr  [4];
 37	unsigned char chaddr  [16];
 38	unsigned char sname   [64];
 39	unsigned char file    [128];
 40	unsigned char magic   [4];
 41	unsigned char optdata [312-4];
 42};
 43
 44enum {
 45	DHCP_DISCOVER =       1,
 46	DHCP_OFFER,
 47	DHCP_REQUEST,
 48	DHCP_DECLINE,
 49	DHCP_ACK,
 50	DHCP_NAK,
 51	DHCP_RELEASE,
 52	DHCP_INFORM,
 53	/* internal sentinel values returned by dhcprecv */
 54	TIMEOUT0 =          200,
 55	TIMEOUT1,
 56	TIMEOUT2,
 57
 58	BOOT_REQUEST =        1,
 59	BOOT_REPLY =          2,
 60	/* bootp flags */
 61	F_BROADCAST =   1 << 15,
 62
 63	/* bootp options */
 64	OB_PAD =              0,
 65	OB_MASK =             1,
 66	OB_ROUTER =           3,
 67	OB_NAMESERVER =       5,
 68	OB_DNSSERVER =        6,
 69	OB_HOSTNAME =        12,
 70	OB_BADDR =           28,
 71	/* DHCP-specific options */
 72	OD_IPADDR =          50, /* 0x32 */
 73	OD_LEASE =           51,
 74	OD_OVERLOAD =        52,
 75	OD_TYPE =            53, /* 0x35 */
 76	OD_SERVERID =        54, /* 0x36 */
 77	OD_PARAMS =          55, /* 0x37 */
 78	OD_MESSAGE =         56,
 79	OD_MAXMSG =          57,
 80	OD_RENEWALTIME =     58,
 81	OD_REBINDINGTIME =   59,
 82	OD_VENDORCLASS =     60,
 83	OD_CLIENTID =        61, /* 0x3d */
 84	OD_TFTPSERVER =      66,
 85	OD_BOOTFILE =        67,
 86	OB_END =            255,
 87};
 88
 89enum { BROADCAST, UNICAST };
 90
 91static struct Bootp bp;
 92static unsigned char magic[] = { 99, 130, 83, 99 };
 93
 94/* conf */
 95static unsigned char xid[sizeof(bp.xid)];
 96static unsigned char hwaddr[16];
 97static char hostname[HOST_NAME_MAX + 1];
 98static time_t starttime;
 99static char *ifname = "eth0";
100static unsigned char cid[16];
101static char *program = "";
102static int sock, timers[3];
103/* negotiated lease values */
104static unsigned char server[4];
105static unsigned char client[4];
106static unsigned char mask[4];
107static unsigned char router[4];
108static unsigned char dns[4];
109
110/* flags: default on=1, foreground=0 */
111static int dflag = 1;
112static int iflag = 1;
113static int fflag = 0;
114
115#define IP(a, b, c, d) (unsigned char[4]){ a, b, c, d }
116
117/* write src as n-byte big-endian into dst */
118static void
119hnput(unsigned char *dst, uint32_t src, size_t n)
120{
121	unsigned int i;
122
123	for (i = 0; n--; i++)
124		dst[i] = (src >> (n * 8)) & 0xff;
125}
126
127static struct sockaddr *
128iptoaddr(struct sockaddr *ifaddr, unsigned char ip[4], int port)
129{
130	struct sockaddr_in *in = (struct sockaddr_in *)ifaddr;
131
132	in->sin_family = AF_INET;
133	in->sin_port = htons(port);
134	memcpy(&(in->sin_addr), ip, sizeof(in->sin_addr));
135
136	return ifaddr;
137}
138
139/* sendto UDP wrapper: sends n bytes of data to bootp server port on ip */
140static ssize_t
141udpsend(unsigned char ip[4], int fd, void *data, size_t n)
142{
143	struct sockaddr addr;
144	socklen_t addrlen = sizeof(addr);
145	ssize_t sent;
146
147	iptoaddr(&addr, ip, 67); /* bootp server */
148	if ((sent = sendto(fd, data, n, 0, &addr, addrlen)) == -1)
149		eprintf("sendto:");
150
151	return sent;
152}
153
154/* recvfrom UDP wrapper: receives into data from bootp client port */
155static ssize_t
156udprecv(unsigned char ip[4], int fd, void *data, size_t n)
157{
158	struct sockaddr addr;
159	socklen_t addrlen = sizeof(addr);
160	ssize_t r;
161
162	iptoaddr(&addr, ip, 68); /* bootp client */
163	if ((r = recvfrom(fd, data, n, 0, &addr, &addrlen)) == -1)
164		eprintf("recvfrom:");
165
166	return r;
167}
168
169static void
170setip(unsigned char ip[4], unsigned char mask[4], unsigned char gateway[4])
171{
172	struct ifreq ifreq;
173	struct rtentry rtreq;
174	int fd;
175
176	memset(&ifreq, 0, sizeof(ifreq));
177	memset(&rtreq, 0, sizeof(rtreq));
178
179	strlcpy(ifreq.ifr_name, ifname, IF_NAMESIZE);
180	iptoaddr(&(ifreq.ifr_addr), ip, 0);
181	if ((fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP)) == -1)
182		eprintf("can't set ip, socket:");
183	ioctl(fd, SIOCSIFADDR, &ifreq);
184	iptoaddr(&(ifreq.ifr_netmask), mask, 0);
185	ioctl(fd, SIOCSIFNETMASK, &ifreq);
186	ifreq.ifr_flags = IFF_UP | IFF_RUNNING | IFF_BROADCAST | IFF_MULTICAST;
187	ioctl(fd, SIOCSIFFLAGS, &ifreq);
188	/* default gw */
189	rtreq.rt_flags = RTF_UP | RTF_GATEWAY;
190	iptoaddr(&(rtreq.rt_gateway), gateway, 0);
191	iptoaddr(&(rtreq.rt_genmask), IP(0, 0, 0, 0), 0);
192	iptoaddr(&(rtreq.rt_dst),     IP(0, 0, 0, 0), 0);
193	ioctl(fd, SIOCADDRT, &rtreq);
194
195	close(fd);
196}
197
198/* copy contents of src path into dfd; silently skips missing files */
199static void
200appendfile(int dfd, char *src)
201{
202	char buf[BUFSIZ];
203	int fd, n;
204
205	if ((fd = open(src, O_RDONLY)) == -1)
206		return;
207	while ((n = read(fd, buf, sizeof(buf))) > 0)
208		writeall(dfd, buf, n);
209	close(fd);
210}
211
212static void
213setdns(unsigned char ip[4])
214{
215	char buf[128];
216	int fd, n;
217
218	if ((fd = creat("/etc/resolv.conf", 0644)) == -1) {
219		weprintf("can't change /etc/resolv.conf:");
220		return;
221	}
222	appendfile(fd, "/etc/resolv.conf.head");
223	n = snprintf(buf, sizeof(buf), "\nnameserver %d.%d.%d.%d\n",
224	    ip[0], ip[1], ip[2], ip[3]);
225	if (n > 0)
226		writeall(fd, buf, n);
227	appendfile(fd, "/etc/resolv.conf.tail");
228	close(fd);
229}
230
231/* scan b's option data for opt; copy up to n bytes into data */
232static void
233optget(struct Bootp *b, void *data, int opt, int n)
234{
235	unsigned char *p = b->optdata;
236	unsigned char *top = ((unsigned char *)b) + sizeof(*b);
237	int code, len;
238
239	while (p < top) {
240		code = *p++;
241		if (code == OB_PAD)
242			continue;
243		if (code == OB_END || p == top)
244			break;
245		len = *p++;
246		if (len > top - p)
247			break;
248		if (code == opt) {
249			memcpy(data, p, MIN(len, n));
250			break;
251		}
252		p += len;
253	}
254}
255
256/* append a raw-bytes option to p; return new write cursor */
257static unsigned char *
258optput(unsigned char *p, int opt, unsigned char *data, size_t len)
259{
260	*p++ = opt;
261	*p++ = (unsigned char)len;
262	memcpy(p, data, len);
263
264	return p + len;
265}
266
267/* append a big-endian numeric option to p; return new write cursor */
268static unsigned char *
269hnoptput(unsigned char *p, int opt, uint32_t data, size_t len)
270{
271	*p++ = opt;
272	*p++ = (unsigned char)len;
273	hnput(p, data, len);
274
275	return p + len;
276}
277
278static void
279dhcpsend(int type, int how)
280{
281	unsigned char *ip, *p;
282
283	memset(&bp, 0, sizeof(bp));
284	hnput(bp.op,    BOOT_REQUEST,              1);
285	hnput(bp.htype, 1,                         1);
286	hnput(bp.hlen,  6,                         1);
287	memcpy(bp.xid,   xid,   sizeof(xid));
288	hnput(bp.flags, F_BROADCAST, sizeof(bp.flags));
289	hnput(bp.secs,  time(NULL) - starttime, sizeof(bp.secs));
290	memcpy(bp.magic, magic, sizeof(bp.magic));
291	memcpy(bp.chaddr, hwaddr, sizeof(bp.chaddr));
292	p = bp.optdata;
293	p = hnoptput(p, OD_TYPE,     type,                 1);
294	p = optput(p,   OD_CLIENTID, cid,   sizeof(cid));
295	p = optput(p,   OB_HOSTNAME, (unsigned char *)hostname, strlen(hostname));
296
297	switch (type) {
298	case DHCP_DISCOVER:
299		break;
300	case DHCP_REQUEST:
301		p = optput(p, OD_IPADDR,   client, sizeof(client));
302		p = optput(p, OD_SERVERID, server, sizeof(server));
303		break;
304	case DHCP_RELEASE:
305		memcpy(bp.ciaddr, client, sizeof(client));
306		p = optput(p, OD_IPADDR,   client, sizeof(client));
307		p = optput(p, OD_SERVERID, server, sizeof(server));
308		break;
309	}
310	*p++ = OB_END;
311
312	ip = (how == BROADCAST) ? IP(255, 255, 255, 255) : server;
313	udpsend(ip, sock, &bp, p - (unsigned char *)&bp);
314}
315
316/* block on poll; return DHCP message type or a TIMEOUT* sentinel */
317static int
318dhcprecv(void)
319{
320	unsigned char type = 0;
321	uint64_t n;
322	struct pollfd pfd[] = {
323		{ .fd = sock,      .events = POLLIN },
324		{ .fd = timers[0], .events = POLLIN },
325		{ .fd = timers[1], .events = POLLIN },
326		{ .fd = timers[2], .events = POLLIN },
327	};
328
329	if (poll(pfd, LEN(pfd), -1) == -1)
330		eprintf("poll:");
331	if (pfd[0].revents) {
332		memset(&bp, 0, sizeof(bp));
333		udprecv(IP(255, 255, 255, 255), sock, &bp, sizeof(bp));
334		optget(&bp, &type, OD_TYPE, sizeof(type));
335		return type;
336	}
337	if (pfd[1].revents) {
338		type = TIMEOUT0;
339		read(timers[0], &n, sizeof(n));
340	}
341	if (pfd[2].revents) {
342		type = TIMEOUT1;
343		read(timers[1], &n, sizeof(n));
344	}
345	if (pfd[3].revents) {
346		type = TIMEOUT2;
347		read(timers[2], &n, sizeof(n));
348	}
349	return type;
350}
351
352static void
353acceptlease(void)
354{
355	char buf[128];
356
357	if (iflag)
358		setip(client, mask, router);
359	if (dflag)
360		setdns(dns);
361	if (*program) {
362		snprintf(buf, sizeof(buf), "%d.%d.%d.%d",
363		    server[0], server[1], server[2], server[3]);
364		setenv("SERVER", buf, 1);
365		snprintf(buf, sizeof(buf), "%d.%d.%d.%d",
366		    client[0], client[1], client[2], client[3]);
367		setenv("CLIENT", buf, 1);
368		snprintf(buf, sizeof(buf), "%d.%d.%d.%d",
369		    mask[0], mask[1], mask[2], mask[3]);
370		setenv("MASK", buf, 1);
371		snprintf(buf, sizeof(buf), "%d.%d.%d.%d",
372		    router[0], router[1], router[2], router[3]);
373		setenv("ROUTER", buf, 1);
374		snprintf(buf, sizeof(buf), "%d.%d.%d.%d",
375		    dns[0], dns[1], dns[2], dns[3]);
376		setenv("DNS", buf, 1);
377		system(program);
378	}
379}
380
381static void
382settimeout(int n, const struct itimerspec *ts)
383{
384	if (timerfd_settime(timers[n], 0, ts, NULL) < 0)
385		eprintf("timerfd_settime:");
386}
387
388/* set ts to expire halfway to the remaining time on timer n, minimum 60s */
389static void
390calctimeout(int n, struct itimerspec *ts)
391{
392	if (timerfd_gettime(timers[n], ts) < 0)
393		eprintf("timerfd_gettime:");
394	ts->it_value.tv_nsec /= 2;
395	if (ts->it_value.tv_sec % 2)
396		ts->it_value.tv_nsec += 500000000;
397	ts->it_value.tv_sec /= 2;
398	if (ts->it_value.tv_sec < 60) {
399		ts->it_value.tv_sec = 60;
400		ts->it_value.tv_nsec = 0;
401	}
402}
403
404/* RFC 2131 DHCP state machine */
405static void
406run(void)
407{
408	int forked = 0, t;
409	struct itimerspec timeout = { 0 };
410	uint32_t renewaltime, rebindingtime, lease;
411
412Init:
413	dhcpsend(DHCP_DISCOVER, BROADCAST);
414	timeout.it_value.tv_sec = 1;
415	timeout.it_value.tv_nsec = 0;
416	settimeout(0, &timeout);
417	goto Selecting;
418Selecting:
419	for (;;) {
420		switch (dhcprecv()) {
421		case DHCP_OFFER:
422			memcpy(client, bp.yiaddr, sizeof(client));
423			optget(&bp, server, OD_SERVERID, sizeof(server));
424			goto Requesting;
425		case TIMEOUT0:
426			goto Init;
427		}
428	}
429Requesting:
430	for (t = 4; t <= 64; t *= 2) {
431		dhcpsend(DHCP_REQUEST, BROADCAST);
432		timeout.it_value.tv_sec = t;
433		settimeout(0, &timeout);
434		for (;;) {
435			switch (dhcprecv()) {
436			case DHCP_ACK:
437				goto Bound;
438			case DHCP_NAK:
439				goto Init;
440			case TIMEOUT0:
441				break;
442			default:
443				continue;
444			}
445			break;
446		}
447	}
448	/* no ACK after several DHCPREQUEST attempts */
449	goto Init;
450Bound:
451	optget(&bp, mask,           OB_MASK,          sizeof(mask));
452	optget(&bp, router,         OB_ROUTER,        sizeof(router));
453	optget(&bp, dns,            OB_DNSSERVER,     sizeof(dns));
454	optget(&bp, &renewaltime,   OD_RENEWALTIME,   sizeof(renewaltime));
455	optget(&bp, &rebindingtime, OD_REBINDINGTIME, sizeof(rebindingtime));
456	optget(&bp, &lease,         OD_LEASE,         sizeof(lease));
457	renewaltime   = ntohl(renewaltime);
458	rebindingtime = ntohl(rebindingtime);
459	lease         = ntohl(lease);
460	acceptlease();
461	puts("Bound. Network configured.");
462	if (!fflag && !forked) {
463		if (fork())
464			exit(0);
465		forked = 1;
466	}
467	timeout.it_value.tv_sec = renewaltime;
468	settimeout(0, &timeout);
469	timeout.it_value.tv_sec = rebindingtime;
470	settimeout(1, &timeout);
471	timeout.it_value.tv_sec = lease;
472	settimeout(2, &timeout);
473	for (;;) {
474		switch (dhcprecv()) {
475		case TIMEOUT0: /* t1: enter renewing */
476			goto Renewing;
477		case TIMEOUT1: /* t2: enter rebinding */
478			goto Rebinding;
479		case TIMEOUT2: /* lease expired */
480			goto Init;
481		}
482	}
483Renewing:
484	dhcpsend(DHCP_REQUEST, UNICAST);
485	calctimeout(1, &timeout);
486	settimeout(0, &timeout);
487	for (;;) {
488		switch (dhcprecv()) {
489		case DHCP_ACK:
490			goto Bound;
491		case TIMEOUT0: /* resend unicast */
492			goto Renewing;
493		case TIMEOUT1: /* t2 elapsed */
494			goto Rebinding;
495		case TIMEOUT2:
496		case DHCP_NAK:
497			goto Init;
498		}
499	}
500Rebinding:
501	calctimeout(2, &timeout);
502	settimeout(0, &timeout);
503	dhcpsend(DHCP_REQUEST, BROADCAST);
504	for (;;) {
505		switch (dhcprecv()) {
506		case DHCP_ACK:
507			goto Bound;
508		case TIMEOUT0: /* resend broadcast */
509			goto Rebinding;
510		case TIMEOUT2: /* lease expired */
511		case DHCP_NAK:
512			goto Init;
513		}
514	}
515}
516
517static void
518cleanexit(int unused)
519{
520	(void)unused;
521	dhcpsend(DHCP_RELEASE, UNICAST);
522	_exit(0);
523}
524
525static void
526usage(void)
527{
528	eprintf("usage: %s [-d] [-e program] [-f] [-i] [ifname] [clientid]\n", argv0);
529}
530
531int
532main(int argc, char *argv[])
533{
534	struct ifreq ifreq;
535	struct sockaddr addr;
536	int bcast = 1;
537	int rnd;
538	size_t i;
539
540	ARGBEGIN {
541	case 'd': /* don't update /etc/resolv.conf */
542		dflag = 0;
543		break;
544	case 'e': /* exec program on each lease */
545		program = EARGF(usage());
546		break;
547	case 'f': /* stay in foreground */
548		fflag = 1;
549		break;
550	case 'i': /* don't configure IP address */
551		iflag = 0;
552		break;
553	default:
554		usage();
555		break;
556	} ARGEND
557
558	if (argc)
559		ifname = argv[0];
560	if (argc >= 2)
561		strlcpy((char *)cid, argv[1], sizeof(cid));
562
563	memset(&ifreq, 0, sizeof(ifreq));
564	signal(SIGTERM, cleanexit);
565
566	if (gethostname(hostname, sizeof(hostname)) == -1)
567		eprintf("gethostname:");
568
569	if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
570		eprintf("socket:");
571	if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &bcast, sizeof(bcast)) == -1)
572		eprintf("setsockopt SO_BROADCAST:");
573
574	strlcpy(ifreq.ifr_name, ifname, IF_NAMESIZE);
575	ioctl(sock, SIOCGIFINDEX, &ifreq);
576	if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, &ifreq, sizeof(ifreq)) == -1)
577		eprintf("setsockopt SO_BINDTODEVICE:");
578	iptoaddr(&addr, IP(255, 255, 255, 255), 68);
579	if (bind(sock, &addr, sizeof(addr)) != 0)
580		eprintf("bind:");
581	ioctl(sock, SIOCGIFHWADDR, &ifreq);
582	memcpy(hwaddr, ifreq.ifr_hwaddr.sa_data, sizeof(ifreq.ifr_hwaddr.sa_data));
583	if (!cid[0])
584		memcpy(cid, hwaddr, sizeof(cid));
585
586	if ((rnd = open("/dev/urandom", O_RDONLY)) == -1)
587		eprintf("open /dev/urandom:");
588	if (read(rnd, xid, sizeof(xid)) != (ssize_t)sizeof(xid))
589		eprintf("read /dev/urandom:");
590	close(rnd);
591
592	for (i = 0; i < LEN(timers); ++i) {
593		timers[i] = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC);
594		if (timers[i] == -1)
595			eprintf("timerfd_create:");
596	}
597
598	starttime = time(NULL);
599	run();
600
601	return 0;
602}