sdhcp

simple dhcp client
git clone git://git.2f30.org/sdhcp.git
Log | Files | Refs | LICENSE

sdhcp.c (13364B)


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