#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)
{
_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);
}
}
#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);
#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));
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;
- if (! options || (iface = (read_interface (options->interface,
- options->metric))) == NULL)
- return (-1);
+ 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 (! 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
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);
+}
- while (1) {
- /* Ensure our fd set is clear */
- FD_ZERO (&rset);
+static bool do_socket (state_t *state, int mode)
+{
+ if (state->interface->fd >= 0)
+ close (state->interface->fd);
+
+ 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;
-
- 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);
-
- 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);
- }
- }
- } else
- retval = 0;
+static bool _send_message (state_t *state, int type, const options_t *options)
+{
+ ssize_t 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;
-
- 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;
- }
-
- timeout = 0;
- xid = 0;
- break;
+ 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));
+}
+
+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);
- 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;
-
- default:
- logger (LOG_ERR,
- "received signal %d, but don't know what to do with it",
- sig);
+ if ((options->timeout == 0 && state->xid) ||
+ (dhcp->leasetime == (unsigned) -1 &&
+ state->state == STATE_BOUND))
+ {
+ int retry = 0;
+
+ 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 if (retval == 0) {
- /* timed out */
-
- /* No NAK, so reset the backoff */
- nakoff = 1;
-
- 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));
-
-#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;
- }
-#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;
- }
-
- /* start over */
- xid = 0;
- break;
- }
- timeout = dhcp->renewaltime;
- }
-#endif
+ return (retval);
+ }
-#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;
- }
-
- 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;
- }
- }
-
- timeout = dhcp->renewaltime;
- xid = 0;
- break;
- }
-#endif
+ /* 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);
+
+ 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);
+ }
- 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;
- }
+ return (retval);
+}
- break;
+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_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;
+ state->state = STATE_RENEW_REQUESTED;
break;
+ case STATE_RENEW_REQUESTED:
case STATE_REQUESTING:
- state = STATE_INIT;
- SOCKET_MODE (SOCKET_CLOSED);
- timeout = 0;
- break;
-
case STATE_RELEASED:
- dhcp->leasetime = -1;
+ state->state = STATE_INIT;
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;
+ state->timeout = 0;
+ state->xid = 0;
+ return (true);
+
+ case SIGHUP:
+ if (state->state != STATE_BOUND &&
+ state->state != STATE_RENEWING &&
+ state->state != STATE_REBINDING)
+ {
+ logger (LOG_ERR,
+ "received SIGHUP, but we no have lease to release");
+ return (false);
+ }
- 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_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);
- 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;
- }
+ default:
+ logger (LOG_ERR,
+ "received signal %d, but don't know what to do with it",
+ sig);
+ }
- /* 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;
- }
+ return (false);
+}
- /* No packets for us, so wait until we get one */
- if (! valid) {
- free (new_dhcp);
- continue;
- }
+static int handle_timeout (state_t *state, const options_t *options)
+{
+ dhcp_t *dhcp = state->dhcp;
+ interface_t *iface = state->interface;
- /* new_dhcp is now our master DHCP message */
- free_dhcp (dhcp);
- free (dhcp);
- dhcp = new_dhcp;
- new_dhcp = NULL;
-
- /* 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;
+ /* 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 (state, options))
+ {
+ if (errno == EINTR)
+ return (0);
+ if (options->dolastlease)
+ return (-1);
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);
+ } 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 (! state->daemonised)
+ return (-1);
+
+ /* start over */
+ state->xid = 0;
+ return (0);
+ }
+ state->timeout = dhcp->renewaltime;
+ }
+#endif
+
+#if defined (ENABLE_INFO) || defined (ENABLE_IPV4LL)
+ if (dhcp->address.s_addr) {
+ if (! state->daemonised &&
+ IN_LINKLOCAL (ntohl (dhcp->address.s_addr)))
+ logger (LOG_WARNING, "using IPV4LL address %s",
+ inet_ntoa (dhcp->address));
+ if (configure (options, iface, dhcp, true) == -1 &&
+ ! state->daemonised)
+ return (-1);
+
+ state->state = STATE_BOUND;
+ if (! state->daemonised && options->daemonise) {
+ switch (daemonise (state->pidfd)) {
+ case -1:
+ return (-1);
+ case 0:
+ state->daemonised = true;
+ return (0);
+ default:
+ state->persistent = true;
+ state->forked = true;
+ return (-1);
}
-
- continue;
}
- /* No NAK, so reset the backoff */
- nakoff = 1;
-
- 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);
+ state->timeout = dhcp->renewaltime;
+ state->xid = 0;
+ return (0);
+ }
+#endif
+
+ if (! state->daemonised)
+ return (-1);
+ }
+
+ 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;
+ }
+
+ 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);
+}
+
+
+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));
+
+ /* 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);
+ }
+
+ 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;
- }
+ if (options->test) {
+ write_info (iface, dhcp, options, false);
+ errno = 0;
+ return (-1);
+ }
#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);
+ _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);
+
#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->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;
+
+ /* 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 (options->doinform) {
- if (options->request_address.s_addr != 0)
- dhcp->address = options->request_address;
- else
- dhcp->address = iface->previous_address;
-
- logger (LOG_INFO, "received approval for %s",
- inet_ntoa (dhcp->address));
- if (iface->previous_netmask.s_addr != dhcp->netmask.s_addr) {
- add_address (iface->name, dhcp->address,
- dhcp->netmask, dhcp->broadcast);
- iface->previous_netmask.s_addr = dhcp->netmask.s_addr;
- }
- 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);
-
- if (dhcp->rebindtime >= dhcp->leasetime) {
- dhcp->rebindtime = (dhcp->leasetime * 0.875);
- logger (LOG_ERR, "rebind time greater than lease "
- "time, forcing to %u seconds",
- dhcp->rebindtime);
- }
-
- if (dhcp->renewaltime > dhcp->rebindtime) {
- dhcp->renewaltime = (dhcp->leasetime * 0.5);
- logger (LOG_ERR, "renewal time greater than rebind time, "
- "forcing to %u seconds",
- dhcp->renewaltime);
- }
-
- if (! dhcp->renewaltime) {
- dhcp->renewaltime = (dhcp->leasetime * 0.5);
- logger (LOG_INFO,
- "no renewal time supplied, assuming %d seconds",
- dhcp->renewaltime);
- } else
- logger (LOG_DEBUG, "renew in %u seconds",
- dhcp->renewaltime);
-
- if (! dhcp->rebindtime) {
- dhcp->rebindtime = (dhcp->leasetime * 0.875);
- logger (LOG_INFO,
- "no rebind time supplied, assuming %d seconds",
- dhcp->rebindtime);
- } else
- logger (LOG_DEBUG, "rebind in %u seconds",
- dhcp->rebindtime);
-
- timeout = dhcp->renewaltime;
- state = STATE_BOUND;
- }
-
- xid = 0;
-
- if (configure (options, iface, dhcp, true) == -1 &&
- ! daemonised)
- {
- retval = EXIT_FAILURE;
- goto eexit;
- }
-
- 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) {
+ if (options->doinform) {
+ if (options->request_address.s_addr != 0)
+ dhcp->address = options->request_address;
+ else
+ dhcp->address = iface->previous_address;
+
+ logger (LOG_INFO, "received approval for %s",
+ inet_ntoa (dhcp->address));
+ if (iface->previous_netmask.s_addr != dhcp->netmask.s_addr) {
+ add_address (iface->name, dhcp->address,
+ dhcp->netmask, dhcp->broadcast);
+ iface->previous_netmask.s_addr = dhcp->netmask.s_addr;
+ }
+ state->timeout = options->leasetime;
+ if (state->timeout == 0)
+ state->timeout = DEFAULT_LEASETIME;
+ state->state = STATE_INIT;
+ } else if (dhcp->leasetime == (unsigned) -1) {
+ dhcp->renewaltime = dhcp->rebindtime = dhcp->leasetime;
+ state->timeout = 1; /* So we 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->leasetime) {
+ dhcp->rebindtime = (dhcp->leasetime * 0.875);
+ logger (LOG_ERR,
+ "rebind time greater than lease "
+ "time, forcing to %u seconds",
+ dhcp->rebindtime);
+ }
+
+ if (dhcp->renewaltime > dhcp->rebindtime) {
+ dhcp->renewaltime = (dhcp->leasetime * 0.5);
+ logger (LOG_ERR,
+ "renewal time greater than rebind time, "
+ "forcing to %u seconds",
+ dhcp->renewaltime);
+ }
+
+ if (! dhcp->renewaltime) {
+ dhcp->renewaltime = (dhcp->leasetime * 0.5);
+ logger (LOG_INFO,
+ "no renewal time supplied, assuming %d seconds",
+ dhcp->renewaltime);
+ } else
+ logger (LOG_DEBUG, "renew in %u seconds",
+ dhcp->renewaltime);
+
+ if (! dhcp->rebindtime) {
+ dhcp->rebindtime = (dhcp->leasetime * 0.875);
+ logger (LOG_INFO,
+ "no rebind time supplied, assuming %d seconds",
+ dhcp->rebindtime);
+ } else
+ logger (LOG_DEBUG, "rebind in %u seconds",
+ dhcp->rebindtime);
+
+ state->timeout = dhcp->renewaltime;
+ state->state = STATE_BOUND;
+ }
+
+ state->xid = 0;
+
+ 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 {
- /* 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;
+ 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);
+}