C++程序  |  1855行  |  48.08 KB

/* 
 * 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"

#define IPV4LL_LEASETIME 	2

/* 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_INIT_IPV4LL	8
#define STATE_PROBING		9
#define STATE_ANNOUNCING	10

/* Constants taken from RFC 2131. */
#define T1			0.5
#define T2			0.875
#define DHCP_BASE		4
#define DHCP_MAX		64
#define DHCP_RAND_MIN		-1
#define DHCP_RAND_MAX		1
#define DHCP_ARP_FAIL		10

/* We should define a maximum for the NAK exponential backoff */ 
#define NAKOFF_MAX              60

#define SOCKET_CLOSED           0
#define SOCKET_OPEN             1

/* These are for IPV4LL, RFC 3927. */
#define PROBE_WAIT		 1
#define PROBE_NUM		 3
#define PROBE_MIN		 1
#define PROBE_MAX		 2
#define ANNOUNCE_WAIT		 2
/* BSD systems always do a grauitous ARP when assigning an address,
 * so we can do one less announce. */
#ifdef BSD
# define ANNOUNCE_NUM		 1
#else
# define ANNOUNCE_NUM		 2
#endif
#define ANNOUNCE_INTERVAL	 2
#define MAX_CONFLICTS		10
#define RATE_LIMIT_INTERVAL	60
#define DEFEND_INTERVAL		10


/* number of usecs in a second. */
#define USECS_SECOND		1000000
/* As we use timevals, we should use the usec part for
 * greater randomisation. */
#define DHCP_RAND_MIN_U		DHCP_RAND_MIN * USECS_SECOND
#define DHCP_RAND_MAX_U		DHCP_RAND_MAX * USECS_SECOND
#define PROBE_MIN_U		PROBE_MIN * USECS_SECOND
#define PROBE_MAX_U		PROBE_MAX * USECS_SECOND

#define timernorm(tvp)						\
	do {							\
		while ((tvp)->tv_usec >= 1000000) {		\
			(tvp)->tv_sec++;			\
			(tvp)->tv_usec -= 1000000;		\
		}						\
	} while (0 /* CONSTCOND */);

#define timerneg(tvp)	((tvp)->tv_sec < 0 || (tvp)->tv_usec < 0)

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 timeout;
	struct timeval stop;
	struct timeval exit;
	int state;
	int messages;
	time_t nakoff;
	uint32_t xid;
	int socket;
	int *pid_fd;
	int signal_fd;
	int carrier;
	int probes;
	int claims;
	int conflicts;
	time_t defend;
	struct in_addr fail;
};

#define LINK_UP 	1
#define LINK_UNKNOWN	0
#define LINK_DOWN 	-1

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;
}

#ifdef THERE_IS_NO_FORK
#define daemonise(a,b) 0
#else
static int
daemonise(struct if_state *state, const struct options *options)
{
	pid_t pid;
	sigset_t full;
	sigset_t old;
	char buf = '\0';
	int sidpipe[2];

	if (state->options & DHCPCD_DAEMONISED ||
	    !(options->options & DHCPCD_DAEMONISE))
		return 0;

	sigfillset(&full);
	sigprocmask(SIG_SETMASK, &full, &old);

	/* 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]);
			if (write(sidpipe[1], &buf, 1) != 1)
				logger(LOG_ERR, "write: %s", strerror(errno));
			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]);
			if (read(sidpipe[0], &buf, 1) != 1)
				logger(LOG_ERR, "read: %s", strerror(errno));
			close(sidpipe[0]);
			break;
	}

	/* 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);
	if (pid == 0) {
		state->options |= DHCPCD_DAEMONISED;
		timerclear(&state->exit);
		return 0;
	}
	state->options |= DHCPCD_PERSISTENT | DHCPCD_FORKED;
	return -1;
}
#endif

#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;
}

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++ = DHO_SUBNETMASK;
	*p++ = sizeof(u32);
	u32 = htonl(LINKLOCAL_MASK);
	memcpy(p, &u32, sizeof(u32));
	p += sizeof(u32);
	*p++ = DHO_BROADCAST;
	*p++ = sizeof(u32);
	u32 = htonl(LINKLOCAL_BRDC);
	memcpy(p, &u32, sizeof(u32));
	p += sizeof(u32);
	*p++ = DHO_END;

	for (;;) {
		dhcp->yiaddr = htonl(LINKLOCAL_ADDR |
				     (((uint32_t)abs((int)arc4random())
				       % 0xFD00) + 0x0100));
		if (dhcp->yiaddr != old_addr &&
		    IN_LINKLOCAL(ntohl(dhcp->yiaddr)))
			break;
	}
	return dhcp;
}

static double
timeval_to_double(struct timeval *tv)
{
	return tv->tv_sec * 1.0 + tv->tv_usec * 1.0e-6;
}

static void
get_lease(struct dhcp_lease *lease, const struct dhcp_message *dhcp)
{
	time_t t;

	if (lease->frominfo)
		return;
	lease->addr.s_addr = dhcp->yiaddr;

	if (get_option_addr(&lease->net, dhcp, DHO_SUBNETMASK) == -1)
		lease->net.s_addr = get_netmask(dhcp->yiaddr);
	if (get_option_uint32(&lease->leasetime, dhcp, DHO_LEASETIME) == 0) {
		/* Ensure that we can use the lease */
		t = 0;
		if (t + (time_t)lease->leasetime < t) {
			logger(LOG_WARNING, "lease of %u would overflow, "
			       "treating as infinite", lease->leasetime);
			lease->leasetime = ~0U; /* Infinite lease */
		}
	} else
		lease->leasetime = DEFAULT_LEASETIME;
	if (get_option_uint32(&lease->renewaltime, dhcp, DHO_RENEWALTIME) != 0)
		lease->renewaltime = 0;
	if (get_option_uint32(&lease->rebindtime, dhcp, DHO_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 = NULL;
	struct timeval tv;
	unsigned int offset = 0;
	struct stat sb;

	if (stat(iface->leasefile, &sb) == -1) {
		if (errno != ENOENT)
			logger(LOG_ERR, "stat: %s", strerror(errno));
		goto eexit;
	}
	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) {
		logger(LOG_INFO, "read_lease: %s", strerror(errno));
		goto eexit;
	}
	lease->frominfo = 0;
	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))) {
		if (!(state->options & DHCPCD_LASTLEASE))
			goto eexit;

		/* 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 > (time_t)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;
		}
		lease->leasetime -= offset;
		lease->rebindtime -= offset;
		lease->renewaltime -= offset;
	}

	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;
	struct timeval tv;
	size_t len = 0;
	unsigned char *duid = NULL;
	uint32_t ul;

	state->state = STATE_INIT;
	state->nakoff = 1;
	state->options = options->options;
	timerclear(&tv);

	if (options->request_address.s_addr == 0 &&
	    (options->options & DHCPCD_INFORM ||
	     options->options & DHCPCD_REQUEST ||
	     (options->options & DHCPCD_DAEMONISED &&
	      !(options->options & DHCPCD_BACKGROUND))))
	{
		if (get_old_lease(state) != 0)
			return -1;
		timerclear(&state->timeout);

		if (!(options->options & DHCPCD_DAEMONISED) &&
		    IN_LINKLOCAL(ntohl(lease->addr.s_addr)))
		{
			logger(LOG_ERR, "cannot request a link local address");
			return -1;
		}
	} 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;
	}

       /* If we haven't specified a ClientID and our hardware address 
	* length is greater than DHCP_CHADDR_LEN then we enforce a ClientID 
	* of the hardware address family and the hardware address. */ 
	if (!(state->options & DHCPCD_CLIENTID) && iface->hwlen > DHCP_CHADDR_LEN) 
		state->options |= DHCPCD_CLIENTID; 
 
	if (*options->clientid) {
		iface->clientid = xmalloc(options->clientid[0] + 1);
		memcpy(iface->clientid,
		       options->clientid, options->clientid[0] + 1);
	} else if (state->options & DHCPCD_CLIENTID) {
		if (state->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_DEBUG, "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);
		}
	}

	if (state->options & DHCPCD_LINK) {
		open_link_socket(iface);
		switch (carrier_status(iface->name)) {
		case 0:
			state->carrier = LINK_DOWN;
			break;
		case 1:
			state->carrier = LINK_UP;
			break;
		default:
			state->carrier = LINK_UNKNOWN;
		}
	}

	if (options->timeout > 0 &&
	    !(state->options & DHCPCD_DAEMONISED))
	{
		if (state->options & DHCPCD_IPV4LL) {
			state->stop.tv_sec = options->timeout;
			if (!(state->options & DHCPCD_BACKGROUND))
				state->exit.tv_sec = state->stop.tv_sec + 10;
		} else if (!(state->options & DHCPCD_BACKGROUND))
			state->exit.tv_sec = options->timeout;
	}
	return 0;
}

static int 
do_socket(struct if_state *state, int mode)
{
	if (state->interface->raw_fd != -1) {
		close(state->interface->raw_fd);
		state->interface->raw_fd = -1;
	}
	if (mode == SOCKET_CLOSED) {
		if (state->interface->udp_fd != -1) {
			close(state->interface->udp_fd);
			state->interface->udp_fd = -1;
		}
		if (state->interface->arp_fd != -1) {
			close(state->interface->arp_fd);
			state->interface->arp_fd = -1;
		}
	}

	/* Always have the UDP socket open to avoid the kernel sending
	 * ICMP unreachable messages. */
	/* For systems without SO_BINDTODEVICE, (ie BSD ones) we may get an
	 * error or EADDRINUSE when binding to INADDR_ANY as another dhcpcd
	 * instance could be running.
	 * Oddly enough, we don't care about this as the socket is there
	 * just to please the kernel - we don't care for reading from it. */
	if (mode == SOCKET_OPEN &&
	    state->interface->udp_fd == -1 &&
	    open_udp_socket(state->interface) == -1 &&
	    (errno != EADDRINUSE || state->interface->addr.s_addr != 0))
		logger(LOG_ERR, "open_udp_socket: %s", strerror(errno));

	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, r;
	struct in_addr from, to;
	in_addr_t a = 0;

	if (state->carrier == LINK_DOWN)
		return 0;
	if (type == DHCP_RELEASE)
		logger(LOG_DEBUG, "sending %s with xid 0x%x",
		       get_dhcp_op(type), state->xid);
	else
		logger(LOG_DEBUG,
		       "sending %s with xid 0x%x, next in %0.2f seconds",
		       get_dhcp_op(type), state->xid,
		       timeval_to_double(&state->timeout));
	state->messages++;
	if (state->messages < 0)
		state->messages = INT_MAX;
	/* If we couldn't open a UDP port for our IP address
	 * then we cannot renew.
	 * This could happen if our IP was pulled out from underneath us. */
	if (state->interface->udp_fd == -1) {
		a = state->interface->addr.s_addr;
		state->interface->addr.s_addr = 0;
	}
	len = make_message(&dhcp, state->interface, &state->lease, state->xid,
			   type, options);
	if (state->interface->udp_fd == -1)
		state->interface->addr.s_addr = a;
	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 && to.s_addr != INADDR_BROADCAST) {
		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);
		r = send_raw_packet(state->interface, ETHERTYPE_IP, udp, len);
		free(udp);
		if (r == -1)
			logger(LOG_ERR, "send_raw_packet: %s", strerror(errno));
	}
	free(dhcp);
	/* Failed to send the packet? Return to the init state */
	if (r == -1) {
		state->state = STATE_INIT;
		timerclear(&state->timeout);
		/* We need to set a timeout so we fall through gracefully */
		state->stop.tv_sec = 1;
		state->stop.tv_usec = 0;
		do_socket(state, SOCKET_CLOSED);
	}
	return r;
}

static void
drop_config(struct if_state *state, const char *reason,
	    const struct options *options)
{
	if (state->new || strcmp(reason, "FAIL") == 0) {
		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 void
reduce_timers(struct if_state *state, const struct timeval *tv)
{
	if (timerisset(&state->exit)) {
		timersub(&state->exit, tv, &state->exit);
		if (!timerisset(&state->exit))
			state->exit.tv_sec = -1;
	}
	if (timerisset(&state->stop)) {
		timersub(&state->stop, tv, &state->stop);
		if (!timerisset(&state->stop))
			state->stop.tv_sec = -1;
	}
	if (timerisset(&state->timeout)) {
		timersub(&state->timeout, tv, &state->timeout);
		if (!timerisset(&state->timeout))
			state->timeout.tv_sec = -1;
	}
}

static struct timeval *
get_lowest_timer(struct if_state *state)
{
	struct timeval *ref = NULL;
	
	if (timerisset(&state->exit))
		ref = &state->exit;
	if (timerisset(&state->stop)) {
		if (!ref || timercmp(&state->stop, ref, <))
			ref = &state->stop;
	}
	if (timerisset(&state->timeout)) {
		if (!ref || timercmp(&state->timeout, ref, <))
			ref = &state->timeout;
	}
	return ref;
}

static int
wait_for_fd(struct if_state *state, int *fd)
{
	struct pollfd fds[4]; /* signal, link, raw, arp */
	struct interface *iface = state->interface;
	int i, r, nfds = 0, msecs = -1;
	struct timeval start, stop, diff, *ref;
	static int lastinf = 0;

	/* Ensure that we haven't already timed out */
	ref = get_lowest_timer(state);
	if (ref && timerneg(ref))
		return 0;

	/* We always listen to signals */
	fds[nfds].fd = state->signal_fd;
	fds[nfds].events = POLLIN;
	nfds++;
	/* And links */
	if (iface->link_fd != -1) {
		fds[nfds].fd = iface->link_fd;
		fds[nfds].events = POLLIN;
		nfds++;
	}

	if (state->lease.leasetime == ~0U &&
	    state->state == STATE_BOUND)
	{
		if (!lastinf) {
			logger(LOG_DEBUG, "waiting for infinity");
			lastinf = 1;
		}
		ref = NULL;
	} else if (state->carrier == LINK_DOWN && !ref) {
		if (!lastinf) {
			logger(LOG_DEBUG, "waiting for carrier");
			lastinf = 1;
		}
		if (timerisset(&state->exit))
			ref = &state->exit;
		else
			ref = NULL;
	} else {
		if (iface->raw_fd != -1) {
			fds[nfds].fd = iface->raw_fd;
			fds[nfds].events = POLLIN;
			nfds++;
		}
		if (iface->arp_fd != -1) {
			fds[nfds].fd = iface->arp_fd;
			fds[nfds].events = POLLIN;
			nfds++;
		}
	}

	/* Wait and then reduce the timers.
	 * If we reduce a timer to zero, set it negative to indicate timeout.
	 * We cannot reliably use select as there is no guarantee we will
	 * actually wait the whole time if greater than 31 days according
	 * to POSIX. So we loop on poll if needed as it's limitation of
	 * INT_MAX milliseconds is known. */
	for (;;) {
		get_monotonic(&start);
		if (ref) {
			lastinf = 0;
			if (ref->tv_sec > INT_MAX / 1000 ||
			    (ref->tv_sec == INT_MAX / 1000 &&
			     (ref->tv_usec + 999) / 1000 > INT_MAX % 1000))
				msecs = INT_MAX;
			else
				msecs = ref->tv_sec * 1000 +
					(ref->tv_usec + 999) / 1000;
		} else
			msecs = -1;
		r = poll(fds, nfds, msecs);
		get_monotonic(&stop);
		timersub(&stop, &start, &diff);
		reduce_timers(state, &diff);
		if (r == -1) {
			if (errno != EINTR)
				logger(LOG_ERR, "poll: %s", strerror(errno));
			return -1;
		}
		if (r)
			break;
		/* We should not have an infinite timeout if we get here */
		if (timerneg(ref))
			return 0;
	}

	/* We configured our array in the order we should deal with them */
	for (i = 0; i < nfds; i++) {
		if (fds[i].revents & POLLERR) {
			syslog(LOG_ERR, "poll: POLLERR on fd %d", fds[i].fd);
			errno = EBADF;
			return -1;
		}
		if (fds[i].revents & POLLNVAL) {
			syslog(LOG_ERR, "poll: POLLNVAL on fd %d", fds[i].fd);
			errno = EINVAL;
			return -1;
		}
		if (fds[i].revents & (POLLIN | POLLHUP)) {
			*fd = fds[i].fd;
			return r;
		}
	}
	/* We should never get here. */
	return 0;
}

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");
		do_socket(state, SOCKET_CLOSED);
		state->state = STATE_RENEW_REQUESTED;
		timerclear(&state->timeout);
		timerclear(&state->stop);
		return 1;
	case SIGHUP:
		logger(LOG_INFO, "received SIGHUP, releasing lease");
		if (lease->addr.s_addr &&
		    !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 0;
}

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 start, stop, diff;
	int retval;

	free(state->old);
	state->old = state->new;
	state->new = state->offer;
	state->offer = NULL;
	state->messages = 0;
	state->conflicts = 0;
	state->defend = 0;
	timerclear(&state->exit);
	if (clock_monotonic)
		get_monotonic(&lease->boundtime);

	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;
		timerclear(&state->stop);
		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;
		timerclear(&state->timeout);
		reason = "IPV4LL";
	} else {
		if (gettimeofday(&start, NULL) == 0)
			lease->leasedfrom = start.tv_sec;

		get_lease(lease, state->new);
		if (lease->frominfo)
			reason = "TIMEOUT";

		if (lease->leasetime == ~0U) {
			lease->renewaltime = lease->rebindtime = lease->leasetime;
			logger(LOG_INFO, "leased %s for infinity",
			       inet_ntoa(lease->addr));
			state->state = STATE_BOUND;
			timerclear(&state->stop);
		} else {
			if (lease->rebindtime == 0)
				lease->rebindtime = lease->leasetime * T2;
			else 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 == 0)
				lease->renewaltime = lease->leasetime * T1;
			else 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);
			}
			logger(LOG_INFO,
			       "leased %s for %u seconds",
			       inet_ntoa(lease->addr), lease->leasetime);
			state->stop.tv_sec = lease->renewaltime;
			state->stop.tv_usec = 0;
		}
		state->state = STATE_BOUND;
	}

	state->xid = 0;
	timerclear(&state->timeout);
	if (!reason) {
		if (state->old) {
			if (state->old->yiaddr == state->new->yiaddr &&
			    lease->server.s_addr)
				reason = "RENEW";
			else
				reason = "REBIND";
		} else
			reason = "BOUND";
	}
	/* If we have a monotonic clock we can safely substract the
	 * script execution time from our timers.
	 * Otherwise we can't as the script may update the real time. */
	if (clock_monotonic)
		get_monotonic(&start);
	retval = configure(iface, reason, state->new, state->old,
			   &state->lease, options, 1);
	if (clock_monotonic) {
		get_monotonic(&stop);
		timersub(&stop, &start, &diff);
		reduce_timers(state, &diff);
	}
	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, r;
	const char *reason = NULL;

	timerclear(&state->stop);
	timerclear(&state->exit);
	if (state->state != STATE_DISCOVERING)
		state->messages = 0;

	switch (state->state) {
	case STATE_INIT:	/* FALLTHROUGH */
	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 if (state->carrier != LINK_DOWN || 
				!(state->options & DHCPCD_DAEMONISED)) 
				logger(LOG_ERR, "timed out");
		}
		do_socket(state, SOCKET_CLOSED);
		if (state->options & DHCPCD_INFORM ||
		    state->options & DHCPCD_TEST)
			return -1;

		if (state->carrier != LINK_DOWN &&
		    (state->options & DHCPCD_IPV4LL ||
		     state->options & DHCPCD_LASTLEASE))
			gotlease = get_old_lease(state);

		if (state->carrier != LINK_DOWN &&
		    state->options & DHCPCD_IPV4LL &&
		    gotlease != 0)
		{
			logger(LOG_INFO, "probing for an IPV4LL address");
			free(state->offer);
			lease->frominfo = 0;
			state->offer = ipv4ll_get_dhcp(0);
			gotlease = 0;
		}

		if (gotlease == 0 &&
		    state->offer->yiaddr != iface->addr.s_addr &&
		    state->options & DHCPCD_ARP)
		{
			state->state = STATE_PROBING;
			state->claims = 0;
			state->probes = 0;
			if (iface->addr.s_addr)
				state->conflicts = 0;
			return 1;
		}

		if (gotlease == 0) {
			r = bind_dhcp(state, options);
			logger(LOG_DEBUG, "renew in %ld seconds",
				(long int)state->stop.tv_sec);
			return r;
		}
		if (iface->addr.s_addr)
			reason = "EXPIRE";
		else
			reason = "FAIL";
		drop_config(state, reason, options);
		if (!(state->options & DHCPCD_DAEMONISED) &&
		    (state->options & DHCPCD_DAEMONISE))
			return -1;
		state->state = STATE_RENEW_REQUESTED;
		return 1;
	case STATE_BOUND:
		logger(LOG_INFO, "renewing lease of %s",inet_ntoa(lease->addr));
		if (state->carrier != LINK_DOWN)
			do_socket(state, SOCKET_OPEN);
		state->xid = arc4random();
		state->state = STATE_RENEWING;
		state->stop.tv_sec = lease->rebindtime - lease->renewaltime;
		break;
	case STATE_RENEWING:
		logger(LOG_ERR, "failed to renew, attempting to rebind");
		state->state = STATE_REBINDING;
		if (lease->server.s_addr == 0)
			state->stop.tv_sec = options->timeout;
		else
			state->stop.tv_sec = lease->rebindtime - \
					     lease->renewaltime;
		lease->server.s_addr = 0;
		break;
	case STATE_REBINDING:
		logger(LOG_ERR, "failed to rebind");
		reason = "EXPIRE";
		drop_config(state, reason, options);
		state->state = STATE_INIT;
		break;
	case STATE_PROBING:    /* FALLTHROUGH */
	case STATE_ANNOUNCING:
		/* We should have lost carrier here and exit timer went */
		logger(LOG_ERR, "timed out");
		return -1;
	default:
		logger(LOG_DEBUG, "handle_timeout_failed: invalid state %d",
		       state->state);
	}

	/* This effectively falls through into the handle_timeout funtion */
	return 1;
}

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 = 0;
	struct in_addr addr;
	struct timeval tv;

	timerclear(&state->timeout);
	if (timerneg(&state->exit))
		return handle_timeout_fail(state, options);

	if (state->state == STATE_RENEW_REQUESTED &&
	    IN_LINKLOCAL(ntohl(lease->addr.s_addr)))
	{
		state->state = STATE_PROBING;
		free(state->offer);
		state->offer = read_lease(state->interface);
		state->probes = 0;
		state->claims = 0;
	}
	switch (state->state) {
	case STATE_INIT_IPV4LL:
		state->state = STATE_PROBING;
		free(state->offer);
		state->offer = ipv4ll_get_dhcp(0);
		state->probes = 0;
		state->claims = 0;
		/* FALLTHROUGH */
	case STATE_PROBING:
		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++;
			if (state->probes < PROBE_NUM) {
				state->timeout.tv_sec = PROBE_MIN;
				state->timeout.tv_usec = arc4random() %
					(PROBE_MAX_U - PROBE_MIN_U);
				timernorm(&state->timeout);
			} else {
				state->timeout.tv_sec = ANNOUNCE_WAIT;
				state->timeout.tv_usec = 0;
			}
			logger(LOG_DEBUG,
			       "sending ARP probe (%d of %d), next in %0.2f seconds",
			       state->probes, PROBE_NUM,
			       timeval_to_double(&state->timeout));
			if (send_arp(iface, ARPOP_REQUEST, 0,
				     state->offer->yiaddr) == -1)
			{
				logger(LOG_ERR, "send_arp: %s", strerror(errno));
				return -1;
			}
			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.tv_sec = ANNOUNCE_INTERVAL;
			state->timeout.tv_usec = 0;
			return i;
		}
		break;
	case STATE_ANNOUNCING:
		if (iface->arp_fd == -1)
			open_socket(iface, ETHERTYPE_ARP);
		if (state->claims < ANNOUNCE_NUM) {
			state->claims++;
			if (state->claims < ANNOUNCE_NUM) {
				state->timeout.tv_sec = ANNOUNCE_INTERVAL;
				state->timeout.tv_usec = 0;
				logger(LOG_DEBUG,
				       "sending ARP announce (%d of %d),"
				       " next in %0.2f seconds",
				       state->claims, ANNOUNCE_NUM,
				       timeval_to_double(&state->timeout));
			} else
				logger(LOG_DEBUG,
				       "sending ARP announce (%d of %d)",
				       state->claims, ANNOUNCE_NUM);
			i = send_arp(iface, ARPOP_REQUEST,
				     state->new->yiaddr, state->new->yiaddr);
			if (i == -1) {
				logger(LOG_ERR, "send_arp: %s", strerror(errno));
				return -1;
			}
		}
		if (state->claims < ANNOUNCE_NUM)
			return 0;
		if (IN_LINKLOCAL(htonl(state->new->yiaddr))) {
			/* We should pretend to be at the end
			 * of the DHCP negotation cycle */
			state->state = STATE_INIT;
			state->messages = DHCP_MAX / DHCP_BASE;
			state->probes = 0;
			state->claims = 0;
			timerclear(&state->stop);
			goto dhcp_timeout;
		} else {
			state->state = STATE_BOUND;
			close(iface->arp_fd);
			iface->arp_fd = -1;
			if (lease->leasetime != ~0U) {
				state->stop.tv_sec = lease->renewaltime;
				state->stop.tv_usec = 0;
				if (clock_monotonic) {
					get_monotonic(&tv);
					timersub(&tv, &lease->boundtime, &tv);
					timersub(&state->stop, &tv, &state->stop);
				} else {
					state->stop.tv_sec -=
						(ANNOUNCE_INTERVAL * ANNOUNCE_NUM);
				}
				logger(LOG_DEBUG, "renew in %ld seconds",
				       (long int)state->stop.tv_sec);
			}
		}
		return 0;
	}

	if (timerneg(&state->stop))
		return handle_timeout_fail(state, options);

	switch (state->state) {
	case STATE_BOUND: /* FALLTHROUGH */
	case STATE_RENEW_REQUESTED:
		timerclear(&state->stop);
		/* FALLTHROUGH */
	case STATE_INIT:
		if (state->carrier == LINK_DOWN)
			return 0;
		do_socket(state, SOCKET_OPEN);
		state->xid = arc4random();
		iface->start_uptime = uptime();
		break;
	}

	switch(state->state) {
	case STATE_RENEW_REQUESTED:
		/* If a renew was requested (ie, didn't timeout) we actually
		 * enter the REBIND state so that we broadcast to all servers.
		 * We need to do this for when we change networks. */
		lease->server.s_addr = 0;
		state->messages = 0;
		if (lease->addr.s_addr && !(state->options & DHCPCD_INFORM)) {
			logger(LOG_INFO, "rebinding lease of %s",
			       inet_ntoa(lease->addr));
			state->state = STATE_REBINDING;
			state->stop.tv_sec = options->timeout;
			state->stop.tv_usec = 0;
			break;
		}
		/* FALLTHROUGH */
	case STATE_INIT:
		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;
		}
		if (!lease->addr.s_addr && !timerisset(&state->stop)) {
			state->stop.tv_sec = DHCP_MAX + DHCP_RAND_MIN;
			state->stop.tv_usec = arc4random() % (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U);
			timernorm(&state->stop);
		}
		break;
	}

dhcp_timeout:
	if (state->carrier == LINK_DOWN) {
		timerclear(&state->timeout);
		return 0;
	}
	state->timeout.tv_sec = DHCP_BASE;
	for (i = 0; i < state->messages; i++) {
		state->timeout.tv_sec *= 2;
		if (state->timeout.tv_sec > DHCP_MAX) {
			state->timeout.tv_sec = DHCP_MAX;
			break;
		}
	}
	state->timeout.tv_sec += DHCP_RAND_MIN;
	state->timeout.tv_usec = arc4random() %
		(DHCP_RAND_MAX_U - DHCP_RAND_MIN_U);
	timernorm(&state->timeout);

	/* We send the message here so that the timeout is reported */
	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:
		if (iface->raw_fd == -1)
			do_socket(state, SOCKET_OPEN);
		send_message(state, DHCP_REQUEST, options);
		break;
	}

	return 0;
}

static void
log_dhcp(int lvl, const char *msg, const struct dhcp_message *dhcp)
{
	char *a;
	struct in_addr addr;
	int r;

	if (strcmp(msg, "NAK:") == 0)
		a = get_option_string(dhcp, DHO_MESSAGE);
	else {
		addr.s_addr = dhcp->yiaddr;
		a = xstrdup(inet_ntoa(addr));
	}
	r = get_option_addr(&addr, dhcp, DHO_SERVERID);
	if (dhcp->servername[0] && r == 0)
		logger(lvl, "%s %s from %s `%s'", msg, a,
		       inet_ntoa(addr), dhcp->servername);
	else if (r == 0)
		logger(lvl, "%s %s from %s", msg, a, inet_ntoa(addr));
	else
		logger(lvl, "%s %s", msg, a);
	free(a);
}

static int
handle_dhcp(struct if_state *state, struct dhcp_message **dhcpp,
	    const struct options *options)
{
	struct dhcp_message *dhcp = *dhcpp;
	struct interface *iface = state->interface;
	struct dhcp_lease *lease = &state->lease;
	uint8_t type, tmp;
	struct in_addr addr;
	size_t i;
	int r;

	/* reset the message counter */
	state->messages = 0;

	/* We have to have DHCP type to work */
	if (get_option_uint8(&type, dhcp, DHO_MESSAGETYPE) == -1) {
		logger(LOG_ERR, "ignoring message; no DHCP type");
		return 0;
	}
	/* Every DHCP message should include ServerID */
	if (get_option_addr(&addr, dhcp, DHO_SERVERID) == -1) {
		logger(LOG_ERR, "ignoring message; no Server ID");
		return 0;
	}

	/* Ensure that it's not from a blacklisted server.
	 * We should expand this to check IP and/or hardware address
	 * at the packet level. */
	if (options->blacklist_len != 0 &&
	    get_option_addr(&addr, dhcp, DHO_SERVERID) == 0)
	{
		for (i = 0; i < options->blacklist_len; i++) {
			if (options->blacklist[i] != addr.s_addr)
				continue;
			if (dhcp->servername[0])
				logger(LOG_WARNING,
				       "ignoring blacklisted server %s `%s'",
					inet_ntoa(addr), dhcp->servername);
			else
				logger(LOG_WARNING,
				       "ignoring blacklisted server %s",
				       inet_ntoa(addr));
			return 0;
		}
	}

	/* We should restart on a NAK */
	if (type == DHCP_NAK) {
		log_dhcp(LOG_WARNING, "NAK:", dhcp);
		drop_config(state, "EXPIRE", options);
		do_socket(state, SOCKET_CLOSED);
		state->state = STATE_INIT;
		/* If we constantly get NAKS then we should slowly back off */
		if (state->nakoff == 0) {
			state->nakoff = 1;
			timerclear(&state->timeout);
		} else {
			state->timeout.tv_sec = state->nakoff;
			state->timeout.tv_usec = 0;
			state->nakoff *= 2;
			if (state->nakoff > NAKOFF_MAX)
				state->nakoff = NAKOFF_MAX;
		} 
		return 0;
	}

	/* No NAK, so reset the backoff */
	state->nakoff = 1;

	/* Ensure that all required options are present */
	for (i = 1; i < 255; i++) {
		if (has_option_mask(options->requiremask, i) &&
		    get_option_uint8(&tmp, dhcp, i) != 0)
		{
			log_dhcp(LOG_WARNING, "reject", dhcp);
			return 0;
		}
	}

	if (type == DHCP_OFFER && state->state == STATE_DISCOVERING) {
		lease->addr.s_addr = dhcp->yiaddr;
		get_option_addr(&lease->server, dhcp, DHO_SERVERID);
		log_dhcp(LOG_INFO, "offered", dhcp);
		if (state->options & DHCPCD_TEST) {
			run_script(options, iface->name, "TEST", dhcp, NULL);
			/* Fake the fact we forked so we return 0 to userland */
			state->options |= DHCPCD_FORKED;
			return -1;
		}
		free(state->offer);
		state->offer = dhcp;
		*dhcpp = NULL;
		timerclear(&state->timeout);
		state->state = STATE_REQUESTING;
		return 1;
	}

	if (type == DHCP_OFFER) {
		log_dhcp(LOG_INFO, "ignoring offer of", dhcp);
		return 0;
	}

	/* We should only be dealing with acks */
	if (type != DHCP_ACK) {
		log_dhcp(LOG_ERR, "not ACK or OFFER", dhcp);
		return 0;
	}
	    
	switch (state->state) {
	case STATE_RENEW_REQUESTED:
	case STATE_REQUESTING:
	case STATE_RENEWING:
	case STATE_REBINDING:
		if (!(state->options & DHCPCD_INFORM)) {
			get_option_addr(&lease->server,
					dhcp, DHO_SERVERID);
			log_dhcp(LOG_INFO, "acknowledged", dhcp);
		}
		free(state->offer);
		state->offer = dhcp;
		*dhcpp = NULL;
		break;
	default:
		logger(LOG_ERR, "wrong state %d", state->state);
	}

	lease->frominfo = 0;
	do_socket(state, SOCKET_CLOSED);
	if (state->options & DHCPCD_ARP &&
	    iface->addr.s_addr != state->offer->yiaddr)
	{
		/* If the interface already has the address configured
		 * then we can't ARP for duplicate detection. */
		addr.s_addr = state->offer->yiaddr;
		if (!has_address(iface->name, &addr, NULL)) {
			state->state = STATE_PROBING;
			state->claims = 0;
			state->probes = 0;
			state->conflicts = 0;
			timerclear(&state->stop);
			return 1;
		}
	}

	r = bind_dhcp(state, options);
	if (!(state->options & DHCPCD_ARP)) {
		if (!(state->options & DHCPCD_INFORM))
			logger(LOG_DEBUG, "renew in %ld seconds",
			       (long int)state->stop.tv_sec);
		return r;
	}
	state->state = STATE_ANNOUNCING;
	if (state->options & DHCPCD_FORKED)
		return r;
	return 1;
}

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 = NULL;
	const uint8_t *pp;
	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);
	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, bytes) == -1)
			continue;
		bytes = get_udp_data(&pp, packet);
		if ((size_t)bytes > sizeof(*dhcp)) {
			logger(LOG_ERR, "packet greater than DHCP size");
			continue;
		}
		if (!dhcp)
			dhcp = xzalloc(sizeof(*dhcp));
		memcpy(dhcp, pp, bytes);
		if (dhcp->cookie != htonl(MAGIC_COOKIE)) {
			logger(LOG_DEBUG, "bogus cookie, ignoring");
			continue;
		}
		/* Ensure it's the right transaction */
		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;
		}
		/* Ensure packet is for us */
		if (iface->hwlen <= sizeof(dhcp->chaddr) &&
		    memcmp(dhcp->chaddr, iface->hwaddr, iface->hwlen))
		{
			logger(LOG_DEBUG, "xid 0x%x is not for our hwaddr %s",
			       dhcp->xid,
			       hwaddr_ntoa(dhcp->chaddr, sizeof(dhcp->chaddr)));
			continue;
		}
		retval = handle_dhcp(state, &dhcp, options);
		if (retval == 0 && state->options & DHCPCD_TEST)
			state->options |= DHCPCD_FORKED;
		break;
	}

	free(packet);
	free(dhcp);
	return retval;
}

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;
		/* Ignore messages from ourself */
		if (reply.ar_hln == iface->hwlen &&
		    memcmp(hw_s, iface->hwaddr, iface->hwlen) == 0)
			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_s == 0 && reply_t == state->offer->yiaddr)))
			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_s == 0 && reply_t == iface->addr.s_addr)))
			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)
{
	time_t up;
	int cookie = state->offer->cookie;

	if (!IN_LINKLOCAL(htonl(state->fail.s_addr))) {
		if (cookie) {
			state->timeout.tv_sec = DHCP_ARP_FAIL;
			state->timeout.tv_usec = 0;
			do_socket(state, SOCKET_OPEN);
			send_message(state, DHCP_DECLINE, options);
			do_socket(state, SOCKET_CLOSED);
		}
		state->state = STATE_INIT;
		free(state->offer);
		state->offer = NULL;
		state->lease.addr.s_addr = 0;
		if (!cookie)
			return 1;
		return 0;
	}

	if (state->fail.s_addr == state->interface->addr.s_addr) {
		if (state->state == STATE_PROBING)
			/* This should only happen when SIGALRM or
			 * link when down/up and we have a conflict. */
			drop_config(state, "EXPIRE", options);
		else {
			up = uptime();
			if (state->defend + DEFEND_INTERVAL > up) {
				drop_config(state, "EXPIRE", options);
				state->conflicts = -1;
				/* drop through to set conflicts to 0 */
			} else {
				state->defend = up;
				return 0;
			}
		}
	}
	do_socket(state, SOCKET_CLOSED);
	state->conflicts++;
	timerclear(&state->stop);
	if (state->conflicts > MAX_CONFLICTS) {
		logger(LOG_ERR, "failed to obtain an IPv4LL address");
		state->state = STATE_INIT;
		timerclear(&state->timeout);
		if (!(state->options & DHCPCD_DAEMONISED) &&
		    (state->options & DHCPCD_DAEMONISE))
			return -1;
		return 1;
	}
	state->state = STATE_INIT_IPV4LL;
	state->timeout.tv_sec = PROBE_WAIT;
	state->timeout.tv_usec = 0;
	return 0;
}

static int
handle_link(struct if_state *state)
{
	int retval;

	retval = link_changed(state->interface);
	if (retval == -1) {
		logger(LOG_ERR, "link_changed: %s", strerror(errno));
		return -1;
	}
	if (retval == 0)
		return 0;

	switch (carrier_status(state->interface->name)) {
	case -1:
		logger(LOG_ERR, "carrier_status: %s", strerror(errno));
		return -1;
	case 0:
		if (state->carrier != LINK_DOWN) {
			logger(LOG_INFO, "carrier lost");
			state->carrier = LINK_DOWN;
			do_socket(state, SOCKET_CLOSED);
			timerclear(&state->timeout);
			if (state->state != STATE_BOUND)
				timerclear(&state->stop);
		}
		break;
	default:
		if (state->carrier != LINK_UP) {
			logger(LOG_INFO, "carrier acquired");
			state->state = STATE_RENEW_REQUESTED;
			state->carrier = LINK_UP;
			timerclear(&state->timeout);
			timerclear(&state->stop);
			return 1;
		}
		break;
	}
	return 0;
}

int
dhcp_run(const struct options *options, int *pid_fd)
{
	struct interface *iface;
	struct if_state *state = NULL;
	int fd = -1, r = 0, sig;

	iface = read_interface(options->interface, options->metric);
	if (!iface) {
		logger(LOG_ERR, "read_interface: %s", strerror(errno));
		goto eexit;
	}
	logger(LOG_DEBUG, "hardware address = %s",
	       hwaddr_ntoa(iface->hwaddr, iface->hwlen));
	state = xzalloc(sizeof(*state));
	state->pid_fd = pid_fd;
	state->interface = iface;
	if (!(options->options & DHCPCD_TEST))
		run_script(options, iface->name, "PREINIT", NULL, NULL);

	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();

	if (state->options & DHCPCD_BACKGROUND &&
	    !(state->options & DHCPCD_DAEMONISED))
		if (daemonise(state, options) == -1)
			goto eexit;

	if (state->carrier == LINK_DOWN)
		logger(LOG_INFO, "waiting for carrier");

	for (;;) {
		if (r == 0)
			r = handle_timeout(state, options);
		else if (r > 0) {
			if (fd == state->signal_fd) {
			    	if ((sig = signal_read()) != -1)
					r = handle_signal(sig, state, options);
			} else if (fd == iface->link_fd)
				r = handle_link(state);
			else if (fd == iface->raw_fd)
				r = handle_dhcp_packet(state, options);
			else if (fd == iface->arp_fd) {
				if ((r = handle_arp_packet(state)) == -1)
					r = handle_arp_fail(state, options);
			} else
				r = 0;
		}
		if (r == -1)
			break;
		if (r == 0) {
			fd = -1;
			r = wait_for_fd(state, &fd);
			if (r == -1 && errno == EINTR) {
				r = 1;
				fd = state->signal_fd;
			}
		} else
			r = 0;
	}

eexit:
	if (iface) {
		do_socket(state, SOCKET_CLOSED);
		if (iface->link_fd != -1)
		    close(iface->link_fd);
		free_routes(iface->routes);
		free(iface->clientid);
		free(iface->buffer);
		free(iface);
	}

	if (state) {
		if (state->options & DHCPCD_FORKED)
			r = 0;
		if (state->options & DHCPCD_DAEMONISED)
			unlink(options->pidfile);
		free(state->offer);
		free(state->new);
		free(state->old);
		free(state);
	}

	return r;
}