]> git.ipfire.org Git - thirdparty/dhcpcd.git/commitdiff
Add dhcpcd-3 re-write
authorRoy Marples <roy@marples.name>
Mon, 27 Nov 2006 20:23:22 +0000 (20:23 +0000)
committerRoy Marples <roy@marples.name>
Mon, 27 Nov 2006 20:23:22 +0000 (20:23 +0000)
25 files changed:
ChangeLog [new file with mode: 0644]
Makefile [new file with mode: 0644]
arp.c [new file with mode: 0644]
arp.h [new file with mode: 0644]
client.c [new file with mode: 0644]
client.h [new file with mode: 0644]
common.c [new file with mode: 0644]
common.h [new file with mode: 0644]
configure.c [new file with mode: 0644]
configure.h [new file with mode: 0644]
dhcp.c [new file with mode: 0644]
dhcp.h [new file with mode: 0644]
dhcpcd.8 [new file with mode: 0644]
dhcpcd.c [new file with mode: 0644]
dhcpcd.h [new file with mode: 0644]
dhcpcd.sh [new file with mode: 0755]
interface.c [new file with mode: 0644]
interface.h [new file with mode: 0644]
logger.c [new file with mode: 0644]
logger.h [new file with mode: 0644]
pathnames.h [new file with mode: 0644]
signals.c [new file with mode: 0644]
signals.h [new file with mode: 0644]
socket.c [new file with mode: 0644]
socket.h [new file with mode: 0644]

diff --git a/ChangeLog b/ChangeLog
new file mode 100644 (file)
index 0000000..3acca3d
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,21 @@
+dhcpcd-3.0.0
+A complete rewrite by Roy Marples <uberlord@gentoo.org>
+
+Functional changes since version 2:
+We now support FreeBSD as well as Linux.
+RFC 3004 User Class support has been added.
+RFC 3442 Classless Static Routes support has been added
+(thanks to apexman for helping).
+Options -r, -e, -v, -w, -B, -C, -D, -L, -S have been dropped.
+-a now means "do arp" instead of "don't arp".
+-o has been dropped, but we never bring down the interface anymore.
+IP address is now required for the -s option.
+-G no longer takes an IP address as an replacement gateway.
+The .cache file is no longer created or used.
+Default script is now /etc/dhcpcd.sh instead of /etc/dhcpc/dhcpcd.exe.
+The .info file has changed slightly to better support mulitple entries with
+more than one entity, such as route now having network,genmask and gateway.
+We no longer create and restore .sv files as it's up to the interface
+manager to restore them correctly as >1 dhcp client may be running.
+
+For ChangeLog for prior versions can be found in their tarballs.
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..5e9bb5f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,45 @@
+# Should work for both GNU make and BSD mke
+
+VERSION = 3.0.0_alpha3
+
+CFLAGS ?= -Wall -O2 -pedantic -std=gnu99
+
+DESTDIR =
+SBINDIR = $(DESTDIR)/sbin
+MANDIR = $(DESDIR)/usr/share/man
+
+SBIN_TARGETS = dhcpcd
+MAN8_TARGETS = dhcpcd.8
+TARGET = $(SBIN_TARGETS)
+
+dhcpcd_H = version.h
+dhcpcd_OBJS = arp.o client.o common.o configure.o dhcp.o dhcpcd.o \
+               interface.o logger.o signals.o socket.o
+
+dhcpcd: $(dhcpcd_H) $(dhcpcd_OBJS)
+       $(CC) $(LDFLAGS) $(dhcpcd_OBJS) -o dhcpcd
+
+version.h:
+       echo '#define VERSION "$(VERSION)"' > version.h
+
+$(dhcpcd_OBJS): 
+       $(CC) $(CFLAGS) -c $*.c
+
+all: $(TARGET)
+
+install: $(TARGET)
+       $(INSTALL) -m 0755 -d $(SBINDIR)
+       $(INSTALL) -m 0755 $(SBIN_TARGETS) $(SBINDIR)
+       $(INSTALL) -m 0755 -d $(MANDIR)/man8
+       $(INSTALL) -m 0755 $(MAN8_TARGETS) $(MANDIR)/man8
+
+clean:
+       rm -f $(TARGET) $(dhcpcd_H) *.o *~
+
+dist:
+       $(INSTALL) -m 0755 -d /tmp/dhcpcd-$(VERSION)
+       cp -RPp . /tmp/dhcpcd-$(VERSION)
+       $(MAKE) -C /tmp/dhcpcd-$(VERSION) clean
+       rm -f /tmp/dhcpcd-$(VERSION)/*.bz2
+       tar cvjpf dhcpcd-$(VERSION).tar.bz2 -C /tmp dhcpcd-$(VERSION)
+       rm -rf /tmp/dhcpcd-$(VERSION)
diff --git a/arp.c b/arp.c
new file mode 100644 (file)
index 0000000..f5c0830
--- /dev/null
+++ b/arp.c
@@ -0,0 +1,157 @@
+/*
+ * dhcpcd - DHCP client daemon -
+ * Copyright (C) 2006 Roy Marples <uberlord@gentoo.org>
+ * 
+ * dhcpcd is an RFC2131 compliant DHCP client daemon.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* OK, a lot of this was lifting from iputils as the existing code
+   for dhcpcd was kinda klunky and had some issues */
+
+#define _BSD_SOURCE
+
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netinet/in_systm.h>
+#ifdef __linux
+#include <netinet/ether.h>
+#include <netpacket/packet.h>
+#endif
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "interface.h"
+#include "logger.h"
+#include "socket.h"
+
+/* Longer is safer and slower - 2 seconds seems a happy medium */
+#define TIMEOUT 2 
+
+/* Linux does not seem to define these handy macros */
+#ifndef ar_sha
+#define ar_sha(ap) (((unsigned char *) ((ap) + 1)) + 0)
+#define ar_spa(ap) (((unsigned char *) ((ap) + 1)) + (ap)->ar_hln)
+#define ar_tha(ap) (((unsigned char *) ((ap) + 1)) + (ap)->ar_hln + (ap)->ar_pln)
+#define ar_tpa(ap) (((unsigned char *) ((ap) + 1)) + 2 * (ap)->ar_hln + (ap)->ar_pln)
+
+#define arphdr_len2(ar_hln, ar_pln) (sizeof (struct arphdr) + 2 * (ar_hln) + 2 * (ar_pln))
+#define arphdr_len(ap) (arphdr_len2 ((ap)->ar_hln, (ap)->ar_pln))
+#endif
+
+int arp_check (interface_t *iface, struct in_addr address)
+{
+  if (! iface->arpable)
+    {
+      logger (LOG_DEBUG, "arp_check: interface `%s' is not ARPable",
+             iface->name);
+      return 0;
+    }
+
+  unsigned char buf[256];
+  struct arphdr *ah = (struct arphdr *) buf;
+
+  memset (&buf, 0, sizeof (buf));
+
+  ah->ar_hrd = htons (ARPHRD_ETHER);
+  ah->ar_pro = htons (ETHERTYPE_IP);
+  ah->ar_hln = ETHER_ADDR_LEN;
+  ah->ar_pln = sizeof (struct in_addr);
+  ah->ar_op = htons (ARPOP_REQUEST);
+  memcpy (ar_sha (ah), &iface->ethernet_address, ah->ar_hln);
+  memcpy (ar_tpa (ah), &address, ah->ar_pln);
+
+  logger (LOG_INFO, "checking %s is available on attached networks", inet_ntoa
+         (address));
+
+  open_socket (iface, true);
+  send_packet (iface, ETHERTYPE_ARP, (unsigned char *) &buf, arphdr_len(ah));
+
+  unsigned char reply[4096];
+  int bytes;
+  unsigned char buffer[iface->buffer_length];
+
+  struct timeval tv;
+  long timeout = 0;
+  fd_set rset;
+
+  timeout = uptime() + TIMEOUT;
+  while (1)
+    {
+      tv.tv_sec = timeout - uptime ();
+      tv.tv_usec = 0;
+
+      if (tv.tv_sec < 1)
+       break; /* Time out */
+
+      FD_ZERO (&rset);
+      FD_SET (iface->fd, &rset);
+
+      if (select (iface->fd + 1, &rset, NULL, NULL, &tv) == 0)
+       break;
+
+      if (! FD_ISSET (iface->fd, &rset))
+       continue;
+
+      memset (buffer, 0, sizeof (buffer));
+      int buflen = sizeof (buffer);
+      int bufpos = -1;
+
+      while (bufpos != 0)
+       {
+         memset (&reply, 0, sizeof (reply));
+         if ((bytes = get_packet (iface, (unsigned char *) &reply, buffer,
+                                  &buflen, &bufpos)) < 0)
+           break;
+
+         ah = (struct arphdr *) reply;
+
+         /* Only these types are recognised */
+         if (ah->ar_op != htons(ARPOP_REPLY)
+             || ah->ar_hrd != htons (ARPHRD_ETHER))
+           continue;
+
+         /* Protocol must be IP. */
+         if (ah->ar_pro != htons (ETHERTYPE_IP))
+           continue;
+         if (ah->ar_pln != sizeof (struct in_addr))
+           continue;
+
+         if (ah->ar_hln != ETHER_ADDR_LEN)
+           continue;
+         if (bytes < sizeof (*ah) + 2 * (4 + ah->ar_hln))
+           continue;
+
+         logger (LOG_ERR, "ARPOP_REPLY received from %s (%s)",
+                 inet_ntoa (* (struct in_addr *) ar_spa (ah)),
+                 ether_ntoa ((struct ether_addr *) ar_sha (ah)));
+         close (iface->fd);
+         iface->fd = -1;
+         return 1;
+       }
+    }
+
+  close (iface->fd);
+  iface->fd = -1;
+  return 0;
+}
+
diff --git a/arp.h b/arp.h
new file mode 100644 (file)
index 0000000..4dfd007
--- /dev/null
+++ b/arp.h
@@ -0,0 +1,29 @@
+/*
+ * dhcpcd - DHCP client daemon -
+ * Copyright (C) 2005 - 2006 Roy Marples <uberlord@gentoo.org>
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef ARP_H
+#define ARP_H
+
+#include <netinet/in.h>
+
+#include "interface.h"
+
+int arp_check (interface_t *iface, struct in_addr address);
+
+#endif
diff --git a/client.c b/client.c
new file mode 100644 (file)
index 0000000..0ed7fb5
--- /dev/null
+++ b/client.c
@@ -0,0 +1,554 @@
+/*
+ * dhcpcd - DHCP client daemon -
+ * Copyright (C) 2006 Roy Marples <uberlord@gentoo.org>
+ * 
+ * dhcpcd is an RFC2131 compliant DHCP client daemon.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <sys/select.h>
+#include <arpa/inet.h>
+#ifdef __linux__
+#include <netinet/ether.h>
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "arp.h"
+#include "common.h"
+#include "configure.h"
+#include "dhcp.h"
+#include "dhcpcd.h"
+#include "interface.h"
+#include "logger.h"
+#include "signals.h"
+#include "socket.h"
+
+/* This is out mini timeout.
+   Basically we resend the last request every TIMEOUT_MINI seconds. */
+#define TIMEOUT_MINI           3
+
+#define STATE_INIT             0
+#define STATE_REQUESTING       1
+#define STATE_BOUND            2
+#define STATE_RENEWING         3
+#define STATE_REBINDING                4
+#define STATE_REBOOT           5
+#define STATE_RENEW_REQUESTED  6
+#define STATE_RELEASED         7
+
+#define SOCKET_CLOSED          0
+#define SOCKET_OPEN            1
+
+#define SOCKET_MODE(_mode) \
+ if (iface->fd >= 0) close (iface->fd); \
+iface->fd = -1; \
+if (_mode == SOCKET_OPEN) \
+if (open_socket (iface, false) < 0) { retval = -1; goto eexit; } \
+mode = _mode;
+
+#define SEND_MESSAGE(_type) \
+ memcpy (&last_dhcp, &dhcp, sizeof (struct dhcp_t)); \
+last_type = _type; \
+send_message (iface, &dhcp, xid, _type, options);
+
+static int daemonise (char *pidfile)
+{
+  FILE *fp;
+
+  if (daemon (0, 0) < 0)
+    {
+      logger (LOG_ERR, "unable to daemonise: %s", strerror (errno));
+      return -1;
+    }
+
+  if ((fp = fopen (pidfile, "w")) == NULL)
+    {
+      logger (LOG_ERR, "fopen `%s': %m", pidfile);
+      return -1;
+    }
+
+  fprintf (fp, "%u\n", getpid ());
+  fclose (fp);
+
+  return 0;
+}
+
+unsigned long random_xid (void)
+{
+  static int initialized;
+
+  if (! initialized)
+    {
+      int fd;
+      unsigned long seed;
+
+      fd = open ("/dev/urandom", 0);
+      if (fd < 0 || read (fd,  &seed, sizeof(seed)) < 0)
+       {
+         logger (LOG_WARNING, "Could not load seed from /dev/urandom: %m");
+         seed = time (0);
+       }
+      if (fd >= 0)
+       close(fd);
+
+      srand(seed);
+      initialized++;
+    }
+
+  return rand();
+}
+
+/* This state machine is based on the one from udhcpc
+   written by Russ Dill */
+int dhcp_run (options_t *options)
+{
+  interface_t *iface;
+  int mode = SOCKET_CLOSED;
+  int state = STATE_INIT;
+  struct timeval tv;
+  int xid = 0;
+  unsigned long timeout = 0;
+  fd_set rset;
+  int maxfd;
+  int retval;
+  dhcpmessage_t message;
+  dhcp_t dhcp;
+  dhcp_t last_dhcp;
+  int type;
+  int last_type = DHCP_REQUEST;
+  bool daemonised = false;
+  unsigned long start = 0;
+  int sig;
+  unsigned char *buffer = NULL;
+  int buffer_len = 0;
+  int buffer_pos = 0;
+
+  if (! options || (iface = (read_interface (options->interface,
+                                            options->metric))) == NULL)
+    return -1;
+
+  /* Remove all existing addresses.
+     After all, we ARE a DHCP client whose job it is to configure the
+     interface. We only do this on start, so persistent addresses can be added
+     afterwards by the user if needed.
+     */
+  flush_addresses (iface->name);
+
+  memset (&dhcp, 0, sizeof (dhcp_t));
+  memset (&last_dhcp, 0, sizeof (dhcp_t));
+
+  dhcp.leasetime = options->leasetime;
+  strcpy (dhcp.classid, options->classid);
+  if (options->clientid[0])
+    strcpy (dhcp.clientid, options->clientid);
+  else
+    sprintf (dhcp.clientid, "%s", ether_ntoa (&iface->ethernet_address));
+
+  if (options->requestaddress.s_addr != 0)
+    memcpy (&dhcp.address, &options->requestaddress, sizeof (struct in_addr));
+
+  signal_setup ();
+
+  while (1)
+    {
+      int timeout_secs = timeout - uptime();
+      tv.tv_sec = timeout - uptime ();
+      tv.tv_usec = 0;
+
+      maxfd = signal_fd_set (&rset, iface->fd);
+
+      if (timeout_secs > 0 || (options->timeout == 0 &&
+                              (state != STATE_INIT || xid)))
+       {
+         if (options->timeout == 0)
+           {
+             logger (LOG_DEBUG, "waiting on select for infinity");
+             retval = select (maxfd + 1, &rset, NULL, NULL, NULL);
+           }
+         else
+           {
+             logger (LOG_DEBUG, "waiting on select for %d seconds",
+                     timeout_secs);
+             /* If we're waiting for a reply, then we re-send the last
+                DHCP request periodically in-case of a bad line */
+             if (iface->fd == -1)
+               {
+                 tv.tv_sec = timeout_secs;
+                 tv.tv_usec = 0;
+                 retval = select (maxfd + 1, &rset, NULL, NULL, &tv);
+               }
+             else
+               {
+                 while (timeout_secs > 0)
+                   {
+                     tv.tv_sec = TIMEOUT_MINI;
+                     tv.tv_usec = 0;
+                     retval = select (maxfd + 1, &rset, NULL, NULL, &tv);
+                     if (retval != 0)
+                       break;
+                     send_message (iface, &last_dhcp, xid, last_type, options);
+                     timeout_secs -= TIMEOUT_MINI;
+                   }
+               }
+           }
+       }
+      else
+       retval = 0;
+
+      /* We should always handle our signals first */
+      if (retval > 0 && (sig = signal_read (&rset)))
+       {
+         switch (sig)
+           {
+           case SIGINT:
+             logger (LOG_INFO, "receieved SIGINT, stopping");
+             retval = 0;
+             goto eexit;
+
+           case SIGTERM:
+             logger (LOG_INFO, "receieved SIGTERM, stopping");
+             retval = 0;
+             goto eexit;
+
+           case SIGALRM:
+
+             logger (LOG_INFO, "receieved SIGALRM, renewing lease");
+             switch (state)
+               {
+               case STATE_BOUND:
+                 SOCKET_MODE (SOCKET_OPEN);
+               case STATE_RENEWING:
+               case STATE_REBINDING:
+                 state = STATE_RENEW_REQUESTED;
+                 break;
+               case STATE_RENEW_REQUESTED:
+               case STATE_REQUESTING:
+               case STATE_RELEASED:
+                 state = STATE_INIT;
+                 break;
+               }
+
+             timeout = 0;
+             xid = 0;
+             break;
+
+           case SIGHUP:
+             if (state == STATE_BOUND || state == STATE_RENEWING
+                 || state == STATE_REBINDING)
+               {
+                 logger (LOG_INFO, "received SIGHUP, releasing lease");
+                 SOCKET_MODE (SOCKET_OPEN);
+                 xid = random_xid ();
+                 if ((open_socket (iface, false)) >= 0)
+                   SEND_MESSAGE (DHCP_RELEASE);
+                 SOCKET_MODE (SOCKET_CLOSED);
+                 unlink (iface->infofile);
+               }
+             else
+               logger (LOG_ERR,
+                       "receieved SIGUP, but no we have lease to release");
+             retval = 0;
+             goto eexit;
+
+           default:
+             logger (LOG_ERR,
+                     "received signal %d, but don't know what to do with it",
+                     sig);
+           }
+       }
+      else if (retval == 0) /* timed out */
+       {
+         switch (state)
+           {
+           case STATE_INIT:
+             if (iface->previous_address.s_addr != 0)
+               {
+                 logger (LOG_ERR, "lost lease");
+                 xid = 0;
+                 SOCKET_MODE (SOCKET_CLOSED);
+                 if (! options->persistent)
+                   {
+                     dhcp.address.s_addr = 0;
+                     dhcp.netmask.s_addr = 0;
+                     dhcp.broadcast.s_addr = 0;
+                     configure (options, iface, &dhcp);
+                   }
+                 if (! daemonised)
+                   {
+                     retval = -1;
+                     goto eexit;
+                   }
+                 break;
+               }
+
+             if (xid == 0)
+               xid = random_xid ();
+             else
+               {
+                 logger (LOG_ERR, "timed out");
+                 if (! daemonised)
+                   {
+                     retval = -1;
+                     goto eexit;
+                   }
+               }
+
+             timeout = uptime () + options->timeout;
+             if (dhcp.address.s_addr == 0)
+               logger (LOG_INFO, "broadcasting for lease");
+             else
+               logger (LOG_INFO, "broadcasting for lease of %s",
+                       inet_ntoa (dhcp.address));
+
+             SOCKET_MODE (SOCKET_OPEN);
+             SEND_MESSAGE (DHCP_DISCOVER);
+             break;
+           case STATE_BOUND:
+             state = STATE_RENEWING;
+           case STATE_RENEWING:
+             logger (LOG_INFO, "renewing lease of %s", inet_ntoa
+                     (dhcp.address));
+             SOCKET_MODE (SOCKET_OPEN);
+             xid = random_xid ();
+             SEND_MESSAGE (DHCP_REQUEST);
+             timeout = uptime() + (dhcp.rebindtime - dhcp.renewaltime);
+             state = STATE_REBINDING;
+             break;
+           case STATE_REBINDING:
+             logger (LOG_ERR, "lost lease, attemping to rebind");
+             xid = random_xid ();
+             SEND_MESSAGE (DHCP_DISCOVER);
+             timeout = uptime() + (dhcp.leasetime - dhcp.rebindtime);
+             state = STATE_INIT;
+             break;
+           case STATE_REQUESTING:
+           case STATE_RENEW_REQUESTED:
+             logger (LOG_ERR, "timed out");
+             if (! daemonised)
+               {
+                 retval = -1;
+                 goto eexit;
+               }
+
+             state = STATE_INIT;
+             timeout = uptime();
+             xid = 0;
+             SOCKET_MODE (SOCKET_OPEN);
+             break;
+
+           case STATE_RELEASED:
+             timeout = 0x7fffffff;
+             break;
+           }
+       }
+      else if (retval > 0 && mode != SOCKET_CLOSED && FD_ISSET(iface->fd, &rset))
+       {
+
+         /* Allocate our buffer space for BPF.
+            We cannot do this until we have opened our socket as we don't
+            know how much of a buffer we need until then. */
+         if (! buffer)
+           buffer = xmalloc (iface->buffer_length);
+         buffer_len = iface->buffer_length;
+         buffer_pos = -1;
+
+         /* We loop through until our buffer is empty.
+            The benefit is that if we get >1 DHCP packet in our buffer and
+            the first one fails for any reason, we can use the next. */
+
+         memset (&message, 0, sizeof (struct dhcpmessage_t));
+         int valid = 0;
+         while (buffer_pos != 0)
+           {
+             if (get_packet (iface, (unsigned char *) &message, buffer,
+                             &buffer_len, &buffer_pos) < 0)
+               break;
+
+             if (xid != message.xid)
+               {
+                 logger (LOG_ERR, "ignoring transaction %d as it's not ours (%d)",
+                         message.xid, xid);
+                 continue;
+               }
+
+             logger (LOG_DEBUG, "got packet with transaction %d", message.xid);
+             if ((type = parse_dhcpmessage (&dhcp, &message)) < 0)
+               {
+                 logger (LOG_ERR, "failed to parse message");
+                 continue;
+               }
+
+             /* If we got here then the DHCP packet is valid and appears to
+                be for us, so let's clear the buffer as we don't care about
+                any more DHCP packets at this point. */
+             valid = 1;
+             break;
+           }
+
+         /* No packets for us, so wait until we get one */
+         if (! valid)
+           continue;
+
+         switch (state)
+           {
+           case STATE_INIT:
+             if (type == DHCP_OFFER)
+               {
+                 logger (LOG_INFO, "offered lease of %s",
+                         inet_ntoa (dhcp.address));
+
+                 SEND_MESSAGE (DHCP_REQUEST);
+                 state = STATE_REQUESTING;
+               }
+             break;
+
+           case STATE_RENEW_REQUESTED:
+           case STATE_REQUESTING:
+           case STATE_RENEWING:
+           case STATE_REBINDING:
+             if (type == DHCP_ACK)
+               {
+                 SOCKET_MODE (SOCKET_CLOSED);
+                 if (options->doarp && iface->previous_address.s_addr !=
+                     dhcp.address.s_addr)
+                   {
+                     if (arp_check (iface, dhcp.address))
+                       {
+                         SOCKET_MODE (SOCKET_OPEN);
+                         SEND_MESSAGE (DHCP_DECLINE);
+                         SOCKET_MODE (SOCKET_CLOSED);
+                         dhcp.address.s_addr = 0;
+                         if (daemonised)
+                           configure (options, iface, &dhcp);
+
+                         xid = 0;
+                         state = STATE_INIT;
+                         /* RFC 2131 says that we should wait for 10 seconds
+                            before doing anything else */
+                         sleep (10);
+                         continue;
+                       }
+                   }
+
+                 if (! dhcp.leasetime)
+                   {
+                     dhcp.leasetime = DEFAULT_TIMEOUT;
+                     logger(LOG_INFO,
+                            "no lease time supplied, assuming %d seconds",
+                            dhcp.leasetime);
+                   }
+
+                 if (! dhcp.renewaltime) 
+                   {
+                     dhcp.renewaltime = dhcp.leasetime / 2;
+                     logger (LOG_INFO,
+                             "no renewal time supplied, assuming %d seconds",
+                             dhcp.renewaltime);
+                   }
+
+                 if (! dhcp.rebindtime)
+                   {
+                     dhcp.rebindtime = (dhcp.leasetime * 0x7) >> 3;
+                     logger (LOG_INFO,
+                             "no rebind time supplied, assuming %d seconds",
+                             dhcp.rebindtime);
+                   }
+
+                 logger (LOG_INFO, "leased %s for %d seconds",
+                         inet_ntoa (dhcp.address), dhcp.leasetime);
+                 state = STATE_BOUND;
+                 start = uptime ();
+                 timeout = start + dhcp.renewaltime;
+                 xid = 0;
+
+                 if (configure (options, iface, &dhcp) < 0 && ! daemonised)
+                   {
+                     retval = -1;
+                     goto eexit;
+                   }
+
+                 if (! daemonised)
+                   {
+                     if ((daemonise (options->pidfile)) < 0 )
+                       {
+                         retval = -1;
+                         goto eexit;
+                       }
+                     daemonised = true;
+                   }
+               }
+             else if (type == DHCP_NAK)
+               logger (LOG_INFO, "received NAK: %s", dhcp.message);
+             else if (type == DHCP_OFFER)
+               logger (LOG_INFO, "got subsequent offer of %s, ignoring ",
+                       inet_ntoa (dhcp.address));
+             else
+               logger (LOG_ERR,
+                       "no idea what to do with DHCP type %d at this point",
+                       type);
+             break;
+           }
+       }
+      else if (retval == -1 && errno == EINTR)
+       {
+         /* Signal interupt will be handled above */
+       }
+      else 
+       {
+         /* An error occured */
+         logger (LOG_ERR, "error on select: %s", strerror (errno));
+       }
+    }
+
+eexit:
+  SOCKET_MODE (SOCKET_CLOSED);
+
+  /* Remove our config if we need to */
+  if (dhcp.address.s_addr != 0 && ! options->persistent && daemonised)
+    {
+      dhcp.address.s_addr = 0;
+      dhcp.netmask.s_addr = 0;
+      dhcp.broadcast.s_addr = 0;
+      configure (options, iface, &dhcp);
+    }
+
+  free_dhcp (&dhcp);
+
+  if (iface)
+    {
+      if (iface->previous_routes)
+       free_route (iface->previous_routes);
+      free (iface);
+    }
+
+  if (buffer)
+    free (buffer);
+
+  logger (LOG_INFO, "exiting");
+
+  /* Unlink our pidfile */
+  unlink (options->pidfile);
+
+  return retval;
+}
+
diff --git a/client.h b/client.h
new file mode 100644 (file)
index 0000000..1183227
--- /dev/null
+++ b/client.h
@@ -0,0 +1,29 @@
+/*
+ * dhcpcd - DHCP client daemon -
+ * Copyright (C) 2006 Roy Marples <uberlord@gentoo.org>
+ * 
+ * dhcpcd is an RFC2131 compliant DHCP client daemon.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef CLIENT_H
+#define CLIENT_H
+
+#include "dhcpcd.h"
+
+int dhcp_run (options_t *options);
+
+#endif
diff --git a/common.c b/common.c
new file mode 100644 (file)
index 0000000..c3203b9
--- /dev/null
+++ b/common.c
@@ -0,0 +1,64 @@
+/*
+ * dhcpcd - DHCP client daemon -
+ * Copyright (C) 2006 Roy Marples <uberlord@gentoo.org>
+ * 
+ * dhcpcd is an RFC2131 compliant DHCP client daemon.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <sys/time.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "logger.h"
+
+/* This requires us to link to rt on glibc, so we use sysinfo instead */
+#ifdef __linux__
+#include <sys/sysinfo.h>
+long uptime (void)
+{
+  struct sysinfo info;
+
+  sysinfo (&info);
+  return info.uptime;
+}
+#else
+#include <time.h>
+long uptime (void)
+{
+  struct timespec tp;
+
+  if (clock_gettime(CLOCK_MONOTONIC, &tp) == -1)
+    {
+      logger (LOG_ERR, "Unable to get uptime: %m");
+      return -1;
+    }
+
+  return tp.tv_sec;
+}
+#endif /* __linux__ */
+
+void *xmalloc (size_t size)
+{
+  register void *value = malloc (size);
+
+  if (value)
+    return value;
+
+  logger (LOG_ERR, "memory exhausted");
+  exit (1);
+}
+
diff --git a/common.h b/common.h
new file mode 100644 (file)
index 0000000..dee34d6
--- /dev/null
+++ b/common.h
@@ -0,0 +1,28 @@
+/*
+ * dhcpcd - DHCP client daemon -
+ * Copyright (C) 2006 Roy Marples <uberlord@gentoo.org>
+ * 
+ * dhcpcd is an RFC2131 compliant DHCP client daemon.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef COMMON_H
+#define COMMON_H
+
+long uptime (void);
+void *xmalloc (size_t size);
+
+#endif
diff --git a/configure.c b/configure.c
new file mode 100644 (file)
index 0000000..ff6cecf
--- /dev/null
@@ -0,0 +1,527 @@
+/*
+ * dhcpcd - DHCP client daemon -
+ * Copyright (C) 2005 - 2006 Roy Marples <uberlord@gentoo.org>
+ *
+ * dhcpcd is an RFC2131 compliant DHCP client daemon.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <arpa/inet.h>
+
+#ifdef __linux__
+#include <netinet/ether.h>
+#endif
+#include <netinet/in.h>
+#include <string.h>
+#include <errno.h>
+#include <netdb.h>
+#include <resolv.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "dhcp.h"
+#include "interface.h"
+#include "dhcpcd.h"
+#include "pathnames.h"
+#include "logger.h"
+#include "socket.h"
+
+static char *cleanmetas (char *cstr)
+{
+  if (!cstr)
+    return "";
+  
+  register char *c = cstr;
+
+  do
+    if (*c == 39)
+      *c = ' ';
+  while (*c++);
+  
+  return cstr;
+}
+
+void exec_script (char *script, char *infofile, char *arg)
+{
+  if (!script || !infofile || !arg)
+    return;
+
+  struct stat buf;
+  if (stat (script, &buf))
+    {
+      if (strcmp (script, DEFAULT_SCRIPT))
+       logger (LOG_ERR, "`%s': %s", script, strerror (ENOENT));
+      return;
+    }
+  
+  char *argc[4];
+
+  argc[0] = script;
+  argc[1] = infofile;
+  argc[2] = arg;
+  argc[3] = NULL;
+  logger (LOG_DEBUG, "exec \"%s %s %s\"", script, infofile, arg);
+  
+  /* We don't wait for the user script to finish - do we trust it? */
+  /* Don't use vfork as we lose our memory when dhcpcd exits
+     causing the script to fail */
+  pid_t pid;
+  if ((pid = fork ()) == 0)
+    {
+      if (execv (script, argc))
+       logger (LOG_ERR, "error executing \"%s %s %s\": %m",
+               argc[0], argc[1], argc[2]);
+      exit (0);
+    }
+  else if (pid == -1)
+    logger (LOG_ERR, "fork: %s", strerror (errno));
+}
+
+static int make_resolv (char *ifname, dhcp_t *dhcp, int wait)
+{
+  FILE *f;
+  struct stat buf;
+  char resolvconf[PATH_MAX];
+  address_t *address;
+
+  if (!stat ("/sbin/resolvconf", &buf))
+    {
+      logger (LOG_DEBUG, "sending DNS information to resolvconf");
+      snprintf (resolvconf, PATH_MAX, "/sbin/resolvconf -a %s", ifname);
+      f = popen (resolvconf, "w");
+
+      if (!f)
+       logger (LOG_ERR, "popen: %m");
+    }
+  else
+    {
+      if (! (f = fopen(RESOLVFILE, "w")))
+       logger (LOG_ERR, "fopen `%s': %m", RESOLVFILE);
+    }
+
+  if (f) 
+    {
+      fprintf (f, "# Generated by dhcpcd for interface %s\n", ifname);
+      if (dhcp->dnssearch)
+       fprintf (f, "search %s\n", dhcp->dnssearch);
+      else if (dhcp->dnsdomain) {
+       fprintf (f, "search %s\n", dhcp->dnsdomain);
+      }
+
+      for (address = dhcp->dnsservers; address; address = address->next)
+       fprintf (f, "nameserver %s\n", inet_ntoa (address->address));
+
+      if (resolvconf)
+       {
+         pclose (f);
+         logger (LOG_DEBUG, "resolvconf completed");
+       }
+      else
+       fclose (f);
+    }
+  else
+    return -1;
+
+  /* Refresh the local resolver */
+  res_init ();
+  return 0;
+}
+
+static void restore_resolv(char *ifname)
+{
+  struct stat buf;
+
+  if (stat ("/sbin/resolvconf", &buf))
+    return;
+
+  logger (LOG_DEBUG, "removing information from resolvconf");
+
+  char *argc[4];
+
+  argc[0] = "/sbin/resolvconf";
+  argc[1] = "-d";
+  argc[2] = ifname;
+  argc[3] = NULL;
+
+  /* Don't wait around here as we should only be called when
+     dhcpcd is closing down and something may do a kill -9
+     if we take too long */
+  /* Don't use vfork as we lose our memory when dhcpcd exits
+     causing the script to fail */
+  pid_t pid;
+  if ((pid = fork ()) == 0)
+    {
+      if (execve (argc[0], argc, NULL))
+       logger (LOG_ERR, "error executing \"%s %s %s\": %m",
+               argc[0], argc[1], argc[2]);
+      exit (0);
+    }
+  else if (pid == -1)
+    logger (LOG_ERR, "fork: %s", strerror (errno));
+}
+
+static int make_ntp (char *ifname, dhcp_t *dhcp)
+{
+  FILE *f;
+  address_t *address;
+  char *a;
+
+  if (! (f = fopen(NTPFILE, "w")))
+    {
+      logger (LOG_ERR, "fopen `%s': %m", NTPFILE);
+      return -1;
+    }
+         
+  fprintf (f, "# Generated by dhcpcd for interface %s\n", ifname);
+  fprintf (f, "restrict default noquery notrust nomodify\n");
+  fprintf (f, "restrict 127.0.0.1\n");
+
+  for (address = dhcp->ntpservers; address; address = address->next)
+    {
+      a = inet_ntoa (address->address);
+      fprintf (f, "restrict %s nomodify notrap noquery\nserver %s\n", a, a);
+    }
+
+  fprintf (f, "driftfile " NTPDRIFTFILE "\n");
+  fprintf (f, "logfile " NTPLOGFILE "\n");
+  fclose (f);
+  return 0;
+}
+
+static int make_nis (char *ifname, dhcp_t *dhcp)
+{
+  FILE *f;
+  address_t *address;
+  char prefix[256] = {0};
+
+  if (! (f = fopen(NISFILE, "w")))
+    {
+      logger (LOG_ERR, "fopen `%s': %m", NISFILE);
+      return -1;
+    }
+
+  fprintf (f, "# Generated by dhcpcd for interface %s\n", ifname);
+  if (dhcp->nisdomain)
+    {
+      setdomainname (dhcp->nisdomain, strlen (dhcp->nisdomain));
+
+      if (dhcp->nisservers)
+       snprintf (prefix, sizeof (prefix), "domain %s server", dhcp->nisdomain);
+      else
+       fprintf (f, "domain %s broadcast\n", dhcp->nisdomain);
+    }
+  else
+    sprintf(prefix, "ypserver %c", '\0');
+
+  for (address = dhcp->nisservers; address; address = address->next)
+    fprintf (f, "%s%s\n", prefix, inet_ntoa (address->address));
+
+  fclose (f);
+  
+  return 0;
+}
+
+static int write_info(interface_t *iface, dhcp_t *dhcp)
+{
+  FILE *f;
+  route_t *route;
+  address_t *address;
+
+  if ((f = fopen (iface->infofile, "w")) == NULL)
+    {
+      logger (LOG_ERR, "fopen `%s': %m", iface->infofile);
+      return -1;
+    }
+
+  fprintf (f, "IPADDR=%s\n", inet_ntoa (dhcp->address));
+  fprintf (f, "NETMASK=%s\n", inet_ntoa (dhcp->netmask));
+  fprintf (f, "BROADCAST=%s\n", inet_ntoa (dhcp->broadcast));
+  if (dhcp->mtu > 0)
+    fprintf (f, "MTU=%d\n", dhcp->mtu);
+  
+  if (dhcp->routes)
+    {
+      fprintf (f, "ROUTES='");
+      for (route = dhcp->routes; route; route = route->next)
+       {
+         fprintf (f, "%s", inet_ntoa (route->destination));
+         fprintf (f, ",%s", inet_ntoa (route->netmask));
+         fprintf (f, ",%s", inet_ntoa (route->gateway));
+         if (route->next)
+           fprintf (f, " ");
+       }
+      fprintf (f, "'\n");
+    }
+
+  if (dhcp->hostname)
+    fprintf (f, "HOSTNAME='%s'\n",cleanmetas (dhcp->hostname));
+
+  if (dhcp->dnsdomain)
+    fprintf (f, "DNSDOMAIN='%s'\n", cleanmetas (dhcp->dnsdomain));
+
+  if (dhcp->dnssearch)
+    fprintf (f, "DNSSEARCH='%s'\n", cleanmetas (dhcp->dnssearch));
+
+  if (dhcp->dnsservers)
+    {
+      fprintf (f, "DNSSERVERS='");
+      for (address = dhcp->dnsservers; address; address = address->next)
+       {
+         fprintf (f, "%s", inet_ntoa (address->address));
+         if (address->next)
+           fprintf (f, " ");
+       }
+      fprintf (f, "'\n");
+    }
+
+  if (dhcp->fqdn)
+    {
+      fprintf (f, "FQDNFLAGS=%u\n", dhcp->fqdn->flags);
+      fprintf (f, "FQDNRCODE1=%u\n", dhcp->fqdn->r1);
+      fprintf (f, "FQDNRCODE2=%u\n", dhcp->fqdn->r2);
+      fprintf (f, "FQDNHOSTNAME='%s'\n", dhcp->fqdn->name);
+    }
+
+  if (dhcp->ntpservers)
+    {
+      fprintf (f, "NTPSERVERS='");
+      for (address = dhcp->ntpservers; address; address = address->next)
+       {
+         fprintf (f, "%s", inet_ntoa (address->address));
+         if (address->next)
+           fprintf (f, " ");
+       }
+      fprintf (f, "'\n");
+    }
+
+  if (dhcp->nisdomain)
+    fprintf (f, "NISDOMAIN='%s'\n", cleanmetas (dhcp->nisdomain));
+
+  if (dhcp->nisservers)
+    {
+      fprintf (f, "NISSERVERS='");
+      for (address = dhcp->nisservers; address; address = address->next)
+       {
+         fprintf (f, "%s", inet_ntoa (address->address));
+         if (address->next)
+           fprintf (f, " ");
+       }
+      fprintf (f, "'\n");
+    }
+  if (dhcp->rootpath)
+    fprintf (f, "ROOTPATH='%s'\n", cleanmetas (dhcp->rootpath));
+
+  fprintf (f, "DHCPSID=%s\n", inet_ntoa (dhcp->serveraddress));
+  fprintf (f, "DHCPCHADDR=%s\n", ether_ntoa (&iface->ethernet_address));
+  fprintf (f, "DHCPSNAME='%s'\n", cleanmetas (dhcp->servername));
+  fprintf (f, "LEASETIME=%u\n", dhcp->leasetime);
+  fprintf (f, "RENEWALTIME=%u\n", dhcp->renewaltime);
+  fprintf (f, "REBINDTIME=%u\n", dhcp->rebindtime);
+  fprintf (f, "INTERFACE='%s'\n", iface->name);
+  fprintf (f, "CLASSID='%s'\n", cleanmetas (dhcp->classid));
+  fprintf (f, "CLIENTID='%s'\n", cleanmetas (dhcp->clientid));
+
+  fclose (f);
+  return 0;
+}
+
+int configure (options_t *options, interface_t *iface, dhcp_t *dhcp)
+{
+  route_t *route = NULL;
+  route_t *new_route = NULL;
+  route_t *old_route = NULL;
+  struct hostent *he = NULL;
+  char *newhostname[HOSTNAME_MAX_LEN] = {0};
+  char curhostname[HOSTNAME_MAX_LEN] = {0};
+  char *dname = NULL;
+  int dnamel = 0;
+  if (!options || !iface || !dhcp)
+    return -1;
+
+  /* Remove old routes
+     Always do this as the interface may have >1 address not added by us
+     so the routes we added may still exist */
+  if (iface->previous_routes)
+    {
+      for (route = iface->previous_routes; route; route = route->next)
+       if (route->destination.s_addr || options->dogateway)
+         {
+           int have = 0;
+           if (dhcp->address.s_addr != 0)
+             for (new_route = dhcp->routes; new_route; new_route = new_route->next)
+               if (new_route->destination.s_addr == route->destination.s_addr
+                   && new_route->netmask.s_addr == route->netmask.s_addr
+                   && new_route->gateway.s_addr == route->gateway.s_addr)
+                  {
+                    have = 1;
+                    break;
+                  }
+           if (! have)
+             del_route (iface->name, route->destination, route->netmask,
+                        route->gateway, options->metric);
+         }
+    }
+
+  /* If we don't have an address, then return */
+  if (dhcp->address.s_addr == 0)
+    {
+      if (iface->previous_routes)
+       {
+         free_route (iface->previous_routes);
+         iface->previous_routes = NULL;
+       }
+
+      if (iface->previous_address.s_addr != 0)
+       del_address (iface->name, iface->previous_address);
+      memset (&iface->previous_address, 0, sizeof (struct in_addr));
+
+      restore_resolv (iface->name);
+      
+      /* We currently don't have a resolvconf style programs for ntp/nis */
+      exec_script (options->script, iface->infofile, "down");
+      return 0;
+    }
+
+  if (add_address (iface->name, dhcp->address, dhcp->netmask,
+                  dhcp->broadcast) < 0 && errno != EEXIST)
+    return -1;
+
+  /* Now delete the old address if different */
+  if (iface->previous_address.s_addr != dhcp->address.s_addr
+      && iface->previous_address.s_addr != 0)
+       del_address (iface->name, iface->previous_address);
+
+#ifdef __linux__
+  /* On linux, we need to change the subnet route to have our metric. */
+  if (iface->previous_address.s_addr != dhcp->address.s_addr
+      && options->metric > 0)
+    {
+      struct in_addr td;
+      struct in_addr tg;
+      memset (&td, 0, sizeof (td));
+      memset (&tg, 0, sizeof (tg));
+      td.s_addr = dhcp->address.s_addr & dhcp->netmask.s_addr;
+      add_route (iface->name, td, dhcp->netmask, tg, options->metric);
+      del_route (iface->name, td, dhcp->netmask, tg, 0);
+    }
+#endif
+
+  /* Remember added routes */
+  if (dhcp->routes)
+    {
+      route_t *new_routes = NULL;
+      
+      for (route = dhcp->routes; route; route = route->next)
+       {
+         int remember = add_route (iface->name, route->destination,
+                                   route->netmask,  route->gateway,
+                                   options->metric);
+         /* If we failed to add the route, we may have already added it
+            ourselves. If so, remember it again. */
+         if (remember < 0)
+           for (old_route = iface->previous_routes; old_route;
+                old_route = old_route->next)
+             if (old_route->destination.s_addr == route->destination.s_addr
+                 && old_route->netmask.s_addr == route->netmask.s_addr
+                 && old_route->gateway.s_addr == route->gateway.s_addr)
+               {
+                 remember = 1;
+                 break;
+               }
+
+         if (remember >= 0)
+           {
+             if (! new_routes)
+               {
+                 new_routes = xmalloc (sizeof (route_t));
+                 memset (new_routes, 0, sizeof (route_t));
+                 new_route = new_routes;
+               }
+             else
+               {
+                 new_route->next = xmalloc (sizeof (route_t));
+                 new_route = new_route->next;
+               }
+             memcpy (new_route, route, sizeof (route_t));
+             new_route -> next = NULL;
+           }
+       }
+
+      if (iface->previous_routes)
+       free_route (iface->previous_routes);
+
+      iface->previous_routes = new_routes;
+    }
+
+  if (options->dodns && dhcp->dnsservers)
+    make_resolv(iface->name, dhcp, (options->dohostname && !dhcp->hostname));
+
+  if (options->dontp && dhcp->ntpservers)
+    make_ntp(iface->name, dhcp);
+
+  if (options->donis && (dhcp->nisservers || dhcp->nisdomain))
+    make_nis(iface->name, dhcp);
+
+  /* Now we have made a resolv.conf we can obtain a hostname if we need one */
+  if (options->dohostname && !dhcp->hostname)
+    {
+      he = gethostbyaddr (inet_ntoa (dhcp->address),
+                         sizeof (struct in_addr), AF_INET);
+      if (he)
+       {
+         dname = he->h_name;
+         while (*dname > 32)
+           dname++;
+         dnamel = dname - he->h_name;
+         memcpy (newhostname, he->h_name, dnamel);
+         newhostname[dnamel] = 0;
+       }
+    }
+
+  gethostname (curhostname, sizeof (curhostname));
+  
+  if (options->dohostname || !strlen (curhostname) 
+      || !strcmp (curhostname, "(none)") || !strcmp (curhostname, "localhost"))
+    {
+      if (dhcp->hostname)
+       strcpy ((char *) newhostname, dhcp->hostname); 
+
+      sethostname ((char *) newhostname, strlen ((char *) newhostname));
+      logger (LOG_INFO, "setting hostname to %s", newhostname);
+    }
+
+  write_info (iface, dhcp);
+
+   if (iface->previous_address.s_addr != dhcp->address.s_addr)
+    {
+      memcpy (&iface->previous_address,
+             &dhcp->address, sizeof (struct in_addr));
+      exec_script (options->script, iface->infofile, "new");
+    }
+  else
+    exec_script (options->script, iface->infofile, "up");
+
+  return 0;
+}
+
diff --git a/configure.h b/configure.h
new file mode 100644 (file)
index 0000000..ce8514b
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * dhcpcd - DHCP client daemon -
+ * Copyright (C) 2005 - 2006 Roy Marples <uberlord@gentoo.org>
+ * 
+ * dhcpcd is an RFC2131 compliant DHCP client daemon.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef DHCPCONFIG_H
+#define DHCPCONFIG_H
+
+#include "dhcpcd.h"
+#include "interface.h"
+#include "dhcp.h"
+
+int configure (options_t *options, interface_t *iface, dhcp_t *dhcp);
+
+#endif
diff --git a/dhcp.c b/dhcp.c
new file mode 100644 (file)
index 0000000..486b964
--- /dev/null
+++ b/dhcp.c
@@ -0,0 +1,648 @@
+/*
+ * dhcpcd - DHCP client daemon -
+ * Copyright (C) 2005 - 2006 Roy Marples <uberlord@gentoo.org>
+ * 
+ * dhcpcd is an RFC2131 compliant DHCP client daemon.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <netinet/in.h>
+#include <net/if_arp.h>
+
+#include <limits.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "common.h"
+#include "dhcp.h"
+#include "interface.h"
+#include "logger.h"
+#include "socket.h"
+
+static char *dhcp_message[] = {
+  [DHCP_DISCOVER]      = "DHCP_DISCOVER",
+  [DHCP_OFFER]         = "DHCP_OFFER",
+  [DHCP_REQUEST]       = "DHCP_REQUEST",
+  [DHCP_DECLINE]       = "DHCP_DECLINE",
+  [DHCP_ACK]           = "DHCP_ACK",
+  [DHCP_NAK]           = "DHCP_NAK",
+  [DHCP_RELEASE]       = "DHCP_RELEASE",
+  [DHCP_INFORM]                = "DHCP_INFORM",
+  [DHCP_INFORM + 1]    = NULL
+};
+
+size_t send_message (interface_t *iface, dhcp_t *dhcp,
+                    unsigned long xid, char type, options_t *options)
+{
+  dhcpmessage_t message;
+  unsigned char *p = (unsigned char *) &message.options;
+  unsigned char *n_params = NULL;
+  unsigned long l;
+
+  if (!iface || !options || !dhcp)
+    return -1;
+
+  memset (&message, 0, sizeof (dhcpmessage_t));
+
+  message.op = DHCP_BOOTREQUEST;
+  message.hwtype = ARPHRD_ETHER;
+  message.hwlen = ETHER_ADDR_LEN;
+  message.secs = htons (10);
+  message.xid = xid;
+  memcpy (&message.hwaddr, &iface->ethernet_address, ETHER_ADDR_LEN);
+  message.cookie = htonl(MAGIC_COOKIE);
+
+  if (iface->previous_address.s_addr != 0 &&
+      iface->previous_address.s_addr == dhcp->address.s_addr)
+    message.ciaddr = iface->previous_address.s_addr; 
+
+  *p++ = DHCP_MESSAGETYPE; 
+  *p++ = 1;
+  *p++ = type;
+
+  if (type == DHCP_REQUEST)
+    {
+      *p++ = DHCP_MAXMESSAGESIZE;
+      *p++ = 2;
+      uint16_t sz = htons (sizeof (struct udp_dhcp_packet));
+      memcpy (p, &sz, 2);
+      p += 2;
+    }
+
+  if (dhcp->address.s_addr != 0 && iface->previous_address.s_addr == 0)
+    {
+      *p++ = DHCP_ADDRESS;
+      *p++ = 4;
+      memcpy (p, &dhcp->address.s_addr, 4);
+      p += 4;
+    }
+
+  if (dhcp->serveraddress.s_addr != 0 && dhcp->address.s_addr !=0 &&
+      iface->previous_address.s_addr == 0)
+    {
+      *p++ = DHCP_SERVERIDENTIFIER;
+      *p++ = 4;
+      memcpy (p, &dhcp->serveraddress.s_addr, 4);
+      p += 4;
+
+      /* Blank out the server address so we broadcast */
+      if (type == DHCP_REQUEST) dhcp->serveraddress.s_addr = 0;
+    }
+
+  if (type == DHCP_REQUEST || type == DHCP_DISCOVER)
+    {
+      if (dhcp->leasetime > 0)
+       {
+         *p++ = DHCP_LEASETIME;
+         *p++ = 4;
+         uint32_t ul = htonl (dhcp->leasetime);
+         memcpy (p, &ul, 4);
+         p += 4;
+       }
+    }
+
+  *p++ = DHCP_PARAMETERREQUESTLIST;
+  n_params = p;
+  *p++ = 0;
+
+  if (type == DHCP_REQUEST)
+    {
+      *p++ = DHCP_RENEWALTIME;
+      *p++ = DHCP_REBINDTIME;
+      *p++ = DHCP_NETMASK;
+      *p++ = DHCP_BROADCAST;
+      *p++ = DHCP_CSR;
+      /* RFC 3442 states classless static routes should be before routers
+       * and static routes as classless static routes override them both */
+      *p++ = DHCP_ROUTERS;
+      *p++ = DHCP_STATICROUTE;
+      *p++ = DHCP_HOSTNAME;
+      *p++ = DHCP_DNSSEARCH;
+      *p++ = DHCP_DNSDOMAIN;
+      *p++ = DHCP_DNSSERVER;
+      *p++ = DHCP_NISDOMAIN;
+      *p++ = DHCP_NISSERVER;
+      *p++ = DHCP_NTPSERVER;
+      /* These parameters were requested by dhcpcd-2.0 and earlier
+        but we never did anything with them */
+      /*    *p++ = DHCP_DEFAULTIPTTL;
+       *p++ = DHCP_MASKDISCOVERY;
+       *p++ = DHCP_ROUTERDISCOVERY; */
+    }
+  else
+    /* Always request one parameter so we don't get the server default
+       when we don't actally need any at this time */
+    *p++ = DHCP_DNSSERVER;
+
+  *n_params = p - n_params - 1;
+
+  if (type == DHCP_REQUEST)
+    {
+      if (options->hostname) 
+       {
+         if (options->fqdn == FQDN_DISABLE)
+           {
+             *p++ = DHCP_HOSTNAME;
+             *p++ = l = strlen (options->hostname);
+             memcpy (p, options->hostname, l);
+             p += l;
+           }
+         else
+           {
+             /* Draft IETF DHC-FQDN option (81) */
+             *p++ = DHCP_FQDN;
+             *p++ = (l = strlen (options->hostname)) + 3;
+             /* Flags: 0000NEOS
+              * S: 1 => Client requests Server to update A RR in DNS as well as PTR
+              * O: 1 => Server indicates to client that DNS has been updated
+              * E: 1 => Name data is DNS format
+              * N: 1 => Client requests Server to not update DNS
+              */
+             *p++ = options->fqdn & 0x9;
+             *p++ = 0; /* rcode1, response from DNS server for PTR RR */
+             *p++ = 0; /* rcode2, response from DNS server for A RR if S=1 */
+             memcpy (p, options->hostname, l);
+             p += l;
+           }
+       }
+    }
+
+  if (options->userclass)
+    {
+      *p++ = DHCP_USERCLASS;
+      *p++ = l = strlen (options->userclass);
+      memcpy (p, options->userclass, l);
+      p += l;
+    }
+
+  *p++ = DHCP_CLASSID;
+  *p++ = l = strlen (options->classid);
+  memcpy (p, options->classid, l);
+  p += l;
+
+  *p++ = DHCP_CLIENTID;
+  if (options->clientid[0])
+    {
+      l = strlen (options->clientid);
+      *p++ = l + 1;
+      *p++ = 0; /* string */
+      memcpy (p, options, l);
+      p += l;
+    }
+  else
+    {
+      *p++ = ETHER_ADDR_LEN + 1;
+      *p++ = ARPHRD_ETHER;
+      memcpy (p, &iface->ethernet_address, ETHER_ADDR_LEN);
+      p += ETHER_ADDR_LEN;
+    }
+
+  *p = DHCP_END;
+
+  struct udp_dhcp_packet packet;
+  memset (&packet, 0, sizeof (struct udp_dhcp_packet));
+  make_dhcp_packet (&packet, (unsigned char *) &message,
+                   dhcp->address, dhcp->serveraddress);
+
+  logger (LOG_DEBUG, "Sending %s with xid %d", dhcp_message[(int) type], xid);
+  return send_packet (iface, ETHERTYPE_IP, (unsigned char *) &packet,
+                     sizeof (struct udp_dhcp_packet));
+}
+
+static unsigned long getnetmask (unsigned long ip_in)
+{
+  unsigned long t, p = ntohl (ip_in);
+
+  if (IN_CLASSA (p))
+    t = ~IN_CLASSA_NET;
+  else
+    {
+      if (IN_CLASSB (p))
+       t = ~IN_CLASSB_NET;
+      else
+       {
+         if (IN_CLASSC (p))
+           t = ~IN_CLASSC_NET;
+         else
+           t = 0;
+       }
+    }
+  while (t & p) t >>= 1;
+  return htonl (~t);
+}
+
+/* Decode an RFC3397 DNS search order option into a space
+   seperated string. Returns length of string (including 
+   terminating zero) or zero on error. out may be NULL
+   to just determine output length. */
+static unsigned int decode_search (u_char *p, int len, char *out)
+{
+  u_char *r, *q = p;
+  unsigned int count = 0, l, hops;
+
+  while (q - p < len)
+    {
+      r = NULL;
+      hops = 0;
+      while ((l = *q++))
+       {
+         unsigned int label_type = l & 0xc0;
+         if (label_type == 0x80 || label_type == 0x40)
+           return 0;
+         else if (label_type == 0xc0) /* pointer */
+           { 
+             l = (l & 0x3f) << 8;
+             l |= *q++;
+
+             /* save source of first jump. */
+             if (!r)
+               r = q;
+
+             hops++;
+             if (hops > 255)
+               return 0;
+
+             q = p + l;
+             if (q - p >= len)
+               return 0;
+           }
+         else 
+           {
+             /* straightforward name segment, add with '.' */
+             count += l + 1;
+             if (out)
+               {
+                 memcpy (out, q, l);
+                 out += l;
+                 *out++ = '.';
+               }
+             q += l;
+           }
+       }
+
+      /* change last dot to space */
+      if (out)
+       *(out - 1) = ' ';
+
+      if (r)
+       q = r;
+    }
+
+  /* change last space to zero terminator */
+  if (out)
+    *(out - 1) = 0;
+
+  return count;  
+}
+
+/* Add our classless static routes to the routes variable
+ * and return the last route set */
+static route_t *decodeCSR(unsigned char *p, int len)
+{
+  /* Minimum is 5 -first is CIDR and a router length of 4 */
+  if (len < 5)
+    return NULL;
+
+  unsigned char *q = p;
+  int cidr;
+  int ocets;
+  route_t *first = xmalloc (sizeof (route_t));
+  route_t *route = first;
+
+  while (q - p < len)
+    {
+      memset (route, 0, sizeof (route_t));
+
+      cidr = (int) *q++;
+      if (cidr == 0)
+       ocets = 0;
+      else if (cidr < 9)
+       ocets = 1;
+      else if (cidr < 17)
+       ocets = 2;
+      else if (cidr < 25)
+       ocets = 3;
+      else
+       ocets = 4;
+
+      if (ocets > 0)
+       {
+         memcpy (&route->destination.s_addr, q, ocets);
+         q += ocets;
+       }
+
+      /* Now enter the netmask */
+      if (ocets > 0)
+       {
+         memset (&route->netmask.s_addr, 255, ocets - 1);
+         memset ((unsigned char *) &route->netmask.s_addr + (ocets - 1),
+                 (256 - (1 << (32 - cidr) % 8)), 1);
+       }
+
+      /* Finally, snag the router */
+      memcpy (&route->gateway.s_addr, q, 4);
+      q += 4;
+
+      /* We have another route */
+      if (q - p < len)
+       {
+         route->next = xmalloc (sizeof (route_t));
+         route = route->next;
+       }
+    }
+
+  return first;
+}
+
+void free_dhcp (dhcp_t *dhcp)
+{
+  if (!dhcp)
+    return;
+
+  if (dhcp->routes)
+    free_route (dhcp->routes);
+
+  if (dhcp->hostname)
+    free (dhcp->hostname);
+
+  if (dhcp->dnsservers)
+    free_address (dhcp->dnsservers);
+  if (dhcp->dnsdomain)
+    free (dhcp->dnsdomain);
+  if (dhcp->dnssearch)
+    free (dhcp->dnssearch);
+
+  if (dhcp->ntpservers)
+    free_address (dhcp->ntpservers);
+
+  if (dhcp->nisdomain)
+    free (dhcp->nisdomain);
+  if (dhcp->nisservers)
+    free_address (dhcp->nisservers);
+
+  if (dhcp->rootpath)
+    free (dhcp->rootpath);
+
+  if (dhcp->fqdn)
+    {
+      if (dhcp->fqdn->name)
+       free (dhcp->fqdn->name);
+      free (dhcp->fqdn);
+    }
+}
+
+static void dhcp_add_address(address_t *address, unsigned char *data, int length)
+{
+  int i;
+  address_t *p = address;
+
+  for (i = 0; i < length; i += 4)
+    {
+      memset (p, 0, sizeof (address_t));
+      memcpy (&p->address.s_addr, data + i, 4);
+      if (length - i > 4)
+       {
+         p->next = xmalloc (sizeof (address_t));
+         p = p->next;
+       }
+    }
+}
+
+int parse_dhcpmessage (dhcp_t *dhcp, dhcpmessage_t *message)
+{
+  unsigned char *p = message->options;
+  unsigned char option;
+  unsigned char length;
+  unsigned char *end = message->options + sizeof (message->options);
+  unsigned int len = 0;
+  int i;
+  int retval = -1;
+  route_t *first_route = xmalloc (sizeof (route_t));
+  route_t *route = first_route;
+  route_t *last_route = NULL;
+  route_t *csr = NULL;
+  char classid[CLASS_ID_MAX_LEN];
+  char clientid[CLIENT_ID_MAX_LEN];
+
+  memset (first_route, 0, sizeof (route_t));
+
+  /* The message back never has the class or client id's so we save them */
+  strcpy (classid, dhcp->classid);
+  strcpy (clientid, dhcp->clientid);
+
+  free_dhcp (dhcp);
+  memset (dhcp, 0, sizeof (dhcp_t));
+
+  dhcp->address.s_addr = message->yiaddr;
+  strcpy (dhcp->servername, message->servername);
+
+  while (p < end)
+    {
+      option = *p++;
+      if (!option)
+       continue;
+
+      length = *p++;
+
+      if (p + length >= end)
+       {
+         retval = -1;
+         goto eexit;
+       }
+
+      switch (option)
+       {
+       case DHCP_END:
+         goto eexit;
+
+       case DHCP_MESSAGETYPE:
+         retval = (int) *p;
+         break;
+
+       case DHCP_ADDRESS:
+         memcpy (&dhcp->address.s_addr, p, 4);
+         break;
+       case DHCP_NETMASK:
+         memcpy (&dhcp->netmask.s_addr, p, 4);
+         break;
+       case DHCP_BROADCAST:
+         memcpy (&dhcp->broadcast.s_addr, p, 4);
+         break;
+       case DHCP_SERVERIDENTIFIER:
+         memcpy (&dhcp->serveraddress.s_addr, p, 4);
+         break;
+
+       case DHCP_LEASETIME:
+         dhcp->leasetime = ntohl (* (uint32_t *) p);
+         break;
+       case DHCP_RENEWALTIME:
+         dhcp->renewaltime = ntohl (* (uint32_t *) p);
+         break;
+       case DHCP_REBINDTIME:
+         dhcp->rebindtime = ntohl (* (uint32_t *) p);
+         break;
+       case DHCP_MTU:
+         dhcp->mtu = ntohs (* (uint16_t *) p);
+         /* Minimum legal mtu is 68 */
+         if (dhcp->mtu > 0 && dhcp->mtu < 68)
+           dhcp->mtu = 68;
+         break;
+
+       case DHCP_HOSTNAME:
+         if (dhcp->hostname)
+           free (dhcp->hostname);
+         dhcp->hostname = xmalloc (length + 1);
+         memcpy (dhcp->hostname, p, length);
+         dhcp->hostname[length] = '\0';
+         break;
+
+       case DHCP_DNSDOMAIN:
+         if (dhcp->dnsdomain)
+           free (dhcp->dnsdomain);
+         dhcp->dnsdomain = xmalloc (length + 1);
+         memcpy (dhcp->dnsdomain, p, length);
+         dhcp->dnsdomain[length] = '\0';
+         break;
+
+       case DHCP_MESSAGE:
+         if (dhcp->message)
+           free (dhcp->message);
+         dhcp->message = xmalloc (length + 1);
+         memcpy (dhcp->message, p, length);
+         dhcp->message[length] = '\0';
+         break;
+
+       case DHCP_ROOTPATH:
+         if (dhcp->rootpath)
+           free (dhcp->rootpath);
+         dhcp->rootpath = xmalloc (length + 1);
+         memcpy (dhcp->rootpath, p, length);
+         dhcp->rootpath[length] = '\0';
+         break;
+
+       case DHCP_NISDOMAIN:
+         if (dhcp->nisdomain)
+           free (dhcp->nisdomain);
+         dhcp->nisdomain = xmalloc (length + 1);
+         memcpy (dhcp->nisdomain, p, length);
+         dhcp->nisdomain[length] = '\0';
+         break;
+
+       case DHCP_DNSSERVER:
+         if (dhcp->dnsservers)
+           free_address (dhcp->dnsservers);
+         dhcp->dnsservers = xmalloc (sizeof (address_t));
+         dhcp_add_address (dhcp->dnsservers, p, length);
+         break;
+       case DHCP_NTPSERVER:
+         if (dhcp->ntpservers)
+           free_address (dhcp->ntpservers);
+         dhcp->ntpservers = xmalloc (sizeof (address_t));
+         dhcp_add_address (dhcp->ntpservers, p, length);
+         break;
+       case DHCP_NISSERVER:
+         if (dhcp->nisservers)
+           free_address (dhcp->nisservers);
+         dhcp->nisservers = xmalloc (sizeof (address_t));
+         dhcp_add_address (dhcp->nisservers, p, length);
+         break;
+
+       case DHCP_DNSSEARCH:
+         if (dhcp->dnssearch)
+           free (dhcp->dnssearch);
+         if ((len = decode_search (p, length, NULL)))
+           {
+             dhcp->dnssearch = xmalloc (len);
+             decode_search (p, length, dhcp->dnssearch);
+           }
+         break;
+
+       case DHCP_CSR:
+         csr = decodeCSR (p, length);
+         break;
+
+       case DHCP_STATICROUTE:
+         for (i = 0; i < length; i += 8)
+           {
+             memcpy (&route->destination.s_addr, p + i, 4);
+             memcpy (&route->gateway.s_addr, p + i + 4, 4);
+             route->netmask.s_addr = getnetmask (route->destination.s_addr); 
+             last_route = route;
+             route->next = xmalloc (sizeof (route_t));
+             route = route->next;
+             memset (route, 0, sizeof (route_t));
+           }
+         break;
+
+       case DHCP_ROUTERS:
+         for (i = 0; i < length; i += 4)
+           {
+             memcpy (&route->gateway.s_addr, p + i, 4);
+             last_route = route;
+             route->next = xmalloc (sizeof (route_t));
+             route = route->next;
+             memset (route, 0, sizeof (route_t));
+           }
+         break;
+
+       default:
+         logger (LOG_DEBUG, "no facility to parse DHCP code %u", option);
+         break;
+       }
+
+      p += length;
+    }
+
+eexit:
+  /* Fill in any missing fields */
+  if (!dhcp->netmask.s_addr)
+    dhcp->netmask.s_addr = getnetmask (dhcp->address.s_addr);
+  if (!dhcp->broadcast.s_addr)
+    dhcp->broadcast.s_addr = dhcp->address.s_addr | ~dhcp->netmask.s_addr;
+
+  /* If we have classess static routes then we discard
+     static routes and routers according to RFC 3442 */
+  if (csr)
+    {
+      dhcp->routes = csr;
+      free_route (first_route); 
+    }
+  else
+    {
+      dhcp->routes = first_route;
+      if (last_route)
+       {
+         free (last_route->next);
+         last_route->next = NULL;
+       }
+      else
+       {
+         free_route (dhcp->routes);
+         dhcp->routes = NULL;
+       }
+    }
+
+  /* The message back never has the class or client id's so we restore them */
+  strcpy (dhcp->classid, classid);
+  strcpy (dhcp->clientid, clientid);
+
+  return retval;
+}
+
diff --git a/dhcp.h b/dhcp.h
new file mode 100644 (file)
index 0000000..93af9cf
--- /dev/null
+++ b/dhcp.h
@@ -0,0 +1,185 @@
+/*
+ * dhcpcd - DHCP client daemon -
+ * Copyright (C) 2005 - 2006 Roy Marples <uberlord@gentoo.org>
+ * 
+ * dhcpcd is an RFC2131 compliant DHCP client daemon.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef DHCP_H
+#define DHCP_H
+
+#include <sys/types.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+
+#include "dhcpcd.h"
+#include "interface.h"
+
+/* UDP port numbers for DHCP */
+#define DHCP_SERVER_PORT 67
+#define DHCP_CLIENT_PORT 68
+
+#define MAGIC_COOKIE 0x63825363
+
+/* DHCP message OP code */
+#define DHCP_BOOTREQUEST       1
+#define DHCP_BOOTREPLY         2
+
+/* DHCP message type */
+#define        DHCP_DISCOVER           1
+#define DHCP_OFFER             2
+#define        DHCP_REQUEST            3
+#define        DHCP_DECLINE            4
+#define        DHCP_ACK                5
+#define DHCP_NAK               6
+#define        DHCP_RELEASE            7
+#define DHCP_INFORM            8
+
+/* DHCP options */
+enum DHCP_OPTIONS
+{
+  DHCP_PAD                             =       0,
+  DHCP_NETMASK                         =       1,
+  DHCP_TIMEROFFSET                     =       2,
+  DHCP_ROUTERS                         =       3,
+  DHCP_TIMESERVER                      =       4,
+  DHCP_NAMESERVER                      =       5,
+  DHCP_DNSSERVER                       =       6,
+  DHCP_LOGSERVER                       =       7,
+  DHCP_COOKIESERVER                    =       8,
+  DHCP_HOSTNAME                                =       12,
+  DHCP_DNSDOMAIN                       =       15,
+  DHCP_ROOTPATH                                =       17,
+  DHCP_DEFAULTIPTTL                    =       23,
+  DHCP_MTU                             =       26,
+  DHCP_BROADCAST                       =       28,
+  DHCP_MASKDISCOVERY                   =       29,
+  DHCP_ROUTERDISCOVERY                 =       31,
+  DHCP_STATICROUTE                     =       33,
+  DHCP_NISDOMAIN                       =       40,
+  DHCP_NISSERVER                       =       41,
+  DHCP_NTPSERVER                       =       42,
+  DHCP_ADDRESS                         =       50,
+  DHCP_LEASETIME                       =       51,
+  DHCP_MESSAGETYPE                     =       53,
+  DHCP_SERVERIDENTIFIER                        =       54,
+  DHCP_PARAMETERREQUESTLIST            =       55,
+  DHCP_MESSAGE                         =       56,
+  DHCP_MAXMESSAGESIZE                  =       57,
+  DHCP_RENEWALTIME                     =       58,
+  DHCP_REBINDTIME                      =       59,
+  DHCP_CLASSID                         =       60,
+  DHCP_CLIENTID                                =       61,
+  DHCP_USERCLASS                       =       77,  /* RFC 3004 */
+  DHCP_FQDN                            =       81,
+  DHCP_DNSSEARCH                       =       119, /* RFC 3397 */
+  DHCP_CSR                             =       121, /* RFC 3442 */
+  DHCP_END                             =       255
+};
+
+/* SetFQDNHostName values - lsnybble used in flags
+ * byte (see buildmsg.c), hsnybble to create order
+ * and to allow 0x00 to mean disable
+ */
+enum FQQN {
+  FQDN_DISABLE                         =       0x00,
+  FQDN_NONE                            =       0x18,
+  FQDN_PTR                             =       0x20,
+  FQDN_BOTH                            =       0x31
+};
+
+typedef struct fqdn_t
+{
+  uint8_t flags;
+  uint8_t r1;
+  uint8_t r2;
+  char *name;
+} fqdn_t;
+
+typedef struct dhcp_t
+{
+  char version[11];
+
+  struct in_addr serveraddress;
+  char serverhw[IF_NAMESIZE];
+  char servername[64];
+
+  char classid[CLASS_ID_MAX_LEN];
+  char clientid[CLIENT_ID_MAX_LEN];
+  
+  struct in_addr address;
+  struct in_addr netmask;
+  struct in_addr broadcast;
+  unsigned short mtu;
+
+  unsigned leasetime;
+  unsigned renewaltime;
+  unsigned rebindtime;
+  
+  route_t *routes;
+
+  char *hostname;
+  fqdn_t *fqdn;
+  address_t *dnsservers;
+  char *dnsdomain;
+  char *dnssearch;
+  
+  address_t *ntpservers;
+
+  address_t *nisservers;
+  char *nisdomain;
+
+  char *message;
+  char *rootpath;
+} dhcp_t;
+
+typedef struct dhcpmessage_t
+{
+  char op;             /* message type */
+  char hwtype;         /* hardware address type */
+  char hwlen;          /* hardware address length */
+  char hwopcount;      /* should be zero in client's message */
+  int32_t xid;         /* transaction id */
+  int16_t secs;                /* elapsed time in sec. from trying to boot */
+  int16_t flags;
+  int32_t ciaddr;              /* (previously allocated) client IP address */
+  int32_t yiaddr;              /* 'your' client IP address */
+  int32_t siaddr;              /* should be zero in client's messages */
+  int32_t giaddr;              /* should be zero in client's messages */
+  unsigned char hwaddr[16];    /* client's hardware address */
+  char servername[64]; /* server host name, null terminated string */
+  char bootfile[128];  /* boot file name, null terminated string */
+  uint32_t cookie;
+  unsigned char options[308];  /* message options - cookie */
+} dhcpmessage_t;
+
+struct udp_dhcp_packet
+{
+  struct ip ip;
+  struct udphdr udp;
+  dhcpmessage_t dhcp;
+};
+
+size_t send_message (interface_t *iface, dhcp_t *dhcp,
+                    unsigned long xid, char type, options_t *options);
+void free_dhcp (dhcp_t *dhcp);
+int parse_dhcpmessage (dhcp_t *dhcp, dhcpmessage_t *message);
+
+#endif
diff --git a/dhcpcd.8 b/dhcpcd.8
new file mode 100644 (file)
index 0000000..936d27a
--- /dev/null
+++ b/dhcpcd.8
@@ -0,0 +1,332 @@
+.\" $Id$
+.\"
+.TH dhcpcd 8 "15 August 2006" "dhcpcd 3.0"
+
+.SH NAME
+dhcpcd \- DHCP client daemon
+
+.SH SYNOPSIS
+.in +.5i
+.ti -.5i
+dhcpcd
+\%[\-adknpGHNRY]
+\%[\-c\ script]
+\%[\-h\ hostname]
+\%[\-i\ vendorClassID]
+\%[\-l\ leasetime]
+\%[\-m\ metric]
+\%[\-s\ ipaddr]
+\%[\-t\ timeout]
+\%[\-u\ userClass]
+\%[\-F\ none | ptr | both]
+\%[\-I\ clientID]
+\%[interface]
+.in -.5i
+.SH DESCRIPTION
+.B dhcpcd
+is an implementation of the DHCP client specified in
+.B RFC2131.
+
+It gets the host information (IP address, netmask, broadcast address,
+etc.) from a DHCP server and configures the network interface of the
+machine on which it is running. It also tries to renew the lease time
+according to
+.B RFC2131.
+
+.SH OPTIONS
+.TP
+.BI interface
+Specifies the network interface name (eth0, eth1, etc.).
+.TP
+.BI \-a
+Do an
+.B ARP
+check on the IP address give to us by the DHCP server. We may need to do this
+if a client on the same network segment has the same IP address, however we do
+not do this by default as most DHCP servers test the IP briefly with an ICMP
+Echo request before assigning the IP address.
+.TP
+.BI \-c \ script
+.B dhcpcd
+will try to execute
+.I script
+instead of the default script
+.I /etc/dhcpcd.sh
+every time it configures or brings down the interface. See the
+description of
+.I dhcpcd.sh
+script in
+.B FILES
+section below.
+.TP
+.BI \-d
+Echos debugging and information messages to the console.
+.TP
+.BI \-h \ hostname
+specifies a string used for the hostname option field when
+.B dhcpcd
+sends DHCP messages. Some DHCP servers, notably those used by
+@Home Networks, require the hostname option
+field containing a specific string in the DHCP messages from clients.
+When combined with the -F switch, specifies the string used for the
+FQDN option field instead of the hostname option field.
+.TP
+.BI \-i \ vendorClassID
+Specifies the vendor class identifier string.
+.B dhcpcd
+uses the default vendor class identifier string (system name, system release,
+and machine type) if it is not specified.
+.TP
+.BI \-k
+Sends
+.B SIGHUP
+signal to the
+.B dhcpcd
+process associated with the specified interface if one is currently running. If
+.B dhcpcd
+receives
+.B SIGHUP
+it will send
+.B DCHP_RELEASE
+message to the server and destroy dhcpcd cache. In a case
+.B dhcpcd
+receives
+.B SIGTERM
+which is normally used by
+.B shutdown(8)
+when rebooting the system
+.B dhcpcd
+will not send
+.B DHCP_RELEASE
+and will not destroy cache. When system boots
+.B dhcpcd
+will use cache to request the same IP address
+from DHCP server which was assigned before the
+system went down. (see also
+.B -p
+)
+.TP
+.BI \-l \ leasetime
+Specifies (in seconds) the recommended lease time to the server. (Note
+that the server can override this value if it sees fit). This value is
+used in the
+.B DHCP_DISCOVER
+message. Use -1 for an infinite lease time. We don't request a specific
+lease time by default.
+.TP
+.BI \-m \ metric
+Routes will be added with the given metric. The default is 0.
+On some systems such as FreeBSD the interface is given the metric.
+.TP
+.BI \-n
+Sends
+.B SIGALRM
+signal to the
+.B dhcpcd
+process that is currently running which
+forces
+.B dhcpcd
+to try to renew the lease. If dhcpcd is not running, the flag
+is ignored and
+.B dhcpcd
+follows the normal startup procedure.
+.TP
+.BI \-p
+Stops
+.B dhcpcd
+from removing the interface configuration when it is terminated with the
+.B SIGTERM
+signal. This is useful when a host is running with an NFS-mounted root
+filesystem over an interface controlled by DHCP. It should not be used
+except in those circumstances, since if 
+.B dhcp
+is stopped it can no longer down an interface at the end of its
+lease period when the lease is not renewed.
+.TP
+.BI \-s \ ipaddr
+Sends DHCP_DISCOVER message to DHCP server requesting to lease ip address
+ipaddr.
+The ipaddr parameter must be in the form xxx.xxx.xxx.xxx.
+.TP
+.BI \-t \ timeout
+Specifies (in seconds ) for how long
+.B dhcpcd
+will try to get an IP address. The default is 10 seconds.
+.B dhcpcd
+will not fork into background until it gets a valid IP address
+in which case dhcpcd will return 0 to the parent process.
+In a case
+.B dhcpcd
+times out before receiving a valid IP address from DHCP server
+.B dhcpcd
+will return exit code 1 to the parent process. Setting the timeout to
+zero disables it: dhcp will keep trying forever to get a lease, and if
+the lease is lost, it will try forever to get another.
+.TP
+.BI \-u \ userClass
+Tags the  DHCP message with the specified user class. DHCP servers can use
+these fields to send back different information instead of grouping by
+fixed hardware addresses. You can specify more than one user class, but the
+total length must be less than 255 characters, -1 character for each user
+class.
+.TP
+.BI \-H
+Forces
+.B dhcpcd
+to set hostname of the host to the hostname option supplied by DHCP server.
+By default
+.B dhcpcd
+will NOT set hostname of the host to the hostname option
+received from DHCP server unless the current hostname is blank, (none) or
+localhost.
+.TP
+.BI \-F \ none | ptr | both
+Forces
+.B dhcpcd
+to request the DHCP server update the DNS using the FQDN option
+instead of the Hostname option. The name used by this option
+is specified with the \fB-h\fP switch, which must be present. If
+the \fB-h\fP switch is not present, the FQDN option is ignored.
+The name should be fully qualified, although servers usually
+accept a simple name.
+.I both
+requests that the DHCP server update both the A and PTR
+records in the DNS.
+.I ptr
+requests that the DHCP server updates only the PTR record in
+the DNS.
+.I none
+requests that the DHCP server perform no updates.
+.B dhcpcd
+does not perform any DNS update, even when the server is
+requested to perform no updates.  This can be easily
+implemented outside the client; all the necessary
+information is recorded in the
+.I /var/lib/dhcpcd/dhcpcd-<interface>.info
+file.
+.TP
+.BI \-I \ clientID
+Specifies the client identifier string.
+.B dhcpcd
+uses the default client identifier (MAC address of the network
+interface) if it is not specified.
+.TP
+.BI \-N
+Prevents
+.B dhcpcd
+from replacing existing
+.I /etc/ntp.conf
+file.
+.TP
+.BI \-R
+Prevents
+.B dhcpcd
+from replacing existing using resolvconf or replacing
+.I /etc/resolv.conf
+file.
+.TP
+.BI \-Y
+Prevents
+.B dhcpcd
+from replacing existing
+.I /etc/yp.conf
+file.
+.TP
+.BI \-G
+Prevents
+.B dhcpcd
+from installing default routes provided by DHCP server.
+.SH NOTES
+.TP
+.B dhcpcd
+uses
+.I LOCAL0
+syslog facility for all logging. To catch
+.B dhcpcd
+debugging output add the following line to
+.I /etc/syslog.conf
+file:
+
+local0.*     /var/log/dhcpcd.log
+
+and then refresh syslogd daemon:
+
+kill -1 `cat /var/run/syslogd.pid`
+
+.SH FILES
+.PD 0
+.TP
+.BI /var/lib/dhcpcd
+Directory used for storing files information files created by
+.B dhcpcd
+that can be used by shell scripts.
+.PD 1
+.TP
+.BI /etc/dhcpcd.sh
+script file, which
+.B dhcpcd
+will try to execute whenever it configures or brings down the interface. The
+path to this executable script can be changed with
+.I \-c \ script 
+option.
+.B dhcpcd
+passes 3 parameters to
+.I dhcpcd.sh
+script:
+.TP
+.I dhcpcd.sh infofile [up | down | new]
+The first parameter infofile is the path to a file containing all DHCP
+information we have. The second parameter value
+.I up | down | new
+mean the interface has been brought up with the same IP address as before ("up"), or
+with the new IP address ("new"), or the interface has been brought down ("down").
+.TP
+.BI /etc/resolv.conf
+file created by
+.B dhcpcd
+when the client receives DNS and domain name options.
+If resolvconf is present on the system then we send the data to it instead
+of overwriting resolv.conf
+.TP
+.BI /etc/yp.conf
+file created by
+.B dhcpcd
+when the client receives NIS options.
+.TP
+.BI /etc/ntp.conf
+file created by
+.B dhcpcd
+when the client receives NTP options.
+.TP
+.BI /var/run/dhcpcd-<interface>.pid
+file containing the process id of
+.B dhcpcd.
+The word
+.I <interface>
+is actually replaced with the network interface name like
+.I eth0
+to which
+.B dhcpcd
+is attached.
+
+.SH SEE ALSO
+.BR dig (1),
+.BR nslookup (8),
+.BR nsupdate (8)
+.LP
+.I Dynamic Host Configuration Protocol,
+RFC2132
+.LP
+.I DHCP Options and BOOTP Vendor Extensions,
+RFC2132
+.LP
+.I Draft DHC FQDN Option specification,
+draft-ietf-dhc-fqdn-option
+
+.SH BUGS
+Probably many.
+Please report them to http://bugs.gentoo.org.
+.PD 0
+
+.SH AUTHORS
+Roy Marples <uberlord@gentoo.org>
diff --git a/dhcpcd.c b/dhcpcd.c
new file mode 100644 (file)
index 0000000..9b03fb0
--- /dev/null
+++ b/dhcpcd.c
@@ -0,0 +1,362 @@
+/*
+ * dhcpcd - DHCP client daemon -
+ * Copyright (C) 2005 - 2006 Roy Marples <uberlord@gentoo.org>
+ * 
+ * dhcpcd is an RFC2131 compliant DHCP client daemon.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <sys/stat.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "client.h"
+#include "dhcpcd.h"
+#include "dhcp.h"
+#include "interface.h"
+#include "logger.h"
+#include "pathnames.h"
+#include "version.h"
+
+#define PACKAGE "dhcpcd"
+
+#define STRINGINT(_string, _int) { \
+  char *_tmp; \
+  errno = 0; \
+  long _number = strtol (_string, &_tmp, 0); \
+  if ((errno != 0 && _number == 0) || _string == _tmp || \
+      (errno == ERANGE && (_number == LONG_MAX || _number == LONG_MIN))) \
+    { \
+      logger (LOG_ERR, "`%s' out of range", _string);; \
+      exit (EXIT_FAILURE); \
+    } \
+  else \
+  _int = (int) _number; \
+}
+
+static pid_t readpid(char *pidfile)
+{
+  FILE *fp;
+  pid_t pid;
+
+  if ((fp = fopen (pidfile, "r")) == NULL)
+    {
+      errno = ENOENT;
+      return 0;
+    }
+
+  fscanf (fp, "%d", &pid);
+  fclose (fp);
+
+  return pid;
+}
+
+static int kill_pid (char *pidfile, int sig)
+{
+  pid_t pid = readpid (pidfile);
+  int r = 0;
+
+  if (!pid || (r = kill (pid, sig)))
+    {
+      logger (LOG_ERR, ""PACKAGE" not running");
+      unlink (pidfile);
+    }
+
+  return r;
+}
+
+static void usage ()
+{
+  printf ("usage: "PACKAGE" [-adknpGHNRY] [-c script] [-h hostame] [-i classID]\n"
+         "              [-l leasetime] [-m metric] [-s ipaddress] [-t timeout]\n"
+         "              [-u userclass] [-F [none | ptr | both]] [-I clientID]\n");
+}
+
+int main(int argc, char **argv)
+{
+  options_t options;
+
+  /* Sanitize our fd's */
+  int zero;
+  if ((zero = open (_PATH_DEVNULL, O_RDWR, 0)) >= 0)
+    {
+      while (zero < 3)
+       zero = dup (zero);
+      close(zero);
+    }
+
+  openlog (PACKAGE, LOG_PID, LOG_LOCAL0);
+
+  memset (&options, 0, sizeof (options_t));
+  options.script = DEFAULT_SCRIPT;
+  snprintf (options.classid, CLASS_ID_MAX_LEN, "%s %s", PACKAGE, VERSION); 
+
+  options.doarp = false;
+  options.dodns = true;
+  options.dontp = true;
+  options.dogateway = true;
+  options.timeout = DEFAULT_TIMEOUT;
+
+  int doversion = 0;
+  int dohelp = 0;
+  int userclasses = 0;
+
+  const struct option longopts[] =
+    {
+       {"arp", no_argument, NULL, 'a'},
+       {"script",required_argument, NULL, 'c'},
+       {"debug", no_argument, NULL, 'd'},
+       {"hostname", required_argument, NULL, 'h'},
+       {"classid", required_argument, NULL, 'i'},
+       {"release", no_argument, NULL, 'k'},
+       {"leasetime", required_argument, NULL, 'l'},
+       {"metric", required_argument, NULL, 'm'},
+       {"renew", no_argument, NULL, 'n'},
+       {"persistent", no_argument, NULL, 'p'},
+       {"request", required_argument, NULL, 's'},
+       {"timeout", required_argument, NULL, 't'},
+       {"userclass", required_argument, NULL, 'u'},
+       {"fqdn", optional_argument, NULL, 'F'},
+       {"nogateway", no_argument, NULL, 'G'},
+       {"sethostname", no_argument, NULL, 'H'},
+       {"clientid", required_argument, NULL, 'I'},
+       {"nontp", no_argument, NULL, 'N'},
+       {"nodns", no_argument, NULL, 'R'},
+       {"nonis", no_argument, NULL, 'Y'},
+       {"help", no_argument, &dohelp, 1},
+       {"version", no_argument, &doversion, 1},
+       {NULL, 0, NULL, 0}
+    };
+
+  int ch;
+  int option_index = 0;
+  while ((ch = getopt_long(argc, argv, "ac:dh:i:kl:m:nps:t:u:F:GHI:NRY", longopts,
+                          &option_index)) != -1)
+    switch (ch)
+      {
+      case 0:
+       if (longopts[option_index].flag)
+         break;
+       logger (LOG_ERR, "option `%s' should set a flag",
+               longopts[option_index].name);
+       exit (EXIT_FAILURE);
+       break;
+
+      case 'a':
+       options.doarp = true;
+       break;
+      case 'c':
+       options.script = optarg;
+       break;
+      case 'd':
+       setloglevel(LOG_DEBUG);
+       break;
+      case 'h':
+       if (strlen (optarg) > HOSTNAME_MAX_LEN)
+         {
+           logger(LOG_ERR, "`%s' too long for HostName string, max is %d",
+                  optarg, HOSTNAME_MAX_LEN);
+           exit (EXIT_FAILURE);
+         }
+       else
+         options.hostname = optarg;
+       break;
+      case 'i':
+       if (strlen(optarg) > CLASS_ID_MAX_LEN)
+         {
+           logger (LOG_ERR, "`%s' too long for ClassID string, max is %d",
+                   optarg, CLASS_ID_MAX_LEN);
+           exit (EXIT_FAILURE);
+         }
+       else
+         sprintf(options.classid, "%s", optarg);
+       break;
+      case 'k':
+       options.signal = SIGHUP;
+       break;
+      case 'l':
+       STRINGINT (optarg, options.leasetime);
+       if (options.leasetime <= 0)
+         {
+           logger (LOG_ERR, "leasetime must be a positive value");
+           exit (EXIT_FAILURE);
+         }
+       break;
+      case 'm':
+       STRINGINT(optarg, options.metric);
+       break;
+      case 'n':
+       options.signal = SIGALRM;
+       break;
+      case 'p':
+       options.persistent = true;
+       break;
+      case 's':
+       if (! inet_aton (optarg, &options.requestaddress))
+         {
+           logger (LOG_ERR, "`%s' is not a valid IP address", optarg);
+           exit (EXIT_FAILURE);
+         }
+       break;
+      case 't':
+       STRINGINT (optarg, options.timeout);
+       if (options.timeout < 0)
+         {
+           logger (LOG_ERR, "timeout must be a positive value");
+           exit (EXIT_FAILURE);
+         }
+       break;
+      case 'u':
+         {
+           int i;
+           int offset = 0;
+           for (i = 0; i < userclasses; i++)
+             offset += (int) options.userclass[offset] + 1;
+           if (offset + 1 + strlen (optarg) > USERCLASS_MAX_LEN)
+             {
+               logger (LOG_ERR, "userclass overrun, max is %d",
+                       USERCLASS_MAX_LEN);
+               exit (EXIT_FAILURE);
+             }
+           userclasses++;
+           memcpy (options.userclass + offset + 1 , optarg, strlen (optarg));
+           options.userclass[offset] = strlen (optarg);
+         }
+       break;
+      case 'F':
+       if (strcmp (optarg, "none") == 0)
+         options.fqdn = FQDN_NONE;
+       else if (strcmp (optarg, "ptr") == 0)
+         options.fqdn = FQDN_PTR;
+       else if (strcmp (optarg, "both") == 0)
+         options.fqdn = FQDN_BOTH;
+       else
+         {
+           logger (LOG_ERR, "invalid value `%s' for FQDN", optarg);
+           exit (EXIT_FAILURE);
+         }
+       break;
+      case 'G':
+       options.dogateway = false;
+       break;
+      case 'H':
+       options.dohostname = true;
+       break;
+      case 'I':
+       if (strlen (optarg) > CLIENT_ID_MAX_LEN)
+         {
+           logger (LOG_ERR, "`%s' is too long for ClientID, max is %d",
+                   optarg, CLIENT_ID_MAX_LEN);
+           exit (EXIT_FAILURE);
+         }
+       else
+         sprintf(options.clientid, "%s", optarg);
+       break;
+      case 'N':
+       options.dontp = false;
+       break;
+      case 'R':
+       options.dodns = false;
+       break;
+      case 'Y':
+       options.donis = false;
+       break;
+      case '?':
+       usage ();
+       exit (EXIT_FAILURE);
+      default:
+       usage ();
+       exit (EXIT_FAILURE);
+      }
+
+  if (doversion)
+    printf (""PACKAGE" "VERSION"\n");
+
+  if (dohelp)
+    usage ();
+
+  if (optind < argc)
+    {
+      if (strlen (argv[optind]) > IF_NAMESIZE)
+       {
+         logger (LOG_ERR, "`%s' is too long for an interface name (max=%d)",
+                 argv[optind], IF_NAMESIZE);
+         exit (EXIT_FAILURE);
+       }
+      options.interface = argv[optind];
+    }
+  else
+    {
+      /* If only version was requested then exit now */
+      if (doversion || dohelp)
+       exit (EXIT_SUCCESS);
+
+      logger (LOG_ERR, "no interface specified", options.interface);
+      exit (EXIT_FAILURE);
+    }
+
+  if (geteuid ())
+    {
+      logger (LOG_ERR, "you need to be root to run "PACKAGE);
+      exit (EXIT_FAILURE);
+    }
+
+  char prefix[IF_NAMESIZE + 3];
+  snprintf (prefix, IF_NAMESIZE, "%s: ", options.interface);
+  setlogprefix (prefix);
+  snprintf (options.pidfile, sizeof (options.pidfile), PIDFILE,
+           options.interface);
+
+  if (options.signal != 0)
+    exit (kill_pid (options.pidfile, options.signal));
+
+  umask (022);
+
+  if (readpid (options.pidfile))
+    {
+      logger (LOG_ERR, ""PACKAGE" already running (%s)", options.pidfile);
+      exit (EXIT_FAILURE);
+    }
+
+  if (mkdir (CONFIGDIR, S_IRUSR |S_IWUSR |S_IXUSR | S_IRGRP | S_IXGRP
+            | S_IROTH | S_IXOTH) && errno != EEXIST )
+    {
+      logger( LOG_ERR, "mkdir(\"%s\",0): %m\n", CONFIGDIR);
+      exit (EXIT_FAILURE);
+    }
+
+  if (mkdir (ETCDIR, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP
+            | S_IROTH | S_IXOTH) && errno != EEXIST )
+    {
+      logger (LOG_ERR, "mkdir(\"%s\",0): %m\n", ETCDIR);
+      exit (EXIT_FAILURE);
+    }
+
+  logger (LOG_INFO, PACKAGE " " VERSION " starting");
+  if (dhcp_run (&options))
+    exit (EXIT_FAILURE);
+
+  exit (EXIT_SUCCESS);
+}
diff --git a/dhcpcd.h b/dhcpcd.h
new file mode 100644 (file)
index 0000000..f0ffb5a
--- /dev/null
+++ b/dhcpcd.h
@@ -0,0 +1,67 @@
+/*
+ * dhcpcd - DHCP client daemon -
+ * Copyright (C) 2005 - 2006 Roy Marples <uberlord@gentoo.org>
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef DHCPCD_H
+#define DHCPCD_H
+
+#ifdef __linux__
+#include <linux/limits.h>
+#endif
+#include <netinet/in.h>
+#include <limits.h>
+#include <stdbool.h>
+
+#include "common.h"
+
+#define DEFAULT_TIMEOUT                10
+// #define DEFAULT_LEASETIME   0xffffffff      /* infinite lease time */
+#define DEFAULT_LEASETIME      3600            /* 1 hour */
+
+#define CLASS_ID_MAX_LEN       48
+#define CLIENT_ID_MAX_LEN      48
+#define HOSTNAME_MAX_LEN       64
+#define USERCLASS_MAX_LEN      255     
+
+typedef struct options_t {
+  char *interface;
+  char *hostname;
+  int fqdn;
+  char classid[CLASS_ID_MAX_LEN];
+  char clientid[CLIENT_ID_MAX_LEN];
+  char userclass[USERCLASS_MAX_LEN];
+  unsigned leasetime;
+  time_t timeout;
+  int metric;
+  struct in_addr requestaddress;
+
+  bool doarp;
+  bool dodns;
+  bool dontp;
+  bool donis;
+  bool dogateway;
+  bool dohostname;
+  bool dodomainname;
+  int signal;
+  bool persistent;
+
+  char *script;
+  char pidfile[PATH_MAX];
+} options_t;
+
+#endif
diff --git a/dhcpcd.sh b/dhcpcd.sh
new file mode 100755 (executable)
index 0000000..7df0c32
--- /dev/null
+++ b/dhcpcd.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+#
+#  This is a sample /etc/dhcpcd.sh script.
+#  /etc/dhcpcd.sh script is executed by dhcpcd daemon
+#  any time it configures or shuts down interface.
+#  The following parameters are passed to dhcpcd.exe script:
+#  $1 = HostInfoFilePath, e.g  "/var/lib/dhcpcd/dhcpcd-eth0.info"
+#  $2 = "up" if interface has been configured with the same
+#       IP address as before reboot;
+#  $2 = "down" if interface has been shut down;
+#  $2 = "new" if interface has been configured with new IP address;
+#
+#  Sanity checks
+
+if [ $# -lt 2 ]; then
+  logger -s -p local0.err -t dhcpcd.sh "wrong usage"
+  exit 1
+fi
+
+hostinfo="$1"
+state="$2"
+
+# Reading HostInfo file for configuration parameters
+. "${hostinfo}"
+
+case "${state}" in
+    up)
+    logger -s -p local0.info -t dhcpcd.sh \
+    "interface ${INTERFACE} has been configured with old IP=${IPADDR}"
+    # Put your code here for when the interface has been brought up with an
+    # old IP address here
+    ;;
+
+    new)
+    logger -s -p local0.info -t dhcpcd.sh \
+    "interface ${INTERFACE} has been configured with new IP=${IPADDR}"
+    # Put your code here for when the interface has been brought up with a
+    # new IP address
+    ;;
+
+    down) logger -s -p local0.info -t dhcpcd.sh \
+    "interface ${INTERFACE} has been brought down"
+    # Put your code here for the when the interface has been shut down
+    ;;
+esac
+exit 0
diff --git a/interface.c b/interface.c
new file mode 100644 (file)
index 0000000..c55a662
--- /dev/null
@@ -0,0 +1,688 @@
+/*
+ * dhcpcd - DHCP client daemon -
+ * Copyright (C) 2006 Roy Marples <uberlord@gentoo.org>
+ * 
+ * dhcpcd is an RFC2131 compliant DHCP client daemon.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+
+#include <arpa/inet.h>
+
+/* Netlink suff */
+#ifdef __linux__ 
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <netinet/ether.h>
+#include <netpacket/packet.h>
+#else
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#endif /* __linux__ */
+
+#include <errno.h>
+#include <ifaddrs.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "interface.h"
+#include "logger.h"
+#include "pathnames.h"
+
+#ifndef IFF_NOTRAILERS
+#define        IFF_NOTRAILERS 0
+#endif
+
+void free_address (address_t *addresses)
+{
+  if (!addresses)
+    return;
+
+  address_t *p = addresses;
+  address_t *n = NULL;
+
+  while (p)
+    {
+      n = p->next;
+      free (p);
+      p = n;
+    }
+}
+
+void free_route (route_t *routes)
+{
+  if (!routes)
+    return;
+
+  route_t *p = routes;
+  route_t *n = NULL;
+
+  while (p)
+    {
+      n = p->next;
+      free (p);
+      p = n;
+    }
+}
+
+interface_t *read_interface (const char *ifname, int metric)
+{
+  if (! ifname)
+    return NULL;
+
+  int s;
+  struct ifreq ifr;
+  interface_t *iface;
+  unsigned char hwaddr[ETHER_ADDR_LEN];
+
+  struct ifaddrs *ifap;
+  struct ifaddrs *p;
+  unsigned int flags;
+
+  if (getifaddrs (&ifap) != 0)
+    return NULL;
+
+  for (p = ifap; p; p = p->ifa_next)
+    {
+      if (strcmp (p->ifa_name, ifname) != 0)
+       continue;
+#ifdef __linux__
+      struct sockaddr_ll *sll = (struct sockaddr_ll*) p->ifa_addr;
+      if (p->ifa_addr->sa_family != AF_PACKET
+         || sll->sll_hatype != ARPHRD_ETHER)
+#else
+       struct sockaddr_dl *sdl = (struct sockaddr_dl *) p->ifa_addr;
+      if (p->ifa_addr->sa_family != AF_LINK || sdl->sdl_type != IFT_ETHER)
+#endif
+       {
+         logger (LOG_ERR, "not Ethernet");
+         freeifaddrs (ifap);
+         return NULL;
+       }
+
+      flags = p->ifa_flags;
+#ifdef __linux__
+      memcpy (hwaddr, sll->sll_addr, ETHER_ADDR_LEN);
+#else
+      memcpy (hwaddr, sdl->sdl_data + sdl->sdl_nlen, ETHER_ADDR_LEN);
+#endif
+      break;
+    }
+  freeifaddrs (ifap);
+
+  if (!p)
+    {
+      logger (LOG_ERR, "could not find interface %s", ifname);
+      return NULL;
+    }
+
+  memset (&ifr, 0, sizeof (struct ifreq));
+  strncpy (ifr.ifr_name, ifname, sizeof (ifr.ifr_name));
+  if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
+    {
+      logger (LOG_ERR, "socket: %s", strerror (errno));
+      return NULL;
+    }
+
+  if (ioctl(s, SIOCGIFFLAGS, &ifr) < 0)
+    {
+      logger (LOG_ERR, "ioctl SIOCGIFFLAGS: %s", strerror (errno));
+      close (s);
+      return NULL;
+    }
+
+  ifr.ifr_flags |= IFF_UP | IFF_BROADCAST | IFF_NOTRAILERS | IFF_RUNNING;
+  if (ioctl(s, SIOCSIFFLAGS, &ifr) < 0)
+    {
+      logger (LOG_ERR, "ioctl SIOCSIFFLAGS: %s", strerror (errno));
+      close (s);
+      return NULL;
+    }
+
+#ifndef __linux__
+  ifr.ifr_metric = metric;
+  if (ioctl(s, SIOCSIFMETRIC, &ifr) < 0)
+    {
+      logger (LOG_ERR, "ioctl SIOCSIFMETRIC: %s", strerror (errno));
+      close (s);
+      return NULL;
+    }
+#endif
+
+  close (s);
+
+  iface = xmalloc (sizeof (interface_t));
+  memset (iface, 0, sizeof (interface_t));
+  strncpy (iface->name, ifname, IF_NAMESIZE);
+  snprintf (iface->infofile, PATH_MAX, INFOFILE, ifname);
+  memcpy (&iface->ethernet_address, &hwaddr, ETHER_ADDR_LEN);
+
+  iface->arpable = ! (ifr.ifr_flags & (IFF_NOARP | IFF_LOOPBACK));
+
+  logger (LOG_INFO, "ethernet address = %s",
+         ether_ntoa (&iface->ethernet_address));
+
+  /* 0 is a valid fd, so init to -1 */
+  iface->fd = -1;
+
+  return iface;
+}
+
+#ifdef __FreeBSD__
+static int do_address (const char *ifname, struct in_addr address,
+                      struct in_addr netmask, struct in_addr broadcast, int del)
+{
+  if (! ifname)
+    return -1;
+
+  int s;
+  if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) 
+    {
+      logger (LOG_ERR, "socket: %s", strerror (errno));
+      return -1;
+    }
+
+  struct ifaliasreq ifa;
+  memset (&ifa, 0, sizeof (ifa));
+  strcpy (ifa.ifra_name, ifname);
+
+#define ADDADDR(_var, _addr) \
+    { \
+      struct sockaddr_in *_sin = (struct sockaddr_in *) &_var; \
+      _sin->sin_family = AF_INET; \
+      _sin->sin_len = sizeof (struct sockaddr_in); \
+      memcpy (&_sin->sin_addr, &_addr, sizeof (struct in_addr)); \
+    }
+
+  ADDADDR (ifa.ifra_addr, address);
+  if (! del)
+    {
+      ADDADDR (ifa.ifra_mask, netmask);
+      ADDADDR (ifa.ifra_broadaddr, broadcast);
+    }
+
+#undef ADDADDR
+
+  if (ioctl (s, del ? SIOCDIFADDR : SIOCAIFADDR, &ifa) == -1)
+    {
+      logger (LOG_ERR, "ioctl %s: %s", del ? "SIOCDIFADDR" : "SIOCAIFADDR",
+             strerror (errno));
+      close (s);
+      return -1;
+    }
+
+  close (s);
+  return 0;
+}
+
+static int do_route (const char *ifname,
+                    struct in_addr destination,
+                    struct in_addr netmask,
+                    struct in_addr gateway,
+                    int metric,
+                    int change, int del)
+{
+  if (! ifname)
+    return -1;
+
+  char *destd = strdup (inet_ntoa (destination));
+  char *gend = strdup (inet_ntoa (netmask));
+  logger (LOG_INFO, "%s route to %s (%s) via %s",
+         change ? "changing" : del ? "removing" : "adding",
+         destd, gend, inet_ntoa(gateway));
+  if (destd)
+    free (destd);
+  if (gend)
+    free (gend);
+
+  int s;
+  if ((s = socket(PF_ROUTE, SOCK_RAW, 0)) < 0) 
+    {
+      logger (LOG_ERR, "socket: %s", strerror (errno));
+      return -1;
+    }
+
+  struct rtm
+    {
+      struct rt_msghdr hdr;
+      struct sockaddr_in destination;
+      struct sockaddr_in gateway;
+      struct sockaddr_in netmask;
+    } rtm;
+  memset (&rtm, 0, sizeof (struct rtm));
+
+  rtm.hdr.rtm_version = RTM_VERSION;
+  static int seq;
+  rtm.hdr.rtm_seq = ++seq;
+  rtm.hdr.rtm_type = change ? RTM_CHANGE : del ? RTM_DELETE : RTM_ADD;
+
+  rtm.hdr.rtm_flags = RTF_UP | RTF_GATEWAY | RTF_STATIC;
+  if (netmask.s_addr == 0xffffffff)
+    rtm.hdr.rtm_flags |= RTF_HOST;
+
+  rtm.hdr.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK;
+
+#define ADDADDR(_var, _addr) \
+  _var.sin_family = AF_INET; \
+  _var.sin_len = sizeof (struct sockaddr_in); \
+  memcpy (&_var.sin_addr, &_addr, sizeof (struct in_addr));
+
+  ADDADDR (rtm.destination, destination);
+  ADDADDR (rtm.gateway, gateway);
+  ADDADDR (rtm.netmask, netmask);
+
+#undef ADDADDR
+
+  rtm.hdr.rtm_msglen = sizeof (rtm);
+
+  if (write(s, &rtm, sizeof (rtm)) < 0)
+    {
+      /* Don't report error about routes already existing */
+      if (errno != EEXIST)
+       logger (LOG_ERR, "write: %s", strerror (errno));
+      close (s);
+      return -1;
+    }
+
+  close (s);
+  return 0;
+}
+
+#elif __linux__
+/* This netlink stuff is overly compex IMO.
+   The BSD implementation is much cleaner and a lot less code.
+   send_netlink handles the actual transmission so we can work out
+   if there was an error or not.
+
+   As always throughout this code, credit is due :)
+   This blatently taken from libnetlink.c from the iproute2 package
+   which is the only good source of netlink code.
+   */
+static int send_netlink(struct nlmsghdr *hdr)
+{
+  int s;
+  if ((s = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) 
+    {
+      logger (LOG_ERR, "socket: %s", strerror (errno));
+      return -1;
+    }
+
+  int mypid = getpid ();
+  struct sockaddr_nl nl;
+  memset (&nl, 0, sizeof (struct sockaddr_nl));
+  nl.nl_family = AF_NETLINK;
+  if (bind (s, (struct sockaddr *) &nl, sizeof (nl)) < 0)
+    {
+      logger (LOG_ERR, "bind: %s", strerror (errno));
+      close (s);
+      return -1;
+    }
+
+  struct iovec iov;
+  memset (&iov, 0, sizeof (struct iovec));
+  iov.iov_base = hdr;
+  iov.iov_len = hdr->nlmsg_len;
+
+  struct msghdr msg;
+  memset (&msg, 0, sizeof (struct msghdr));
+  msg.msg_name = &nl;
+  msg.msg_namelen = sizeof (nl);
+  msg.msg_iov = &iov;
+  msg.msg_iovlen = 1;
+
+  /* Request a reply */
+  hdr->nlmsg_flags |= NLM_F_ACK;
+  static int seq;
+  hdr->nlmsg_seq = ++seq;
+
+  if (sendmsg (s, &msg, 0) < 0)
+    {
+      logger (LOG_ERR, "write: %s", strerror (errno));
+      close (s);
+      return -1;
+    }
+
+  char buffer[16384];
+  memset (&buffer, 0, sizeof (buffer));
+  iov.iov_base = buffer;
+
+  struct nlmsghdr *h;
+  while (1) 
+    {
+      iov.iov_len = sizeof (buffer);
+      int bytes = recvmsg(s, &msg, 0);
+
+      if (bytes < 0)
+       {
+         if (errno != EINTR)
+           logger (LOG_ERR, "overrun");
+         continue;
+       }
+
+      if (bytes == 0)
+       {
+         logger (LOG_ERR, "EOF on netlink");
+         goto eexit;
+       }
+
+      if (msg.msg_namelen != sizeof (nl))
+       {
+         logger (LOG_ERR, "sender address length == %d", msg.msg_namelen);
+         goto eexit;
+       }
+
+      for (h = (struct nlmsghdr *) buffer; bytes >= sizeof (*h); )
+       {
+         int len = h->nlmsg_len;
+         int l = len - sizeof (*h);
+
+         if (l < 0 || len > bytes)
+           {
+             if (msg.msg_flags & MSG_TRUNC)
+               logger (LOG_ERR, "truncated message");
+             else
+               logger (LOG_ERR, "malformed message");
+             goto eexit;
+           }
+
+         if (nl.nl_pid != 0 ||
+             h->nlmsg_pid != mypid ||
+             h->nlmsg_seq != seq)
+           /* Message isn't for us, so skip it */
+           goto next;
+
+         /* We get an NLMSG_ERROR back with a code of zero for success */
+         if (h->nlmsg_type == NLMSG_ERROR)
+           {
+             struct nlmsgerr *err = (struct nlmsgerr *) NLMSG_DATA (h);
+             if (l < sizeof (struct nlmsgerr))
+               logger (LOG_ERR, "truncated error message");
+             else
+               {
+                 errno = -err->error;
+                 if (errno == 0)
+                   {
+                     close (s);
+                     return 0;
+                   }
+
+                 /* Don't report on something already existing */
+                 if (errno != EEXIST)
+                   logger (LOG_ERR, "RTNETLINK answers: %s", strerror (errno));
+               }
+             goto eexit;
+           }
+
+         logger (LOG_ERR, "unexpected reply");
+next:
+         bytes -= NLMSG_ALIGN (len);
+         h = (struct nlmsghdr *) ((char *) h + NLMSG_ALIGN (len));
+       }
+
+      if (msg.msg_flags & MSG_TRUNC)
+       {
+         logger (LOG_ERR, "message truncated");
+         continue;
+       }
+
+      if (bytes)
+       {
+         logger (LOG_ERR, "remnant of size %d", bytes);
+         goto eexit;
+       }
+    }
+
+eexit:
+  close (s);
+  return -1;
+}
+
+#define NLMSG_TAIL(nmsg) \
+ ((struct rtattr *) (((unsigned char *) (nmsg)) \
+                    + NLMSG_ALIGN((nmsg)->nlmsg_len)))
+static int add_attr_l(struct nlmsghdr *n, int maxlen, int type, const void *data,
+                     int alen)
+{
+  int len = RTA_LENGTH(alen);
+  struct rtattr *rta;
+
+  if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen)
+    {
+      logger (LOG_ERR, "add_attr_l: message exceeded bound of %d\n", maxlen);
+      return -1;
+    }
+
+  rta = NLMSG_TAIL (n);
+  rta->rta_type = type;
+  rta->rta_len = len;
+  memcpy (RTA_DATA (rta), data, alen);
+  n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len);
+
+  return 0;
+}
+
+static int add_attr_32(struct nlmsghdr *n, int maxlen, int type, uint32_t data)
+{
+  int len = RTA_LENGTH (sizeof (uint32_t));
+  struct rtattr *rta;
+  if (NLMSG_ALIGN (n->nlmsg_len) + len > maxlen)
+    {
+      logger (LOG_ERR, "add_attr32: message exceeded bound of %d\n", maxlen);
+      return -1;
+    }
+
+  rta = NLMSG_TAIL (n);
+  rta->rta_type = type;
+  rta->rta_len = len;
+  memcpy (RTA_DATA (rta), &data, sizeof (uint32_t));
+  n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + len;
+
+  return 0;
+}
+
+
+static int do_address(const char *ifname,
+                     struct in_addr address, struct in_addr netmask,
+                     struct in_addr broadcast, int del)
+{
+  if (!ifname)
+    return -1;
+
+  struct
+    {
+      struct nlmsghdr hdr;
+      struct ifaddrmsg ifa;
+      char buffer[256];
+    }
+  nlm;
+
+  memset (&nlm, 0, sizeof (nlm));
+
+  nlm.hdr.nlmsg_len = NLMSG_LENGTH (sizeof (struct ifaddrmsg));
+  nlm.hdr.nlmsg_flags = NLM_F_REQUEST;
+  nlm.hdr.nlmsg_type = del ? RTM_DELADDR : RTM_NEWADDR;
+  nlm.ifa.ifa_index = if_nametoindex (ifname);
+  nlm.ifa.ifa_family = AF_INET;
+
+  /* Store the netmask in the prefix */
+  uint32_t mask = htonl (netmask.s_addr);
+  while (mask)
+    {
+      nlm.ifa.ifa_prefixlen++;
+      mask <<= 1;
+    }
+
+  add_attr_l (&nlm.hdr, sizeof (nlm), IFA_LOCAL, &address.s_addr,
+             sizeof (address.s_addr));
+  if (! del)
+    add_attr_l (&nlm.hdr, sizeof (nlm), IFA_BROADCAST, &broadcast.s_addr,
+               sizeof (broadcast.s_addr));
+
+  return send_netlink (&nlm.hdr);
+}
+
+static int do_route (const char *ifname,
+                    struct in_addr destination,
+                    struct in_addr netmask,
+                    struct in_addr gateway,
+                    int metric, int change, int del)
+{
+  if (! ifname)
+    return -1;
+
+  char *dstd = strdup (inet_ntoa (destination));
+  char *gend = strdup (inet_ntoa (netmask));
+  logger (LOG_INFO, "%s route to %s (%s) via %s, metric %d",
+         change ? "changing" : del ? "removing" : "adding",
+         dstd, gend, inet_ntoa (gateway), metric);
+  if (dstd)
+    free (dstd);
+  if (gend)
+    free (gend);
+
+  struct
+    {
+      struct nlmsghdr hdr;
+      struct rtmsg rt;
+      char buffer[256];
+    }
+  nlm;
+  memset (&nlm, 0, sizeof (nlm));
+
+  nlm.hdr.nlmsg_len = NLMSG_LENGTH (sizeof (struct rtmsg));
+  if (change)
+    nlm.hdr.nlmsg_flags = NLM_F_REPLACE;
+  else if (! del)
+    nlm.hdr.nlmsg_flags = NLM_F_CREATE | NLM_F_EXCL;
+  nlm.hdr.nlmsg_flags |= NLM_F_REQUEST;
+  nlm.hdr.nlmsg_type = del ? RTM_DELROUTE : RTM_NEWROUTE;
+  nlm.rt.rtm_family = AF_INET;
+  nlm.rt.rtm_table = RT_TABLE_MAIN;
+
+  if (del)
+    nlm.rt.rtm_scope = RT_SCOPE_NOWHERE;
+  else
+    {
+      nlm.hdr.nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL;
+      nlm.rt.rtm_protocol = RTPROT_BOOT;
+      if (gateway.s_addr == 0)
+       nlm.rt.rtm_scope = RT_SCOPE_LINK;
+      else
+       nlm.rt.rtm_scope = RT_SCOPE_UNIVERSE;
+      nlm.rt.rtm_type = RTN_UNICAST;
+    }
+
+  /* Store the netmask in the prefix */
+  uint32_t mask = htonl (netmask.s_addr);
+  while (mask)
+    {
+      nlm.rt.rtm_dst_len++;
+      mask <<= 1;
+    }
+
+  add_attr_l (&nlm.hdr, sizeof (nlm), RTA_DST, &destination.s_addr,
+             sizeof (destination.s_addr));
+  if (gateway.s_addr != 0)
+    add_attr_l (&nlm.hdr, sizeof (nlm), RTA_GATEWAY, &gateway.s_addr,
+               sizeof (gateway.s_addr));
+
+  add_attr_32 (&nlm.hdr, sizeof (nlm), RTA_OIF, if_nametoindex (ifname));
+  add_attr_32 (&nlm.hdr, sizeof (nlm), RTA_PRIORITY, metric);
+
+  return send_netlink (&nlm.hdr);
+}
+
+#else
+#error "Platform not supported!"
+#error "We currently support BPF and Linux sockets."
+#error "Other platforms may work using BPF. If yours does, please let me know"
+#error "so I can add it to our list."
+#endif
+
+
+int add_address (const char *ifname, struct in_addr address,
+                struct in_addr netmask, struct in_addr broadcast)
+{
+  char *daddress = strdup (inet_ntoa (address));
+  logger (LOG_INFO, "adding IP address %s netmask %s",
+         daddress, inet_ntoa (netmask));
+  free (daddress);
+
+  return (do_address (ifname, address, netmask, broadcast, 0));
+}
+
+int del_address (const char *ifname, struct in_addr address)
+{
+  logger (LOG_INFO, "deleting IP address %s", inet_ntoa (address));
+
+  struct in_addr t;
+  memset (&t, 0, sizeof (t));
+  return (do_address (ifname, address, t, t, 1));
+}
+
+/* This should work on all platforms */
+int flush_addresses (const char *ifname)
+{
+  if (! ifname)
+    return -1;
+
+  struct ifaddrs *ifap;
+  struct ifaddrs *p;
+
+  if (getifaddrs (&ifap) != 0)
+    return -1;
+
+  for (p = ifap; p; p = p->ifa_next)
+    {
+      if (strcmp (p->ifa_name, ifname) != 0)
+       continue;
+
+      struct sockaddr_in *sin = (struct sockaddr_in*) p->ifa_addr;
+      if (sin->sin_family == AF_INET)
+       del_address (ifname, sin->sin_addr);
+    }
+  freeifaddrs (ifap);
+
+  return 0;
+}
+
+int add_route (const char *ifname, struct in_addr destination,
+              struct in_addr netmask, struct in_addr gateway, int metric)
+{
+  return (do_route (ifname, destination, netmask, gateway, metric, 0, 0));
+}
+
+int change_route (const char *ifname, struct in_addr destination,
+                 struct in_addr netmask, struct in_addr gateway, int metric)
+{
+  return (do_route (ifname, destination, netmask, gateway, metric, 1, 0));
+}
+
+int del_route (const char *ifname, struct in_addr destination,
+              struct in_addr netmask, struct in_addr gateway, int metric)
+{
+  return (do_route (ifname, destination, netmask, gateway, metric, 0, 1));
+}
+
diff --git a/interface.h b/interface.h
new file mode 100644 (file)
index 0000000..f118dc1
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * dhcpcd - DHCP client daemon -
+ * Copyright (C) 2006 Roy Marples <uberlord@gentoo.org>
+ * 
+ * dhcpcd is an RFC2131 compliant DHCP client daemon.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef INTERFACE_H
+#define INTERFACE_H
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/if_ether.h>
+#include <netinet/in.h>
+#include <limits.h>
+#include <stdbool.h>
+
+typedef struct route_t
+{
+  struct in_addr destination; 
+  struct in_addr netmask;
+  struct in_addr gateway;
+  struct route_t *next;
+} route_t;
+
+typedef struct address_t
+{
+  struct in_addr address;
+  struct address_t *next;
+} address_t;
+
+typedef struct interface_t
+{
+  char name[IF_NAMESIZE];
+  struct ether_addr ethernet_address;
+  bool arpable;
+
+  int fd;
+  int buffer_length;
+
+#ifdef __linux__
+  int socket_protocol;
+#endif
+
+  char infofile[PATH_MAX];
+
+  struct in_addr previous_address;
+  route_t *previous_routes;
+} interface_t;
+
+void free_address (address_t *addresses);
+void free_route (route_t *routes);
+interface_t *read_interface (const char *ifname, int metric);
+
+int add_address (const char *ifname, struct in_addr address,
+                struct in_addr netmask, struct in_addr broadcast);
+int del_address (const char *ifname, struct in_addr address);
+int flush_addresses (const char *ifname);
+
+int add_route (const char *ifname, struct in_addr destination,
+             struct in_addr netmask, struct in_addr gateway, int metric);
+int change_route (const char *ifname, struct in_addr destination,
+                 struct in_addr netmask, struct in_addr gateway, int metric);
+int del_route (const char *ifname, struct in_addr destination,
+             struct in_addr netmask, struct in_addr gateway, int metric);
+#endif
diff --git a/logger.c b/logger.c
new file mode 100644 (file)
index 0000000..aea56fd
--- /dev/null
+++ b/logger.c
@@ -0,0 +1,112 @@
+/*
+ * dhcpcd - DHCP client daemon -
+ * Copyright (C) 2006 Roy Marples <uberlord@gentoo.org>
+ * 
+ * dhcpcd is an RFC2131 compliant DHCP client daemon.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "common.h"
+#include "logger.h"
+
+static int loglevel = LOG_WARNING;
+static char logprefix[12] = {0};
+
+static char *syslog_level_msg[] = {
+  [LOG_EMERG]          = "EMERGENCY!",
+  [LOG_ALERT]          = "ALERT!",
+  [LOG_CRIT]           = "Critical!",
+  [LOG_WARNING]        = "Warning",
+  [LOG_ERR]            = "Error",
+  [LOG_INFO]           = "Info",
+  [LOG_DEBUG]          = "Debug",
+  [LOG_DEBUG + 1]      = NULL
+};
+
+static char *syslog_level[] = {
+  [LOG_EMERG]          = "LOG_EMERG",
+  [LOG_ALERT]          = "LOG_ALERT",
+  [LOG_CRIT]           = "LOG_CRIT",
+  [LOG_ERR]            = "LOG_ERR",
+  [LOG_WARNING]        = "LOG_WARNING",
+  [LOG_NOTICE]         = "LOG_NOTICE",
+  [LOG_INFO]           = "LOG_INFO",
+  [LOG_DEBUG]          = "LOG_DEBUG",
+  [LOG_DEBUG + 1]              = NULL
+};
+
+int logtolevel (const char *priority)
+{
+  int i = 0;
+
+  while (syslog_level[i])
+    {
+      if (!strcmp (priority, syslog_level[i]))
+       return i;
+      i++;
+    }
+  return -1;
+}
+
+void setloglevel (int level)
+{
+  loglevel = level;
+}
+
+void setlogprefix (const char *prefix)
+{
+  snprintf (logprefix, sizeof (logprefix), "%s", prefix);
+}
+
+void logger(int level, const char *fmt, ...)
+{
+  va_list p;
+  va_list p2;
+  FILE *f = stderr;
+
+  va_start (p, fmt);
+  va_copy (p2, p);
+
+  if (level <= LOG_ERR || level <= loglevel)
+    {
+      if (level == LOG_DEBUG || level == LOG_INFO)
+       f = stdout;
+      fprintf (f, "%s, %s", syslog_level_msg[level], logprefix);
+      vfprintf (f, fmt, p);
+      fputc ('\n', f);
+    }
+
+  if (level < LOG_DEBUG || level <= loglevel)
+    {
+      int len = strlen (logprefix);
+      char *fmt2 = xmalloc (strlen (fmt) + len + 1);
+      char *p = fmt2;
+      memcpy (p, &logprefix, len);
+      p += len;
+      strcpy (p, fmt);
+      vsyslog (level, fmt2, p2);
+      free (fmt2);
+    }
+
+  va_end (p);
+}
+
diff --git a/logger.h b/logger.h
new file mode 100644 (file)
index 0000000..d98d5cf
--- /dev/null
+++ b/logger.h
@@ -0,0 +1,32 @@
+/*
+ * dhcpcd - DHCP client daemon -
+ * Copyright (C) 2006 Roy Marples <uberlord@gentoo.org>
+ * 
+ * dhcpcd is an RFC2131 compliant DHCP client daemon.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef LOGGER_H
+#define LOGGER_H
+
+#include <syslog.h>
+
+int logtolevel (const char *priority);
+void setloglevel (int level);
+void setlogprefix (const char *prefix);
+void logger (int level, const char *fmt, ...);
+
+#endif
diff --git a/pathnames.h b/pathnames.h
new file mode 100644 (file)
index 0000000..f4a1b14
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * dhcpcd - DHCP client daemon -
+ * Copyright (C) 2005 - 2006 Roy Marples <uberlord@gentoo.org>
+ *
+ * dhcpcd is an RFC2131 compliant DHCP client daemon.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef PATHNAMES_H
+#define PATHNAMES_H
+
+#define PACKAGE                        "dhcpcd"
+
+#define ETCDIR                 "/etc"
+#define RESOLVFILE             ETCDIR "/resolv.conf"
+#define NISFILE                        ETCDIR "/yp.conf"
+#define NTPFILE                        ETCDIR "/ntp.conf"
+#define NTPDRIFTFILE           ETCDIR "/ntp.drift"
+#define DEFAULT_SCRIPT         ETCDIR "/" PACKAGE ".sh"
+
+#define STATEDIR               "/var"
+#define PIDFILE                        STATEDIR "/run/" PACKAGE "-%s.pid"
+
+#define CONFIGDIR              STATEDIR "/lib/" PACKAGE
+#define INFOFILE               CONFIGDIR "/" PACKAGE "-%s.info"
+
+#define NTPLOGFILE             "/var/log/ntp.log"
+
+#endif
diff --git a/signals.c b/signals.c
new file mode 100644 (file)
index 0000000..b565b8d
--- /dev/null
+++ b/signals.c
@@ -0,0 +1,91 @@
+/* 
+ * Shameless taken from udhcp as I think it's a good idea.
+ * Signal pipe infrastructure. A reliable way of delivering signals.
+ *
+ * Russ Dill <Russ.Dill@asu.edu> December 2003
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "logger.h"
+
+static int signal_pipe[2];
+
+static void signal_handler (int sig)
+{
+  if (send (signal_pipe[1], &sig, sizeof (sig), MSG_DONTWAIT) < 0)
+    logger (LOG_ERR, "Could not send signal: %s", strerror (errno));
+}
+
+
+/* Call this before doing anything else. Sets up the socket pair
+ * and installs the signal handler */
+void signal_setup(void)
+{
+  int i;
+  int flags;
+
+  socketpair (AF_UNIX, SOCK_STREAM, 0, signal_pipe);
+
+  /* Stop any scripts from inheriting us */
+  for (i = 0; i < 2; i++)
+    if ((flags = fcntl (signal_pipe[i], F_GETFD, 0)) < 0 ||
+       fcntl (signal_pipe[i], F_SETFD, flags | FD_CLOEXEC) < 0)
+      logger (LOG_ERR ,"fcntl: %s", strerror (errno));
+  
+  signal (SIGHUP, signal_handler);
+  signal (SIGALRM, signal_handler);
+  signal (SIGTERM, signal_handler);
+  signal (SIGINT, signal_handler);
+}
+
+
+/* Quick little function to setup the rfds. Will return the
+ * max_fd for use with select. Limited in that you can only pass
+ * one extra fd */
+int signal_fd_set (fd_set *rfds, int extra_fd)
+{
+  FD_ZERO (rfds);
+  FD_SET (signal_pipe[0], rfds);
+  if (extra_fd >= 0)
+    FD_SET (extra_fd, rfds);
+  return signal_pipe[0] > extra_fd ? signal_pipe[0] : extra_fd;
+}
+
+
+/* Read a signal from the signal pipe. Returns 0 if there is
+ * no signal, -1 on error (and sets errno appropriately), and
+ * your signal on success */
+int signal_read (fd_set *rfds)
+{
+  int sig;
+
+  if (!FD_ISSET (signal_pipe[0], rfds))
+    return 0;
+
+  if (read (signal_pipe[0], &sig, sizeof (sig)) < 0)
+    return -1;
+
+  return sig;
+}
+
diff --git a/signals.h b/signals.h
new file mode 100644 (file)
index 0000000..62795d8
--- /dev/null
+++ b/signals.h
@@ -0,0 +1,27 @@
+/* 
+ * Shameless taken from udhcp as I think it's a good idea.
+ * Russ Dill <Russ.Dill@asu.edu> December 2003
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef SIGNALS_H
+#define SIGNALS_H
+
+void signal_setup (void);
+int signal_fd_set (fd_set *rfds, int extra_fd);
+int signal_read (fd_set *rfds);
+
+#endif
diff --git a/socket.c b/socket.c
new file mode 100644 (file)
index 0000000..3996d3b
--- /dev/null
+++ b/socket.c
@@ -0,0 +1,528 @@
+/*
+ * Copyright (C) 2006 Roy Marples <uberlord@gentoo.org>
+ * although a lot was lifted from udhcp
+ * 
+ * dhcpcd is an RFC2131 compliant DHCP client daemon.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* We use BSD structure so our code is more portable */
+#define _BSD_SOURCE
+
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <arpa/inet.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+#include <netinet/if_ether.h>
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "dhcp.h"
+#include "interface.h"
+#include "logger.h"
+
+/* A suitably large buffer for all transactions.
+   BPF buffer size is set by the kernel, so no define. */
+#ifdef __linux__
+#define BUFFER_LENGTH 4096
+#endif
+
+static uint16_t checksum (unsigned char *addr, uint16_t len)
+{
+  register uint32_t sum = 0;
+  register uint16_t *w = (uint16_t *) addr;
+  register uint16_t nleft = len;
+
+  while (nleft > 1)
+    {
+      sum += *w++;
+      nleft -= 2;
+    }
+
+  if (nleft == 1)
+    {
+      uint8_t a = 0;
+      memcpy (&a, w, 1);
+      sum += ntohs (a) << 8;
+      // sum += a;
+    }
+
+  sum = (sum >> 16) + (sum & 0xffff);
+  sum += (sum >> 16);
+
+  return ~sum;
+}
+
+void make_dhcp_packet(struct udp_dhcp_packet *packet,
+                     unsigned char *data,
+                     struct in_addr source, struct in_addr dest)
+{
+  struct ip *ip = &packet->ip;
+  struct udphdr *udp = &packet->udp;
+
+  /* OK, this is important :)
+     We copy the data to our packet and then create a small part of the
+     ip structure and an invalid ip_len (basically udp length).
+     We then fill the udp structure and put the checksum
+     of the whole packet into the udp checksum.
+     Finally we complete the ip structure and ip checksum.
+     If we don't do the ordering like so then the udp checksum will be
+     broken, so find another way of doing it! */
+
+  memcpy (&packet->dhcp, data, sizeof (dhcpmessage_t));
+
+  ip->ip_p = IPPROTO_UDP;
+  ip->ip_src.s_addr = htonl (source.s_addr);
+  if (dest.s_addr == 0)
+    ip->ip_dst.s_addr = htonl (INADDR_BROADCAST);
+  else
+    ip->ip_dst.s_addr = htonl (dest.s_addr);
+
+  udp->uh_sport = htons (DHCP_CLIENT_PORT);
+  udp->uh_dport = htons (DHCP_SERVER_PORT);
+  udp->uh_ulen = htons (sizeof (struct udphdr) + sizeof (struct dhcpmessage_t));
+  ip->ip_len = udp->uh_ulen;
+  udp->uh_sum = checksum ((unsigned char *) packet,
+                         sizeof (struct udp_dhcp_packet));
+
+  ip->ip_v = IPVERSION;
+  ip->ip_hl = 5;
+  ip->ip_id = 0;
+  ip->ip_tos = IPTOS_LOWDELAY;
+  ip->ip_len = htons (sizeof (struct ip) + sizeof (struct udphdr) +
+                     sizeof (struct dhcpmessage_t));
+  ip->ip_id = 0;
+  ip->ip_off = 0;
+  ip->ip_ttl = IPDEFTTL;
+
+  ip->ip_sum = checksum ((unsigned char *) ip, sizeof (struct ip));
+}
+
+static int valid_dhcp_packet (unsigned char * data)
+{
+  struct udp_dhcp_packet *packet = (struct udp_dhcp_packet *) data;
+  uint16_t bytes = ntohs (packet->ip.ip_len);
+  uint16_t ipsum = packet->ip.ip_sum;
+  uint16_t iplen = packet->ip.ip_len;
+  uint16_t udpsum = packet->udp.uh_sum;
+  struct in_addr source;
+  struct in_addr dest;
+  int retval = 0;
+
+  packet->ip.ip_sum = 0;
+  if (ipsum != checksum ((unsigned char *) &packet->ip, sizeof (struct ip)))
+    {
+      logger (LOG_DEBUG, "bad IP header checksum, ignoring");
+      retval = -1;
+      goto eexit;
+    }
+
+  memcpy (&source, &packet->ip.ip_src, sizeof (struct in_addr));
+  memcpy (&dest, &packet->ip.ip_dst, sizeof (struct in_addr));
+  memset (&packet->ip, 0, sizeof (struct ip));
+  packet->udp.uh_sum = 0;
+
+  packet->ip.ip_p = IPPROTO_UDP;
+  memcpy (&packet->ip.ip_src, &source, sizeof (struct in_addr));
+  memcpy (&packet->ip.ip_dst, &dest, sizeof (struct in_addr));
+  packet->ip.ip_len = packet->udp.uh_ulen;
+  if (udpsum && udpsum != checksum ((unsigned char *) packet, bytes))
+    {
+      logger (LOG_ERR, "bad UDP checksum, ignoring");
+      retval = -1;
+    }
+
+eexit:
+  packet->ip.ip_sum = ipsum;
+  packet->ip.ip_len = iplen;
+  packet->udp.uh_sum = udpsum;
+
+  return retval;
+}
+
+#ifdef __FreeBSD__
+
+/* Credit where credit is due :)
+   The below BPF filter is taken from ISC DHCP */
+
+# include <net/bpf.h>
+
+static struct bpf_insn dhcp_bpf_filter [] = {
+  /* Make sure this is an IP packet... */
+  BPF_STMT (BPF_LD + BPF_H + BPF_ABS, 12),
+  BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8),
+
+  /* Make sure it's a UDP packet... */
+  BPF_STMT (BPF_LD + BPF_B + BPF_ABS, 23),
+  BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6),
+
+  /* Make sure this isn't a fragment... */
+  BPF_STMT (BPF_LD + BPF_H + BPF_ABS, 20),
+  BPF_JUMP (BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0),
+
+  /* Get the IP header length... */
+  BPF_STMT (BPF_LDX + BPF_B + BPF_MSH, 14),
+
+  /* Make sure it's to the right port... */
+  BPF_STMT (BPF_LD + BPF_H + BPF_IND, 16),
+  BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, DHCP_CLIENT_PORT, 0, 1),
+
+  /* If we passed all the tests, ask for the whole packet. */
+  BPF_STMT (BPF_RET+BPF_K, (u_int) - 1),
+
+  /* Otherwise, drop it. */
+  BPF_STMT (BPF_RET+BPF_K, 0),
+};
+
+static struct bpf_insn arp_bpf_filter [] = {
+  /* Make sure this is an ARP packet... */
+  BPF_STMT (BPF_LD + BPF_H + BPF_ABS, 12),
+  BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_ARP, 0, 3),
+
+  /* Make sure this is an ARP REPLY... */
+  BPF_STMT (BPF_LD + BPF_H + BPF_ABS, 20),
+  BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 0, 1),
+
+  /* If we passed all the tests, ask for the whole packet. */
+  BPF_STMT (BPF_RET+BPF_K, (u_int) - 1),
+
+  /* Otherwise, drop it. */
+  BPF_STMT (BPF_RET+BPF_K, 0),
+};
+
+int open_socket (interface_t *iface, bool arp)
+{
+  int n = 0;
+  int fd = 0;
+  char device[PATH_MAX];
+
+  do
+    {
+      snprintf (device, PATH_MAX, "/dev/bpf%d",  n++);
+      fd = open (device, O_RDWR);
+    } while (fd < 0 && errno == EBUSY);
+
+  if (fd < 0)
+    {
+      logger (LOG_ERR, "unable to open a BPF device");
+      return -1;
+    }
+
+  int flags;
+  if ((flags = fcntl (fd, F_GETFD, 0)) < 0
+      || fcntl (fd, F_SETFD, flags | FD_CLOEXEC) < 0)
+    {
+      logger (LOG_ERR, "fcntl: %s", strerror (errno));
+      close (fd);
+      return -1;
+    }
+
+  struct ifreq ifr;
+  strncpy (ifr.ifr_name, iface->name, sizeof (ifr.ifr_name));
+  if (ioctl (fd, BIOCSETIF, &ifr) < 0)
+    {
+      logger (LOG_ERR, "cannot attach interface `%s' to bpf device `%s': %s",
+             iface->name, strerror (errno));
+      close (fd);
+      return -1;
+    }
+
+  /* Get the required BPF buffer length from the kernel. */
+  int buf = 0;
+  if (ioctl (fd, BIOCGBLEN, &buf) < 0)
+    {
+      logger (LOG_ERR, "ioctl BIOCGBLEN: %s", strerror (errno));
+      close (fd);
+      return -1;
+    }
+  iface->buffer_length = buf;
+
+  int flag = 1;
+  if (ioctl (fd, BIOCIMMEDIATE, &flag) < 0)
+    {
+      logger (LOG_ERR, "ioctl BIOCIMMEDIATE: %s", strerror (errno));
+      close (fd);
+      return -1;
+    }
+
+  /* Install the DHCP filter */
+  struct bpf_program p;
+  if (arp)
+    {
+      p.bf_insns = arp_bpf_filter;
+      p.bf_len = sizeof (arp_bpf_filter) / sizeof (struct bpf_insn);
+    }
+  else
+    {
+      p.bf_insns = dhcp_bpf_filter;
+      p.bf_len = sizeof (dhcp_bpf_filter) / sizeof (struct bpf_insn);
+    }
+  if (ioctl (fd, BIOCSETF, &p) < 0)
+    {
+      logger (LOG_ERR, "ioctl BIOCSETF: %s", strerror (errno));
+      close (fd);
+      return -1;
+    }
+
+  if (iface->fd > -1)
+    close (iface->fd);
+  iface->fd = fd;
+
+  return fd;
+}
+
+int send_packet (interface_t *iface, int type, unsigned char *data,
+                unsigned int len)
+{
+  /* We only support ethernet atm */
+  struct ether_header hw;
+  memset (&hw, 0, sizeof (struct ether_header));
+  memset (&hw.ether_dhost, 0xff, ETHER_ADDR_LEN);
+  hw.ether_type = htons (type);
+
+  int retval = -1;
+  struct iovec iov[2];
+
+  iov[0].iov_base = &hw;
+  iov[0].iov_len = sizeof (struct ether_header);
+  iov[1].iov_base = data;
+  iov[1].iov_len = len;
+
+  if ((retval = writev(iface->fd, iov, 2)) == -1)
+    logger (LOG_ERR, "writev: %s", strerror (errno));
+
+  return retval;
+}
+
+/* BPF requires that we read the entire buffer.
+   So we pass the buffer in the API so we can loop on >1 dhcp packet. */
+int get_packet (interface_t *iface, unsigned char *data,
+               unsigned char *buffer, int *buffer_len, int *buffer_pos)
+{
+  unsigned char *buf = buffer;
+  struct bpf_hdr *packet;
+  struct ether_header *hw;
+  unsigned char *hdr;
+
+  if (*buffer_pos < 1)
+    {
+      memset (buf, 0, iface->buffer_length);
+      *buffer_len = read (iface->fd, buf, iface->buffer_length);
+      *buffer_pos = 0;
+      if (*buffer_len < 1)
+       {
+         logger (LOG_ERR, "read: %s", strerror (errno));
+         return -1;
+       }
+    }
+  else
+    buf += *buffer_pos;
+
+  packet = (struct bpf_hdr *) buf;
+  while (packet)
+    {
+      /* Ensure that the entire packet is in our buffer */
+      if (*buffer_pos + packet->bh_hdrlen + packet->bh_caplen > *buffer_len)
+        break;
+
+      hw = (struct ether_header *) ((char *) packet + packet->bh_hdrlen);
+      hdr = (unsigned char *) ((char *) hw + sizeof (struct ether_header));
+
+      /* If it's an ARP reply, then just send it back */
+      int len = -1;
+      if (hw->ether_type == htons (ETHERTYPE_ARP))
+       {
+         len = packet->bh_caplen - sizeof (struct ether_header);
+         memcpy (data, hdr, len);
+       }
+      else
+       {
+         if (valid_dhcp_packet (hdr) >= 0)
+           {
+             struct udp_dhcp_packet *dhcp = (struct udp_dhcp_packet *) hdr;
+             len = ntohs (dhcp->ip.ip_len) - sizeof (struct ip) -
+              sizeof (struct udphdr);
+             memcpy (data, &dhcp->dhcp, len);
+           }
+       }
+
+      /* Update the buffer_pos pointer */
+      packet += BPF_WORDALIGN (packet->bh_hdrlen + packet->bh_caplen);
+      if (packet - (struct bpf_hdr *) buffer <  *buffer_len)
+       *buffer_pos = (packet - (struct bpf_hdr *) buffer);
+      else
+       *buffer_pos = 0;
+
+      if (len != -1)
+       return len;
+
+      if (*buffer_pos == 0)
+       break;
+    }
+
+  /* No valid packets left, so return */
+  *buffer_pos = 0;
+  return -1;
+}
+
+#elif __linux__
+
+#include <netpacket/packet.h>
+
+int open_socket (interface_t *iface, bool arp)
+{
+  int fd;
+  int flags;
+  struct sockaddr_ll sll;
+
+  if ((fd = socket (PF_PACKET, SOCK_DGRAM, htons (ETH_P_IP))) == -1)
+    {
+      logger (LOG_ERR, "socket: %s", strerror (errno));
+      return -1;
+    }
+
+  if ((flags = fcntl (fd, F_GETFD, 0)) < 0
+      || fcntl (fd, F_SETFD, flags | FD_CLOEXEC) < 0)
+    {
+      logger (LOG_ERR, "fcntl: %s", strerror (errno));
+      close (fd);
+      return -1;
+    }
+
+  memset (&sll, 0, sizeof (struct sockaddr_ll));
+  sll.sll_family = AF_PACKET;
+  if (arp)
+    sll.sll_protocol = htons (ETH_P_ARP);
+  else
+    sll.sll_protocol = htons (ETH_P_IP);
+  sll.sll_ifindex = if_nametoindex (iface->name);
+  sll.sll_halen = ETHER_ADDR_LEN;
+  memset(sll.sll_addr, 0xff, sizeof (sll.sll_addr));
+
+  if (bind(fd, (struct sockaddr *) &sll, sizeof (struct sockaddr_ll)) == -1)
+    {
+      logger (LOG_ERR, "bind: %s", strerror (errno));
+      close (fd);
+      return -1;
+    }
+
+  if (iface->fd > -1)
+    close (iface->fd);
+  iface->fd = fd;
+  iface->socket_protocol = ntohs (sll.sll_protocol);
+
+  iface->buffer_length = BUFFER_LENGTH;
+
+  return fd;
+}
+
+int send_packet (interface_t *iface, int type, unsigned char *data, int len)
+{
+  struct sockaddr_ll sll;
+  int retval;
+
+  if (! iface)
+    return -1;
+
+  memset (&sll, 0, sizeof (struct sockaddr_ll));
+  sll.sll_family = AF_PACKET;
+  sll.sll_protocol = htons (type);
+  sll.sll_ifindex = if_nametoindex (iface->name);
+  sll.sll_halen = ETHER_ADDR_LEN;
+  memset(sll.sll_addr, 0xff, sizeof (sll.sll_addr));
+
+  if ((retval = sendto (iface->fd, data, len, 0, (struct sockaddr *) &sll,
+                       sizeof (struct sockaddr_ll))) < 0)
+
+    logger (LOG_ERR, "sendto: %s", strerror (errno));
+  return retval;
+}
+
+/* Linux has no need for the buffer as we can read as much as we want.
+   We only have the buffer listed to keep the same API. */
+size_t get_packet (interface_t *iface, unsigned char *data,
+                  unsigned char *buffer, int *buffer_len, int *buffer_pos)
+{
+  long bytes;
+
+  /* We don't use the given buffer, but we need to rewind the position */
+  *buffer_pos = 0;
+
+  memset (buffer, 0, iface->buffer_length);
+  bytes = read (iface->fd, buffer, iface->buffer_length);
+  if (bytes < 0)
+    {
+      logger (LOG_ERR, "read: %s", strerror (errno));
+      return -1;
+    }
+
+  *buffer_len = bytes;
+  /* If it's an ARP reply, then just send it back */
+  if (iface->socket_protocol == ETH_P_ARP)
+    {
+      memcpy (data, buffer, bytes);
+      return bytes;
+    }
+
+  if (bytes < (sizeof (struct ip) + sizeof (struct udphdr)))
+    {
+      logger (LOG_DEBUG, "message too short, ignoring");
+      return -1;
+    }
+
+  struct udp_dhcp_packet *dhcp = (struct udp_dhcp_packet *) buffer;
+  if (bytes < ntohs (dhcp->ip.ip_len))
+    {
+      logger (LOG_DEBUG, "truncated packet, ignoring");
+      return -1;
+    }
+
+  bytes = ntohs (dhcp->ip.ip_len);
+
+  /* This is like our BPF filter above */
+  if (dhcp->ip.ip_p != IPPROTO_UDP || dhcp->ip.ip_v != IPVERSION ||
+      dhcp->ip.ip_hl != sizeof (dhcp->ip) >> 2 ||
+      dhcp->udp.uh_dport != htons (DHCP_CLIENT_PORT) ||
+      bytes > (int) sizeof (struct udp_dhcp_packet) ||
+      ntohs (dhcp->udp.uh_ulen) != (uint16_t) (bytes - sizeof (dhcp->ip)))
+    {
+      return -1;
+    }
+
+  if (valid_dhcp_packet (buffer) < 0)
+    return -1;
+
+  memcpy(data, &dhcp->dhcp, bytes - (sizeof (dhcp->ip) +
+                                    sizeof (dhcp->udp)));
+
+  return bytes - (sizeof (dhcp->ip) + sizeof (dhcp->udp));
+}
+
+#else
+#error "Platform not supported!"
+#error "We currently support BPF and Linux sockets."
+#error "Other platforms may work using BPF. If yours does, please let me know"
+#error "so I can add it to our list."
+#endif
diff --git a/socket.h b/socket.h
new file mode 100644 (file)
index 0000000..023b862
--- /dev/null
+++ b/socket.h
@@ -0,0 +1,39 @@
+/*
+ * dhcpcd - DHCP client daemon -
+ * Copyright (C) 2006 Roy Marples <uberlord@gentoo.org>
+ * although a lot was lifted from udhcp
+ * 
+ * dhcpcd is an RFC2131 compliant DHCP client daemon.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef SOCKET_H
+#define SOCKET_H
+
+#include <stdbool.h>
+
+#include "dhcp.h"
+#include "interface.h"
+
+void make_dhcp_packet(struct udp_dhcp_packet *packet,
+                     unsigned char *data,
+                     struct in_addr source, struct in_addr dest);
+
+int open_socket (interface_t *iface, bool arp);
+int send_packet (interface_t *iface, int type, unsigned char *data, unsigned int len);
+int get_packet (interface_t *iface, unsigned char *data,
+               unsigned char *buffer, int *buffer_len, int *buffer_pos);
+#endif