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 }