aboutsummaryrefslogtreecommitdiffstats
path: root/client.c
diff options
context:
space:
mode:
Diffstat (limited to 'client.c')
-rw-r--r--client.c1545
1 files changed, 1545 insertions, 0 deletions
diff --git a/client.c b/client.c
new file mode 100644
index 0000000..99d0fa8
--- /dev/null
+++ b/client.c
@@ -0,0 +1,1545 @@
+/*
+ * 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/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+
+#ifdef __linux__
+# include <netinet/ether.h>
+#endif
+
+#include <errno.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "common.h"
+#include "client.h"
+#include "configure.h"
+#include "dhcp.h"
+#include "dhcpcd.h"
+#include "net.h"
+#include "logger.h"
+#include "signals.h"
+
+#ifdef ENABLE_IPV4LL
+# ifndef ENABLE_ARP
+ # error "IPv4LL requires ENABLE_ARP to work"
+# endif
+# define IPV4LL_LEASETIME 2
+#endif
+
+/* Some platforms don't define INFTIM */
+#ifndef INFTIM
+# define INFTIM -1
+#endif
+
+#define STATE_INIT 0
+#define STATE_DISCOVERING 1
+#define STATE_REQUESTING 2
+#define STATE_BOUND 3
+#define STATE_RENEWING 4
+#define STATE_REBINDING 5
+#define STATE_REBOOT 6
+#define STATE_RENEW_REQUESTED 7
+#define STATE_PROBING 8
+#define STATE_ANNOUNCING 9
+
+/* Constants taken from RFC 2131.
+ * We multiply some numbers by 1000 so they are suitable for use in poll(). */
+#define T1 0.5
+#define T2 0.875
+#define DHCP_BASE 4 * 1000
+#define DHCP_RAND_MIN -1 * 1000
+#define DHCP_RAND_MAX 1 * 1000
+#define DHCP_MAX 64 * 1000
+
+/* 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
+#define POLLFD_ARP 2
+
+/* These are really for IPV4LL, RFC 3927.
+ * We multiply some numbers by 1000 so they are suitable for use in poll(). */
+#define PROBE_WAIT 1 * 1000
+#define PROBE_NUM 3
+#define PROBE_MIN 1 * 1000
+#define PROBE_MAX 2 * 1000
+#define ANNOUNCE_WAIT 2 * 1000
+#define ANNOUNCE_NUM 2
+#define ANNOUNCE_INTERVAL 2 * 1000
+#define MAX_CONFLICTS 10
+#define RATE_LIMIT_INTERVAL 60
+#define DEFEND_INTERVAL 10
+
+struct if_state {
+ int options;
+ struct interface *interface;
+ struct dhcp_message *offer;
+ struct dhcp_message *new;
+ struct dhcp_message *old;
+ struct dhcp_lease lease;
+ struct timeval start;
+ struct timeval stop;
+ int state;
+ int messages;
+ long timeout;
+ time_t nakoff;
+ uint32_t xid;
+ int socket;
+ int *pid_fd;
+ int signal_fd;
+#ifdef ENABLE_ARP
+ int probes;
+ int claims;
+ int conflicts;
+ time_t defend;
+ struct in_addr fail;
+#endif
+};
+
+struct dhcp_op {
+ uint8_t value;
+ const char *name;
+};
+
+static const struct dhcp_op const dhcp_ops[] = {
+ { 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" },
+ { 0, NULL }
+};
+
+static const char *
+get_dhcp_op(uint8_t type)
+{
+ const struct dhcp_op *d;
+
+ for (d = dhcp_ops; d->name; d++)
+ if (d->value == type)
+ return d->name;
+ return NULL;
+}
+
+static int
+daemonise(struct if_state *state, const struct options *options)
+{
+ pid_t pid;
+ sigset_t full;
+ sigset_t old;
+#ifdef THERE_IS_NO_FORK
+ char **argv;
+ int i;
+#else
+ char buf = '\0';
+ int sidpipe[2];
+#endif
+
+ if (state->options & DHCPCD_DAEMONISED ||
+ !(options->options & DHCPCD_DAEMONISE))
+ return 0;
+
+ sigfillset(&full);
+ sigprocmask(SIG_SETMASK, &full, &old);
+
+#ifndef THERE_IS_NO_FORK
+ /* Setup a signal pipe so parent knows when to exit. */
+ if (pipe(sidpipe) == -1) {
+ logger(LOG_ERR,"pipe: %s", strerror(errno));
+ return -1;
+ }
+
+ logger(LOG_DEBUG, "forking to background");
+ switch (pid = fork()) {
+ case -1:
+ logger(LOG_ERR, "fork: %s", strerror(errno));
+ exit(EXIT_FAILURE);
+ /* NOTREACHED */
+ case 0:
+ setsid();
+ /* Notify parent it's safe to exit as we've detached. */
+ close(sidpipe[0]);
+ write(sidpipe[1], &buf, 1);
+ close(sidpipe[1]);
+ close_fds();
+ break;
+ default:
+ /* Reset signals as we're the parent about to exit. */
+ signal_reset();
+ /* Wait for child to detach */
+ close(sidpipe[1]);
+ read(sidpipe[0], &buf, 1);
+ close(sidpipe[0]);
+ 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);
+ /* Must not use stdio here. */
+ write(STDERR_FILENO, "exec failed\n", 12);
+ _exit(EXIT_FAILURE);
+ }
+
+ free(argv);
+#endif
+
+ /* Done with the fd now */
+ if (pid != 0) {
+ writepid(*state->pid_fd, pid);
+ close(*state->pid_fd);
+ *state->pid_fd = -1;
+ }
+
+ sigprocmask(SIG_SETMASK, &old, NULL);
+
+ state->state = STATE_BOUND;
+ if (pid == 0) {
+ state->options |= DHCPCD_DAEMONISED;
+ return 0;
+ }
+
+ state->options |= DHCPCD_PERSISTENT | DHCPCD_FORKED;
+ return -1;
+}
+
+#ifndef MINIMAL
+#define THIRTY_YEARS_IN_SECONDS 946707779
+static size_t
+get_duid(unsigned char *duid, const struct interface *iface)
+{
+ FILE *f;
+ uint16_t type = 0;
+ uint16_t hw = 0;
+ uint32_t ul;
+ time_t t;
+ int x = 0;
+ unsigned char *p = duid;
+ size_t len = 0, l = 0;
+ char *buffer = NULL, *line, *option;
+
+ /* If we already have a DUID then use it as it's never supposed
+ * to change once we have one even if the interfaces do */
+ if ((f = fopen(DUID, "r"))) {
+ while ((get_line(&buffer, &len, f))) {
+ line = buffer;
+ while ((option = strsep(&line, " \t")))
+ if (*option != '\0')
+ break;
+ if (!option || *option == '\0' || *option == '#')
+ continue;
+ l = hwaddr_aton(NULL, option);
+ if (l && l <= DUID_LEN) {
+ hwaddr_aton(duid, option);
+ break;
+ }
+ l = 0;
+ }
+ fclose(f);
+ free(buffer);
+ if (l)
+ return l;
+ } else {
+ if (errno != ENOENT)
+ return 0;
+ }
+
+ /* No file? OK, lets make one based on our interface */
+ if (!(f = fopen(DUID, "w")))
+ return 0;
+ type = htons(1); /* DUI-D-LLT */
+ memcpy(p, &type, 2);
+ p += 2;
+ hw = htons(iface->family);
+ memcpy(p, &hw, 2);
+ p += 2;
+ /* time returns seconds from jan 1 1970, but DUID-LLT is
+ * seconds from jan 1 2000 modulo 2^32 */
+ t = time(NULL) - THIRTY_YEARS_IN_SECONDS;
+ ul = htonl(t & 0xffffffff);
+ memcpy(p, &ul, 4);
+ p += 4;
+ /* Finally, add the MAC address of the interface */
+ memcpy(p, iface->hwaddr, iface->hwlen);
+ p += iface->hwlen;
+ len = p - duid;
+ x = fprintf(f, "%s\n", hwaddr_ntoa(duid, len));
+ fclose(f);
+ /* Failed to write the duid? scrub it, we cannot use it */
+ if (x < 1) {
+ len = 0;
+ unlink(DUID);
+ }
+ return len;
+}
+#endif
+
+#ifdef ENABLE_IPV4LL
+static struct dhcp_message*
+ipv4ll_get_dhcp(uint32_t old_addr)
+{
+ uint32_t u32;
+ struct dhcp_message *dhcp;
+ uint8_t *p;
+
+ dhcp = xzalloc(sizeof(*dhcp));
+ /* Put some LL options in */
+ p = dhcp->options;
+ *p++ = DHCP_SUBNETMASK;
+ *p += sizeof(u32);
+ u32 = LINKLOCAL_MASK;
+ memcpy(p, &u32, sizeof(u32));
+ p += sizeof(u32);
+ *p++ = DHCP_BROADCAST;
+ *p += sizeof(u32);
+ u32 = LINKLOCAL_BRDC;
+ memcpy(p, &u32, sizeof(u32));
+ p += sizeof(u32);
+ *p++ = DHCP_END;
+
+ for (;;) {
+ dhcp->yiaddr = htonl(LINKLOCAL_ADDR |
+ (((uint32_t)abs((int)arc4random())
+ % 0xFD00) + 0x0100));
+ if (dhcp->yiaddr != old_addr)
+ break;
+ }
+ return dhcp;
+}
+#endif
+
+static void
+get_lease(struct dhcp_lease *lease, const struct dhcp_message *dhcp)
+{
+ lease->frominfo = 0;
+ lease->addr.s_addr = dhcp->yiaddr;
+
+ if (get_option_addr(&lease->net.s_addr, dhcp, DHCP_SUBNETMASK) == -1)
+ lease->net.s_addr = get_netmask(dhcp->yiaddr);
+ if (get_option_uint32(&lease->leasetime, dhcp, DHCP_LEASETIME) != 0)
+ lease->leasetime = DEFAULT_LEASETIME;
+ if (get_option_uint32(&lease->renewaltime, dhcp, DHCP_RENEWALTIME) != 0)
+ lease->renewaltime = 0;
+ if (get_option_uint32(&lease->rebindtime, dhcp, DHCP_REBINDTIME) != 0)
+ lease->rebindtime = 0;
+}
+
+static int
+get_old_lease(struct if_state *state)
+{
+ struct interface *iface = state->interface;
+ struct dhcp_lease *lease = &state->lease;
+ struct dhcp_message *dhcp;
+ struct timeval tv;
+ unsigned int offset = 0;
+ struct stat sb;
+
+ if (!IN_LINKLOCAL(ntohl(iface->addr.s_addr)))
+ logger(LOG_INFO, "trying to use old lease in `%s'",
+ iface->leasefile);
+ if ((dhcp = read_lease(iface)) == NULL) {
+ if (errno != ENOENT)
+ logger(LOG_INFO, "read_lease: %s", strerror(errno));
+ goto eexit;
+ }
+ if (stat(iface->leasefile, &sb) == -1) {
+ logger(LOG_ERR, "stat: %s", strerror(errno));
+ goto eexit;
+ }
+ get_lease(&state->lease, dhcp);
+ lease->frominfo = 1;
+ lease->leasedfrom = sb.st_mtime;
+
+ /* Vitaly important we remove the server information here */
+ state->lease.server.s_addr = 0;
+ dhcp->servername[0] = '\0';
+
+ if (!IN_LINKLOCAL(ntohl(dhcp->yiaddr))) {
+#ifndef THERE_IS_NO_FORK
+ if (!(state->options & DHCPCD_LASTLEASE))
+ goto eexit;
+#endif
+
+ /* Ensure that we can still use the lease */
+ if (gettimeofday(&tv, NULL) == -1) {
+ logger(LOG_ERR, "gettimeofday: %s", strerror(errno));
+ goto eexit;
+ }
+
+ offset = tv.tv_sec - lease->leasedfrom;
+ if (lease->leasedfrom &&
+ tv.tv_sec - lease->leasedfrom > lease->leasetime)
+ {
+ logger(LOG_ERR, "lease expired %u seconds ago",
+ offset + lease->leasetime);
+ /* Persistent interfaces should still try and use the
+ * lease if we can't contact a DHCP server.
+ * We just set the timeout to 1 second. */
+ if (state->options & DHCPCD_PERSISTENT)
+ offset = lease->renewaltime - 1;
+ else
+ goto eexit;
+ }
+ }
+
+ if (lease->leasedfrom == 0)
+ offset = 0;
+ state->timeout = lease->renewaltime - offset;
+ iface->start_uptime = uptime();
+ free(state->old);
+ state->old = state->new;
+ state->new = NULL;
+ state->offer = dhcp;
+ return 0;
+
+eexit:
+ lease->addr.s_addr = 0;
+ free(dhcp);
+ return -1;
+}
+
+static int
+client_setup(struct if_state *state, const struct options *options)
+{
+ struct interface *iface = state->interface;
+ struct dhcp_lease *lease = &state->lease;
+ struct in_addr addr;
+#ifndef MINIMAL
+ size_t len = 0;
+ unsigned char *duid = NULL;
+ uint32_t ul;
+#endif
+
+ state->state = STATE_INIT;
+ state->nakoff = 1;
+ state->options = options->options;
+
+ if (options->request_address.s_addr == 0 &&
+ (options->options & DHCPCD_INFORM ||
+ options->options & DHCPCD_REQUEST ||
+ options->options & DHCPCD_DAEMONISED))
+ {
+ if (get_old_lease(state) != 0)
+ return -1;
+ state->timeout = 0;
+
+ if (!(options->options & DHCPCD_DAEMONISED) &&
+ IN_LINKLOCAL(ntohl(lease->addr.s_addr)))
+ {
+ logger(LOG_ERR, "cannot request a link local address");
+ return -1;
+ }
+#ifdef THERE_IS_NO_FORK
+ if (options->options & DHCPCD_DAEMONISED) {
+ state->state = STATE_BOUND;
+ state->timeout = state->lease.renewaltime;
+ iface->addr.s_addr = lease->addr.s_addr;
+ iface->net.s_addr = lease->net.s_addr;
+ get_option_addr(&lease->server.s_addr,
+ state->offer, DHCP_SERVERID);
+ }
+#endif
+ } else {
+ lease->addr.s_addr = options->request_address.s_addr;
+ lease->net.s_addr = options->request_netmask.s_addr;
+ }
+
+ /* If INFORMing, ensure the interface has the address */
+ if (state->options & DHCPCD_INFORM &&
+ has_address(iface->name, &lease->addr, &lease->net) < 1)
+ {
+ addr.s_addr = lease->addr.s_addr | ~lease->net.s_addr;
+ logger(LOG_DEBUG, "adding IP address %s/%d",
+ inet_ntoa(lease->addr), inet_ntocidr(lease->net));
+ if (add_address(iface->name, &lease->addr,
+ &lease->net, &addr) == -1)
+ {
+ logger(LOG_ERR, "add_address: %s", strerror(errno));
+ return -1;
+ }
+ iface->addr.s_addr = lease->addr.s_addr;
+ iface->net.s_addr = lease->net.s_addr;
+ }
+
+#ifndef MINIMAL
+ if (*options->clientid) {
+ iface->clientid = xmalloc(options->clientid[0] + 1);
+ memcpy(iface->clientid,
+ options->clientid, options->clientid[0] + 1);
+ } else if (options->options & DHCPCD_CLIENTID) {
+ if (options->options & DHCPCD_DUID) {
+ duid = xmalloc(DUID_LEN);
+ if ((len = get_duid(duid, iface)) == 0)
+ logger(LOG_ERR, "get_duid: %s",
+ strerror(errno));
+ }
+
+ if (len > 0) {
+ logger(LOG_INFO, "DUID = %s",
+ hwaddr_ntoa(duid, len));
+
+ iface->clientid = xmalloc(len + 6);
+ iface->clientid[0] = len + 5;
+ iface->clientid[1] = 255; /* RFC 4361 */
+
+ /* IAID is 4 bytes, so if the iface name is 4 bytes
+ * or less, use it */
+ ul = strlen(iface->name);
+ if (ul < 5) {
+ memcpy(iface->clientid + 2, iface->name, ul);
+ if (ul < 4)
+ memset(iface->clientid + 2 + ul,
+ 0, 4 - ul);
+ } else {
+ /* Name isn't 4 bytes, so use the index */
+ ul = htonl(if_nametoindex(iface->name));
+ memcpy(iface->clientid + 2, &ul, 4);
+ }
+
+ memcpy(iface->clientid + 6, duid, len);
+ free(duid);
+ }
+ if (len == 0) {
+ len = iface->hwlen + 1;
+ iface->clientid = xmalloc(len + 1);
+ iface->clientid[0] = len;
+ iface->clientid[1] = iface->family;
+ memcpy(iface->clientid + 2, iface->hwaddr, iface->hwlen);
+ }
+ }
+#endif
+
+ return 0;
+}
+
+static int
+do_socket(struct if_state *state, int mode)
+{
+ if (state->interface->fd >= 0) {
+ close(state->interface->fd);
+ state->interface->fd = -1;
+ }
+ if (mode == SOCKET_CLOSED && state->interface->udp_fd >= 0) {
+ close(state->interface->udp_fd);
+ state->interface->udp_fd = -1;
+ }
+
+ /* We need to bind to a port, otherwise we generate ICMP messages
+ * that cannot connect the port when we have an address.
+ * We don't actually use this fd at all, instead using our packet
+ * filter socket. */
+ if (mode == SOCKET_OPEN &&
+ state->interface->udp_fd == -1 &&
+ state->lease.addr.s_addr != 0)
+ if (open_udp_socket(state->interface) == -1) {
+ logger(LOG_ERR, "open_udp_socket: %s", strerror(errno));
+ return -1;
+ }
+
+ if (mode == SOCKET_OPEN)
+ if (open_socket(state->interface, ETHERTYPE_IP) == -1) {
+ logger(LOG_ERR, "open_socket: %s", strerror(errno));
+ return -1;
+ }
+ state->socket = mode;
+ return 0;
+}
+
+static ssize_t
+send_message(struct if_state *state, int type, const struct options *options)
+{
+ struct dhcp_message *dhcp;
+ uint8_t *udp;
+ ssize_t len;
+ ssize_t r;
+ struct in_addr from;
+ struct in_addr to;
+
+ logger(LOG_DEBUG, "sending %s with xid 0x%x",
+ get_dhcp_op(type), state->xid);
+ state->messages++;
+ len = make_message(&dhcp, state->interface, &state->lease, state->xid,
+ type, options);
+ from.s_addr = dhcp->ciaddr;
+ if (from.s_addr)
+ to.s_addr = state->lease.server.s_addr;
+ else
+ to.s_addr = 0;
+ if (to.s_addr) {
+ r = send_packet(state->interface, to, (uint8_t *)dhcp, len);
+ if (r == -1)
+ logger(LOG_ERR, "send_packet: %s", strerror(errno));
+ } else {
+ len = make_udp_packet(&udp, (uint8_t *)dhcp, len, from, to);
+ free(dhcp);
+ r = send_raw_packet(state->interface, ETHERTYPE_IP, udp, len);
+ if (r == -1)
+ logger(LOG_ERR, "send_raw_packet: %s", strerror(errno));
+ free(udp);
+ }
+ return r;
+}
+
+static void
+drop_config(struct if_state *state, const char *reason, const struct options *options)
+{
+ configure(state->interface, reason, NULL, state->new,
+ &state->lease, options, 0);
+ free(state->old);
+ state->old = NULL;
+ free(state->new);
+ state->new = NULL;
+
+ state->lease.addr.s_addr = 0;
+}
+
+static int
+wait_for_packet(struct if_state *state)
+{
+ struct pollfd fds[3]; /* iface, arp, signal */
+ int retval, timeout, nfds = 0;
+ time_t start;
+ struct timeval now, d;
+
+ /* We always listen to signals */
+ fds[nfds].fd = state->signal_fd;
+ fds[nfds].events = POLLIN;
+ nfds++;
+
+ if (state->lease.leasetime == ~0U && state->state == STATE_BOUND) {
+ logger(LOG_DEBUG, "waiting for infinity");
+ timeout = INFTIM;
+ } else {
+ timeout = state->timeout;
+ if (timerisset(&state->stop)) {
+ get_time(&now);
+ if (timercmp(&state->stop, &now, >)) {
+ timersub(&state->stop, &now, &d);
+ retval = d.tv_sec * 1000 + (d.tv_usec + 999) / 1000;
+ if (retval < timeout)
+ timeout = retval;
+ }
+ }
+ if (timeout <= 0)
+ return 0;
+ if (state->interface->fd != -1) {
+ fds[nfds].fd = state->interface->fd;
+ fds[nfds].events = POLLIN;
+ nfds++;
+ }
+#ifdef ENABLE_ARP
+ if (state->interface->arp_fd != -1) {
+ fds[nfds].fd = state->interface->arp_fd;
+ fds[nfds].events = POLLIN;
+ nfds++;
+ }
+#endif
+ logger(LOG_DEBUG, "waiting for %0.3f seconds",
+ (float)timeout / 1000);
+ }
+
+ start = uptime();
+ retval = poll(fds, nfds, timeout);
+ if (timeout != INFTIM) {
+ state->timeout -= uptime() - start;
+ if (state->timeout < 0)
+ state->timeout = 0;
+ }
+ if (retval == -1) {
+ if (errno == EINTR)
+ return 0;
+ logger(LOG_ERR, "poll: %s", strerror(errno));
+ }
+ return retval;
+}
+
+static int
+handle_signal(int sig, struct if_state *state, const struct options *options)
+{
+ struct dhcp_lease *lease = &state->lease;
+
+ switch (sig) {
+ case SIGINT:
+ logger(LOG_INFO, "received SIGINT, stopping");
+ if (!(state->options & DHCPCD_PERSISTENT))
+ drop_config(state, "STOP", options);
+ return -1;
+ case SIGTERM:
+ logger(LOG_INFO, "received SIGTERM, stopping");
+ if (!(state->options & DHCPCD_PERSISTENT))
+ drop_config(state, "STOP", options);
+ return -1;
+
+ case SIGALRM:
+ logger (LOG_INFO, "received SIGALRM, renewing lease");
+ switch (state->state) {
+ case STATE_BOUND:
+ case STATE_RENEWING:
+ case STATE_REBINDING:
+ case STATE_ANNOUNCING:
+ state->state = STATE_RENEW_REQUESTED;
+ break;
+ case STATE_RENEW_REQUESTED:
+ case STATE_REQUESTING:
+ state->state = STATE_INIT;
+ break;
+ }
+ timerclear(&state->stop);
+ state->timeout = 0;
+ return 0;
+
+ case SIGHUP:
+ if (state->state != STATE_BOUND &&
+ state->state != STATE_RENEWING &&
+ state->state != STATE_REBINDING)
+ {
+ logger(LOG_ERR,
+ "received SIGHUP, but no lease to release");
+ return -1;
+ }
+
+ logger (LOG_INFO, "received SIGHUP, releasing lease");
+ if (!IN_LINKLOCAL(ntohl(lease->addr.s_addr))) {
+ do_socket(state, SOCKET_OPEN);
+ state->xid = arc4random();
+ send_message(state, DHCP_RELEASE, options);
+ do_socket(state, SOCKET_CLOSED);
+ }
+ drop_config(state, "RELEASE", options);
+ return -1;
+
+ default:
+ logger (LOG_ERR,
+ "received signal %d, but don't know what to do with it",
+ sig);
+ }
+
+ return -1;
+}
+
+static int bind_dhcp(struct if_state *state, const struct options *options)
+{
+ struct interface *iface = state->interface;
+ struct dhcp_lease *lease = &state->lease;
+ const char *reason = NULL;
+ struct timeval tv;
+ int retval;
+
+ free(state->old);
+ state->old = state->new;
+ state->new = state->offer;
+ state->offer = NULL;
+#ifdef ENABLE_ARP
+ state->conflicts = 0;
+ state->defend = 0;
+#endif
+
+ if (options->options & DHCPCD_INFORM) {
+ if (options->request_address.s_addr != 0)
+ lease->addr.s_addr = options->request_address.s_addr;
+ else
+ lease->addr.s_addr = iface->addr.s_addr;
+ logger(LOG_INFO, "received approval for %s",
+ inet_ntoa(lease->addr));
+ state->state = STATE_BOUND;
+ state->lease.leasetime = ~0U;
+ reason = "INFORM";
+ } else if (IN_LINKLOCAL(htonl(state->new->yiaddr))) {
+ get_lease(lease, state->new);
+ logger(LOG_INFO, "using IPv4LL address %s",
+ inet_ntoa(lease->addr));
+ state->state = STATE_INIT;
+ state->timeout = 0;
+ reason = "IPV4LL";
+ } else {
+ if (gettimeofday(&tv, NULL) == 0)
+ lease->leasedfrom = tv.tv_sec;
+
+ get_lease(lease, state->new);
+ if (lease->frominfo)
+ reason = "TIMEOUT";
+
+ if (lease->leasetime == ~0U) {
+ lease->renewaltime = lease->rebindtime = lease->leasetime;
+ state->timeout = 1; /* So we wait for infinity */
+ logger(LOG_INFO, "leased %s for infinity",
+ inet_ntoa(lease->addr));
+ state->state = STATE_BOUND;
+ } else {
+ logger(LOG_INFO, "leased %s for %u seconds",
+ inet_ntoa(lease->addr), lease->leasetime);
+
+ if (lease->rebindtime >= lease->leasetime) {
+ lease->rebindtime = (lease->leasetime * T2);
+ logger(LOG_ERR,
+ "rebind time greater than lease "
+ "time, forcing to %u seconds",
+ lease->rebindtime);
+ }
+
+ if (lease->renewaltime > lease->rebindtime) {
+ lease->renewaltime = (lease->leasetime * T1);
+ logger(LOG_ERR,
+ "renewal time greater than rebind time, "
+ "forcing to %u seconds",
+ lease->renewaltime);
+ }
+
+ if (!lease->renewaltime) {
+ lease->renewaltime = (lease->leasetime * T1);
+ logger(LOG_INFO,
+ "no renewal time supplied, assuming %d seconds",
+ lease->renewaltime);
+ } else
+ logger(LOG_DEBUG, "renew in %u seconds",
+ lease->renewaltime);
+
+ if (!lease->rebindtime) {
+ lease->rebindtime = (lease->leasetime * T2);
+ logger(LOG_INFO,
+ "no rebind time supplied, assuming %d seconds",
+ lease->rebindtime);
+ } else
+ logger(LOG_DEBUG, "rebind in %u seconds",
+ lease->rebindtime);
+
+ state->timeout = lease->renewaltime * 1000;
+ }
+ state->state = STATE_BOUND;
+ }
+
+ state->xid = 0;
+ timerclear(&state->stop);
+ if (!reason) {
+ if (state->old) {
+ if (state->old->yiaddr == state->new->yiaddr &&
+ lease->server.s_addr)
+ reason = "RENEW";
+ else
+ reason = "REBIND";
+ } else
+ reason = "BOUND";
+ }
+ retval = configure(iface, reason, state->new, state->old,
+ &state->lease, options, 1);
+ if (retval != 0)
+ return -1;
+ return daemonise(state, options);
+}
+
+static int
+handle_timeout_fail(struct if_state *state, const struct options *options)
+{
+ struct dhcp_lease *lease = &state->lease;
+ struct interface *iface = state->interface;
+ int gotlease = -1;
+ const char *reason = NULL;
+ struct timeval tv;
+
+ timerclear(&tv);
+ /* Clear our timers and counters as we've failed.
+ * We'll either abort or move to another state with new timers */
+ timerclear(&state->stop);
+ state->messages = 0;
+ state->timeout = 0;
+
+ switch (state->state) {
+ case STATE_DISCOVERING:
+ /* FALLTHROUGH */
+ case STATE_REQUESTING:
+ if (IN_LINKLOCAL(ntohl(iface->addr.s_addr))) {
+ if (!(state->options & DHCPCD_DAEMONISED))
+ logger(LOG_ERR, "timed out");
+ } else {
+ if (iface->addr.s_addr != 0 &&
+ !(state->options & DHCPCD_INFORM))
+ logger(LOG_ERR, "lost lease");
+ else
+ logger(LOG_ERR, "timed out");
+ }
+ do_socket(state, SOCKET_CLOSED);
+ if (state->options & DHCPCD_INFORM ||
+ state->options & DHCPCD_TEST)
+ return -1;
+
+ if (state->options & DHCPCD_IPV4LL ||
+ state->options & DHCPCD_LASTLEASE)
+ gotlease = get_old_lease(state);
+
+#ifdef ENABLE_IPV4LL
+ if (state->options & DHCPCD_IPV4LL && gotlease != 0) {
+ logger(LOG_INFO, "probing for an IPV4LL address");
+ free(state->offer);
+ state->offer = ipv4ll_get_dhcp(0);
+ gotlease = 0;
+ }
+#endif
+
+#ifdef ENABLE_ARP
+ if (gotlease == 0 &&
+ state->offer->yiaddr != iface->addr.s_addr)
+ {
+ state->state = STATE_PROBING;
+ state->claims = 0;
+ state->probes = 0;
+ state->conflicts = 0;
+ return 0;
+ }
+#endif
+
+ if (gotlease == 0)
+ return bind_dhcp(state, options);
+
+ reason = "FAIL";
+ drop_config(state, reason, options);
+ if (!(state->options & DHCPCD_DAEMONISED) &&
+ (state->options & DHCPCD_DAEMONISE))
+ return -1;
+ state->state = STATE_INIT;
+ break;
+ case STATE_RENEWING:
+ logger(LOG_ERR, "failed to renew, attempting to rebind");
+ lease->addr.s_addr = 0;
+ state->state = STATE_REBINDING;
+ tv.tv_sec = lease->rebindtime - lease->renewaltime;
+ break;
+ case STATE_REBINDING:
+ logger(LOG_ERR, "failed to rebind, attempting to discover");
+ reason = "EXPIRE";
+ drop_config(state, reason, options);
+ state->state = STATE_INIT;
+ break;
+ default:
+ logger(LOG_DEBUG, "handle_timeout_failed: invalid state %d",
+ state->state);
+ }
+
+ get_time(&state->start);
+ if (timerisset(&tv))
+ timeradd(&state->start, &tv, &state->stop);
+
+ /* This effectively falls through into the handle_timeout funtion */
+ return 0;
+}
+
+static int
+handle_timeout(struct if_state *state, const struct options *options)
+{
+ struct dhcp_lease *lease = &state->lease;
+ struct interface *iface = state->interface;
+ int i;
+ struct timeval tv;
+ struct in_addr addr;
+
+#ifdef ENABLE_ARP
+ switch (state->state) {
+ case STATE_PROBING:
+ timerclear(&state->stop);
+ if (iface->arp_fd == -1)
+ open_socket(iface, ETHERTYPE_ARP);
+ if (state->probes < PROBE_NUM) {
+ if (state->probes == 0) {
+ addr.s_addr = state->offer->yiaddr;
+ logger(LOG_INFO, "checking %s is available"
+ " on attached networks",
+ inet_ntoa(addr));
+ }
+ state->probes++;
+ logger(LOG_DEBUG, "sending ARP probe #%d",
+ state->probes);
+ if (state->probes < PROBE_NUM)
+ state->timeout = (arc4random() %
+ (PROBE_MAX - PROBE_MIN)) + PROBE_MIN;
+ else
+ state->timeout = ANNOUNCE_WAIT;
+ send_arp(iface, ARPOP_REQUEST, 0, state->offer->yiaddr);
+ return 0;
+ } else {
+ /* We've waited for ANNOUNCE_WAIT after the final probe
+ * so the address is now ours */
+ i = bind_dhcp(state, options);
+ state->state = STATE_ANNOUNCING;
+ state->timeout = ANNOUNCE_INTERVAL;
+ return i;
+ }
+ case STATE_ANNOUNCING:
+ timerclear(&state->stop);
+ if (state->claims < ANNOUNCE_NUM) {
+ state->claims++;
+ logger(LOG_DEBUG, "sending ARP announce #%d",
+ state->claims);
+ send_arp(iface, ARPOP_REQUEST,
+ state->new->yiaddr, state->new->yiaddr);
+ if (state->claims < ANNOUNCE_NUM)
+ state->timeout = ANNOUNCE_INTERVAL;
+ else if (IN_LINKLOCAL(htonl(lease->addr.s_addr))) {
+ state->state = STATE_INIT;
+ state->timeout = 0;
+ } else {
+ state->state = STATE_BOUND;
+ state->timeout = lease->renewaltime * 1000 -
+ (ANNOUNCE_INTERVAL * ANNOUNCE_NUM);
+ close(iface->arp_fd);
+ iface->arp_fd = -1;
+ }
+ }
+ return 0;
+ }
+#endif
+
+ if (timerisset(&state->stop)) {
+ get_time(&tv);
+ if (timercmp(&tv, &state->stop, >))
+ return handle_timeout_fail(state, options);
+ }
+ timerclear(&tv);
+
+ switch (state->state) {
+ case STATE_INIT: /* FALLTHROUGH */
+ case STATE_BOUND: /* FALLTHROUGH */
+ case STATE_RENEW_REQUESTED:
+ do_socket(state, SOCKET_OPEN);
+ state->xid = arc4random();
+ state->messages = 0;
+ state->nakoff = 1;
+ iface->start_uptime = uptime();
+ get_time(&state->start);
+ timerclear(&state->stop);
+ }
+
+ switch(state->state) {
+ case STATE_INIT:
+ /* 21Jul08 - was && DHCPCD_DAEMONISED */
+ if (!(state->state & DHCPCD_DAEMONISED) &&
+ options->timeout &&
+ !IN_LINKLOCAL(htonl(iface->addr.s_addr)))
+ {
+ get_time(&state->start);
+ tv.tv_sec = options->timeout;
+ timeradd(&state->start, &tv, &state->stop);
+ }
+ if (lease->addr.s_addr == 0 ||
+ IN_LINKLOCAL(ntohl(iface->addr.s_addr)))
+ {
+ logger(LOG_INFO, "broadcasting for a lease");
+ state->state = STATE_DISCOVERING;
+ } else if (state->options & DHCPCD_INFORM) {
+ logger(LOG_INFO, "broadcasting inform for %s",
+ inet_ntoa(lease->addr));
+ state->state = STATE_REQUESTING;
+ } else {
+ logger(LOG_INFO, "broadcasting for a lease of %s",
+ inet_ntoa(lease->addr));
+ state->state = STATE_REQUESTING;
+ }
+ break;
+ case STATE_RENEW_REQUESTED:
+ case STATE_BOUND:
+ if (IN_LINKLOCAL(ntohl(lease->addr.s_addr))) {
+ lease->addr.s_addr = 0;
+ state->state = STATE_INIT;
+ state->timeout = 0;
+ break;
+ }
+ logger(LOG_INFO, "renewing lease of %s",inet_ntoa(lease->addr));
+ state->state = STATE_RENEWING;
+ break;
+ }
+
+ switch (state->state) {
+ case STATE_DISCOVERING:
+ send_message(state, DHCP_DISCOVER, options);
+ break;
+ case STATE_REQUESTING:
+ if (state->options & DHCPCD_INFORM) {
+ send_message(state, DHCP_INFORM, options);
+ break;
+ }
+ /* FALLTHROUGH */
+ case STATE_RENEWING: /* FALLTHROUGH */
+ case STATE_REBINDING:
+ send_message(state, DHCP_REQUEST, options);
+ break;
+ }
+
+ state->timeout = DHCP_BASE;
+ for (i = 1; i < state->messages; i++) {
+ state->timeout *= 2;
+ if (state->timeout > DHCP_MAX) {
+ state->timeout = DHCP_MAX;
+ break;
+ }
+ }
+ state->timeout += (arc4random() % (DHCP_RAND_MAX - DHCP_RAND_MIN)) +
+ DHCP_RAND_MIN;
+ return 0;
+}
+
+static int
+handle_dhcp(struct if_state *state, struct dhcp_message **dhcpp,
+ const struct options *options)
+{
+ struct timespec ts;
+ struct dhcp_message *dhcp = *dhcpp;
+ struct interface *iface = state->interface;
+ struct dhcp_lease *lease = &state->lease;
+ char *addr;
+ struct in_addr saddr;
+ uint8_t type;
+ int r;
+
+ if (get_option_uint8(&type, dhcp, DHCP_MESSAGETYPE) == -1) {
+ logger(LOG_ERR, "no DHCP type in message");
+ return -1;
+ }
+
+ /* reset the message counter */
+ state->messages = 0;
+
+ /* We should restart on a NAK */
+ if (type == DHCP_NAK) {
+ addr = get_option_string(dhcp, DHCP_MESSAGE);
+ logger(LOG_WARNING, "received NAK: %s", addr);
+ free(addr);
+ state->state = STATE_INIT;
+ state->timeout = 0;
+ lease->addr.s_addr = 0;
+ timerclear(&state->stop);
+
+ /* If we constantly get NAKS then we should slowly back off */
+ if (state->nakoff > 0) {
+ logger(LOG_DEBUG, "sleeping for %lu seconds",
+ (unsigned 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_DISCOVERING) {
+ lease->addr.s_addr = dhcp->yiaddr;
+ addr = xstrdup(inet_ntoa(lease->addr));
+ r = get_option_addr(&lease->server.s_addr, dhcp, DHCP_SERVERID);
+ if (dhcp->servername[0] && r == 0)
+ logger(LOG_INFO, "offered %s from %s `%s'",
+ addr, inet_ntoa(lease->server),
+ dhcp->servername);
+ else if (r == 0)
+ logger(LOG_INFO, "offered %s from %s",
+ addr, inet_ntoa(lease->server));
+ else
+ logger(LOG_INFO, "offered %s", addr);
+ free(addr);
+
+ if (state->options & DHCPCD_TEST) {
+ exec_script(options, iface->name, "TEST", dhcp, NULL);
+ free(dhcp);
+ return 0;
+ }
+
+ free(dhcp);
+ state->state = STATE_REQUESTING;
+ state->timeout = 0;
+ return 0;
+ }
+
+ if (type == DHCP_OFFER) {
+ saddr.s_addr = dhcp->yiaddr;
+ logger(LOG_INFO, "got subsequent offer of %s, ignoring ",
+ inet_ntoa(saddr));
+ free(dhcp);
+ return 0;
+ }
+
+ /* We should only be dealing with acks */
+ if (type != DHCP_ACK) {
+ logger(LOG_ERR, "%d not an ACK or OFFER", type);
+ free(dhcp);
+ return 0;
+ }
+
+ switch (state->state) {
+ case STATE_RENEW_REQUESTED:
+ case STATE_REQUESTING:
+ case STATE_RENEWING:
+ case STATE_REBINDING:
+ if (!(state->options & DHCPCD_INFORM)) {
+ saddr.s_addr = dhcp->yiaddr;
+ logger(LOG_INFO, "lease of %s acknowledged",
+ inet_ntoa(saddr));
+ }
+ break;
+ default:
+ logger(LOG_ERR, "wrong state %d", state->state);
+ }
+
+ do_socket(state, SOCKET_CLOSED);
+ free(state->offer);
+ state->offer = dhcp;
+ *dhcpp = NULL;
+
+#ifdef ENABLE_ARP
+ if (state->options & DHCPCD_ARP &&
+ iface->addr.s_addr != state->offer->yiaddr)
+ {
+ state->state = STATE_PROBING;
+ state->timeout = 0;
+ state->claims = 0;
+ state->probes = 0;
+ state->conflicts = 0;
+ timerclear(&state->stop);
+ return 0;
+ }
+#endif
+
+ return bind_dhcp(state, options);
+}
+
+static int
+handle_dhcp_packet(struct if_state *state, const struct options *options)
+{
+ uint8_t *packet;
+ struct interface *iface = state->interface;
+ struct dhcp_message *dhcp;
+ const uint8_t *pp;
+ uint8_t *p;
+ ssize_t bytes;
+ int retval = -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. */
+ packet = xmalloc(udp_dhcp_len);
+ dhcp = xmalloc(sizeof(*dhcp));
+ for(;;) {
+ bytes = get_raw_packet(iface, ETHERTYPE_IP,
+ packet, udp_dhcp_len);
+ if (bytes == 0) {
+ retval = 0;
+ break;
+ }
+ if (bytes == -1)
+ break;
+ if (valid_udp_packet(packet) == -1)
+ continue;
+ bytes = get_udp_data(&pp, packet);
+ if ((size_t)bytes > sizeof(*dhcp)) {
+ logger(LOG_ERR, "packet greater than DHCP size");
+ continue;
+ }
+ memcpy(dhcp, pp, bytes);
+ if (dhcp->cookie != htonl(MAGIC_COOKIE)) {
+ logger(LOG_DEBUG, "bogus cookie, ignoring");
+ continue;
+ }
+ if (state->xid != dhcp->xid) {
+ logger(LOG_DEBUG,
+ "ignoring packet with xid 0x%x as"
+ " it's not ours (0x%x)",
+ dhcp->xid, state->xid);
+ continue;
+ }
+ /* We should ensure that the packet is terminated correctly
+ * if we have space for the terminator */
+ if ((size_t)bytes < sizeof(struct dhcp_message)) {
+ p = (uint8_t *)dhcp + bytes - 1;
+ while (p > dhcp->options && *p == DHCP_PAD)
+ p--;
+ if (*p != DHCP_END)
+ *++p = DHCP_END;
+ }
+ free(packet);
+ if (handle_dhcp(state, &dhcp, options) == 0) {
+ /* Fake the fact we forked so we return 0 to userland */
+ if (state->options & DHCPCD_TEST)
+ state->options |= DHCPCD_FORKED;
+ else
+ return 0;
+ }
+ if (state->options & DHCPCD_FORKED)
+ return -1;
+ }
+
+ free(packet);
+ free(dhcp);
+ return retval;
+}
+
+#ifdef ENABLE_ARP
+static int
+handle_arp_packet(struct if_state *state)
+{
+ struct arphdr reply;
+ uint32_t reply_s;
+ uint32_t reply_t;
+ uint8_t arp_reply[sizeof(reply) + 2 * sizeof(reply_s) + 2 * HWADDR_LEN];
+ uint8_t *hw_s, *hw_t;
+ ssize_t bytes;
+ struct interface *iface = state->interface;
+
+ state->fail.s_addr = 0;
+
+ for(;;) {
+ bytes = get_raw_packet(iface, ETHERTYPE_ARP,
+ arp_reply, sizeof(arp_reply));
+ if (bytes == 0 || bytes == -1)
+ return (int)bytes;
+ /* We must have a full ARP header */
+ if ((size_t)bytes < sizeof(reply))
+ continue;
+ memcpy(&reply, arp_reply, sizeof(reply));
+ /* Protocol must be IP. */
+ if (reply.ar_pro != htons(ETHERTYPE_IP))
+ continue;
+ if (reply.ar_pln != sizeof(reply_s))
+ continue;
+ /* Only these types are recognised */
+ if (reply.ar_op != htons(ARPOP_REPLY) &&
+ reply.ar_op != htons(ARPOP_REQUEST))
+ continue;
+
+ /* Get pointers to the hardware addreses */
+ hw_s = arp_reply + sizeof(reply);
+ hw_t = hw_s + reply.ar_hln + reply.ar_pln;
+ /* Ensure we got all the data */
+ if ((hw_t + reply.ar_hln + reply.ar_pln) - arp_reply > bytes)
+ continue;
+ /* Copy out the IP addresses */
+ memcpy(&reply_s, hw_s + reply.ar_hln, reply.ar_pln);
+ memcpy(&reply_t, hw_t + reply.ar_hln, reply.ar_pln);
+
+ /* Check for conflict */
+ if (state->offer &&
+ (reply_s == state->offer->yiaddr ||
+ (reply_t == state->offer->yiaddr &&
+ reply.ar_op == htons(ARPOP_REQUEST) &&
+ (iface->hwlen != reply.ar_hln ||
+ memcmp(hw_s, iface->hwaddr, iface->hwlen) != 0))))
+ state->fail.s_addr = state->offer->yiaddr;
+
+ /* Handle IPv4LL conflicts */
+ if (IN_LINKLOCAL(htonl(iface->addr.s_addr)) &&
+ (reply_s == iface->addr.s_addr ||
+ (reply_t == iface->addr.s_addr &&
+ reply.ar_op == htons(ARPOP_REQUEST) &&
+ (iface->hwlen != reply.ar_hln ||
+ memcmp(hw_s, iface->hwaddr, iface->hwlen) != 0))))
+ state->fail.s_addr = iface->addr.s_addr;
+
+ if (state->fail.s_addr) {
+ logger(LOG_ERR, "hardware address %s claims %s",
+ hwaddr_ntoa((unsigned char *)hw_s,
+ (size_t)reply.ar_hln),
+ inet_ntoa(state->fail));
+ errno = EEXIST;
+ return -1;
+ }
+ }
+}
+
+static int
+handle_arp_fail(struct if_state *state, const struct options *options)
+{
+ struct timespec ts;
+ time_t up;
+
+ if (IN_LINKLOCAL(htonl(state->fail.s_addr))) {
+ if (state->fail.s_addr == state->interface->addr.s_addr) {
+ up = uptime();
+ if (state->defend + DEFEND_INTERVAL > up) {
+ drop_config(state, "FAIL", options);
+ state->state = STATE_PROBING;
+ state->timeout = 0;
+ state->claims = 0;
+ state->probes = 0;
+ state->conflicts = 0;
+ timerclear(&state->stop);
+ } else
+ state->defend = up;
+ return 0;
+ }
+
+ timerclear(&state->stop);
+ state->conflicts++;
+ state->timeout = 0;
+ state->claims = 0;
+ state->probes = 0;
+ state->state = STATE_PROBING;
+ free(state->offer);
+ if (state->conflicts > MAX_CONFLICTS) {
+ /* RFC 3927 says we should rate limit */
+ logger(LOG_INFO, "sleeping for %d seconds",
+ RATE_LIMIT_INTERVAL);
+ ts.tv_sec = RATE_LIMIT_INTERVAL;
+ ts.tv_nsec = 0;
+ nanosleep(&ts, NULL);
+ }
+ state->offer = ipv4ll_get_dhcp(0);
+ return 0;
+ }
+
+ do_socket(state, SOCKET_OPEN);
+ send_message(state, DHCP_DECLINE, options);
+ 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;
+}
+#endif
+
+int
+dhcp_run(const struct options *options, int *pid_fd)
+{
+ struct interface *iface;
+ struct if_state *state = NULL;
+ int retval = -1;
+ int sig;
+
+ iface = read_interface(options->interface, options->metric);
+ if (!iface) {
+ logger(LOG_ERR, "read_interface: %s", strerror(errno));
+ goto eexit;
+ }
+
+ logger(LOG_INFO, "hardware address = %s",
+ hwaddr_ntoa(iface->hwaddr, iface->hwlen));
+
+ state = xzalloc(sizeof(*state));
+ state->pid_fd = pid_fd;
+ state->interface = iface;
+
+ if (client_setup(state, options) == -1)
+ goto eexit;
+ if (signal_init() == -1)
+ goto eexit;
+ if (signal_setup() == -1)
+ goto eexit;
+
+ state->signal_fd = signal_fd();
+
+ for (;;) {
+ retval = wait_for_packet(state);
+
+ /* We should always handle our signals first */
+ if ((sig = (signal_read(state->signal_fd))) != -1) {
+ retval = handle_signal(sig, state, options);
+ } else if (retval == 0)
+ retval = handle_timeout(state, options);
+ else if (retval == -1) {
+ if (errno == EINTR)
+ /* The interupt will be handled above */
+ retval = 0;
+ } else if (retval > 0) {
+ if (fd_hasdata(state->interface->fd) == 1)
+ retval = handle_dhcp_packet(state, options);
+#ifdef ENABLE_ARP
+ else if (fd_hasdata(state->interface->arp_fd) == 1) {
+ retval = handle_arp_packet(state);
+ if (retval == -1)
+ retval = handle_arp_fail(state, options);
+ }
+#endif
+ else
+ retval = 0;
+ }
+
+ if (retval != 0)
+ break;
+ }
+
+eexit:
+ if (iface) {
+ do_socket(state, SOCKET_CLOSED);
+ free_routes(iface->routes);
+ free(iface->clientid);
+ free(iface->buffer);
+ free(iface);
+ }
+
+ if (state) {
+ if (state->options & DHCPCD_FORKED)
+ retval = 0;
+ if (state->options & DHCPCD_DAEMONISED)
+ unlink(options->pidfile);
+ free(state->offer);
+ free(state->new);
+ free(state->old);
+ free(state);
+ }
+
+ return retval;
+}