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}