aboutsummaryrefslogtreecommitdiffstats
path: root/client.c
diff options
context:
space:
mode:
Diffstat (limited to 'client.c')
-rw-r--r--client.c1854
1 files changed, 0 insertions, 1854 deletions
diff --git a/client.c b/client.c
deleted file mode 100644
index 90657b3..0000000
--- a/client.c
+++ /dev/null
@@ -1,1854 +0,0 @@
-/*
- * dhcpcd - DHCP client daemon
- * Copyright 2006-2008 Roy Marples <roy@marples.name>
- * All rights reserved
-
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#include <sys/stat.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <arpa/inet.h>
-
-#ifdef __linux__
-# include <netinet/ether.h>
-#endif
-
-#include <errno.h>
-#include <poll.h>
-#include <signal.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <time.h>
-#include <unistd.h>
-
-#include "config.h"
-#include "common.h"
-#include "client.h"
-#include "configure.h"
-#include "dhcp.h"
-#include "dhcpcd.h"
-#include "net.h"
-#include "logger.h"
-#include "signals.h"
-
-#define IPV4LL_LEASETIME 2
-
-/* Some platforms don't define INFTIM */
-#ifndef INFTIM
-# define INFTIM -1
-#endif
-
-#define STATE_INIT 0
-#define STATE_DISCOVERING 1
-#define STATE_REQUESTING 2
-#define STATE_BOUND 3
-#define STATE_RENEWING 4
-#define STATE_REBINDING 5
-#define STATE_REBOOT 6
-#define STATE_RENEW_REQUESTED 7
-#define STATE_INIT_IPV4LL 8
-#define STATE_PROBING 9
-#define STATE_ANNOUNCING 10
-
-/* Constants taken from RFC 2131. */
-#define T1 0.5
-#define T2 0.875
-#define DHCP_BASE 4
-#define DHCP_MAX 64
-#define DHCP_RAND_MIN -1
-#define DHCP_RAND_MAX 1
-#define DHCP_ARP_FAIL 10
-
-/* We should define a maximum for the NAK exponential backoff */
-#define NAKOFF_MAX 60
-
-#define SOCKET_CLOSED 0
-#define SOCKET_OPEN 1
-
-/* These are for IPV4LL, RFC 3927. */
-#define PROBE_WAIT 1
-#define PROBE_NUM 3
-#define PROBE_MIN 1
-#define PROBE_MAX 2
-#define ANNOUNCE_WAIT 2
-/* BSD systems always do a grauitous ARP when assigning an address,
- * so we can do one less announce. */
-#ifdef BSD
-# define ANNOUNCE_NUM 1
-#else
-# define ANNOUNCE_NUM 2
-#endif
-#define ANNOUNCE_INTERVAL 2
-#define MAX_CONFLICTS 10
-#define RATE_LIMIT_INTERVAL 60
-#define DEFEND_INTERVAL 10
-
-
-/* number of usecs in a second. */
-#define USECS_SECOND 1000000
-/* As we use timevals, we should use the usec part for
- * greater randomisation. */
-#define DHCP_RAND_MIN_U DHCP_RAND_MIN * USECS_SECOND
-#define DHCP_RAND_MAX_U DHCP_RAND_MAX * USECS_SECOND
-#define PROBE_MIN_U PROBE_MIN * USECS_SECOND
-#define PROBE_MAX_U PROBE_MAX * USECS_SECOND
-
-#define timernorm(tvp) \
- do { \
- while ((tvp)->tv_usec >= 1000000) { \
- (tvp)->tv_sec++; \
- (tvp)->tv_usec -= 1000000; \
- } \
- } while (0 /* CONSTCOND */);
-
-#define timerneg(tvp) ((tvp)->tv_sec < 0 || (tvp)->tv_usec < 0)
-
-struct if_state {
- int options;
- struct interface *interface;
- struct dhcp_message *offer;
- struct dhcp_message *new;
- struct dhcp_message *old;
- struct dhcp_lease lease;
- struct timeval timeout;
- struct timeval stop;
- struct timeval exit;
- int state;
- int messages;
- time_t nakoff;
- uint32_t xid;
- int socket;
- int *pid_fd;
- int signal_fd;
- int carrier;
- int probes;
- int claims;
- int conflicts;
- time_t defend;
- struct in_addr fail;
-};
-
-#define LINK_UP 1
-#define LINK_UNKNOWN 0
-#define LINK_DOWN -1
-
-struct dhcp_op {
- uint8_t value;
- const char *name;
-};
-
-static const struct dhcp_op const dhcp_ops[] = {
- { DHCP_DISCOVER, "DHCP_DISCOVER" },
- { DHCP_OFFER, "DHCP_OFFER" },
- { DHCP_REQUEST, "DHCP_REQUEST" },
- { DHCP_DECLINE, "DHCP_DECLINE" },
- { DHCP_ACK, "DHCP_ACK" },
- { DHCP_NAK, "DHCP_NAK" },
- { DHCP_RELEASE, "DHCP_RELEASE" },
- { DHCP_INFORM, "DHCP_INFORM" },
- { 0, NULL }
-};
-
-static const char *
-get_dhcp_op(uint8_t type)
-{
- const struct dhcp_op *d;
-
- for (d = dhcp_ops; d->name; d++)
- if (d->value == type)
- return d->name;
- return NULL;
-}
-
-#ifdef THERE_IS_NO_FORK
-#define daemonise(a,b) 0
-#else
-static int
-daemonise(struct if_state *state, const struct options *options)
-{
- pid_t pid;
- sigset_t full;
- sigset_t old;
- char buf = '\0';
- int sidpipe[2];
-
- if (state->options & DHCPCD_DAEMONISED ||
- !(options->options & DHCPCD_DAEMONISE))
- return 0;
-
- sigfillset(&full);
- sigprocmask(SIG_SETMASK, &full, &old);
-
- /* Setup a signal pipe so parent knows when to exit. */
- if (pipe(sidpipe) == -1) {
- logger(LOG_ERR,"pipe: %s", strerror(errno));
- return -1;
- }
-
- logger(LOG_DEBUG, "forking to background");
- switch (pid = fork()) {
- case -1:
- logger(LOG_ERR, "fork: %s", strerror(errno));
- exit(EXIT_FAILURE);
- /* NOTREACHED */
- case 0:
- setsid();
- /* Notify parent it's safe to exit as we've detached. */
- close(sidpipe[0]);
- if (write(sidpipe[1], &buf, 1) != 1)
- logger(LOG_ERR, "write: %s", strerror(errno));
- close(sidpipe[1]);
- close_fds();
- break;
- default:
- /* Reset signals as we're the parent about to exit. */
- signal_reset();
- /* Wait for child to detach */
- close(sidpipe[1]);
- if (read(sidpipe[0], &buf, 1) != 1)
- logger(LOG_ERR, "read: %s", strerror(errno));
- close(sidpipe[0]);
- break;
- }
-
- /* Done with the fd now */
- if (pid != 0) {
- writepid(*state->pid_fd, pid);
- close(*state->pid_fd);
- *state->pid_fd = -1;
- }
-
- sigprocmask(SIG_SETMASK, &old, NULL);
- if (pid == 0) {
- state->options |= DHCPCD_DAEMONISED;
- timerclear(&state->exit);
- return 0;
- }
- state->options |= DHCPCD_PERSISTENT | DHCPCD_FORKED;
- return -1;
-}
-#endif
-
-#define THIRTY_YEARS_IN_SECONDS 946707779
-static size_t
-get_duid(unsigned char *duid, const struct interface *iface)
-{
- FILE *f;
- uint16_t type = 0;
- uint16_t hw = 0;
- uint32_t ul;
- time_t t;
- int x = 0;
- unsigned char *p = duid;
- size_t len = 0, l = 0;
- char *buffer = NULL, *line, *option;
-
- /* If we already have a DUID then use it as it's never supposed
- * to change once we have one even if the interfaces do */
- if ((f = fopen(DUID, "r"))) {
- while ((get_line(&buffer, &len, f))) {
- line = buffer;
- while ((option = strsep(&line, " \t")))
- if (*option != '\0')
- break;
- if (!option || *option == '\0' || *option == '#')
- continue;
- l = hwaddr_aton(NULL, option);
- if (l && l <= DUID_LEN) {
- hwaddr_aton(duid, option);
- break;
- }
- l = 0;
- }
- fclose(f);
- free(buffer);
- if (l)
- return l;
- } else {
- if (errno != ENOENT)
- return 0;
- }
-
- /* No file? OK, lets make one based on our interface */
- if (!(f = fopen(DUID, "w")))
- return 0;
- type = htons(1); /* DUI-D-LLT */
- memcpy(p, &type, 2);
- p += 2;
- hw = htons(iface->family);
- memcpy(p, &hw, 2);
- p += 2;
- /* time returns seconds from jan 1 1970, but DUID-LLT is
- * seconds from jan 1 2000 modulo 2^32 */
- t = time(NULL) - THIRTY_YEARS_IN_SECONDS;
- ul = htonl(t & 0xffffffff);
- memcpy(p, &ul, 4);
- p += 4;
- /* Finally, add the MAC address of the interface */
- memcpy(p, iface->hwaddr, iface->hwlen);
- p += iface->hwlen;
- len = p - duid;
- x = fprintf(f, "%s\n", hwaddr_ntoa(duid, len));
- fclose(f);
- /* Failed to write the duid? scrub it, we cannot use it */
- if (x < 1) {
- len = 0;
- unlink(DUID);
- }
- return len;
-}
-
-static struct dhcp_message*
-ipv4ll_get_dhcp(uint32_t old_addr)
-{
- uint32_t u32;
- struct dhcp_message *dhcp;
- uint8_t *p;
-
- dhcp = xzalloc(sizeof(*dhcp));
- /* Put some LL options in */
- p = dhcp->options;
- *p++ = DHO_SUBNETMASK;
- *p++ = sizeof(u32);
- u32 = htonl(LINKLOCAL_MASK);
- memcpy(p, &u32, sizeof(u32));
- p += sizeof(u32);
- *p++ = DHO_BROADCAST;
- *p++ = sizeof(u32);
- u32 = htonl(LINKLOCAL_BRDC);
- memcpy(p, &u32, sizeof(u32));
- p += sizeof(u32);
- *p++ = DHO_END;
-
- for (;;) {
- dhcp->yiaddr = htonl(LINKLOCAL_ADDR |
- (((uint32_t)abs((int)arc4random())
- % 0xFD00) + 0x0100));
- if (dhcp->yiaddr != old_addr &&
- IN_LINKLOCAL(ntohl(dhcp->yiaddr)))
- break;
- }
- return dhcp;
-}
-
-static double
-timeval_to_double(struct timeval *tv)
-{
- return tv->tv_sec * 1.0 + tv->tv_usec * 1.0e-6;
-}
-
-static void
-get_lease(struct dhcp_lease *lease, const struct dhcp_message *dhcp)
-{
- time_t t;
-
- if (lease->frominfo)
- return;
- lease->addr.s_addr = dhcp->yiaddr;
-
- if (get_option_addr(&lease->net, dhcp, DHO_SUBNETMASK) == -1)
- lease->net.s_addr = get_netmask(dhcp->yiaddr);
- if (get_option_uint32(&lease->leasetime, dhcp, DHO_LEASETIME) == 0) {
- /* Ensure that we can use the lease */
- t = 0;
- if (t + (time_t)lease->leasetime < t) {
- logger(LOG_WARNING, "lease of %u would overflow, "
- "treating as infinite", lease->leasetime);
- lease->leasetime = ~0U; /* Infinite lease */
- }
- } else
- lease->leasetime = DEFAULT_LEASETIME;
- if (get_option_uint32(&lease->renewaltime, dhcp, DHO_RENEWALTIME) != 0)
- lease->renewaltime = 0;
- if (get_option_uint32(&lease->rebindtime, dhcp, DHO_REBINDTIME) != 0)
- lease->rebindtime = 0;
-}
-
-static int
-get_old_lease(struct if_state *state)
-{
- struct interface *iface = state->interface;
- struct dhcp_lease *lease = &state->lease;
- struct dhcp_message *dhcp = NULL;
- struct timeval tv;
- unsigned int offset = 0;
- struct stat sb;
-
- if (stat(iface->leasefile, &sb) == -1) {
- if (errno != ENOENT)
- logger(LOG_ERR, "stat: %s", strerror(errno));
- goto eexit;
- }
- if (!IN_LINKLOCAL(ntohl(iface->addr.s_addr)))
- logger(LOG_INFO, "trying to use old lease in `%s'",
- iface->leasefile);
- if ((dhcp = read_lease(iface)) == NULL) {
- logger(LOG_INFO, "read_lease: %s", strerror(errno));
- goto eexit;
- }
- lease->frominfo = 0;
- get_lease(&state->lease, dhcp);
- lease->frominfo = 1;
- lease->leasedfrom = sb.st_mtime;
-
- /* Vitaly important we remove the server information here */
- state->lease.server.s_addr = 0;
- dhcp->servername[0] = '\0';
-
- if (!IN_LINKLOCAL(ntohl(dhcp->yiaddr))) {
- if (!(state->options & DHCPCD_LASTLEASE))
- goto eexit;
-
- /* Ensure that we can still use the lease */
- if (gettimeofday(&tv, NULL) == -1) {
- logger(LOG_ERR, "gettimeofday: %s", strerror(errno));
- goto eexit;
- }
-
- offset = tv.tv_sec - lease->leasedfrom;
- if (lease->leasedfrom &&
- tv.tv_sec - lease->leasedfrom > (time_t)lease->leasetime)
- {
- logger(LOG_ERR, "lease expired %u seconds ago",
- offset + lease->leasetime);
- /* Persistent interfaces should still try and use the
- * lease if we can't contact a DHCP server.
- * We just set the timeout to 1 second. */
- if (state->options & DHCPCD_PERSISTENT)
- offset = lease->renewaltime - 1;
- else
- goto eexit;
- }
- lease->leasetime -= offset;
- lease->rebindtime -= offset;
- lease->renewaltime -= offset;
- }
-
- free(state->old);
- state->old = state->new;
- state->new = NULL;
- state->offer = dhcp;
- return 0;
-
-eexit:
- lease->addr.s_addr = 0;
- free(dhcp);
- return -1;
-}
-
-static int
-client_setup(struct if_state *state, const struct options *options)
-{
- struct interface *iface = state->interface;
- struct dhcp_lease *lease = &state->lease;
- struct in_addr addr;
- struct timeval tv;
- size_t len = 0;
- unsigned char *duid = NULL;
- uint32_t ul;
-
- state->state = STATE_INIT;
- state->nakoff = 1;
- state->options = options->options;
- timerclear(&tv);
-
- if (options->request_address.s_addr == 0 &&
- (options->options & DHCPCD_INFORM ||
- options->options & DHCPCD_REQUEST ||
- (options->options & DHCPCD_DAEMONISED &&
- !(options->options & DHCPCD_BACKGROUND))))
- {
- if (get_old_lease(state) != 0)
- return -1;
- timerclear(&state->timeout);
-
- if (!(options->options & DHCPCD_DAEMONISED) &&
- IN_LINKLOCAL(ntohl(lease->addr.s_addr)))
- {
- logger(LOG_ERR, "cannot request a link local address");
- return -1;
- }
- } else {
- lease->addr.s_addr = options->request_address.s_addr;
- lease->net.s_addr = options->request_netmask.s_addr;
- }
-
- /* If INFORMing, ensure the interface has the address */
- if (state->options & DHCPCD_INFORM &&
- has_address(iface->name, &lease->addr, &lease->net) < 1)
- {
- addr.s_addr = lease->addr.s_addr | ~lease->net.s_addr;
- logger(LOG_DEBUG, "adding IP address %s/%d",
- inet_ntoa(lease->addr), inet_ntocidr(lease->net));
- if (add_address(iface->name, &lease->addr,
- &lease->net, &addr) == -1)
- {
- logger(LOG_ERR, "add_address: %s", strerror(errno));
- return -1;
- }
- iface->addr.s_addr = lease->addr.s_addr;
- iface->net.s_addr = lease->net.s_addr;
- }
-
- /* If we haven't specified a ClientID and our hardware address
- * length is greater than DHCP_CHADDR_LEN then we enforce a ClientID
- * of the hardware address family and the hardware address. */
- if (!(state->options & DHCPCD_CLIENTID) && iface->hwlen > DHCP_CHADDR_LEN)
- state->options |= DHCPCD_CLIENTID;
-
- if (*options->clientid) {
- iface->clientid = xmalloc(options->clientid[0] + 1);
- memcpy(iface->clientid,
- options->clientid, options->clientid[0] + 1);
- } else if (state->options & DHCPCD_CLIENTID) {
- if (state->options & DHCPCD_DUID) {
- duid = xmalloc(DUID_LEN);
- if ((len = get_duid(duid, iface)) == 0)
- logger(LOG_ERR, "get_duid: %s",
- strerror(errno));
- }
-
- if (len > 0) {
- logger(LOG_DEBUG, "DUID = %s",
- hwaddr_ntoa(duid, len));
-
- iface->clientid = xmalloc(len + 6);
- iface->clientid[0] = len + 5;
- iface->clientid[1] = 255; /* RFC 4361 */
-
- /* IAID is 4 bytes, so if the iface name is 4 bytes
- * or less, use it */
- ul = strlen(iface->name);
- if (ul < 5) {
- memcpy(iface->clientid + 2, iface->name, ul);
- if (ul < 4)
- memset(iface->clientid + 2 + ul,
- 0, 4 - ul);
- } else {
- /* Name isn't 4 bytes, so use the index */
- ul = htonl(if_nametoindex(iface->name));
- memcpy(iface->clientid + 2, &ul, 4);
- }
-
- memcpy(iface->clientid + 6, duid, len);
- free(duid);
- }
- if (len == 0) {
- len = iface->hwlen + 1;
- iface->clientid = xmalloc(len + 1);
- iface->clientid[0] = len;
- iface->clientid[1] = iface->family;
- memcpy(iface->clientid + 2, iface->hwaddr, iface->hwlen);
- }
- }
-
- if (state->options & DHCPCD_LINK) {
- open_link_socket(iface);
- switch (carrier_status(iface->name)) {
- case 0:
- state->carrier = LINK_DOWN;
- break;
- case 1:
- state->carrier = LINK_UP;
- break;
- default:
- state->carrier = LINK_UNKNOWN;
- }
- }
-
- if (options->timeout > 0 &&
- !(state->options & DHCPCD_DAEMONISED))
- {
- if (state->options & DHCPCD_IPV4LL) {
- state->stop.tv_sec = options->timeout;
- if (!(state->options & DHCPCD_BACKGROUND))
- state->exit.tv_sec = state->stop.tv_sec + 10;
- } else if (!(state->options & DHCPCD_BACKGROUND))
- state->exit.tv_sec = options->timeout;
- }
- return 0;
-}
-
-static int
-do_socket(struct if_state *state, int mode)
-{
- if (state->interface->raw_fd != -1) {
- close(state->interface->raw_fd);
- state->interface->raw_fd = -1;
- }
- if (mode == SOCKET_CLOSED) {
- if (state->interface->udp_fd != -1) {
- close(state->interface->udp_fd);
- state->interface->udp_fd = -1;
- }
- if (state->interface->arp_fd != -1) {
- close(state->interface->arp_fd);
- state->interface->arp_fd = -1;
- }
- }
-
- /* Always have the UDP socket open to avoid the kernel sending
- * ICMP unreachable messages. */
- /* For systems without SO_BINDTODEVICE, (ie BSD ones) we may get an
- * error or EADDRINUSE when binding to INADDR_ANY as another dhcpcd
- * instance could be running.
- * Oddly enough, we don't care about this as the socket is there
- * just to please the kernel - we don't care for reading from it. */
- if (mode == SOCKET_OPEN &&
- state->interface->udp_fd == -1 &&
- open_udp_socket(state->interface) == -1 &&
- (errno != EADDRINUSE || state->interface->addr.s_addr != 0))
- logger(LOG_ERR, "open_udp_socket: %s", strerror(errno));
-
- if (mode == SOCKET_OPEN)
- if (open_socket(state->interface, ETHERTYPE_IP) == -1) {
- logger(LOG_ERR, "open_socket: %s", strerror(errno));
- return -1;
- }
- state->socket = mode;
- return 0;
-}
-
-static ssize_t
-send_message(struct if_state *state, int type, const struct options *options)
-{
- struct dhcp_message *dhcp;
- uint8_t *udp;
- ssize_t len, r;
- struct in_addr from, to;
- in_addr_t a = 0;
-
- if (state->carrier == LINK_DOWN)
- return 0;
- if (type == DHCP_RELEASE)
- logger(LOG_DEBUG, "sending %s with xid 0x%x",
- get_dhcp_op(type), state->xid);
- else
- logger(LOG_DEBUG,
- "sending %s with xid 0x%x, next in %0.2f seconds",
- get_dhcp_op(type), state->xid,
- timeval_to_double(&state->timeout));
- state->messages++;
- if (state->messages < 0)
- state->messages = INT_MAX;
- /* If we couldn't open a UDP port for our IP address
- * then we cannot renew.
- * This could happen if our IP was pulled out from underneath us. */
- if (state->interface->udp_fd == -1) {
- a = state->interface->addr.s_addr;
- state->interface->addr.s_addr = 0;
- }
- len = make_message(&dhcp, state->interface, &state->lease, state->xid,
- type, options);
- if (state->interface->udp_fd == -1)
- state->interface->addr.s_addr = a;
- from.s_addr = dhcp->ciaddr;
- if (from.s_addr)
- to.s_addr = state->lease.server.s_addr;
- else
- to.s_addr = 0;
- if (to.s_addr && to.s_addr != INADDR_BROADCAST) {
- r = send_packet(state->interface, to, (uint8_t *)dhcp, len);
- if (r == -1)
- logger(LOG_ERR, "send_packet: %s", strerror(errno));
- } else {
- len = make_udp_packet(&udp, (uint8_t *)dhcp, len, from, to);
- r = send_raw_packet(state->interface, ETHERTYPE_IP, udp, len);
- free(udp);
- if (r == -1)
- logger(LOG_ERR, "send_raw_packet: %s", strerror(errno));
- }
- free(dhcp);
- /* Failed to send the packet? Return to the init state */
- if (r == -1) {
- state->state = STATE_INIT;
- timerclear(&state->timeout);
- /* We need to set a timeout so we fall through gracefully */
- state->stop.tv_sec = 1;
- state->stop.tv_usec = 0;
- do_socket(state, SOCKET_CLOSED);
- }
- return r;
-}
-
-static void
-drop_config(struct if_state *state, const char *reason,
- const struct options *options)
-{
- if (state->new || strcmp(reason, "FAIL") == 0) {
- configure(state->interface, reason, NULL, state->new,
- &state->lease, options, 0);
- free(state->old);
- state->old = NULL;
- free(state->new);
- state->new = NULL;
- }
- state->lease.addr.s_addr = 0;
-}
-
-static void
-reduce_timers(struct if_state *state, const struct timeval *tv)
-{
- if (timerisset(&state->exit)) {
- timersub(&state->exit, tv, &state->exit);
- if (!timerisset(&state->exit))
- state->exit.tv_sec = -1;
- }
- if (timerisset(&state->stop)) {
- timersub(&state->stop, tv, &state->stop);
- if (!timerisset(&state->stop))
- state->stop.tv_sec = -1;
- }
- if (timerisset(&state->timeout)) {
- timersub(&state->timeout, tv, &state->timeout);
- if (!timerisset(&state->timeout))
- state->timeout.tv_sec = -1;
- }
-}
-
-static struct timeval *
-get_lowest_timer(struct if_state *state)
-{
- struct timeval *ref = NULL;
-
- if (timerisset(&state->exit))
- ref = &state->exit;
- if (timerisset(&state->stop)) {
- if (!ref || timercmp(&state->stop, ref, <))
- ref = &state->stop;
- }
- if (timerisset(&state->timeout)) {
- if (!ref || timercmp(&state->timeout, ref, <))
- ref = &state->timeout;
- }
- return ref;
-}
-
-static int
-wait_for_fd(struct if_state *state, int *fd)
-{
- struct pollfd fds[4]; /* signal, link, raw, arp */
- struct interface *iface = state->interface;
- int i, r, nfds = 0, msecs = -1;
- struct timeval start, stop, diff, *ref;
- static int lastinf = 0;
-
- /* Ensure that we haven't already timed out */
- ref = get_lowest_timer(state);
- if (ref && timerneg(ref))
- return 0;
-
- /* We always listen to signals */
- fds[nfds].fd = state->signal_fd;
- fds[nfds].events = POLLIN;
- nfds++;
- /* And links */
- if (iface->link_fd != -1) {
- fds[nfds].fd = iface->link_fd;
- fds[nfds].events = POLLIN;
- nfds++;
- }
-
- if (state->lease.leasetime == ~0U &&
- state->state == STATE_BOUND)
- {
- if (!lastinf) {
- logger(LOG_DEBUG, "waiting for infinity");
- lastinf = 1;
- }
- ref = NULL;
- } else if (state->carrier == LINK_DOWN && !ref) {
- if (!lastinf) {
- logger(LOG_DEBUG, "waiting for carrier");
- lastinf = 1;
- }
- if (timerisset(&state->exit))
- ref = &state->exit;
- else
- ref = NULL;
- } else {
- if (iface->raw_fd != -1) {
- fds[nfds].fd = iface->raw_fd;
- fds[nfds].events = POLLIN;
- nfds++;
- }
- if (iface->arp_fd != -1) {
- fds[nfds].fd = iface->arp_fd;
- fds[nfds].events = POLLIN;
- nfds++;
- }
- }
-
- /* Wait and then reduce the timers.
- * If we reduce a timer to zero, set it negative to indicate timeout.
- * We cannot reliably use select as there is no guarantee we will
- * actually wait the whole time if greater than 31 days according
- * to POSIX. So we loop on poll if needed as it's limitation of
- * INT_MAX milliseconds is known. */
- for (;;) {
- get_monotonic(&start);
- if (ref) {
- lastinf = 0;
- if (ref->tv_sec > INT_MAX / 1000 ||
- (ref->tv_sec == INT_MAX / 1000 &&
- (ref->tv_usec + 999) / 1000 > INT_MAX % 1000))
- msecs = INT_MAX;
- else
- msecs = ref->tv_sec * 1000 +
- (ref->tv_usec + 999) / 1000;
- } else
- msecs = -1;
- r = poll(fds, nfds, msecs);
- get_monotonic(&stop);
- timersub(&stop, &start, &diff);
- reduce_timers(state, &diff);
- if (r == -1) {
- if (errno != EINTR)
- logger(LOG_ERR, "poll: %s", strerror(errno));
- return -1;
- }
- if (r)
- break;
- /* We should not have an infinite timeout if we get here */
- if (timerneg(ref))
- return 0;
- }
-
- /* We configured our array in the order we should deal with them */
- for (i = 0; i < nfds; i++) {
- if (fds[i].revents & POLLERR) {
- syslog(LOG_ERR, "poll: POLLERR on fd %d", fds[i].fd);
- errno = EBADF;
- return -1;
- }
- if (fds[i].revents & POLLNVAL) {
- syslog(LOG_ERR, "poll: POLLNVAL on fd %d", fds[i].fd);
- errno = EINVAL;
- return -1;
- }
- if (fds[i].revents & (POLLIN | POLLHUP)) {
- *fd = fds[i].fd;
- return r;
- }
- }
- /* We should never get here. */
- return 0;
-}
-
-static int
-handle_signal(int sig, struct if_state *state, const struct options *options)
-{
- struct dhcp_lease *lease = &state->lease;
-
- switch (sig) {
- case SIGINT:
- logger(LOG_INFO, "received SIGINT, stopping");
- if (!(state->options & DHCPCD_PERSISTENT))
- drop_config(state, "STOP", options);
- return -1;
- case SIGTERM:
- logger(LOG_INFO, "received SIGTERM, stopping");
- if (!(state->options & DHCPCD_PERSISTENT))
- drop_config(state, "STOP", options);
- return -1;
- case SIGALRM:
- logger(LOG_INFO, "received SIGALRM, renewing lease");
- do_socket(state, SOCKET_CLOSED);
- state->state = STATE_RENEW_REQUESTED;
- timerclear(&state->timeout);
- timerclear(&state->stop);
- return 1;
- case SIGHUP:
- logger(LOG_INFO, "received SIGHUP, releasing lease");
- if (lease->addr.s_addr &&
- !IN_LINKLOCAL(ntohl(lease->addr.s_addr)))
- {
- do_socket(state, SOCKET_OPEN);
- state->xid = arc4random();
- send_message(state, DHCP_RELEASE, options);
- do_socket(state, SOCKET_CLOSED);
- }
- drop_config(state, "RELEASE", options);
- return -1;
- default:
- logger (LOG_ERR,
- "received signal %d, but don't know what to do with it",
- sig);
- }
-
- return 0;
-}
-
-static int bind_dhcp(struct if_state *state, const struct options *options)
-{
- struct interface *iface = state->interface;
- struct dhcp_lease *lease = &state->lease;
- const char *reason = NULL;
- struct timeval start, stop, diff;
- int retval;
-
- free(state->old);
- state->old = state->new;
- state->new = state->offer;
- state->offer = NULL;
- state->messages = 0;
- state->conflicts = 0;
- state->defend = 0;
- timerclear(&state->exit);
- if (clock_monotonic)
- get_monotonic(&lease->boundtime);
-
- if (options->options & DHCPCD_INFORM) {
- if (options->request_address.s_addr != 0)
- lease->addr.s_addr = options->request_address.s_addr;
- else
- lease->addr.s_addr = iface->addr.s_addr;
- logger(LOG_INFO, "received approval for %s",
- inet_ntoa(lease->addr));
- state->state = STATE_BOUND;
- state->lease.leasetime = ~0U;
- timerclear(&state->stop);
- reason = "INFORM";
- } else if (IN_LINKLOCAL(htonl(state->new->yiaddr))) {
- get_lease(lease, state->new);
- logger(LOG_INFO, "using IPv4LL address %s",
- inet_ntoa(lease->addr));
- state->state = STATE_INIT;
- timerclear(&state->timeout);
- reason = "IPV4LL";
- } else {
- if (gettimeofday(&start, NULL) == 0)
- lease->leasedfrom = start.tv_sec;
-
- get_lease(lease, state->new);
- if (lease->frominfo)
- reason = "TIMEOUT";
-
- if (lease->leasetime == ~0U) {
- lease->renewaltime = lease->rebindtime = lease->leasetime;
- logger(LOG_INFO, "leased %s for infinity",
- inet_ntoa(lease->addr));
- state->state = STATE_BOUND;
- timerclear(&state->stop);
- } else {
- if (lease->rebindtime == 0)
- lease->rebindtime = lease->leasetime * T2;
- else if (lease->rebindtime >= lease->leasetime) {
- lease->rebindtime = lease->leasetime * T2;
- logger(LOG_ERR,
- "rebind time greater than lease "
- "time, forcing to %u seconds",
- lease->rebindtime);
- }
- if (lease->renewaltime == 0)
- lease->renewaltime = lease->leasetime * T1;
- else if (lease->renewaltime > lease->rebindtime) {
- lease->renewaltime = lease->leasetime * T1;
- logger(LOG_ERR,
- "renewal time greater than rebind time, "
- "forcing to %u seconds",
- lease->renewaltime);
- }
- logger(LOG_INFO,
- "leased %s for %u seconds",
- inet_ntoa(lease->addr), lease->leasetime);
- state->stop.tv_sec = lease->renewaltime;
- state->stop.tv_usec = 0;
- }
- state->state = STATE_BOUND;
- }
-
- state->xid = 0;
- timerclear(&state->timeout);
- if (!reason) {
- if (state->old) {
- if (state->old->yiaddr == state->new->yiaddr &&
- lease->server.s_addr)
- reason = "RENEW";
- else
- reason = "REBIND";
- } else
- reason = "BOUND";
- }
- /* If we have a monotonic clock we can safely substract the
- * script execution time from our timers.
- * Otherwise we can't as the script may update the real time. */
- if (clock_monotonic)
- get_monotonic(&start);
- retval = configure(iface, reason, state->new, state->old,
- &state->lease, options, 1);
- if (clock_monotonic) {
- get_monotonic(&stop);
- timersub(&stop, &start, &diff);
- reduce_timers(state, &diff);
- }
- if (retval != 0)
- return -1;
- return daemonise(state, options);
-}
-
-static int
-handle_timeout_fail(struct if_state *state, const struct options *options)
-{
- struct dhcp_lease *lease = &state->lease;
- struct interface *iface = state->interface;
- int gotlease = -1, r;
- const char *reason = NULL;
-
- timerclear(&state->stop);
- timerclear(&state->exit);
- if (state->state != STATE_DISCOVERING)
- state->messages = 0;
-
- switch (state->state) {
- case STATE_INIT: /* FALLTHROUGH */
- case STATE_DISCOVERING: /* FALLTHROUGH */
- case STATE_REQUESTING:
- if (IN_LINKLOCAL(ntohl(iface->addr.s_addr))) {
- if (!(state->options & DHCPCD_DAEMONISED))
- logger(LOG_ERR, "timed out");
- } else {
- if (iface->addr.s_addr != 0 &&
- !(state->options & DHCPCD_INFORM))
- logger(LOG_ERR, "lost lease");
- else if (state->carrier != LINK_DOWN ||
- !(state->options & DHCPCD_DAEMONISED))
- logger(LOG_ERR, "timed out");
- }
- do_socket(state, SOCKET_CLOSED);
- if (state->options & DHCPCD_INFORM ||
- state->options & DHCPCD_TEST)
- return -1;
-
- if (state->carrier != LINK_DOWN &&
- (state->options & DHCPCD_IPV4LL ||
- state->options & DHCPCD_LASTLEASE))
- gotlease = get_old_lease(state);
-
- if (state->carrier != LINK_DOWN &&
- state->options & DHCPCD_IPV4LL &&
- gotlease != 0)
- {
- logger(LOG_INFO, "probing for an IPV4LL address");
- free(state->offer);
- lease->frominfo = 0;
- state->offer = ipv4ll_get_dhcp(0);
- gotlease = 0;
- }
-
- if (gotlease == 0 &&
- state->offer->yiaddr != iface->addr.s_addr &&
- state->options & DHCPCD_ARP)
- {
- state->state = STATE_PROBING;
- state->claims = 0;
- state->probes = 0;
- if (iface->addr.s_addr)
- state->conflicts = 0;
- return 1;
- }
-
- if (gotlease == 0) {
- r = bind_dhcp(state, options);
- logger(LOG_DEBUG, "renew in %ld seconds",
- (long int)state->stop.tv_sec);
- return r;
- }
- if (iface->addr.s_addr)
- reason = "EXPIRE";
- else
- reason = "FAIL";
- drop_config(state, reason, options);
- if (!(state->options & DHCPCD_DAEMONISED) &&
- (state->options & DHCPCD_DAEMONISE))
- return -1;
- state->state = STATE_RENEW_REQUESTED;
- return 1;
- case STATE_BOUND:
- logger(LOG_INFO, "renewing lease of %s",inet_ntoa(lease->addr));
- if (state->carrier != LINK_DOWN)
- do_socket(state, SOCKET_OPEN);
- state->xid = arc4random();
- state->state = STATE_RENEWING;
- state->stop.tv_sec = lease->rebindtime - lease->renewaltime;
- break;
- case STATE_RENEWING:
- logger(LOG_ERR, "failed to renew, attempting to rebind");
- state->state = STATE_REBINDING;
- if (lease->server.s_addr == 0)
- state->stop.tv_sec = options->timeout;
- else
- state->stop.tv_sec = lease->rebindtime - \
- lease->renewaltime;
- lease->server.s_addr = 0;
- break;
- case STATE_REBINDING:
- logger(LOG_ERR, "failed to rebind");
- reason = "EXPIRE";
- drop_config(state, reason, options);
- state->state = STATE_INIT;
- break;
- case STATE_PROBING: /* FALLTHROUGH */
- case STATE_ANNOUNCING:
- /* We should have lost carrier here and exit timer went */
- logger(LOG_ERR, "timed out");
- return -1;
- default:
- logger(LOG_DEBUG, "handle_timeout_failed: invalid state %d",
- state->state);
- }
-
- /* This effectively falls through into the handle_timeout funtion */
- return 1;
-}
-
-static int
-handle_timeout(struct if_state *state, const struct options *options)
-{
- struct dhcp_lease *lease = &state->lease;
- struct interface *iface = state->interface;
- int i = 0;
- struct in_addr addr;
- struct timeval tv;
-
- timerclear(&state->timeout);
- if (timerneg(&state->exit))
- return handle_timeout_fail(state, options);
-
- if (state->state == STATE_RENEW_REQUESTED &&
- IN_LINKLOCAL(ntohl(lease->addr.s_addr)))
- {
- state->state = STATE_PROBING;
- free(state->offer);
- state->offer = read_lease(state->interface);
- state->probes = 0;
- state->claims = 0;
- }
- switch (state->state) {
- case STATE_INIT_IPV4LL:
- state->state = STATE_PROBING;
- free(state->offer);
- state->offer = ipv4ll_get_dhcp(0);
- state->probes = 0;
- state->claims = 0;
- /* FALLTHROUGH */
- case STATE_PROBING:
- if (iface->arp_fd == -1)
- open_socket(iface, ETHERTYPE_ARP);
- if (state->probes < PROBE_NUM) {
- if (state->probes == 0) {
- addr.s_addr = state->offer->yiaddr;
- logger(LOG_INFO, "checking %s is available"
- " on attached networks",
- inet_ntoa(addr));
- }
- state->probes++;
- if (state->probes < PROBE_NUM) {
- state->timeout.tv_sec = PROBE_MIN;
- state->timeout.tv_usec = arc4random() %
- (PROBE_MAX_U - PROBE_MIN_U);
- timernorm(&state->timeout);
- } else {
- state->timeout.tv_sec = ANNOUNCE_WAIT;
- state->timeout.tv_usec = 0;
- }
- logger(LOG_DEBUG,
- "sending ARP probe (%d of %d), next in %0.2f seconds",
- state->probes, PROBE_NUM,
- timeval_to_double(&state->timeout));
- if (send_arp(iface, ARPOP_REQUEST, 0,
- state->offer->yiaddr) == -1)
- {
- logger(LOG_ERR, "send_arp: %s", strerror(errno));
- return -1;
- }
- return 0;
- } else {
- /* We've waited for ANNOUNCE_WAIT after the final probe
- * so the address is now ours */
- i = bind_dhcp(state, options);
- state->state = STATE_ANNOUNCING;
- state->timeout.tv_sec = ANNOUNCE_INTERVAL;
- state->timeout.tv_usec = 0;
- return i;
- }
- break;
- case STATE_ANNOUNCING:
- if (iface->arp_fd == -1)
- open_socket(iface, ETHERTYPE_ARP);
- if (state->claims < ANNOUNCE_NUM) {
- state->claims++;
- if (state->claims < ANNOUNCE_NUM) {
- state->timeout.tv_sec = ANNOUNCE_INTERVAL;
- state->timeout.tv_usec = 0;
- logger(LOG_DEBUG,
- "sending ARP announce (%d of %d),"
- " next in %0.2f seconds",
- state->claims, ANNOUNCE_NUM,
- timeval_to_double(&state->timeout));
- } else
- logger(LOG_DEBUG,
- "sending ARP announce (%d of %d)",
- state->claims, ANNOUNCE_NUM);
- i = send_arp(iface, ARPOP_REQUEST,
- state->new->yiaddr, state->new->yiaddr);
- if (i == -1) {
- logger(LOG_ERR, "send_arp: %s", strerror(errno));
- return -1;
- }
- }
- if (state->claims < ANNOUNCE_NUM)
- return 0;
- if (IN_LINKLOCAL(htonl(state->new->yiaddr))) {
- /* We should pretend to be at the end
- * of the DHCP negotation cycle */
- state->state = STATE_INIT;
- state->messages = DHCP_MAX / DHCP_BASE;
- state->probes = 0;
- state->claims = 0;
- timerclear(&state->stop);
- goto dhcp_timeout;
- } else {
- state->state = STATE_BOUND;
- close(iface->arp_fd);
- iface->arp_fd = -1;
- if (lease->leasetime != ~0U) {
- state->stop.tv_sec = lease->renewaltime;
- state->stop.tv_usec = 0;
- if (clock_monotonic) {
- get_monotonic(&tv);
- timersub(&tv, &lease->boundtime, &tv);
- timersub(&state->stop, &tv, &state->stop);
- } else {
- state->stop.tv_sec -=
- (ANNOUNCE_INTERVAL * ANNOUNCE_NUM);
- }
- logger(LOG_DEBUG, "renew in %ld seconds",
- (long int)state->stop.tv_sec);
- }
- }
- return 0;
- }
-
- if (timerneg(&state->stop))
- return handle_timeout_fail(state, options);
-
- switch (state->state) {
- case STATE_BOUND: /* FALLTHROUGH */
- case STATE_RENEW_REQUESTED:
- timerclear(&state->stop);
- /* FALLTHROUGH */
- case STATE_INIT:
- if (state->carrier == LINK_DOWN)
- return 0;
- do_socket(state, SOCKET_OPEN);
- state->xid = arc4random();
- iface->start_uptime = uptime();
- break;
- }
-
- switch(state->state) {
- case STATE_RENEW_REQUESTED:
- /* If a renew was requested (ie, didn't timeout) we actually
- * enter the REBIND state so that we broadcast to all servers.
- * We need to do this for when we change networks. */
- lease->server.s_addr = 0;
- state->messages = 0;
- if (lease->addr.s_addr && !(state->options & DHCPCD_INFORM)) {
- logger(LOG_INFO, "rebinding lease of %s",
- inet_ntoa(lease->addr));
- state->state = STATE_REBINDING;
- state->stop.tv_sec = options->timeout;
- state->stop.tv_usec = 0;
- break;
- }
- /* FALLTHROUGH */
- case STATE_INIT:
- if (lease->addr.s_addr == 0 ||
- IN_LINKLOCAL(ntohl(iface->addr.s_addr)))
- {
- logger(LOG_INFO, "broadcasting for a lease");
- state->state = STATE_DISCOVERING;
- } else if (state->options & DHCPCD_INFORM) {
- logger(LOG_INFO, "broadcasting inform for %s",
- inet_ntoa(lease->addr));
- state->state = STATE_REQUESTING;
- } else {
- logger(LOG_INFO, "broadcasting for a lease of %s",
- inet_ntoa(lease->addr));
- state->state = STATE_REQUESTING;
- }
- if (!lease->addr.s_addr && !timerisset(&state->stop)) {
- state->stop.tv_sec = DHCP_MAX + DHCP_RAND_MIN;
- state->stop.tv_usec = arc4random() % (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U);
- timernorm(&state->stop);
- }
- break;
- }
-
-dhcp_timeout:
- if (state->carrier == LINK_DOWN) {
- timerclear(&state->timeout);
- return 0;
- }
- state->timeout.tv_sec = DHCP_BASE;
- for (i = 0; i < state->messages; i++) {
- state->timeout.tv_sec *= 2;
- if (state->timeout.tv_sec > DHCP_MAX) {
- state->timeout.tv_sec = DHCP_MAX;
- break;
- }
- }
- state->timeout.tv_sec += DHCP_RAND_MIN;
- state->timeout.tv_usec = arc4random() %
- (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U);
- timernorm(&state->timeout);
-
- /* We send the message here so that the timeout is reported */
- switch (state->state) {
- case STATE_DISCOVERING:
- send_message(state, DHCP_DISCOVER, options);
- break;
- case STATE_REQUESTING:
- if (state->options & DHCPCD_INFORM) {
- send_message(state, DHCP_INFORM, options);
- break;
- }
- /* FALLTHROUGH */
- case STATE_RENEWING: /* FALLTHROUGH */
- case STATE_REBINDING:
- if (iface->raw_fd == -1)
- do_socket(state, SOCKET_OPEN);
- send_message(state, DHCP_REQUEST, options);
- break;
- }
-
- return 0;
-}
-
-static void
-log_dhcp(int lvl, const char *msg, const struct dhcp_message *dhcp)
-{
- char *a;
- struct in_addr addr;
- int r;
-
- if (strcmp(msg, "NAK:") == 0)
- a = get_option_string(dhcp, DHO_MESSAGE);
- else {
- addr.s_addr = dhcp->yiaddr;
- a = xstrdup(inet_ntoa(addr));
- }
- r = get_option_addr(&addr, dhcp, DHO_SERVERID);
- if (dhcp->servername[0] && r == 0)
- logger(lvl, "%s %s from %s `%s'", msg, a,
- inet_ntoa(addr), dhcp->servername);
- else if (r == 0)
- logger(lvl, "%s %s from %s", msg, a, inet_ntoa(addr));
- else
- logger(lvl, "%s %s", msg, a);
- free(a);
-}
-
-static int
-handle_dhcp(struct if_state *state, struct dhcp_message **dhcpp,
- const struct options *options)
-{
- struct dhcp_message *dhcp = *dhcpp;
- struct interface *iface = state->interface;
- struct dhcp_lease *lease = &state->lease;
- uint8_t type, tmp;
- struct in_addr addr;
- size_t i;
- int r;
-
- /* reset the message counter */
- state->messages = 0;
-
- /* We have to have DHCP type to work */
- if (get_option_uint8(&type, dhcp, DHO_MESSAGETYPE) == -1) {
- logger(LOG_ERR, "ignoring message; no DHCP type");
- return 0;
- }
- /* Every DHCP message should include ServerID */
- if (get_option_addr(&addr, dhcp, DHO_SERVERID) == -1) {
- logger(LOG_ERR, "ignoring message; no Server ID");
- return 0;
- }
-
- /* Ensure that it's not from a blacklisted server.
- * We should expand this to check IP and/or hardware address
- * at the packet level. */
- if (options->blacklist_len != 0 &&
- get_option_addr(&addr, dhcp, DHO_SERVERID) == 0)
- {
- for (i = 0; i < options->blacklist_len; i++) {
- if (options->blacklist[i] != addr.s_addr)
- continue;
- if (dhcp->servername[0])
- logger(LOG_WARNING,
- "ignoring blacklisted server %s `%s'",
- inet_ntoa(addr), dhcp->servername);
- else
- logger(LOG_WARNING,
- "ignoring blacklisted server %s",
- inet_ntoa(addr));
- return 0;
- }
- }
-
- /* We should restart on a NAK */
- if (type == DHCP_NAK) {
- log_dhcp(LOG_WARNING, "NAK:", dhcp);
- drop_config(state, "EXPIRE", options);
- do_socket(state, SOCKET_CLOSED);
- state->state = STATE_INIT;
- /* If we constantly get NAKS then we should slowly back off */
- if (state->nakoff == 0) {
- state->nakoff = 1;
- timerclear(&state->timeout);
- } else {
- state->timeout.tv_sec = state->nakoff;
- state->timeout.tv_usec = 0;
- state->nakoff *= 2;
- if (state->nakoff > NAKOFF_MAX)
- state->nakoff = NAKOFF_MAX;
- }
- return 0;
- }
-
- /* No NAK, so reset the backoff */
- state->nakoff = 1;
-
- /* Ensure that all required options are present */
- for (i = 1; i < 255; i++) {
- if (has_option_mask(options->requiremask, i) &&
- get_option_uint8(&tmp, dhcp, i) != 0)
- {
- log_dhcp(LOG_WARNING, "reject", dhcp);
- return 0;
- }
- }
-
- if (type == DHCP_OFFER && state->state == STATE_DISCOVERING) {
- lease->addr.s_addr = dhcp->yiaddr;
- get_option_addr(&lease->server, dhcp, DHO_SERVERID);
- log_dhcp(LOG_INFO, "offered", dhcp);
- if (state->options & DHCPCD_TEST) {
- run_script(options, iface->name, "TEST", dhcp, NULL);
- /* Fake the fact we forked so we return 0 to userland */
- state->options |= DHCPCD_FORKED;
- return -1;
- }
- free(state->offer);
- state->offer = dhcp;
- *dhcpp = NULL;
- timerclear(&state->timeout);
- state->state = STATE_REQUESTING;
- return 1;
- }
-
- if (type == DHCP_OFFER) {
- log_dhcp(LOG_INFO, "ignoring offer of", dhcp);
- return 0;
- }
-
- /* We should only be dealing with acks */
- if (type != DHCP_ACK) {
- log_dhcp(LOG_ERR, "not ACK or OFFER", dhcp);
- return 0;
- }
-
- switch (state->state) {
- case STATE_RENEW_REQUESTED:
- case STATE_REQUESTING:
- case STATE_RENEWING:
- case STATE_REBINDING:
- if (!(state->options & DHCPCD_INFORM)) {
- get_option_addr(&lease->server,
- dhcp, DHO_SERVERID);
- log_dhcp(LOG_INFO, "acknowledged", dhcp);
- }
- free(state->offer);
- state->offer = dhcp;
- *dhcpp = NULL;
- break;
- default:
- logger(LOG_ERR, "wrong state %d", state->state);
- }
-
- lease->frominfo = 0;
- do_socket(state, SOCKET_CLOSED);
- if (state->options & DHCPCD_ARP &&
- iface->addr.s_addr != state->offer->yiaddr)
- {
- /* If the interface already has the address configured
- * then we can't ARP for duplicate detection. */
- addr.s_addr = state->offer->yiaddr;
- if (!has_address(iface->name, &addr, NULL)) {
- state->state = STATE_PROBING;
- state->claims = 0;
- state->probes = 0;
- state->conflicts = 0;
- timerclear(&state->stop);
- return 1;
- }
- }
-
- r = bind_dhcp(state, options);
- if (!(state->options & DHCPCD_ARP)) {
- if (!(state->options & DHCPCD_INFORM))
- logger(LOG_DEBUG, "renew in %ld seconds",
- (long int)state->stop.tv_sec);
- return r;
- }
- state->state = STATE_ANNOUNCING;
- if (state->options & DHCPCD_FORKED)
- return r;
- return 1;
-}
-
-static int
-handle_dhcp_packet(struct if_state *state, const struct options *options)
-{
- uint8_t *packet;
- struct interface *iface = state->interface;
- struct dhcp_message *dhcp = NULL;
- const uint8_t *pp;
- ssize_t bytes;
- int retval = -1;
-
- /* We loop through until our buffer is empty.
- * The benefit is that if we get >1 DHCP packet in our buffer and
- * the first one fails for any reason, we can use the next. */
- packet = xmalloc(udp_dhcp_len);
- for(;;) {
- bytes = get_raw_packet(iface, ETHERTYPE_IP,
- packet, udp_dhcp_len);
- if (bytes == 0) {
- retval = 0;
- break;
- }
- if (bytes == -1)
- break;
- if (valid_udp_packet(packet, bytes) == -1)
- continue;
- bytes = get_udp_data(&pp, packet);
- if ((size_t)bytes > sizeof(*dhcp)) {
- logger(LOG_ERR, "packet greater than DHCP size");
- continue;
- }
- if (!dhcp)
- dhcp = xzalloc(sizeof(*dhcp));
- memcpy(dhcp, pp, bytes);
- if (dhcp->cookie != htonl(MAGIC_COOKIE)) {
- logger(LOG_DEBUG, "bogus cookie, ignoring");
- continue;
- }
- /* Ensure it's the right transaction */
- if (state->xid != dhcp->xid) {
- logger(LOG_DEBUG,
- "ignoring packet with xid 0x%x as"
- " it's not ours (0x%x)",
- dhcp->xid, state->xid);
- continue;
- }
- /* Ensure packet is for us */
- if (iface->hwlen <= sizeof(dhcp->chaddr) &&
- memcmp(dhcp->chaddr, iface->hwaddr, iface->hwlen))
- {
- logger(LOG_DEBUG, "xid 0x%x is not for our hwaddr %s",
- dhcp->xid,
- hwaddr_ntoa(dhcp->chaddr, sizeof(dhcp->chaddr)));
- continue;
- }
- retval = handle_dhcp(state, &dhcp, options);
- if (retval == 0 && state->options & DHCPCD_TEST)
- state->options |= DHCPCD_FORKED;
- break;
- }
-
- free(packet);
- free(dhcp);
- return retval;
-}
-
-static int
-handle_arp_packet(struct if_state *state)
-{
- struct arphdr reply;
- uint32_t reply_s;
- uint32_t reply_t;
- uint8_t arp_reply[sizeof(reply) + 2 * sizeof(reply_s) + 2 * HWADDR_LEN];
- uint8_t *hw_s, *hw_t;
- ssize_t bytes;
- struct interface *iface = state->interface;
-
- state->fail.s_addr = 0;
- for(;;) {
- bytes = get_raw_packet(iface, ETHERTYPE_ARP,
- arp_reply, sizeof(arp_reply));
- if (bytes == 0 || bytes == -1)
- return (int)bytes;
- /* We must have a full ARP header */
- if ((size_t)bytes < sizeof(reply))
- continue;
- memcpy(&reply, arp_reply, sizeof(reply));
- /* Protocol must be IP. */
- if (reply.ar_pro != htons(ETHERTYPE_IP))
- continue;
- if (reply.ar_pln != sizeof(reply_s))
- continue;
- /* Only these types are recognised */
- if (reply.ar_op != htons(ARPOP_REPLY) &&
- reply.ar_op != htons(ARPOP_REQUEST))
- continue;
-
- /* Get pointers to the hardware addreses */
- hw_s = arp_reply + sizeof(reply);
- hw_t = hw_s + reply.ar_hln + reply.ar_pln;
- /* Ensure we got all the data */
- if ((hw_t + reply.ar_hln + reply.ar_pln) - arp_reply > bytes)
- continue;
- /* Ignore messages from ourself */
- if (reply.ar_hln == iface->hwlen &&
- memcmp(hw_s, iface->hwaddr, iface->hwlen) == 0)
- continue;
- /* Copy out the IP addresses */
- memcpy(&reply_s, hw_s + reply.ar_hln, reply.ar_pln);
- memcpy(&reply_t, hw_t + reply.ar_hln, reply.ar_pln);
-
- /* Check for conflict */
- if (state->offer &&
- (reply_s == state->offer->yiaddr ||
- (reply_s == 0 && reply_t == state->offer->yiaddr)))
- state->fail.s_addr = state->offer->yiaddr;
-
- /* Handle IPv4LL conflicts */
- if (IN_LINKLOCAL(htonl(iface->addr.s_addr)) &&
- (reply_s == iface->addr.s_addr ||
- (reply_s == 0 && reply_t == iface->addr.s_addr)))
- state->fail.s_addr = iface->addr.s_addr;
-
- if (state->fail.s_addr) {
- logger(LOG_ERR, "hardware address %s claims %s",
- hwaddr_ntoa((unsigned char *)hw_s,
- (size_t)reply.ar_hln),
- inet_ntoa(state->fail));
- errno = EEXIST;
- return -1;
- }
- }
-}
-
-static int
-handle_arp_fail(struct if_state *state, const struct options *options)
-{
- time_t up;
- int cookie = state->offer->cookie;
-
- if (!IN_LINKLOCAL(htonl(state->fail.s_addr))) {
- if (cookie) {
- state->timeout.tv_sec = DHCP_ARP_FAIL;
- state->timeout.tv_usec = 0;
- do_socket(state, SOCKET_OPEN);
- send_message(state, DHCP_DECLINE, options);
- do_socket(state, SOCKET_CLOSED);
- }
- state->state = STATE_INIT;
- free(state->offer);
- state->offer = NULL;
- state->lease.addr.s_addr = 0;
- if (!cookie)
- return 1;
- return 0;
- }
-
- if (state->fail.s_addr == state->interface->addr.s_addr) {
- if (state->state == STATE_PROBING)
- /* This should only happen when SIGALRM or
- * link when down/up and we have a conflict. */
- drop_config(state, "EXPIRE", options);
- else {
- up = uptime();
- if (state->defend + DEFEND_INTERVAL > up) {
- drop_config(state, "EXPIRE", options);
- state->conflicts = -1;
- /* drop through to set conflicts to 0 */
- } else {
- state->defend = up;
- return 0;
- }
- }
- }
- do_socket(state, SOCKET_CLOSED);
- state->conflicts++;
- timerclear(&state->stop);
- if (state->conflicts > MAX_CONFLICTS) {
- logger(LOG_ERR, "failed to obtain an IPv4LL address");
- state->state = STATE_INIT;
- timerclear(&state->timeout);
- if (!(state->options & DHCPCD_DAEMONISED) &&
- (state->options & DHCPCD_DAEMONISE))
- return -1;
- return 1;
- }
- state->state = STATE_INIT_IPV4LL;
- state->timeout.tv_sec = PROBE_WAIT;
- state->timeout.tv_usec = 0;
- return 0;
-}
-
-static int
-handle_link(struct if_state *state)
-{
- int retval;
-
- retval = link_changed(state->interface);
- if (retval == -1) {
- logger(LOG_ERR, "link_changed: %s", strerror(errno));
- return -1;
- }
- if (retval == 0)
- return 0;
-
- switch (carrier_status(state->interface->name)) {
- case -1:
- logger(LOG_ERR, "carrier_status: %s", strerror(errno));
- return -1;
- case 0:
- if (state->carrier != LINK_DOWN) {
- logger(LOG_INFO, "carrier lost");
- state->carrier = LINK_DOWN;
- do_socket(state, SOCKET_CLOSED);
- timerclear(&state->timeout);
- if (state->state != STATE_BOUND)
- timerclear(&state->stop);
- }
- break;
- default:
- if (state->carrier != LINK_UP) {
- logger(LOG_INFO, "carrier acquired");
- state->state = STATE_RENEW_REQUESTED;
- state->carrier = LINK_UP;
- timerclear(&state->timeout);
- timerclear(&state->stop);
- return 1;
- }
- break;
- }
- return 0;
-}
-
-int
-dhcp_run(const struct options *options, int *pid_fd)
-{
- struct interface *iface;
- struct if_state *state = NULL;
- int fd = -1, r = 0, sig;
-
- iface = read_interface(options->interface, options->metric);
- if (!iface) {
- logger(LOG_ERR, "read_interface: %s", strerror(errno));
- goto eexit;
- }
- logger(LOG_DEBUG, "hardware address = %s",
- hwaddr_ntoa(iface->hwaddr, iface->hwlen));
- state = xzalloc(sizeof(*state));
- state->pid_fd = pid_fd;
- state->interface = iface;
- if (!(options->options & DHCPCD_TEST))
- run_script(options, iface->name, "PREINIT", NULL, NULL);
-
- if (client_setup(state, options) == -1)
- goto eexit;
- if (signal_init() == -1)
- goto eexit;
- if (signal_setup() == -1)
- goto eexit;
- state->signal_fd = signal_fd();
-
- if (state->options & DHCPCD_BACKGROUND &&
- !(state->options & DHCPCD_DAEMONISED))
- if (daemonise(state, options) == -1)
- goto eexit;
-
- if (state->carrier == LINK_DOWN)
- logger(LOG_INFO, "waiting for carrier");
-
- for (;;) {
- if (r == 0)
- r = handle_timeout(state, options);
- else if (r > 0) {
- if (fd == state->signal_fd) {
- if ((sig = signal_read()) != -1)
- r = handle_signal(sig, state, options);
- } else if (fd == iface->link_fd)
- r = handle_link(state);
- else if (fd == iface->raw_fd)
- r = handle_dhcp_packet(state, options);
- else if (fd == iface->arp_fd) {
- if ((r = handle_arp_packet(state)) == -1)
- r = handle_arp_fail(state, options);
- } else
- r = 0;
- }
- if (r == -1)
- break;
- if (r == 0) {
- fd = -1;
- r = wait_for_fd(state, &fd);
- if (r == -1 && errno == EINTR) {
- r = 1;
- fd = state->signal_fd;
- }
- } else
- r = 0;
- }
-
-eexit:
- if (iface) {
- do_socket(state, SOCKET_CLOSED);
- if (iface->link_fd != -1)
- close(iface->link_fd);
- free_routes(iface->routes);
- free(iface->clientid);
- free(iface->buffer);
- free(iface);
- }
-
- if (state) {
- if (state->options & DHCPCD_FORKED)
- r = 0;
- if (state->options & DHCPCD_DAEMONISED)
- unlink(options->pidfile);
- free(state->offer);
- free(state->new);
- free(state->old);
- free(state);
- }
-
- return r;
-}