sdhcp.c - sdhcp - simple dhcp client | |
git clone git://git.codemadness.org/sdhcp | |
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[… | |
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_MUL… | |
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(host… | |
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], ser… | |
352 setenv("SERVER", buf, 1); | |
353 snprintf(buf, sizeof(buf), "%d.%d.%d.%d", client[0], cli… | |
354 setenv("CLIENT", buf, 1); | |
355 snprintf(buf, sizeof(buf), "%d.%d.%d.%d", mask[0], mask[… | |
356 setenv("MASK", buf, 1); | |
357 snprintf(buf, sizeof(buf), "%d.%d.%d.%d", router[0], rou… | |
358 setenv("ROUTER", buf, 1); | |
359 snprintf(buf, sizeof(buf), "%d.%d.%d.%d", dns[0], dns[1]… | |
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… | |
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 IN… | |
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(rebindingtim… | |
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] [clienti… | |
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(bc… | |
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… | |
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… | |
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 tran… | |
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 } |