changeset 298:d73dd36a3948 draft

Re-factor our state engine, splitting out the massive switch block into smaller functions. Hopefully I've not introduced any bugs as it seems to work fine.
author Roy Marples <roy@marples.name>
date Mon, 21 Jan 2008 15:13:59 +0000
parents 197a1ef06532
children 3861aae6c690
files client.c
diffstat 1 files changed, 738 insertions(+), 636 deletions(-) [+]
line wrap: on
line diff
--- a/client.c	Mon Jan 21 15:12:51 2008 +0000
+++ b/client.c	Mon Jan 21 15:13:59 2008 +0000
@@ -105,29 +105,25 @@
 #define SOCKET_CLOSED           0
 #define SOCKET_OPEN             1
 
-#define SOCKET_MODE(_mode) { \
-	if (iface->fd >= 0) close (iface->fd); \
-	iface->fd = -1; \
-	if (_mode == SOCKET_OPEN) \
-	if (open_socket (iface, false) == -1) { retval = EXIT_FAILURE; goto eexit; } \
-	mode = _mode; \
-}
-
-#define SEND_MESSAGE(_type) { \
-	last_type = _type; \
-	last_send = uptime (); \
-	if (send_message (iface, dhcp, xid, _type, options) == (size_t) -1) { \
-		retval = -1; \
-		goto eexit; \
-	} \
-}
-
-#define DROP_CONFIG { \
-	if (! persistent) \
-	configure (options, iface, dhcp, false); \
-	free_dhcp (dhcp); \
-	memset (dhcp, 0, sizeof (dhcp_t)); \
-}
+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;
+	long nakoff;
+	bool daemonised;
+	bool persistent;
+	unsigned char *buffer;
+	ssize_t buffer_len;
+	ssize_t buffer_pos;
+} state_t;
 
 static pid_t daemonise (int *pidfd)
 {
@@ -166,7 +162,8 @@
 			_exit (EXIT_FAILURE);
 		case 0:
 			execvp (dhcpcd, argv);
-			logger (LOG_ERR, "execl `%s': %s", dhcpcd, strerror (errno));
+			logger (LOG_ERR, "execl `%s': %s", dhcpcd,
+				strerror (errno));
 			_exit (EXIT_FAILURE);
 	}
 
@@ -184,14 +181,16 @@
 }
 
 #ifdef ENABLE_INFO
-static bool get_old_lease (const options_t *options, interface_t *iface,
-						   dhcp_t *dhcp, long *timeout)
+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);
+		logger (LOG_INFO, "trying to use old lease in `%s'",
+			iface->infofile);
 	if (! read_info (iface, dhcp))
 		return (false);
 
@@ -202,9 +201,9 @@
 #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)))))
+	     (IN_LINKLOCAL (ntohl (dhcp->address.s_addr)) &&
+	      (! options->doipv4ll ||
+	       arp_claim (iface, dhcp->address)))))
 	{
 		memset (&dhcp->address, 0, sizeof (struct in_addr));
 		memset (&dhcp->netmask, 0, sizeof (struct in_addr));
@@ -225,132 +224,113 @@
 
 	offset = tv.tv_sec - dhcp->leasedfrom;
 	if (dhcp->leasedfrom &&
-		tv.tv_sec - dhcp->leasedfrom > dhcp->leasetime)
+	    tv.tv_sec - dhcp->leasedfrom > dhcp->leasetime)
 	{
 		logger (LOG_ERR, "lease expired %u seconds ago",
-				offset + dhcp->leasetime);
+			offset + dhcp->leasetime);
 		return (false);
 	}
 
 	if (dhcp->leasedfrom == 0)
 		offset = 0;
-	if (timeout)
-		*timeout = dhcp->renewaltime - offset;
+	state->timeout = dhcp->renewaltime - offset;
 	iface->start_uptime = uptime ();
 	return (true);
 }
 #endif
 
-int dhcp_run (const options_t *options, int *pidfd)
+#ifdef THERE_IS_NO_FORK
+static void remove_skiproutes (dhcp_t *dhcp, interface_t *iface)
 {
-	interface_t *iface;
-	int mode = SOCKET_CLOSED;
-	int state = STATE_INIT;
-	struct timeval tv;
-	uint32_t xid = 0;
-	long timeout = 0;
-	fd_set rset;
-	int maxfd;
-	int retval;
-	dhcpmessage_t message;
-	dhcp_t *dhcp;
-	int type = DHCP_DISCOVER;
-	int last_type = DHCP_DISCOVER;
-	bool daemonised = options->daemonised;
-	bool persistent = options->persistent;
-	time_t start = 0;
-	time_t last_send = 0;
-	int sig;
-	unsigned char *buffer = NULL;
-	int buffer_len = 0;
-	int buffer_pos = 0;
-	long nakoff = 1;
+	int i = -1;
+	route_t *route;
+	route_t *iroute = NULL;
+
+	free_route (iface->previous_routes);
+
+	for (route = dhcp->routes; route; route = route->next) {
+		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 (! options || (iface = (read_interface (options->interface,
-											   options->metric))) == NULL)
-		return (-1);
+		if (! iroute)
+			iroute = iface->previous_routes =
+				xmalloc (sizeof (route_t));
+
+		memcpy (iroute, route, sizeof (route_t));
+		if (route->next) {
+			iroute->next = xmalloc (sizeof (route_t));
+			iroute = iroute->next;
+		}
+	}
+
+	/* 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;
 
 #ifdef ENABLE_DUID
 	if (options->clientid_len == 0) {
 		get_duid (iface);
 		if (iface->duid_length > 0)
 			logger (LOG_INFO, "DUID = %s",
-					hwaddr_ntoa (iface->duid, iface->duid_length));
+				hwaddr_ntoa (iface->duid, iface->duid_length));
 	}
 #endif
 
-	dhcp = xmalloc (sizeof (dhcp_t));
-	memset (dhcp, 0, sizeof (dhcp_t));
-
 	if (options->request_address.s_addr == 0 &&
-		(options->doinform || options->dorequest || options->daemonised))
+	    (options->doinform || options->dorequest || options->daemonised))
 	{
 #ifdef ENABLE_INFO
-		if (! get_old_lease (options, iface, dhcp, NULL))
+		if (! get_old_lease (state, options))
 #endif
 		{
 			free (dhcp);
-			return (-1);
+			return (false);
 		}
+		state->timeout = 0;
 
 		if (! options->daemonised &&
-			IN_LINKLOCAL (ntohl (dhcp->address.s_addr)))
+		    IN_LINKLOCAL (ntohl (dhcp->address.s_addr)))
 		{
 			logger (LOG_ERR, "cannot request a link local address");
-			return (-1);
+			return (false);
 		}
-
 #ifdef THERE_IS_NO_FORK
 		if (options->daemonised) {
-			state = STATE_BOUND;
-			timeout = dhcp->renewaltime;
+			state->state = STATE_BOUND;
+			state->timeout = dhcp->renewaltime;
 			iface->previous_address = dhcp->address;
 			iface->previous_netmask = dhcp->netmask;
-
-			/* FIXME: Some routes may not be added for whatever reason.
-			 * This is especially true on BSD platforms where we can only
-			 * have one default route. */
-			if (dhcp->routes) {
-				int i = -1;
-				route_t *droute;
-				route_t *iroute = NULL;
-				
-				free_route (iface->previous_routes);
-
-				for (droute = dhcp->routes; droute; droute = droute->next) {
-					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 (! iroute)
-						iroute = iface->previous_routes = xmalloc (sizeof (route_t));
-					memcpy (iroute, droute, sizeof (route_t));
-					if (droute->next) {
-						iroute->next = xmalloc (sizeof (route_t));
-						iroute = iroute->next;
-					}
-				}
-
-				/* We no longer need this argument */
-				free (dhcpcd_skiproutes);
-				dhcpcd_skiproutes = NULL;
-			}
+			remove_skiproutes (dhcp, iface);
 		}
 #endif
 
@@ -359,589 +339,711 @@
 		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;
+		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. */
+	 * 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);
+			/* 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;
 			}
 		}
 	}
 
-	signal_setup ();
+	return (true);
+}
+
+static bool do_socket (state_t *state, int mode)
+{
+	if (state->interface->fd >= 0)
+		close (state->interface->fd);
 
-	while (1) {
-		/* Ensure our fd set is clear */
-		FD_ZERO (&rset);
+	state->interface->fd = -1; 
+	if (mode == SOCKET_OPEN) 
+		if (open_socket (state->interface, false) == -1)
+			return (false);
+	state->socket = mode;
+	return (true);
+}
 
-		if (timeout > 0 || (options->timeout == 0 &&
-							(state != STATE_INIT || xid)))
-		{
-			if ((options->timeout == 0 && xid) ||
-				(dhcp->leasetime == (unsigned) -1 && state == STATE_BOUND))
-			{
-				int retry = 0;
-				logger (LOG_DEBUG, "waiting on select for infinity");
-				retval = 0;
-				while (retval == 0)	{
-					maxfd = signal_fd_set (&rset, iface->fd);
-					if (iface->fd == -1)
-						retval = select (maxfd + 1, &rset, NULL, NULL, NULL);
-					else {
-						/* Slow down our requests */
-						if (retry < TIMEOUT_MINI_INF)
-							retry += TIMEOUT_MINI;
-						else if (retry > TIMEOUT_MINI_INF)
-							retry = TIMEOUT_MINI_INF;
+static bool _send_message (state_t *state, int type, const options_t *options)
+{
+	ssize_t retval;
+
+	state->last_type = type;
+	state->last_sent = uptime ();
+	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 (dhcp_t));
+}
 
-						tv.tv_sec = retry;
-						tv.tv_usec = 0;
-						retval = select (maxfd + 1, &rset, NULL, NULL, &tv);
-						if (retval == 0)
-							SEND_MESSAGE (last_type);
-					}
-				}
-			} else {
-				/* Resend our message if we're getting loads of packets
-				   that aren't for us. This mainly happens on Linux as it
-				   doesn't have a nice BPF filter. */
-				if (iface->fd > -1 && uptime () - last_send >= TIMEOUT_MINI)
-					SEND_MESSAGE (last_type);
+static int wait_for_packet (fd_set *rset, state_t *state,
+			    const options_t *options)
+{
+	dhcp_t *dhcp = state->dhcp;
+	interface_t *iface = state->interface;
+	int retval = 0;
+	struct timeval tv;
+	int maxfd;
+
+	if (! (state->timeout > 0 ||
+	       (options->timeout == 0 &&
+		(state->state != STATE_INIT || state->xid))))
+		return (0);
+
+	if ((options->timeout == 0 && state->xid) ||
+	    (dhcp->leasetime == (unsigned) -1 &&
+	     state->state == STATE_BOUND))
+	{
+		int retry = 0;
 
-				logger (LOG_DEBUG, "waiting on select for %ld seconds",
-						(unsigned long) 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 (timeout > 0 && retval == 0) {
-					if (iface->fd == -1)
-						tv.tv_sec = SELECT_MAX;
-					else
-						tv.tv_sec = TIMEOUT_MINI;
-					if (timeout < tv.tv_sec)
-						tv.tv_sec = timeout;
-					tv.tv_usec = 0;
-					start = uptime ();
-					maxfd = signal_fd_set (&rset, iface->fd);
-					retval = select (maxfd + 1, &rset, NULL, NULL, &tv);
-					timeout -= uptime () - start;
-					if (retval == 0 && iface->fd != -1 && timeout > 0)
-						SEND_MESSAGE (last_type);
-				}
+		logger (LOG_DEBUG, "waiting on select for infinity");
+		while (retval == 0)	{
+			maxfd = signal_fd_set (rset, iface->fd);
+			if (iface->fd == -1)
+				retval = select (maxfd + 1, rset,
+						 NULL, NULL, NULL);
+			else {
+				/* Slow down our requests */
+				if (retry < TIMEOUT_MINI_INF)
+					retry += TIMEOUT_MINI;
+				else if (retry > TIMEOUT_MINI_INF)
+					retry = TIMEOUT_MINI_INF;
+
+				tv.tv_sec = retry;
+				tv.tv_usec = 0;
+				retval = select (maxfd + 1, rset,
+						 NULL, NULL, &tv);
+				if (retval == 0)
+					_send_message (state, state->last_type, options);
 			}
-		} else
-			retval = 0;
+		}
+
+		return (retval);
+	}
 
-		/* We should always handle our signals first */
-		if ((sig = (signal_read (&rset))) != -1)
-		{
-			switch (sig) {
-				case SIGINT:
-					logger (LOG_INFO, "received SIGINT, stopping");
-					retval = daemonised ? EXIT_SUCCESS : EXIT_FAILURE;
-					goto eexit;
-
-				case SIGTERM:
-					logger (LOG_INFO, "received SIGTERM, stopping");
-					retval = daemonised ? EXIT_SUCCESS : EXIT_FAILURE;
-					goto eexit;
+	/* Resend our message if we're getting loads of packets
+	   that aren't for us. This mainly happens on Linux as it
+	   doesn't have a nice BPF filter. */
+	if (iface->fd > -1 && uptime () - state->last_sent >= TIMEOUT_MINI)
+		_send_message (state, state->last_type, options);
 
-				case SIGALRM:
-					logger (LOG_INFO, "received SIGALRM, renewing lease");
-					switch (state) {
-						case STATE_BOUND:
-						case STATE_RENEWING:
-						case STATE_REBINDING:
-							state = STATE_RENEW_REQUESTED;
-							break;
-						case STATE_RENEW_REQUESTED:
-						case STATE_REQUESTING:
-						case STATE_RELEASED:
-							state = STATE_INIT;
-							break;
-					}
+	logger (LOG_DEBUG, "waiting on select 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)
+			tv.tv_sec = SELECT_MAX;
+		else
+			tv.tv_sec = TIMEOUT_MINI;
+		if (state->timeout < tv.tv_sec)
+			tv.tv_sec = state->timeout;
+		tv.tv_usec = 0;
+		state->start = uptime ();
+		maxfd = signal_fd_set (rset, iface->fd);
+		retval = select (maxfd + 1, rset, NULL, NULL, &tv);
+		state->timeout -= uptime () - state->start;
+		if (retval == 0 && iface->fd != -1 && state->timeout > 0)
+			_send_message (state, state->last_type, options);
+	}
 
-					timeout = 0;
-					xid = 0;
-					break;
+	return (retval);
+}
 
-				case SIGHUP:
-					if (state == STATE_BOUND || state == STATE_RENEWING
-						|| state == STATE_REBINDING)
-					{
-						logger (LOG_INFO, "received SIGHUP, releasing lease");
-						if (! IN_LINKLOCAL (ntohl (dhcp->address.s_addr))) {
-							SOCKET_MODE (SOCKET_OPEN);
-							xid = random ();
-							if ((open_socket (iface, false)) >= 0)
-								SEND_MESSAGE (DHCP_RELEASE);
-							SOCKET_MODE (SOCKET_CLOSED);
-						}
-						unlink (iface->infofile);
-					}
-					else
-						logger (LOG_ERR,
-								"received SIGHUP, but we no have lease to release");
-					retval = daemonised ? EXIT_SUCCESS : EXIT_FAILURE;
-					goto eexit;
+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);
 
-				default:
-					logger (LOG_ERR,
-							"received signal %d, but don't know what to do with it",
-							sig);
+		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);
 			}
-		} else if (retval == 0) {
-			/* timed out */
+
+			logger (LOG_INFO, "received SIGHUP, releasing lease");
+			if (! IN_LINKLOCAL (ntohl (state->dhcp->address.s_addr))) {
+				do_socket (state, SOCKET_OPEN);
+				state->xid = 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);
 
-			/* No NAK, so reset the backoff */
-			nakoff = 1;
+		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;
 
-			switch (state) {
-				case STATE_INIT:
-					if (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;
-						} else if (! IN_LINKLOCAL (ntohl (iface->previous_address.s_addr)))
-							logger (LOG_ERR, "timed out");
-						
-						SOCKET_MODE (SOCKET_CLOSED);
-						free_dhcp (dhcp);
-						memset (dhcp, 0, sizeof (dhcp_t));
-					
+	/* 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_t));
+
 #ifdef ENABLE_INFO
-						if (! options->test &&
-							(options->doipv4ll || options->dolastlease))
-						{
-							errno = 0;
-							if (! get_old_lease (options, iface, dhcp, &timeout))
-							{
-								if (errno == EINTR)
-									break;
-								if (options->dolastlease) {
-									retval = EXIT_FAILURE;
-									goto eexit;
-								}
-								free_dhcp (dhcp);
-								memset (dhcp, 0, sizeof (dhcp_t));
-							} else if (errno == EINTR)
-								break;
-						}
+		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_t));
+			} 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_t));
-							if (ipv4ll_get_address (iface, dhcp) == -1) {
-								if (! daemonised) {
-									retval = EXIT_FAILURE;
-									goto eexit;
-								}
+		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_t));
+			if (ipv4ll_get_address (iface, dhcp) == -1) {
+				if (! state->daemonised)
+					return (-1);
 
-								/* start over */
-								xid = 0;
-								break;
-							}
-							timeout = dhcp->renewaltime;
-						}
+				/* 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 (! 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 &&
-								! daemonised)
-							{
-								retval = EXIT_FAILURE;
-								goto eexit;
-							}
+		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_BOUND;
-							if (! daemonised && options->daemonise) {
-								switch (daemonise (pidfd)) {
-									case -1:
-										retval = EXIT_FAILURE;
-										goto eexit;
-									case 0:
-										daemonised = true;
-										break;
-									default:
-										persistent = true;
-										retval = EXIT_SUCCESS;
-										goto eexit;
-								}
-							}
+			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);
+				}
+			}
 
-							timeout = dhcp->renewaltime;
-							xid = 0;
-							break;
-						}
+			state->timeout = dhcp->renewaltime;
+			state->xid = 0;
+			return (0);
+		}
 #endif
 
-						if (! daemonised) {
-							retval = EXIT_FAILURE;
-							goto eexit;
-						}
-					}
-
-					xid = random ();
-					SOCKET_MODE (SOCKET_OPEN);
-					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 (DHCP_DISCOVER);
-					} else if (options->doinform) {
-						logger (LOG_INFO, "broadcasting inform for %s",
-								inet_ntoa (dhcp->address));
-						SEND_MESSAGE (DHCP_INFORM);
-						state = STATE_REQUESTING;
-					} else {
-						logger (LOG_INFO, "broadcasting for a lease of %s",
-								inet_ntoa (dhcp->address));
-						SEND_MESSAGE (DHCP_REQUEST);
-						state = STATE_REQUESTING;
-					}
+		if (! state->daemonised)
+			return (-1);
+	}
 
-					break;
-				case STATE_BOUND:
-				case STATE_RENEW_REQUESTED:
-					if (IN_LINKLOCAL (ntohl (dhcp->address.s_addr))) {
-						memset (&dhcp->address, 0, sizeof (struct in_addr));
-						state = STATE_INIT;
-						xid = 0;
-						break;
-					}
-					state = STATE_RENEWING;
-					xid = random ();
-				case STATE_RENEWING:
-					iface->start_uptime = uptime ();
-					logger (LOG_INFO, "renewing lease of %s", inet_ntoa
-							(dhcp->address));
-					SOCKET_MODE (SOCKET_OPEN);
-					SEND_MESSAGE (DHCP_REQUEST);
-					timeout = dhcp->rebindtime - dhcp->renewaltime;
-					state = STATE_REBINDING;
-					break;
-				case STATE_REBINDING:
-					logger (LOG_ERR, "lost lease, attemping to rebind");
-					memset (&dhcp->address, 0, sizeof (struct in_addr));
-					SOCKET_MODE (SOCKET_OPEN);
-					if (xid == 0)
-						xid = random ();
-					SEND_MESSAGE (DHCP_REQUEST);
-					timeout = dhcp->leasetime - dhcp->rebindtime;
-					state = STATE_REQUESTING;
-					break;
-				case STATE_REQUESTING:
-					state = STATE_INIT;
-					SOCKET_MODE (SOCKET_CLOSED);
-					timeout = 0;
-					break;
-
-				case STATE_RELEASED:
-					dhcp->leasetime = -1;
-					break;
-			}
-		} else if (retval > 0 &&
-				   mode != SOCKET_CLOSED &&
-				   FD_ISSET(iface->fd, &rset))
-		{
-			int valid = 0;
-			struct dhcp_t *new_dhcp;
-
-			/* Allocate our buffer space for BPF.
-			   We cannot do this until we have opened our socket as we don't
-			   know how much of a buffer we need until then. */
-			if (! buffer)
-				buffer = xmalloc (iface->buffer_length);
-			buffer_len = iface->buffer_length;
-			buffer_pos = -1;
-
-			/* We loop through until our buffer is empty.
-			   The benefit is that if we get >1 DHCP packet in our buffer and
-			   the first one fails for any reason, we can use the next. */
-
-			memset (&message, 0, sizeof (struct dhcpmessage_t));
-			new_dhcp = xmalloc (sizeof (dhcp_t));
-
-			while (buffer_pos != 0) {
-				if (get_packet (iface, (unsigned char *) &message, buffer,
-								&buffer_len, &buffer_pos) == -1)
-					break;
-
-				if (xid != message.xid) {
-					logger (LOG_DEBUG,
-							"ignoring packet with xid 0x%x as it's not ours (0x%x)",
-							message.xid, xid);
-					continue;
-				}
-
-				logger (LOG_DEBUG, "got a packet with xid 0x%x", message.xid);
-				memset (new_dhcp, 0, sizeof (dhcp_t));
-				if ((type = parse_dhcpmessage (new_dhcp, &message)) == -1) {
-					logger (LOG_ERR, "failed to parse packet");
-					free_dhcp (new_dhcp);
-					continue;
-				}
-
-				/* If we got here then the DHCP packet is valid and appears to
-				   be for us, so let's clear the buffer as we don't care about
-				   any more DHCP packets at this point. */
-				valid = 1;
-				break;
-			}
-
-			/* No packets for us, so wait until we get one */
-			if (! valid) {
-				free (new_dhcp);
-				continue;
+	switch (state->state) {
+		case STATE_INIT:
+			state->xid = 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;
 			}
 
-			/* new_dhcp is now our master DHCP message */
-			free_dhcp (dhcp);
-			free (dhcp);
-			dhcp = new_dhcp;
-			new_dhcp = NULL;
+			break;
+		case STATE_BOUND:
+		case STATE_RENEW_REQUESTED:
+			if (IN_LINKLOCAL (ntohl (dhcp->address.s_addr))) {
+				memset (&dhcp->address, 0, sizeof (struct in_addr));
+				state->state = STATE_INIT;
+				state->xid = 0;
+				break;
+			}
+			state->state = STATE_RENEWING;
+			state->xid = random ();
+		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 (struct in_addr));
+			do_socket (state, SOCKET_OPEN);
+			if (state->xid == 0)
+				state->xid = random ();
+			_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 = -1;
+			break;
+	}
+
+	return (0);
+}
 
-			/* We should restart on a NAK */
-			if (type == DHCP_NAK) {
-				logger (LOG_INFO, "received NAK: %s", dhcp->message);
-				state = STATE_INIT;
-				timeout = 0;
-				xid = 0;
-				free_dhcp (dhcp);
-				memset (dhcp, 0, sizeof (dhcp_t));
-				
-				/* If we constantly get NAKS then we should slowly back off */
-				if (nakoff > 0) {
-					logger (LOG_DEBUG, "sleeping for %ld seconds", nakoff);
-					tv.tv_sec = nakoff;
-					tv.tv_usec = 0;
-					nakoff *= 2;
-					if (nakoff > NAKOFF_MAX)
-						nakoff = NAKOFF_MAX;
-					select (0, NULL, NULL, NULL, &tv);
-				}
-	
-				continue;
-			}
+
+static int handle_dhcp (state_t *state, int type, const options_t *options)
+{
+	struct timeval tv;
+	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);
+		state->state = STATE_INIT;
+		state->timeout = 0;
+		state->xid = 0;
+		free_dhcp (dhcp);
+		memset (dhcp, 0, sizeof (dhcp_t));
 
-			/* No NAK, so reset the backoff */
-			nakoff = 1;
+		/* If we constantly get NAKS then we should slowly back off */
+		if (state->nakoff > 0) {
+			logger (LOG_DEBUG, "sleeping for %ld seconds",
+				state->nakoff);
+			tv.tv_sec = state->nakoff;
+			tv.tv_usec = 0;
+			state->nakoff *= 2;
+			if (state->nakoff > NAKOFF_MAX)
+				state->nakoff = NAKOFF_MAX;
+			select (0, NULL, NULL, NULL, &tv);
+		}
 
-			switch (state) {
-				case STATE_INIT:
-					if (type == DHCP_OFFER) {
-						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);
+		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);
 
 #ifdef ENABLE_INFO
-						if (options->test) {
-							write_info (iface, dhcp, options, false);
-							goto eexit;
-						}
-#endif
-						
-						SEND_MESSAGE (DHCP_REQUEST);
-						state = STATE_REQUESTING;
-					}
-					break;
-
-				case STATE_RENEW_REQUESTED:
-				case STATE_REQUESTING:
-				case STATE_RENEWING:
-				case STATE_REBINDING:
-					if (type == DHCP_ACK) {
-						SOCKET_MODE (SOCKET_CLOSED);
-#ifdef ENABLE_ARP
-						if (options->doarp && iface->previous_address.s_addr !=
-							dhcp->address.s_addr)
-						{
-							errno = 0;
-							if (arp_claim (iface, dhcp->address)) {
-								SOCKET_MODE (SOCKET_OPEN);
-								SEND_MESSAGE (DHCP_DECLINE);
-								SOCKET_MODE (SOCKET_CLOSED);
-
-								free_dhcp (dhcp);
-								memset (dhcp, 0, sizeof (dhcp_t));
-								xid = 0;
-								timeout = 0;
-								state = STATE_INIT;
-
-								/* RFC 2131 says that we should wait for 10 seconds
-								   before doing anything else */
-								logger (LOG_INFO, "sleeping for 10 seconds");
-								tv.tv_sec = 10;
-								tv.tv_usec = 0;
-								select (0, NULL, NULL, NULL, &tv);
-								continue;
-							} else if (errno == EINTR)
-								break;
-						}
+		if (options->test) {
+			write_info (iface, dhcp, options, false);
+			errno = 0;
+			return (-1);
+		}
 #endif
 
-						if (options->doinform) {
-							if (options->request_address.s_addr != 0)
-								dhcp->address = options->request_address;
-							else
-								dhcp->address = iface->previous_address;
+		_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);
+	}
+	    
+	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);
 
-							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;
-							}
-							timeout = options->leasetime;
-							if (timeout == 0)
-								timeout = DEFAULT_LEASETIME;
-							state = STATE_INIT;
-						} else if (dhcp->leasetime == (unsigned) -1) {
-							dhcp->renewaltime = dhcp->rebindtime = dhcp->leasetime;
-							timeout = 1; /* So we select on infinity */
-							logger (LOG_INFO, "leased %s for infinity",
-									inet_ntoa (dhcp->address));
-							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);
+#ifdef ENABLE_ARP
+	if (options->doarp && iface->previous_address.s_addr !=
+	    dhcp->address.s_addr)
+	{
+		errno = 0;
+		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_t));
+			state->xid = 0;
+			state->timeout = 0;
+			state->state = STATE_INIT;
 
-							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);
-							}
+			/* RFC 2131 says that we should wait for 10 seconds
+			   before doing anything else */
+			logger (LOG_INFO, "sleeping for 10 seconds");
+			tv.tv_sec = 10;
+			tv.tv_usec = 0;
+			select (0, NULL, NULL, NULL, &tv);
+			return (0);
+		} else if (errno == EINTR)
+			return (0);	
+	}
+#endif
 
-							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 (options->doinform) {
+		if (options->request_address.s_addr != 0)
+			dhcp->address = options->request_address;
+		else
+			dhcp->address = iface->previous_address;
 
-							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);
+		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 select on 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->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);
-
-							timeout = dhcp->renewaltime;
-							state = STATE_BOUND;
-						}
-
-						xid = 0;
-
-						if (configure (options, iface, dhcp, true) == -1 &&
-							! daemonised)
-						{
-							retval = EXIT_FAILURE;
-							goto eexit;
-						}
+		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 (! daemonised && options->daemonise) {
-							switch (daemonise (pidfd)) {
-								case -1:
-									retval = EXIT_FAILURE;
-									goto eexit;
-								case 0:
-									daemonised = true;
-									break;
-								default:
-									persistent = true;
-									retval = EXIT_SUCCESS;
-									goto eexit;
-							}
-						}
-					} else if (type == DHCP_OFFER)
-						logger (LOG_INFO, "got subsequent offer of %s, ignoring ",
-								inet_ntoa (dhcp->address));
-					else
-						logger (LOG_ERR,
-								"no idea what to do with DHCP type %d at this point",
-								type);
-					break;
-			}
-		} else if (retval == -1 && errno == EINTR) {
-			/* The interupt will be handled above */
-		} else {
-			/* An error occured. As we heavily depend on select, we abort. */
-			logger (LOG_ERR, "error on select: %s", strerror (errno));
-			retval = EXIT_FAILURE;
-			goto eexit;
+		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;
+
+	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 = -1;
+
+	/* We loop through until our buffer is empty.
+	   The benefit is that if we get >1 DHCP packet in our buffer and
+	   the first one fails for any reason, we can use the next. */
+
+	memset (&message, 0, sizeof (struct dhcpmessage_t));
+	new_dhcp = xmalloc (sizeof (dhcp_t));
+
+	while (state->buffer_pos != 0) {
+		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 (dhcp_t));
+		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;
+	}
+
+	/* 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;
+	fd_set rset;
+	int retval = -1;
+	int sig;
+
+	if (! options)
+		return (-1);	
+
+	iface = read_interface (options->interface, options->metric);
+	if (! iface)
+		goto eexit;
+
+
+	state = xmalloc (sizeof (state_t));
+	memset (state, 0, sizeof (state_t));
+	
+	state->dhcp = xmalloc (sizeof (dhcp_t));
+	memset (state->dhcp, 0, sizeof (dhcp_t));
+
+	state->pidfd = pidfd;
+	state->interface = iface;
+
+	if (! client_setup (state, options))
+		goto eexit;
+
+	signal_setup ();
+
+	while (1) {
+		retval = wait_for_packet (&rset, state, options);
+
+		/* We should always handle our signals first */
+		if ((sig = (signal_read (&rset))) != -1) {
+			if (! handle_signal (sig, state, options))
+				retval = -1;
+		} else if (retval == 0)
+			retval = handle_timeout (state, options);
+		else if (retval > 0 &&
+			 state->socket != SOCKET_CLOSED &&
+			 FD_ISSET (iface->fd, &rset))
+			retval = handle_packet (state, options);
+		else if (retval == -1 && errno == EINTR) {
+			/* The interupt will be handled above */
+			retval = 0;
+		} else {
+			logger (LOG_ERR, "error on select: %s",
+				strerror (errno));
+			retval = -1;
+		}
+
+		if (retval != 0)
+			break;
+	}
+
 eexit:
-	SOCKET_MODE (SOCKET_CLOSED);
-	DROP_CONFIG;
-	free (dhcp);
+	do_socket (state, SOCKET_CLOSED);
+	drop_config (state, options);
 
 	if (iface) {
 		free_route (iface->previous_routes);
 		free (iface);
 	}
-	free (buffer);
 
-	if (daemonised)
+	if (state->forked)
+		retval = 0;
+
+	if (state->daemonised)
 		unlink (options->pidfile);
 
-	return retval;
+	free_dhcp (state->dhcp);
+	free (state->dhcp);
+	free (state->buffer);
+	free (state);
+
+	return (retval);
 }
-