master xplshn/aruu / cmd / net / sdhcp.c
  1/* sdhcp: simple DHCP client, ported from git.2f30.org/sdhcp */
  2/* See LICENSE file for copyright and license details */
  3#include "arg.h"
  4#include "util.h"
  5#include "wexec.h"
  6
  7#include <errno.h>
  8#include <fcntl.h>
  9#include <limits.h>
 10#include <net/if.h>
 11#include <net/route.h>
 12#include <netinet/in.h>
 13#include <poll.h>
 14#include <signal.h>
 15#include <stdint.h>
 16#include <stdio.h>
 17#include <stdlib.h>
 18#include <string.h>
 19#include <sys/ioctl.h>
 20#include <sys/socket.h>
 21#include <sys/timerfd.h>
 22#include <time.h>
 23#include <unistd.h>
 24
 25/* bootp packet layout: fields are byte arrays to avoid alignment/endian issues
 26 */
 27struct Bootp {
 28  unsigned char op[1];
 29  unsigned char htype[1];
 30  unsigned char hlen[1];
 31  unsigned char hops[1];
 32  unsigned char xid[4];
 33  unsigned char secs[2];
 34  unsigned char flags[2];
 35  unsigned char ciaddr[4];
 36  unsigned char yiaddr[4];
 37  unsigned char siaddr[4];
 38  unsigned char giaddr[4];
 39  unsigned char chaddr[16];
 40  unsigned char sname[64];
 41  unsigned char file[128];
 42  unsigned char magic[4];
 43  unsigned char optdata[312 - 4];
 44};
 45
 46enum {
 47  DHCP_DISCOVER = 1,
 48  DHCP_OFFER,
 49  DHCP_REQUEST,
 50  DHCP_DECLINE,
 51  DHCP_ACK,
 52  DHCP_NAK,
 53  DHCP_RELEASE,
 54  DHCP_INFORM,
 55  /* internal sentinel values returned by dhcprecv */
 56  TIMEOUT0 = 200,
 57  TIMEOUT1,
 58  TIMEOUT2,
 59
 60  BOOT_REQUEST = 1,
 61  BOOT_REPLY   = 2,
 62  /* bootp flags */
 63  F_BROADCAST = 1 << 15,
 64
 65  /* bootp options */
 66  OB_PAD        = 0,
 67  OB_MASK       = 1,
 68  OB_ROUTER     = 3,
 69  OB_NAMESERVER = 5,
 70  OB_DNSSERVER  = 6,
 71  OB_HOSTNAME   = 12,
 72  OB_BADDR      = 28,
 73  /* DHCP-specific options */
 74  OD_IPADDR        = 50, /* 0x32 */
 75  OD_LEASE         = 51,
 76  OD_OVERLOAD      = 52,
 77  OD_TYPE          = 53, /* 0x35 */
 78  OD_SERVERID      = 54, /* 0x36 */
 79  OD_PARAMS        = 55, /* 0x37 */
 80  OD_MESSAGE       = 56,
 81  OD_MAXMSG        = 57,
 82  OD_RENEWALTIME   = 58,
 83  OD_REBINDINGTIME = 59,
 84  OD_VENDORCLASS   = 60,
 85  OD_CLIENTID      = 61, /* 0x3d */
 86  OD_TFTPSERVER    = 66,
 87  OD_BOOTFILE      = 67,
 88  OB_END           = 255,
 89};
 90
 91enum { BROADCAST, UNICAST };
 92
 93static struct Bootp  bp;
 94static unsigned char magic[] = {99, 130, 83, 99};
 95
 96/* conf */
 97static unsigned char xid[sizeof(bp.xid)];
 98static unsigned char hwaddr[16];
 99static char          hostname[HOST_NAME_MAX + 1];
100static time_t        starttime;
101static char         *ifname = "eth0";
102static unsigned char cid[16];
103static char         *program = "";
104static int           sock, timers[3];
105/* negotiated lease values */
106static unsigned char server[4];
107static unsigned char client[4];
108static unsigned char mask[4];
109static unsigned char router[4];
110static unsigned char dns[4];
111
112/* flags: default on=1, foreground=0 */
113static int dflag = 1;
114static int iflag = 1;
115static int fflag = 0;
116
117#define IP(a, b, c, d)                                                                             \
118  (unsigned char[4])                                                                               \
119  {                                                                                                \
120    a, b, c, d                                                                                     \
121  }
122
123/* write src as n-byte big-endian into dst */
124static void
125hnput(unsigned char *dst, uint32_t src, size_t n)
126{
127  unsigned int i;
128
129  for (i = 0; n--; i++)
130    dst[i] = (src >> (n * 8)) & 0xff;
131}
132
133static struct sockaddr *
134iptoaddr(struct sockaddr *ifaddr, unsigned char ip[4], int port)
135{
136  struct sockaddr_in *in = (struct sockaddr_in *)ifaddr;
137
138  in->sin_family = AF_INET;
139  in->sin_port   = htons(port);
140  memcpy(&(in->sin_addr), ip, sizeof(in->sin_addr));
141
142  return ifaddr;
143}
144
145/* sendto UDP wrapper: sends n bytes of data to bootp server port on ip */
146static ssize_t
147udpsend(unsigned char ip[4], int fd, void *data, size_t n)
148{
149  struct sockaddr addr;
150  socklen_t       addrlen = sizeof(addr);
151  ssize_t         sent;
152
153  iptoaddr(&addr, ip, 67); /* bootp server */
154  if ((sent = sendto(fd, data, n, 0, &addr, addrlen)) == -1)
155    eprintf("sendto:");
156
157  return sent;
158}
159
160/* recvfrom UDP wrapper: receives into data from bootp client port */
161static ssize_t
162udprecv(unsigned char ip[4], int fd, void *data, size_t n)
163{
164  struct sockaddr addr;
165  socklen_t       addrlen = sizeof(addr);
166  ssize_t         r;
167
168  iptoaddr(&addr, ip, 68); /* bootp client */
169  if ((r = recvfrom(fd, data, n, 0, &addr, &addrlen)) == -1)
170    eprintf("recvfrom:");
171
172  return r;
173}
174
175static void
176setip(unsigned char ip[4], unsigned char mask[4], unsigned char gateway[4])
177{
178  struct ifreq   ifreq;
179  struct rtentry rtreq;
180  int            fd;
181
182  memset(&ifreq, 0, sizeof(ifreq));
183  memset(&rtreq, 0, sizeof(rtreq));
184
185  strlcpy(ifreq.ifr_name, ifname, IF_NAMESIZE);
186  iptoaddr(&(ifreq.ifr_addr), ip, 0);
187  if ((fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP)) == -1)
188    eprintf("can't set ip, socket:");
189  ioctl(fd, SIOCSIFADDR, &ifreq);
190  iptoaddr(&(ifreq.ifr_netmask), mask, 0);
191  ioctl(fd, SIOCSIFNETMASK, &ifreq);
192  ifreq.ifr_flags = IFF_UP | IFF_RUNNING | IFF_BROADCAST | IFF_MULTICAST;
193  ioctl(fd, SIOCSIFFLAGS, &ifreq);
194  /* default gw */
195  rtreq.rt_flags = RTF_UP | RTF_GATEWAY;
196  iptoaddr(&(rtreq.rt_gateway), gateway, 0);
197  iptoaddr(&(rtreq.rt_genmask), IP(0, 0, 0, 0), 0);
198  iptoaddr(&(rtreq.rt_dst), IP(0, 0, 0, 0), 0);
199  ioctl(fd, SIOCADDRT, &rtreq);
200
201  close(fd);
202}
203
204/* copy contents of src path into dfd; silently skips missing files */
205static void
206appendfile(int dfd, char *src)
207{
208  char buf[BUFSIZ];
209  int  fd, n;
210
211  if ((fd = open(src, O_RDONLY)) == -1)
212    return;
213  while ((n = read(fd, buf, sizeof(buf))) > 0)
214    writeall(dfd, buf, n);
215  close(fd);
216}
217
218static void
219setdns(unsigned char ip[4])
220{
221  char buf[128];
222  int  fd, n;
223
224  if ((fd = creat("/etc/resolv.conf", 0644)) == -1) {
225    weprintf("can't change /etc/resolv.conf:");
226    return;
227  }
228  appendfile(fd, "/etc/resolv.conf.head");
229  n = snprintf(buf, sizeof(buf), "\nnameserver %d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]);
230  if (n > 0)
231    writeall(fd, buf, n);
232  appendfile(fd, "/etc/resolv.conf.tail");
233  close(fd);
234}
235
236/* scan b's option data for opt; copy up to n bytes into data */
237static void
238optget(struct Bootp *b, void *data, int opt, int n)
239{
240  unsigned char *p   = b->optdata;
241  unsigned char *top = ((unsigned char *)b) + sizeof(*b);
242  int            code, len;
243
244  while (p < top) {
245    code = *p++;
246    if (code == OB_PAD)
247      continue;
248    if (code == OB_END || p == top)
249      break;
250    len = *p++;
251    if (len > top - p)
252      break;
253    if (code == opt) {
254      memcpy(data, p, MIN(len, n));
255      break;
256    }
257    p += len;
258  }
259}
260
261/* append a raw-bytes option to p; return new write cursor */
262static unsigned char *
263optput(unsigned char *p, int opt, unsigned char *data, size_t len)
264{
265  *p++ = opt;
266  *p++ = (unsigned char)len;
267  memcpy(p, data, len);
268
269  return p + len;
270}
271
272/* append a big-endian numeric option to p; return new write cursor */
273static unsigned char *
274hnoptput(unsigned char *p, int opt, uint32_t data, size_t len)
275{
276  *p++ = opt;
277  *p++ = (unsigned char)len;
278  hnput(p, data, len);
279
280  return p + len;
281}
282
283static void
284dhcpsend(int type, int how)
285{
286  unsigned char *ip, *p;
287
288  memset(&bp, 0, sizeof(bp));
289  hnput(bp.op, BOOT_REQUEST, 1);
290  hnput(bp.htype, 1, 1);
291  hnput(bp.hlen, 6, 1);
292  memcpy(bp.xid, xid, sizeof(xid));
293  hnput(bp.flags, F_BROADCAST, sizeof(bp.flags));
294  hnput(bp.secs, time(NULL) - starttime, sizeof(bp.secs));
295  memcpy(bp.magic, magic, sizeof(bp.magic));
296  memcpy(bp.chaddr, hwaddr, sizeof(bp.chaddr));
297  p = bp.optdata;
298  p = hnoptput(p, OD_TYPE, type, 1);
299  p = optput(p, OD_CLIENTID, cid, sizeof(cid));
300  p = optput(p, OB_HOSTNAME, (unsigned char *)hostname, strlen(hostname));
301
302  switch (type) {
303    case DHCP_DISCOVER:
304      break;
305    case DHCP_REQUEST:
306      p = optput(p, OD_IPADDR, client, sizeof(client));
307      p = optput(p, OD_SERVERID, server, sizeof(server));
308      break;
309    case DHCP_RELEASE:
310      memcpy(bp.ciaddr, client, sizeof(client));
311      p = optput(p, OD_IPADDR, client, sizeof(client));
312      p = optput(p, OD_SERVERID, server, sizeof(server));
313      break;
314  }
315  *p++ = OB_END;
316
317  ip = (how == BROADCAST) ? IP(255, 255, 255, 255) : server;
318  udpsend(ip, sock, &bp, p - (unsigned char *)&bp);
319}
320
321/* block on poll; return DHCP message type or a TIMEOUT* sentinel */
322static int
323dhcprecv(void)
324{
325  unsigned char type = 0;
326  uint64_t      n;
327  struct pollfd pfd[] = {
328      {.fd = sock, .events = POLLIN},
329      {.fd = timers[0], .events = POLLIN},
330      {.fd = timers[1], .events = POLLIN},
331      {.fd = timers[2], .events = POLLIN},
332  };
333
334  if (poll(pfd, LEN(pfd), -1) == -1)
335    eprintf("poll:");
336  if (pfd[0].revents) {
337    memset(&bp, 0, sizeof(bp));
338    udprecv(IP(255, 255, 255, 255), sock, &bp, sizeof(bp));
339    optget(&bp, &type, OD_TYPE, sizeof(type));
340    return type;
341  }
342  if (pfd[1].revents) {
343    type = TIMEOUT0;
344    read(timers[0], &n, sizeof(n));
345  }
346  if (pfd[2].revents) {
347    type = TIMEOUT1;
348    read(timers[1], &n, sizeof(n));
349  }
350  if (pfd[3].revents) {
351    type = TIMEOUT2;
352    read(timers[2], &n, sizeof(n));
353  }
354  return type;
355}
356
357static void
358acceptlease(void)
359{
360  char buf[128];
361
362  if (iflag)
363    setip(client, mask, router);
364  if (dflag)
365    setdns(dns);
366  if (*program) {
367    snprintf(buf, sizeof(buf), "%d.%d.%d.%d", server[0], server[1], server[2], server[3]);
368    setenv("SERVER", buf, 1);
369    snprintf(buf, sizeof(buf), "%d.%d.%d.%d", client[0], client[1], client[2], client[3]);
370    setenv("CLIENT", buf, 1);
371    snprintf(buf, sizeof(buf), "%d.%d.%d.%d", mask[0], mask[1], mask[2], mask[3]);
372    setenv("MASK", buf, 1);
373    snprintf(buf, sizeof(buf), "%d.%d.%d.%d", router[0], router[1], router[2], router[3]);
374    setenv("ROUTER", buf, 1);
375    snprintf(buf, sizeof(buf), "%d.%d.%d.%d", dns[0], dns[1], dns[2], dns[3]);
376    setenv("DNS", buf, 1);
377    wsystem(program);
378  }
379}
380
381static void
382settimeout(int n, const struct itimerspec *ts)
383{
384  if (timerfd_settime(timers[n], 0, ts, NULL) < 0)
385    eprintf("timerfd_settime:");
386}
387
388/* set ts to expire halfway to the remaining time on timer n, minimum 60s */
389static void
390calctimeout(int n, struct itimerspec *ts)
391{
392  if (timerfd_gettime(timers[n], ts) < 0)
393    eprintf("timerfd_gettime:");
394  ts->it_value.tv_nsec /= 2;
395  if (ts->it_value.tv_sec % 2)
396    ts->it_value.tv_nsec += 500000000;
397  ts->it_value.tv_sec /= 2;
398  if (ts->it_value.tv_sec < 60) {
399    ts->it_value.tv_sec  = 60;
400    ts->it_value.tv_nsec = 0;
401  }
402}
403
404/* RFC 2131 DHCP state machine */
405static void
406run(void)
407{
408  int               forked  = 0, t;
409  struct itimerspec timeout = {0};
410  uint32_t          renewaltime, rebindingtime, lease;
411
412Init:
413  dhcpsend(DHCP_DISCOVER, BROADCAST);
414  timeout.it_value.tv_sec  = 1;
415  timeout.it_value.tv_nsec = 0;
416  settimeout(0, &timeout);
417  goto Selecting;
418Selecting:
419  for (;;) {
420    switch (dhcprecv()) {
421      case DHCP_OFFER:
422        memcpy(client, bp.yiaddr, sizeof(client));
423        optget(&bp, server, OD_SERVERID, sizeof(server));
424        goto Requesting;
425      case TIMEOUT0:
426        goto Init;
427    }
428  }
429Requesting:
430  for (t = 4; t <= 64; t *= 2) {
431    dhcpsend(DHCP_REQUEST, BROADCAST);
432    timeout.it_value.tv_sec = t;
433    settimeout(0, &timeout);
434    for (;;) {
435      switch (dhcprecv()) {
436        case DHCP_ACK:
437          goto Bound;
438        case DHCP_NAK:
439          goto Init;
440        case TIMEOUT0:
441          break;
442        default:
443          continue;
444      }
445      break;
446    }
447  }
448  /* no ACK after several DHCPREQUEST attempts */
449  goto Init;
450Bound:
451  optget(&bp, mask, OB_MASK, sizeof(mask));
452  optget(&bp, router, OB_ROUTER, sizeof(router));
453  optget(&bp, dns, OB_DNSSERVER, sizeof(dns));
454  optget(&bp, &renewaltime, OD_RENEWALTIME, sizeof(renewaltime));
455  optget(&bp, &rebindingtime, OD_REBINDINGTIME, sizeof(rebindingtime));
456  optget(&bp, &lease, OD_LEASE, sizeof(lease));
457  renewaltime   = ntohl(renewaltime);
458  rebindingtime = ntohl(rebindingtime);
459  lease         = ntohl(lease);
460  acceptlease();
461  puts("Bound. Network configured.");
462  if (!fflag && !forked) {
463    if (fork())
464      exit(0);
465    forked = 1;
466  }
467  timeout.it_value.tv_sec = renewaltime;
468  settimeout(0, &timeout);
469  timeout.it_value.tv_sec = rebindingtime;
470  settimeout(1, &timeout);
471  timeout.it_value.tv_sec = lease;
472  settimeout(2, &timeout);
473  for (;;) {
474    switch (dhcprecv()) {
475      case TIMEOUT0: /* t1: enter renewing */
476        goto Renewing;
477      case TIMEOUT1: /* t2: enter rebinding */
478        goto Rebinding;
479      case TIMEOUT2: /* lease expired */
480        goto Init;
481    }
482  }
483Renewing:
484  dhcpsend(DHCP_REQUEST, UNICAST);
485  calctimeout(1, &timeout);
486  settimeout(0, &timeout);
487  for (;;) {
488    switch (dhcprecv()) {
489      case DHCP_ACK:
490        goto Bound;
491      case TIMEOUT0: /* resend unicast */
492        goto Renewing;
493      case TIMEOUT1: /* t2 elapsed */
494        goto Rebinding;
495      case TIMEOUT2:
496      case DHCP_NAK:
497        goto Init;
498    }
499  }
500Rebinding:
501  calctimeout(2, &timeout);
502  settimeout(0, &timeout);
503  dhcpsend(DHCP_REQUEST, BROADCAST);
504  for (;;) {
505    switch (dhcprecv()) {
506      case DHCP_ACK:
507        goto Bound;
508      case TIMEOUT0: /* resend broadcast */
509        goto Rebinding;
510      case TIMEOUT2: /* lease expired */
511      case DHCP_NAK:
512        goto Init;
513    }
514  }
515}
516
517static void
518cleanexit(int unused)
519{
520  (void)unused;
521  dhcpsend(DHCP_RELEASE, UNICAST);
522  _exit(0);
523}
524
525static void
526usage(void)
527{
528  eprintf("usage: %s [-d] [-e program] [-f] [-i] [ifname] [clientid]\n", argv0);
529}
530
531// ?man sdhcp: minimal DHCP client
532// ?man arguments: [ifname] [clientid]
533// ?man obtain an IPv4 lease via DHCP on the given interface
534int
535main(int argc, char *argv[])
536{
537  struct ifreq    ifreq;
538  struct sockaddr addr;
539  int             bcast = 1;
540  int             rnd;
541  size_t          i;
542
543  ARGBEGIN
544  {
545    case 'd': /* don't update /etc/resolv.conf */
546      dflag = 0;
547      break;
548    case 'e': /* exec program on each lease */
549      program = EARGF(usage());
550      break;
551    case 'f': /* stay in foreground */
552      fflag = 1;
553      break;
554    case 'i': /* don't configure IP address */
555      iflag = 0;
556      break;
557    default:
558      usage();
559      break;
560  }
561  ARGEND
562
563  if (argc)
564    ifname = argv[0];
565  if (argc >= 2)
566    strlcpy((char *)cid, argv[1], sizeof(cid));
567
568  memset(&ifreq, 0, sizeof(ifreq));
569  signal(SIGTERM, cleanexit);
570
571  if (gethostname(hostname, sizeof(hostname)) == -1)
572    eprintf("gethostname:");
573
574  if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
575    eprintf("socket:");
576  if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &bcast, sizeof(bcast)) == -1)
577    eprintf("setsockopt SO_BROADCAST:");
578
579  strlcpy(ifreq.ifr_name, ifname, IF_NAMESIZE);
580  ioctl(sock, SIOCGIFINDEX, &ifreq);
581  if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, &ifreq, sizeof(ifreq)) == -1)
582    eprintf("setsockopt SO_BINDTODEVICE:");
583  iptoaddr(&addr, IP(255, 255, 255, 255), 68);
584  if (bind(sock, &addr, sizeof(addr)) != 0)
585    eprintf("bind:");
586  ioctl(sock, SIOCGIFHWADDR, &ifreq);
587  memcpy(hwaddr, ifreq.ifr_hwaddr.sa_data, sizeof(ifreq.ifr_hwaddr.sa_data));
588  if (!cid[0])
589    memcpy(cid, hwaddr, sizeof(cid));
590
591  if ((rnd = open("/dev/urandom", O_RDONLY)) == -1)
592    eprintf("open /dev/urandom:");
593  if (read(rnd, xid, sizeof(xid)) != (ssize_t)sizeof(xid))
594    eprintf("read /dev/urandom:");
595  close(rnd);
596
597  for (i = 0; i < LEN(timers); ++i) {
598    timers[i] = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC);
599    if (timers[i] == -1)
600      eprintf("timerfd_create:");
601  }
602
603  starttime = time(NULL);
604  run();
605
606  return 0;
607}