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