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#include "wexec.h"
6
7#include <errno.h>
8#include <fcntl.h>
9#include <limits.h>
10#include <net/if.h>
11#include <net/route.h>
12#include <netinet/in.h>
13#include <poll.h>
14#include <signal.h>
15#include <stdint.h>
16#include <stdio.h>
17#include <stdlib.h>
18#include <string.h>
19#include <sys/ioctl.h>
20#include <sys/socket.h>
21#include <sys/timerfd.h>
22#include <time.h>
23#include <unistd.h>
24
25/* bootp packet layout: fields are byte arrays to avoid alignment/endian issues
26 */
27struct Bootp {
28 unsigned char op[1];
29 unsigned char htype[1];
30 unsigned char hlen[1];
31 unsigned char hops[1];
32 unsigned char xid[4];
33 unsigned char secs[2];
34 unsigned char flags[2];
35 unsigned char ciaddr[4];
36 unsigned char yiaddr[4];
37 unsigned char siaddr[4];
38 unsigned char giaddr[4];
39 unsigned char chaddr[16];
40 unsigned char sname[64];
41 unsigned char file[128];
42 unsigned char magic[4];
43 unsigned char optdata[312 - 4];
44};
45
46enum {
47 DHCP_DISCOVER = 1,
48 DHCP_OFFER,
49 DHCP_REQUEST,
50 DHCP_DECLINE,
51 DHCP_ACK,
52 DHCP_NAK,
53 DHCP_RELEASE,
54 DHCP_INFORM,
55 /* internal sentinel values returned by dhcprecv */
56 TIMEOUT0 = 200,
57 TIMEOUT1,
58 TIMEOUT2,
59
60 BOOT_REQUEST = 1,
61 BOOT_REPLY = 2,
62 /* bootp flags */
63 F_BROADCAST = 1 << 15,
64
65 /* bootp options */
66 OB_PAD = 0,
67 OB_MASK = 1,
68 OB_ROUTER = 3,
69 OB_NAMESERVER = 5,
70 OB_DNSSERVER = 6,
71 OB_HOSTNAME = 12,
72 OB_BADDR = 28,
73 /* DHCP-specific options */
74 OD_IPADDR = 50, /* 0x32 */
75 OD_LEASE = 51,
76 OD_OVERLOAD = 52,
77 OD_TYPE = 53, /* 0x35 */
78 OD_SERVERID = 54, /* 0x36 */
79 OD_PARAMS = 55, /* 0x37 */
80 OD_MESSAGE = 56,
81 OD_MAXMSG = 57,
82 OD_RENEWALTIME = 58,
83 OD_REBINDINGTIME = 59,
84 OD_VENDORCLASS = 60,
85 OD_CLIENTID = 61, /* 0x3d */
86 OD_TFTPSERVER = 66,
87 OD_BOOTFILE = 67,
88 OB_END = 255,
89};
90
91enum { BROADCAST, UNICAST };
92
93static struct Bootp bp;
94static unsigned char magic[] = {99, 130, 83, 99};
95
96/* conf */
97static unsigned char xid[sizeof(bp.xid)];
98static unsigned char hwaddr[16];
99static char hostname[HOST_NAME_MAX + 1];
100static time_t starttime;
101static char *ifname = "eth0";
102static unsigned char cid[16];
103static char *program = "";
104static int sock, timers[3];
105/* negotiated lease values */
106static unsigned char server[4];
107static unsigned char client[4];
108static unsigned char mask[4];
109static unsigned char router[4];
110static unsigned char dns[4];
111
112/* flags: default on=1, foreground=0 */
113static int dflag = 1;
114static int iflag = 1;
115static int fflag = 0;
116
117#define IP(a, b, c, d) \
118 (unsigned char[4]) \
119 { \
120 a, b, c, d \
121 }
122
123/* write src as n-byte big-endian into dst */
124static void
125hnput(unsigned char *dst, uint32_t src, size_t n)
126{
127 unsigned int i;
128
129 for (i = 0; n--; i++)
130 dst[i] = (src >> (n * 8)) & 0xff;
131}
132
133static struct sockaddr *
134iptoaddr(struct sockaddr *ifaddr, unsigned char ip[4], int port)
135{
136 struct sockaddr_in *in = (struct sockaddr_in *)ifaddr;
137
138 in->sin_family = AF_INET;
139 in->sin_port = htons(port);
140 memcpy(&(in->sin_addr), ip, sizeof(in->sin_addr));
141
142 return ifaddr;
143}
144
145/* sendto UDP wrapper: sends n bytes of data to bootp server port on ip */
146static ssize_t
147udpsend(unsigned char ip[4], int fd, void *data, size_t n)
148{
149 struct sockaddr addr;
150 socklen_t addrlen = sizeof(addr);
151 ssize_t sent;
152
153 iptoaddr(&addr, ip, 67); /* bootp server */
154 if ((sent = sendto(fd, data, n, 0, &addr, addrlen)) == -1)
155 eprintf("sendto:");
156
157 return sent;
158}
159
160/* recvfrom UDP wrapper: receives into data from bootp client port */
161static ssize_t
162udprecv(unsigned char ip[4], int fd, void *data, size_t n)
163{
164 struct sockaddr addr;
165 socklen_t addrlen = sizeof(addr);
166 ssize_t r;
167
168 iptoaddr(&addr, ip, 68); /* bootp client */
169 if ((r = recvfrom(fd, data, n, 0, &addr, &addrlen)) == -1)
170 eprintf("recvfrom:");
171
172 return r;
173}
174
175static void
176setip(unsigned char ip[4], unsigned char mask[4], unsigned char gateway[4])
177{
178 struct ifreq ifreq;
179 struct rtentry rtreq;
180 int fd;
181
182 memset(&ifreq, 0, sizeof(ifreq));
183 memset(&rtreq, 0, sizeof(rtreq));
184
185 strlcpy(ifreq.ifr_name, ifname, IF_NAMESIZE);
186 iptoaddr(&(ifreq.ifr_addr), ip, 0);
187 if ((fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP)) == -1)
188 eprintf("can't set ip, socket:");
189 ioctl(fd, SIOCSIFADDR, &ifreq);
190 iptoaddr(&(ifreq.ifr_netmask), mask, 0);
191 ioctl(fd, SIOCSIFNETMASK, &ifreq);
192 ifreq.ifr_flags = IFF_UP | IFF_RUNNING | IFF_BROADCAST | IFF_MULTICAST;
193 ioctl(fd, SIOCSIFFLAGS, &ifreq);
194 /* default gw */
195 rtreq.rt_flags = RTF_UP | RTF_GATEWAY;
196 iptoaddr(&(rtreq.rt_gateway), gateway, 0);
197 iptoaddr(&(rtreq.rt_genmask), IP(0, 0, 0, 0), 0);
198 iptoaddr(&(rtreq.rt_dst), IP(0, 0, 0, 0), 0);
199 ioctl(fd, SIOCADDRT, &rtreq);
200
201 close(fd);
202}
203
204/* copy contents of src path into dfd; silently skips missing files */
205static void
206appendfile(int dfd, char *src)
207{
208 char buf[BUFSIZ];
209 int fd, n;
210
211 if ((fd = open(src, O_RDONLY)) == -1)
212 return;
213 while ((n = read(fd, buf, sizeof(buf))) > 0)
214 writeall(dfd, buf, n);
215 close(fd);
216}
217
218static void
219setdns(unsigned char ip[4])
220{
221 char buf[128];
222 int fd, n;
223
224 if ((fd = creat("/etc/resolv.conf", 0644)) == -1) {
225 weprintf("can't change /etc/resolv.conf:");
226 return;
227 }
228 appendfile(fd, "/etc/resolv.conf.head");
229 n = snprintf(buf, sizeof(buf), "\nnameserver %d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]);
230 if (n > 0)
231 writeall(fd, buf, n);
232 appendfile(fd, "/etc/resolv.conf.tail");
233 close(fd);
234}
235
236/* scan b's option data for opt; copy up to n bytes into data */
237static void
238optget(struct Bootp *b, void *data, int opt, int n)
239{
240 unsigned char *p = b->optdata;
241 unsigned char *top = ((unsigned char *)b) + sizeof(*b);
242 int code, len;
243
244 while (p < top) {
245 code = *p++;
246 if (code == OB_PAD)
247 continue;
248 if (code == OB_END || p == top)
249 break;
250 len = *p++;
251 if (len > top - p)
252 break;
253 if (code == opt) {
254 memcpy(data, p, MIN(len, n));
255 break;
256 }
257 p += len;
258 }
259}
260
261/* append a raw-bytes option to p; return new write cursor */
262static unsigned char *
263optput(unsigned char *p, int opt, unsigned char *data, size_t len)
264{
265 *p++ = opt;
266 *p++ = (unsigned char)len;
267 memcpy(p, data, len);
268
269 return p + len;
270}
271
272/* append a big-endian numeric option to p; return new write cursor */
273static unsigned char *
274hnoptput(unsigned char *p, int opt, uint32_t data, size_t len)
275{
276 *p++ = opt;
277 *p++ = (unsigned char)len;
278 hnput(p, data, len);
279
280 return p + len;
281}
282
283static void
284dhcpsend(int type, int how)
285{
286 unsigned char *ip, *p;
287
288 memset(&bp, 0, sizeof(bp));
289 hnput(bp.op, BOOT_REQUEST, 1);
290 hnput(bp.htype, 1, 1);
291 hnput(bp.hlen, 6, 1);
292 memcpy(bp.xid, xid, sizeof(xid));
293 hnput(bp.flags, F_BROADCAST, sizeof(bp.flags));
294 hnput(bp.secs, time(NULL) - starttime, sizeof(bp.secs));
295 memcpy(bp.magic, magic, sizeof(bp.magic));
296 memcpy(bp.chaddr, hwaddr, sizeof(bp.chaddr));
297 p = bp.optdata;
298 p = hnoptput(p, OD_TYPE, type, 1);
299 p = optput(p, OD_CLIENTID, cid, sizeof(cid));
300 p = optput(p, OB_HOSTNAME, (unsigned char *)hostname, strlen(hostname));
301
302 switch (type) {
303 case DHCP_DISCOVER:
304 break;
305 case DHCP_REQUEST:
306 p = optput(p, OD_IPADDR, client, sizeof(client));
307 p = optput(p, OD_SERVERID, server, sizeof(server));
308 break;
309 case DHCP_RELEASE:
310 memcpy(bp.ciaddr, client, sizeof(client));
311 p = optput(p, OD_IPADDR, client, sizeof(client));
312 p = optput(p, OD_SERVERID, server, sizeof(server));
313 break;
314 }
315 *p++ = OB_END;
316
317 ip = (how == BROADCAST) ? IP(255, 255, 255, 255) : server;
318 udpsend(ip, sock, &bp, p - (unsigned char *)&bp);
319}
320
321/* block on poll; return DHCP message type or a TIMEOUT* sentinel */
322static int
323dhcprecv(void)
324{
325 unsigned char type = 0;
326 uint64_t n;
327 struct pollfd pfd[] = {
328 {.fd = sock, .events = POLLIN},
329 {.fd = timers[0], .events = POLLIN},
330 {.fd = timers[1], .events = POLLIN},
331 {.fd = timers[2], .events = POLLIN},
332 };
333
334 if (poll(pfd, LEN(pfd), -1) == -1)
335 eprintf("poll:");
336 if (pfd[0].revents) {
337 memset(&bp, 0, sizeof(bp));
338 udprecv(IP(255, 255, 255, 255), sock, &bp, sizeof(bp));
339 optget(&bp, &type, OD_TYPE, sizeof(type));
340 return type;
341 }
342 if (pfd[1].revents) {
343 type = TIMEOUT0;
344 read(timers[0], &n, sizeof(n));
345 }
346 if (pfd[2].revents) {
347 type = TIMEOUT1;
348 read(timers[1], &n, sizeof(n));
349 }
350 if (pfd[3].revents) {
351 type = TIMEOUT2;
352 read(timers[2], &n, sizeof(n));
353 }
354 return type;
355}
356
357static void
358acceptlease(void)
359{
360 char buf[128];
361
362 if (iflag)
363 setip(client, mask, router);
364 if (dflag)
365 setdns(dns);
366 if (*program) {
367 snprintf(buf, sizeof(buf), "%d.%d.%d.%d", server[0], server[1], server[2], server[3]);
368 setenv("SERVER", buf, 1);
369 snprintf(buf, sizeof(buf), "%d.%d.%d.%d", client[0], client[1], client[2], client[3]);
370 setenv("CLIENT", buf, 1);
371 snprintf(buf, sizeof(buf), "%d.%d.%d.%d", mask[0], mask[1], mask[2], mask[3]);
372 setenv("MASK", buf, 1);
373 snprintf(buf, sizeof(buf), "%d.%d.%d.%d", router[0], router[1], router[2], router[3]);
374 setenv("ROUTER", buf, 1);
375 snprintf(buf, sizeof(buf), "%d.%d.%d.%d", dns[0], dns[1], dns[2], dns[3]);
376 setenv("DNS", buf, 1);
377 wsystem(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
531// ?man sdhcp: minimal DHCP client
532// ?man arguments: [ifname] [clientid]
533// ?man obtain an IPv4 lease via DHCP on the given interface
534int
535main(int argc, char *argv[])
536{
537 struct ifreq ifreq;
538 struct sockaddr addr;
539 int bcast = 1;
540 int rnd;
541 size_t i;
542
543 ARGBEGIN
544 {
545 case 'd': /* don't update /etc/resolv.conf */
546 dflag = 0;
547 break;
548 case 'e': /* exec program on each lease */
549 program = EARGF(usage());
550 break;
551 case 'f': /* stay in foreground */
552 fflag = 1;
553 break;
554 case 'i': /* don't configure IP address */
555 iflag = 0;
556 break;
557 default:
558 usage();
559 break;
560 }
561 ARGEND
562
563 if (argc)
564 ifname = argv[0];
565 if (argc >= 2)
566 strlcpy((char *)cid, argv[1], sizeof(cid));
567
568 memset(&ifreq, 0, sizeof(ifreq));
569 signal(SIGTERM, cleanexit);
570
571 if (gethostname(hostname, sizeof(hostname)) == -1)
572 eprintf("gethostname:");
573
574 if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
575 eprintf("socket:");
576 if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &bcast, sizeof(bcast)) == -1)
577 eprintf("setsockopt SO_BROADCAST:");
578
579 strlcpy(ifreq.ifr_name, ifname, IF_NAMESIZE);
580 ioctl(sock, SIOCGIFINDEX, &ifreq);
581 if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, &ifreq, sizeof(ifreq)) == -1)
582 eprintf("setsockopt SO_BINDTODEVICE:");
583 iptoaddr(&addr, IP(255, 255, 255, 255), 68);
584 if (bind(sock, &addr, sizeof(addr)) != 0)
585 eprintf("bind:");
586 ioctl(sock, SIOCGIFHWADDR, &ifreq);
587 memcpy(hwaddr, ifreq.ifr_hwaddr.sa_data, sizeof(ifreq.ifr_hwaddr.sa_data));
588 if (!cid[0])
589 memcpy(cid, hwaddr, sizeof(cid));
590
591 if ((rnd = open("/dev/urandom", O_RDONLY)) == -1)
592 eprintf("open /dev/urandom:");
593 if (read(rnd, xid, sizeof(xid)) != (ssize_t)sizeof(xid))
594 eprintf("read /dev/urandom:");
595 close(rnd);
596
597 for (i = 0; i < LEN(timers); ++i) {
598 timers[i] = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC);
599 if (timers[i] == -1)
600 eprintf("timerfd_create:");
601 }
602
603 starttime = time(NULL);
604 run();
605
606 return 0;
607}