diff options
Diffstat (limited to 'client.c')
-rw-r--r-- | client.c | 1854 |
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; -} |