diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:29:22 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:29:22 -0800 |
commit | f7c5421560640d23fc10803b9d59a9ff1d83e467 (patch) | |
tree | d6ae69d0d3f4a4d760a3254ec326bca4a8afacfe /client.c | |
parent | d97c47cad830d00c9da685cc4ea157d6185f6c97 (diff) | |
download | external_dhcpcd-f7c5421560640d23fc10803b9d59a9ff1d83e467.zip external_dhcpcd-f7c5421560640d23fc10803b9d59a9ff1d83e467.tar.gz external_dhcpcd-f7c5421560640d23fc10803b9d59a9ff1d83e467.tar.bz2 |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'client.c')
-rw-r--r-- | client.c | 1839 |
1 files changed, 1839 insertions, 0 deletions
diff --git a/client.c b/client.c new file mode 100644 index 0000000..c34d318 --- /dev/null +++ b/client.c @@ -0,0 +1,1839 @@ +/* + * 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]); + write(sidpipe[1], &buf, 1); + 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]); + read(sidpipe[0], &buf, 1); + 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; + + lease->frominfo = 0; + lease->addr.s_addr = dhcp->yiaddr; + + if (get_option_addr(&lease->net.s_addr, 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; + } + 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; + } + } + + if (lease->leasedfrom == 0) + offset = 0; + iface->start_uptime = uptime(); + state->timeout.tv_sec = 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 (options->options & DHCPCD_REQUEST && + state->options & DHCPCD_ARP && + !state->offer) + { + state->offer = xzalloc(sizeof(*state->offer)); + state->offer->yiaddr = options->request_address.s_addr; + state->state = STATE_PROBING; + state->xid = arc4random(); + } + + /* 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 (*options->clientid) { + iface->clientid = xmalloc(options->clientid[0] + 1); + memcpy(iface->clientid, + options->clientid, options->clientid[0] + 1); + } else if (options->options & DHCPCD_CLIENTID) { + if (options->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); + timerclear(&state->stop); + 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 & POLLIN) { + *fd = fds[i].fd; + return r; + } + return r; +} + +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 >= 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 > lease->rebindtime) { + lease->renewaltime = lease->leasetime * T1; + logger(LOG_ERR, + "renewal time greater than rebind time, " + "forcing to %u seconds", + lease->renewaltime); + } + if (!lease->renewaltime) + lease->renewaltime = lease->leasetime * T1; + if (!lease->rebindtime) + lease->rebindtime = lease->leasetime * T2; + 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; + 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); + state->offer = ipv4ll_get_dhcp(0); + gotlease = 0; + } + + if (gotlease == 0 && + state->offer->yiaddr != iface->addr.s_addr) + { + state->state = STATE_PROBING; + state->claims = 0; + state->probes = 0; + if (iface->addr.s_addr) + state->conflicts = 0; + return 1; + } + + if (gotlease == 0) + return bind_dhcp(state, options); + + 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 */ + if (IN_LINKLOCAL(htonl(state->offer->yiaddr))) { + i = bind_dhcp(state, options); + state->state = STATE_ANNOUNCING; + state->timeout.tv_sec = ANNOUNCE_INTERVAL; + state->timeout.tv_usec = 0; + return i; + } + state->state = STATE_REQUESTING; + } + 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: + 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 (state->carrier == LINK_DOWN) + return 0; + 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.s_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) { + log_dhcp(LOG_ERR, "no DHCP type in", dhcp); + 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.s_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.s_addr, 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); + 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; + } + } + 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.s_addr, + 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); + } + + do_socket(state, SOCKET_CLOSED); + 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; + uint8_t *p; + 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) == -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 = xmalloc(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; + } + /* We should ensure that the packet is terminated correctly + * if we have space for the terminator */ + if ((size_t)bytes < sizeof(struct dhcp_message)) { + p = (uint8_t *)dhcp + bytes - 1; + while (p > dhcp->options && *p == DHO_PAD) + p--; + if (*p != DHO_END) + *++p = DHO_END; + } + 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))) { + state->state = STATE_INIT; + free(state->offer); + state->offer = NULL; + state->lease.addr.s_addr = 0; + if (!cookie) + return 1; + 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); + 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; + + timerclear(&state->timeout); + 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); + 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->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; +} |