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+}