sdhcp.c - sdhcp - simple dhcp client
(HTM) git clone git://git.codemadness.org/sdhcp
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) 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 }