/* * 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; }