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 }