Mercurial > hg > dhcpcd
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); } -
