summaryrefslogtreecommitdiffstats
path: root/src/customdhcpcd/client.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/customdhcpcd/client.c')
-rw-r--r--src/customdhcpcd/client.c1149
1 files changed, 1149 insertions, 0 deletions
diff --git a/src/customdhcpcd/client.c b/src/customdhcpcd/client.c
new file mode 100644
index 0000000..b007fd6
--- /dev/null
+++ b/src/customdhcpcd/client.c
@@ -0,0 +1,1149 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright 2006-2008 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+#ifdef __linux__
+# include <netinet/ether.h>
+#endif
+#include <ctype.h>
+#include <errno.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "common.h"
+#ifdef ENABLE_ARP
+# include "arp.h"
+#endif
+#include "client.h"
+#include "configure.h"
+#include "dhcp.h"
+#include "dhcpcd.h"
+#include "info.h"
+#include "interface.h"
+#ifdef ENABLE_IPV4LL
+# include "ipv4ll.h"
+#endif
+#include "logger.h"
+#include "signal.h"
+#include "socket.h"
+
+#include "logwriter.h"
+
+#ifdef ENABLE_DUID
+# include "duid.h"
+#endif
+
+#ifdef ENABLE_INFO
+# include "info.h"
+#endif
+
+#ifdef THERE_IS_NO_FORK
+# ifndef ENABLE_INFO
+ # error "Non MMU requires ENABLE_INFO to work"
+# endif
+#endif
+
+/* Some platforms don't define INFTIM */
+#ifndef INFTIM
+# define INFTIM -1
+#endif
+
+/* This is out mini timeout.
+ * Basically we resend the last request every TIMEOUT_MINI seconds. */
+#define TIMEOUT_MINI 3
+/* Except for an infinite timeout. We keep adding TIMEOUT_MINI to
+ * ourself until TIMEOUT_MINI_INF is reached. */
+#define TIMEOUT_MINI_INF 60
+
+#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
+
+/* We should define a maximum for the NAK exponential backoff */
+#define NAKOFF_MAX 60
+
+#define SOCKET_CLOSED 0
+#define SOCKET_OPEN 1
+
+/* Indexes for pollfds */
+#define POLLFD_SIGNAL 0
+#define POLLFD_IFACE 1
+
+typedef struct _state {
+ int *pidfd;
+ bool forked;
+ int state;
+ uint32_t xid;
+ dhcp_t *dhcp;
+ int socket;
+ interface_t *interface;
+ time_t start;
+ time_t last_sent;
+ time_t last_type;
+ long timeout;
+ time_t nakoff;
+ bool daemonised;
+ bool persistent;
+ unsigned char *buffer;
+ size_t buffer_len;
+ size_t buffer_pos;
+} state_t;
+
+static pid_t daemonise (int *pidfd)
+{
+ pid_t pid;
+ sigset_t full;
+ sigset_t old;
+#ifdef THERE_IS_NO_FORK
+ char **argv;
+ int i;
+#endif
+
+ sigfillset (&full);
+ sigprocmask (SIG_SETMASK, &full, &old);
+
+#ifndef THERE_IS_NO_FORK
+ logger (LOG_DEBUG, "forking to background");
+ switch (pid = fork()) {
+ case -1:
+ logger (LOG_ERR, "fork: %s", strerror (errno));
+ exit (EXIT_FAILURE);
+ /* NOT REACHED */
+ case 0:
+ setsid ();
+ close_fds ();
+ break;
+ default:
+ /* Reset our signals as we're the parent about to exit. */
+ signal_reset ();
+ break;
+ }
+#else
+ logger (LOG_INFO, "forking to background");
+
+ /* We need to add --daemonise to our options */
+ argv = xmalloc (sizeof (char *) * (dhcpcd_argc + 4));
+ argv[0] = dhcpcd;
+ for (i = 1; i < dhcpcd_argc; i++)
+ argv[i] = dhcpcd_argv[i];
+ argv[i] = (char *) "--daemonised";
+ if (dhcpcd_skiproutes) {
+ argv[++i] = (char *) "--skiproutes";
+ argv[++i] = dhcpcd_skiproutes;
+ }
+ argv[i + 1] = NULL;
+
+ switch (pid = vfork ()) {
+ case -1:
+ logger (LOG_ERR, "vfork: %s", strerror (errno));
+ _exit (EXIT_FAILURE);
+ case 0:
+ signal_reset ();
+ sigprocmask (SIG_SETMASK, &old, NULL);
+ execvp (dhcpcd, argv);
+ logger (LOG_ERR, "execl `%s': %s", dhcpcd,
+ strerror (errno));
+ _exit (EXIT_FAILURE);
+ }
+
+ free (argv);
+#endif
+
+ /* Done with the fd now */
+ if (pid != 0) {
+ writepid (*pidfd, pid);
+ close (*pidfd);
+ *pidfd = -1;
+
+ }
+
+ sigprocmask (SIG_SETMASK, &old, NULL);
+ return (pid);
+}
+
+#ifdef ENABLE_INFO
+static bool get_old_lease (state_t *state, const options_t *options)
+{
+ interface_t *iface = state->interface;
+ dhcp_t *dhcp = state->dhcp;
+ struct timeval tv;
+ unsigned int offset = 0;
+
+ if (! IN_LINKLOCAL (ntohl (iface->previous_address.s_addr)))
+ logger (LOG_INFO, "trying to use old lease in `%s'",
+ iface->infofile);
+ if (! read_info (iface, dhcp))
+ return (false);
+
+ /* Vitaly important we remove the server information here */
+ memset (&dhcp->serveraddress, 0, sizeof (dhcp->serveraddress));
+ memset (dhcp->servername, 0, sizeof (dhcp->servername));
+
+#ifdef ENABLE_ARP
+ /* Check that no-one is using the address */
+ if ((options->dolastlease ||
+ (IN_LINKLOCAL (ntohl (dhcp->address.s_addr)) &&
+ (! options->doipv4ll ||
+ arp_claim (iface, dhcp->address)))))
+ {
+ memset (&dhcp->address, 0, sizeof (dhcp->address));
+ memset (&dhcp->netmask, 0, sizeof (dhcp->netmask));
+ memset (&dhcp->broadcast, 0, sizeof (dhcp->broadcast));
+ return (false);
+ }
+
+ /* Ok, lets use this */
+ if (IN_LINKLOCAL (dhcp->address.s_addr))
+ return (true);
+#endif
+
+ /* Ensure that we can still use the lease */
+ if (gettimeofday (&tv, NULL) == -1) {
+ logger (LOG_ERR, "gettimeofday: %s", strerror (errno));
+ return (false);
+ }
+
+ offset = tv.tv_sec - dhcp->leasedfrom;
+ if (dhcp->leasedfrom &&
+ tv.tv_sec - dhcp->leasedfrom > dhcp->leasetime)
+ {
+ logger (LOG_ERR, "lease expired %u seconds ago",
+ offset + dhcp->leasetime);
+ return (false);
+ }
+
+ if (dhcp->leasedfrom == 0)
+ offset = 0;
+ state->timeout = dhcp->renewaltime - offset;
+ iface->start_uptime = uptime ();
+ return (true);
+}
+#endif
+
+#ifdef THERE_IS_NO_FORK
+static void remove_skiproutes (dhcp_t *dhcp, interface_t *iface)
+{
+ int i = -1;
+ route_t *route;
+ route_t *newroute;
+
+ free_route (iface->previous_routes);
+ iface->previous_routes = NULL;
+
+ NSTAILQ_FOREACH (route, dhcp->routes, entries) {
+ i++;
+
+ /* Check that we did add this route or not */
+ if (dhcpcd_skiproutes) {
+ char *sk = xstrdup (dhcpcd_skiproutes);
+ char *skp = sk;
+ char *token;
+ bool found = false;
+
+ while ((token = strsep (&skp, ","))) {
+ if (isdigit (*token) && atoi (token) == i) {
+ found = true;
+ break;
+ }
+ }
+ free (sk);
+ if (found)
+ continue;
+ }
+
+ if (! iface->previous_routes) {
+ iface->previous_routes = xmalloc (sizeof (*iface->previous_routes));
+ STAILQ_INIT (iface->previous_routes);
+ }
+
+ newroute = xmalloc (sizeof (*newroute));
+ memcpy (newroute, route, sizeof (*newroute));
+ STAILQ_INSERT_TAIL (iface->previous_routes, newroute, entries);
+ }
+
+ /* We no longer need this argument */
+ free (dhcpcd_skiproutes);
+ dhcpcd_skiproutes = NULL;
+}
+#endif
+
+static bool client_setup (state_t *state, const options_t *options)
+{
+ dhcp_t *dhcp = state->dhcp;
+ interface_t *iface = state->interface;
+
+ state->state = STATE_INIT;
+ state->last_type = DHCP_DISCOVER;
+ state->nakoff = 1;
+ state->daemonised = options->daemonised;
+ state->persistent = options->persistent;
+
+ if (options->request_address.s_addr == 0 &&
+ (options->doinform || options->dorequest || options->daemonised))
+ {
+#ifdef ENABLE_INFO
+ if (! get_old_lease (state, options))
+#endif
+ {
+ free (dhcp);
+ return (false);
+ }
+ state->timeout = 0;
+
+ if (! options->daemonised &&
+ IN_LINKLOCAL (ntohl (dhcp->address.s_addr)))
+ {
+ logger (LOG_ERR, "cannot request a link local address");
+ return (false);
+ }
+#ifdef THERE_IS_NO_FORK
+ if (options->daemonised) {
+ state->state = STATE_BOUND;
+ state->timeout = dhcp->renewaltime;
+ iface->previous_address = dhcp->address;
+ iface->previous_netmask = dhcp->netmask;
+ remove_skiproutes (dhcp, iface);
+ }
+#endif
+
+ } else {
+ dhcp->address = options->request_address;
+ dhcp->netmask = options->request_netmask;
+ if (dhcp->netmask.s_addr == 0)
+ dhcp->netmask.s_addr = get_netmask (dhcp->address.s_addr);
+ dhcp->broadcast.s_addr = dhcp->address.s_addr |
+ ~dhcp->netmask.s_addr;
+ }
+
+ /* 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. */
+ if (! options->test && ! options->daemonised) {
+ if (! options->doinform) {
+ flush_addresses (iface->name);
+ } else {
+ /* The inform address HAS to be configured for it to
+ * work with most DHCP servers */
+ if (options->doinform &&
+ has_address (iface->name, dhcp->address) < 1)
+ {
+ add_address (iface->name, dhcp->address,
+ dhcp->netmask, dhcp->broadcast);
+ iface->previous_address = dhcp->address;
+ iface->previous_netmask = dhcp->netmask;
+ }
+ }
+ }
+
+ if (*options->clientid) {
+ /* Attempt to see if the ClientID is a hardware address */
+ iface->clientid_len = hwaddr_aton (NULL, options->clientid);
+ if (iface->clientid_len) {
+ iface->clientid = xmalloc (iface->clientid_len);
+ hwaddr_aton (iface->clientid, options->clientid);
+ } else {
+ /* Nope, so mark it as-is */
+ iface->clientid_len = strlen (options->clientid) + 1;
+ iface->clientid = xmalloc (iface->clientid_len);
+ *iface->clientid = '\0';
+ memcpy (iface->clientid + 1,
+ options->clientid, iface->clientid_len - 1);
+ }
+ } else {
+#ifdef ENABLE_DUID
+ unsigned char *duid = NULL;
+ size_t duid_len = 0;
+
+ if (options->doduid) {
+ duid = xmalloc (DUID_LEN);
+ duid_len = get_duid (duid, iface);
+ }
+
+ if (duid_len > 0) {
+ logger (LOG_INFO, "DUID = %s", hwaddr_ntoa (duid, duid_len));
+
+ iface->clientid_len = duid_len + 5;
+ iface->clientid = xmalloc (iface->clientid_len);
+ *iface->clientid = 255; /* RFC 4361 */
+
+ /* IAID is 4 bytes, so if the iface name is 4 bytes use it */
+ if (strlen (iface->name) == 4) {
+ memcpy (iface->clientid + 1, iface->name, 4);
+ } else {
+ /* Name isn't 4 bytes, so use the index */
+ uint32_t ul = htonl (if_nametoindex (iface->name));
+ memcpy (iface->clientid + 1, &ul, 4);
+ }
+
+ memcpy (iface->clientid + 5, duid, duid_len);
+ free (duid);
+ } else {
+#else
+ {
+#endif
+ iface->clientid_len = iface->hwlen + 1;
+ iface->clientid = xmalloc (iface->clientid_len);
+ *iface->clientid = iface->family;
+ memcpy (iface->clientid + 1, iface->hwaddr, iface->hwlen);
+ }
+ }
+
+ return (true);
+}
+
+static bool do_socket (state_t *state, int mode)
+{
+ if (state->interface->fd >= 0)
+ close (state->interface->fd);
+#ifdef __linux
+ if (mode == SOCKET_CLOSED && state->interface->listen_fd >= 0) {
+ close (state->interface->listen_fd);
+ state->interface->listen_fd = -1;
+ }
+#endif
+
+ state->interface->fd = -1;
+ if (mode == SOCKET_OPEN)
+ if (open_socket (state->interface, ETHERTYPE_IP) == -1)
+ return (false);
+ state->socket = mode;
+ return (true);
+}
+
+static bool _send_message (state_t *state, int type, const options_t *options)
+{
+ ssize_t retval;
+
+ state->last_type = type;
+ state->last_sent = uptime ();
+ logSendToQt(type);
+ retval = send_message (state->interface, state->dhcp, state->xid,
+ type, options);
+ return (retval == -1 ? false : true);
+}
+
+static void drop_config (state_t *state, const options_t *options)
+{
+ if (! state->persistent)
+ configure (options, state->interface, state->dhcp, false);
+
+ free_dhcp (state->dhcp);
+ memset (state->dhcp, 0, sizeof (*state->dhcp));
+}
+
+static int wait_for_packet (struct pollfd *fds, state_t *state,
+ const options_t *options)
+{
+ dhcp_t *dhcp = state->dhcp;
+ interface_t *iface = state->interface;
+ int timeout = 0;
+ int retval = 0;
+
+ if (! (state->timeout > 0 ||
+ (options->timeout == 0 &&
+ (state->state != STATE_INIT || state->xid)))) {
+ /* We need to zero our signal fd, otherwise we will block
+ * trying to read a signal. */
+ fds[POLLFD_SIGNAL].revents = 0;
+ return (0);
+ }
+
+ fds[POLLFD_IFACE].fd = iface->fd;
+
+ if ((options->timeout == 0 && state->xid) ||
+ (dhcp->leasetime == (unsigned) -1 &&
+ state->state == STATE_BOUND))
+ {
+ logger (LOG_DEBUG, "waiting for infinity");
+ while (retval == 0) {
+ if (iface->fd == -1)
+ retval = poll (fds, 1, INFTIM);
+ else {
+ /* Slow down our requests */
+ if (timeout < TIMEOUT_MINI_INF)
+ timeout += TIMEOUT_MINI;
+ else if (timeout > TIMEOUT_MINI_INF)
+ timeout = TIMEOUT_MINI_INF;
+
+ retval = poll (fds, 2, timeout * 1000);
+ if (retval == -1 && errno == EINTR) {
+ /* If interupted, continue as normal as
+ * the signal will be delivered down
+ * the pipe */
+ retval = 0;
+ continue;
+ }
+ if (retval == 0)
+ _send_message (state, state->last_type,
+ options);
+ }
+ }
+
+ return (retval);
+ }
+
+ /* Resend our message if we're getting loads of packets.
+ * As we use BPF or LPF, we shouldn't hit this as much, but it's
+ * still nice to have. */
+ if (iface->fd > -1 && uptime () - state->last_sent >= TIMEOUT_MINI)
+ _send_message (state, state->last_type, options);
+
+ logger (LOG_DEBUG, "waiting for %ld seconds",
+ (unsigned long) state->timeout);
+ /* If we're waiting for a reply, then we re-send the last
+ * DHCP request periodically in-case of a bad line */
+ retval = 0;
+ while (state->timeout > 0 && retval == 0) {
+ if (iface->fd == -1)
+ timeout = (int) state->timeout;
+ else {
+ timeout = TIMEOUT_MINI;
+ if (state->timeout < timeout)
+ timeout = (int) state->timeout;
+ }
+ timeout *= 1000;
+ state->start = uptime ();
+ retval = poll (fds, iface->fd == -1 ? 1 : 2, timeout);
+ state->timeout -= uptime () - state->start;
+ if (retval == -1 && errno == EINTR) {
+ /* If interupted, continue as normal as the signal
+ * will be delivered down the pipe */
+ retval = 0;
+ continue;
+ }
+ if (retval == 0 && iface->fd != -1 && state->timeout > 0)
+ _send_message (state, state->last_type, options);
+ }
+
+ return (retval);
+}
+
+static bool handle_signal (int sig, state_t *state, const options_t *options)
+{
+ switch (sig) {
+ case SIGINT:
+ logger (LOG_INFO, "received SIGINT, stopping");
+ return (false);
+ case SIGTERM:
+ logger (LOG_INFO, "received SIGTERM, stopping");
+ return (false);
+
+ case SIGALRM:
+ logger (LOG_INFO, "received SIGALRM, renewing lease");
+ switch (state->state) {
+ case STATE_BOUND:
+ case STATE_RENEWING:
+ case STATE_REBINDING:
+ state->state = STATE_RENEW_REQUESTED;
+ break;
+ case STATE_RENEW_REQUESTED:
+ case STATE_REQUESTING:
+ case STATE_RELEASED:
+ state->state = STATE_INIT;
+ break;
+ }
+ state->timeout = 0;
+ state->xid = 0;
+ return (true);
+
+ case SIGHUP:
+ if (state->state != STATE_BOUND &&
+ state->state != STATE_RENEWING &&
+ state->state != STATE_REBINDING)
+ {
+ logger (LOG_ERR,
+ "received SIGHUP, but we no have lease to release");
+ return (false);
+ }
+
+ logger (LOG_INFO, "received SIGHUP, releasing lease");
+ if (! IN_LINKLOCAL (ntohl (state->dhcp->address.s_addr))) {
+ do_socket (state, SOCKET_OPEN);
+ state->xid = (uint32_t) random ();
+ if ((open_socket (state->interface, false)) >= 0) {
+ _send_message (state, DHCP_RELEASE, options);
+ }
+ do_socket (state, SOCKET_CLOSED);
+ }
+ unlink (state->interface->infofile);
+ return (false);
+
+ default:
+ logger (LOG_ERR,
+ "received signal %d, but don't know what to do with it",
+ sig);
+ }
+
+ return (false);
+}
+
+static int handle_timeout (state_t *state, const options_t *options)
+{
+ dhcp_t *dhcp = state->dhcp;
+ interface_t *iface = state->interface;
+
+ /* No NAK, so reset the backoff */
+ state->nakoff = 1;
+
+ if (state->state == STATE_INIT && state->xid != 0) {
+ if (iface->previous_address.s_addr != 0 &&
+ ! IN_LINKLOCAL (ntohl (iface->previous_address.s_addr)) &&
+ ! options->doinform)
+ {
+ logger (LOG_ERR, "lost lease");
+ if (! options->persistent)
+ drop_config (state, options);
+ } else if (! IN_LINKLOCAL (ntohl (iface->previous_address.s_addr)))
+ logger (LOG_ERR, "timed out");
+
+ do_socket (state, SOCKET_CLOSED);
+ free_dhcp (dhcp);
+ memset (dhcp, 0, sizeof (*dhcp));
+
+#ifdef ENABLE_INFO
+ if (! options->test &&
+ (options->doipv4ll || options->dolastlease))
+ {
+ errno = 0;
+ if (! get_old_lease (state, options))
+ {
+ if (errno == EINTR)
+ return (0);
+ if (options->dolastlease)
+ return (-1);
+ free_dhcp (dhcp);
+ memset (dhcp, 0, sizeof (*dhcp));
+ } else if (errno == EINTR)
+ return (0);
+ }
+#endif
+
+#ifdef ENABLE_IPV4LL
+ if (! options->test && options->doipv4ll &&
+ (! dhcp->address.s_addr ||
+ (! IN_LINKLOCAL (ntohl (dhcp->address.s_addr)) &&
+ ! options->dolastlease)))
+ {
+ logger (LOG_INFO, "probing for an IPV4LL address");
+ free_dhcp (dhcp);
+ memset (dhcp, 0, sizeof (*dhcp));
+ if (ipv4ll_get_address (iface, dhcp) == -1) {
+ if (! state->daemonised)
+ return (-1);
+
+ /* start over */
+ state->xid = 0;
+ return (0);
+ }
+ state->timeout = dhcp->renewaltime;
+ }
+#endif
+
+#if defined (ENABLE_INFO) || defined (ENABLE_IPV4LL)
+ if (dhcp->address.s_addr) {
+ if (! state->daemonised &&
+ IN_LINKLOCAL (ntohl (dhcp->address.s_addr)))
+ logger (LOG_WARNING, "using IPV4LL address %s",
+ inet_ntoa (dhcp->address));
+ if (configure (options, iface, dhcp, true) == -1 &&
+ ! state->daemonised)
+ return (-1);
+
+ state->state = STATE_BOUND;
+ if (! state->daemonised && options->daemonise) {
+ switch (daemonise (state->pidfd)) {
+ case -1:
+ return (-1);
+ case 0:
+ state->daemonised = true;
+ return (0);
+ default:
+ state->persistent = true;
+ state->forked = true;
+ return (-1);
+ }
+ }
+
+ state->timeout = dhcp->renewaltime;
+ state->xid = 0;
+ return (0);
+ }
+#endif
+
+ if (! state->daemonised)
+ return (-1);
+ }
+
+ switch (state->state) {
+ case STATE_INIT:
+ state->xid = (uint32_t) random ();
+ do_socket (state, SOCKET_OPEN);
+ state->timeout = options->timeout;
+ iface->start_uptime = uptime ();
+ if (dhcp->address.s_addr == 0) {
+ if (! IN_LINKLOCAL (ntohl (iface->previous_address.s_addr)))
+ logger (LOG_INFO, "broadcasting for a lease");
+ _send_message (state, DHCP_DISCOVER, options);
+ } else if (options->doinform) {
+ logger (LOG_INFO, "broadcasting inform for %s",
+ inet_ntoa (dhcp->address));
+ _send_message (state, DHCP_INFORM, options);
+ state->state = STATE_REQUESTING;
+ } else {
+ logger (LOG_INFO, "broadcasting for a lease of %s",
+ inet_ntoa (dhcp->address));
+ _send_message (state, DHCP_REQUEST, options);
+ state->state = STATE_REQUESTING;
+ }
+
+ break;
+ case STATE_BOUND:
+ case STATE_RENEW_REQUESTED:
+ if (IN_LINKLOCAL (ntohl (dhcp->address.s_addr))) {
+ memset (&dhcp->address, 0, sizeof (dhcp->address));
+ state->state = STATE_INIT;
+ state->xid = 0;
+ break;
+ }
+ state->state = STATE_RENEWING;
+ state->xid = (uint32_t) random ();
+ /* FALLTHROUGH */
+ case STATE_RENEWING:
+ iface->start_uptime = uptime ();
+ logger (LOG_INFO, "renewing lease of %s", inet_ntoa
+ (dhcp->address));
+ do_socket (state, SOCKET_OPEN);
+ _send_message (state, DHCP_REQUEST, options);
+ state->timeout = dhcp->rebindtime - dhcp->renewaltime;
+ state->state = STATE_REBINDING;
+ break;
+ case STATE_REBINDING:
+ logger (LOG_ERR, "lost lease, attemping to rebind");
+ memset (&dhcp->address, 0, sizeof (dhcp->address));
+ do_socket (state, SOCKET_OPEN);
+ if (state->xid == 0)
+ state->xid = (uint32_t) random ();
+ dhcp->serveraddress.s_addr = 0;
+ _send_message (state, DHCP_REQUEST, options);
+ state->timeout = dhcp->leasetime - dhcp->rebindtime;
+ state->state = STATE_REQUESTING;
+ break;
+ case STATE_REQUESTING:
+ state->state = STATE_INIT;
+ do_socket (state, SOCKET_CLOSED);
+ state->timeout = 0;
+ break;
+
+ case STATE_RELEASED:
+ dhcp->leasetime = 0;
+ break;
+ }
+
+ return (0);
+}
+
+
+static int handle_dhcp (state_t *state, int type, const options_t *options)
+{
+ struct timespec ts;
+ interface_t *iface = state->interface;
+ dhcp_t *dhcp = state->dhcp;
+
+ /* We should restart on a NAK */
+ if (type == DHCP_NAK) {
+ logger (LOG_INFO, "received NAK: %s", dhcp->message);
+ logToQt(LOG_INFO, DHCP_NAK, "");
+ state->state = STATE_INIT;
+ state->timeout = 0;
+ state->xid = 0;
+ free_dhcp (dhcp);
+ memset (dhcp, 0, sizeof (*dhcp));
+
+ /* If we constantly get NAKS then we should slowly back off */
+ if (state->nakoff > 0) {
+ logger (LOG_DEBUG, "sleeping for %ld seconds",
+ (long) state->nakoff);
+ ts.tv_sec = state->nakoff;
+ ts.tv_nsec = 0;
+ state->nakoff *= 2;
+ if (state->nakoff > NAKOFF_MAX)
+ state->nakoff = NAKOFF_MAX;
+ nanosleep (&ts, NULL);
+ }
+
+ return (0);
+ }
+
+ /* No NAK, so reset the backoff */
+ state->nakoff = 1;
+
+ if (type == DHCP_OFFER && state->state == STATE_INIT) {
+ char *addr = strdup (inet_ntoa (dhcp->address));
+ if (dhcp->servername[0])
+ logger (LOG_INFO, "offered %s from %s `%s'",
+ addr, inet_ntoa (dhcp->serveraddress),
+ dhcp->servername);
+ else
+ logger (LOG_INFO, "offered %s from %s",
+ addr, inet_ntoa (dhcp->serveraddress));
+ free (addr);
+
+ logToQt(LOG_INFO, DHCP_OFFER, "");
+
+#ifdef ENABLE_INFO
+ if (options->test) {
+ write_info (iface, dhcp, options, false);
+ errno = 0;
+ return (-1);
+ }
+#endif
+
+ _send_message (state, DHCP_REQUEST, options);
+ state->state = STATE_REQUESTING;
+
+ return (0);
+ }
+
+ if (type == DHCP_OFFER) {
+ logger (LOG_INFO, "got subsequent offer of %s, ignoring ",
+ inet_ntoa (dhcp->address));
+ return (0);
+ }
+
+ /* We should only be dealing with acks */
+ if (type != DHCP_ACK) {
+ logger (LOG_ERR, "%d not an ACK or OFFER", type);
+ return (0);
+ }
+
+ /* if we are here, than we received an ACK and can go on with configuration */
+ logToQt(LOG_INFO, DHCP_ACK, "");
+
+ switch (state->state) {
+ case STATE_RENEW_REQUESTED:
+ case STATE_REQUESTING:
+ case STATE_RENEWING:
+ case STATE_REBINDING:
+ break;
+ default:
+ logger (LOG_ERR, "wrong state %d", state->state);
+ }
+
+ do_socket (state, SOCKET_CLOSED);
+
+#ifdef ENABLE_ARP
+ if (options->doarp && iface->previous_address.s_addr !=
+ dhcp->address.s_addr)
+ {
+ errno = 0;
+ logToQt(LOG_INFO, DHCPCD_ARP_TEST, "");
+ if (arp_claim (iface, dhcp->address)) {
+ do_socket (state, SOCKET_OPEN);
+ _send_message (state, DHCP_DECLINE, options);
+ do_socket (state, SOCKET_CLOSED);
+
+ free_dhcp (dhcp);
+ memset (dhcp, 0, sizeof (*dhcp));
+ state->xid = 0;
+ state->timeout = 0;
+ state->state = STATE_INIT;
+
+ /* RFC 2131 says that we should wait for 10 seconds
+ * before doing anything else */
+ logger (LOG_INFO, "sleeping for 10 seconds");
+ ts.tv_sec = 10;
+ ts.tv_nsec = 0;
+ nanosleep (&ts, NULL);
+ return (0);
+ } else if (errno == EINTR)
+ return (0);
+ }
+#endif
+
+ if (options->doinform) {
+ if (options->request_address.s_addr != 0)
+ dhcp->address = options->request_address;
+ else
+ dhcp->address = iface->previous_address;
+
+ logger (LOG_INFO, "received approval for %s",
+ inet_ntoa (dhcp->address));
+ if (iface->previous_netmask.s_addr != dhcp->netmask.s_addr) {
+ add_address (iface->name, dhcp->address,
+ dhcp->netmask, dhcp->broadcast);
+ iface->previous_netmask.s_addr = dhcp->netmask.s_addr;
+ }
+ state->timeout = options->leasetime;
+ if (state->timeout == 0)
+ state->timeout = DEFAULT_LEASETIME;
+ state->state = STATE_INIT;
+ } else if (dhcp->leasetime == (unsigned) -1) {
+ dhcp->renewaltime = dhcp->rebindtime = dhcp->leasetime;
+ state->timeout = 1; /* So we wait for infinity */
+ logger (LOG_INFO, "leased %s for infinity",
+ inet_ntoa (dhcp->address));
+ state->state = STATE_BOUND;
+ } else {
+ if (! dhcp->leasetime) {
+ dhcp->leasetime = DEFAULT_LEASETIME;
+ logger(LOG_INFO,
+ "no lease time supplied, assuming %d seconds",
+ dhcp->leasetime);
+ }
+ logger (LOG_INFO, "leased %s for %u seconds",
+ inet_ntoa (dhcp->address), dhcp->leasetime);
+
+ if (dhcp->rebindtime >= dhcp->leasetime) {
+ dhcp->rebindtime = (dhcp->leasetime * 0.875);
+ logger (LOG_ERR,
+ "rebind time greater than lease "
+ "time, forcing to %u seconds",
+ dhcp->rebindtime);
+ }
+
+ if (dhcp->renewaltime > dhcp->rebindtime) {
+ dhcp->renewaltime = (dhcp->leasetime * 0.5);
+ logger (LOG_ERR,
+ "renewal time greater than rebind time, "
+ "forcing to %u seconds",
+ dhcp->renewaltime);
+ }
+
+ if (! dhcp->renewaltime) {
+ dhcp->renewaltime = (dhcp->leasetime * 0.5);
+ logger (LOG_INFO,
+ "no renewal time supplied, assuming %d seconds",
+ dhcp->renewaltime);
+ } else
+ logger (LOG_DEBUG, "renew in %u seconds",
+ dhcp->renewaltime);
+
+ if (! dhcp->rebindtime) {
+ dhcp->rebindtime = (dhcp->leasetime * 0.875);
+ logger (LOG_INFO,
+ "no rebind time supplied, assuming %d seconds",
+ dhcp->rebindtime);
+ } else
+ logger (LOG_DEBUG, "rebind in %u seconds",
+ dhcp->rebindtime);
+
+ state->timeout = dhcp->renewaltime;
+ state->state = STATE_BOUND;
+ }
+
+ state->xid = 0;
+
+ logToQt(LOG_INFO, DHCPCD_CONFIGURE, "");
+ if (configure (options, iface, dhcp, true) == -1 &&
+ ! state->daemonised)
+ return (-1);
+
+ if (! state->daemonised && options->daemonise) {
+ switch (daemonise (state->pidfd)) {
+ case 0:
+ state->daemonised = true;
+ return (0);
+ case -1:
+ return (-1);
+ default:
+ state->persistent = true;
+ state->forked = true;
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+static int handle_packet (state_t *state, const options_t *options)
+{
+ interface_t *iface = state->interface;
+ bool valid = false;
+ int type;
+ struct dhcp_t *new_dhcp;
+ dhcpmessage_t message;
+
+ /* 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 (! state->buffer)
+ state->buffer = xmalloc (iface->buffer_length);
+ state->buffer_len = iface->buffer_length;
+ state->buffer_pos = 0;
+
+ /* 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 (message));
+ new_dhcp = xmalloc (sizeof (*new_dhcp));
+
+ do {
+ if (get_packet (iface, (unsigned char *) &message,
+ state->buffer,
+ &state->buffer_len, &state->buffer_pos) == -1)
+ break;
+
+ if (state->xid != message.xid) {
+ logger (LOG_DEBUG,
+ "ignoring packet with xid 0x%x as it's not ours (0x%x)",
+ message.xid, state->xid);
+ continue;
+ }
+
+ logger (LOG_DEBUG, "got a packet with xid 0x%x", message.xid);
+ memset (new_dhcp, 0, sizeof (*new_dhcp));
+ type = parse_dhcpmessage (new_dhcp, &message);
+ if (type == -1) {
+ logger (LOG_ERR, "failed to parse packet");
+ free_dhcp (new_dhcp);
+ /* We don't abort on this, so return zero */
+ return (0);
+ }
+
+ /* 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 = true;
+ break;
+ } while (state->buffer_pos != 0);
+
+ /* No packets for us, so wait until we get one */
+ if (! valid) {
+ free (new_dhcp);
+ return (0);
+ }
+
+ /* new_dhcp is now our master DHCP message */
+ free_dhcp (state->dhcp);
+ free (state->dhcp);
+ state->dhcp = new_dhcp;
+ new_dhcp = NULL;
+
+ return (handle_dhcp (state, type, options));
+}
+
+int dhcp_run (const options_t *options, int *pidfd)
+{
+ interface_t *iface;
+ state_t *state = NULL;
+ struct pollfd fds[] = {
+ { -1, POLLIN, 0 },
+ { -1, POLLIN, 0 }
+ };
+ int retval = -1;
+ int sig;
+
+ if (! options)
+ return (-1);
+
+ /*read_interface : defined in interface.c*/
+ iface = read_interface (options->interface, options->metric);
+ if (! iface)
+ goto eexit;
+
+ state = xzalloc (sizeof (*state));
+ state->dhcp = xzalloc (sizeof (*state->dhcp));
+ state->pidfd = pidfd;
+ state->interface = iface;
+
+ if (! client_setup (state, options))
+ goto eexit;
+
+ if (signal_init () == -1)
+ goto eexit;
+ if (signal_setup () == -1)
+ goto eexit;
+
+ fds[POLLFD_SIGNAL].fd = signal_fd ();
+
+ for (;;) {
+ retval = wait_for_packet (fds, state, options);
+
+ /* We should always handle our signals first */
+ if ((sig = (signal_read (&fds[POLLFD_SIGNAL]))) != -1) {
+ if (handle_signal (sig, state, options))
+ retval = 0;
+ else
+ retval = -1;
+ } else if (retval == 0)
+ retval = handle_timeout (state, options);
+ else if (retval > 0 &&
+ state->socket != SOCKET_CLOSED &&
+ fds[POLLFD_IFACE].revents & POLLIN)
+ retval = handle_packet (state, options);
+ else if (retval == -1 && errno == EINTR) {
+ /* The interupt will be handled above */
+ retval = 0;
+ } else {
+ logger (LOG_ERR, "poll: %s", strerror (errno));
+ retval = -1;
+ }
+
+ if (retval != 0)
+ break;
+ }
+
+eexit:
+ if (iface) {
+ do_socket (state, SOCKET_CLOSED);
+ drop_config (state, options);
+ free_route (iface->previous_routes);
+ free (iface->clientid);
+ free (iface);
+ }
+
+ if (state) {
+ if (state->forked)
+ retval = 0;
+
+ if (state->daemonised)
+ unlink (options->pidfile);
+
+ free_dhcp (state->dhcp);
+ free (state->dhcp);
+ free (state->buffer);
+ free (state);
+ }
+
+ return (retval);
+}