From f2ed769880271654297a4be420f26ab94d39666b Mon Sep 17 00:00:00 2001 From: Sebastian Huber Date: Thu, 30 Jan 2014 13:29:46 +0100 Subject: DHCPCD(8): Import Import DHCPCD(8) from: http://roy.marples.name/projects/dhcpcd/ The upstream sources can be obtained via: fossil clone http://roy.marples.name/projects/dhcpcd The imported version is 2014-01-29 19:46:44 [6b209507bb]. --- dhcpcd/dhcp.c | 2797 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2797 insertions(+) create mode 100644 dhcpcd/dhcp.c (limited to 'dhcpcd/dhcp.c') diff --git a/dhcpcd/dhcp.c b/dhcpcd/dhcp.c new file mode 100644 index 00000000..1641f44c --- /dev/null +++ b/dhcpcd/dhcp.c @@ -0,0 +1,2797 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2014 Roy Marples + * 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 +#include +#include + +#ifdef __linux__ +# include /* for systems with broken headers */ +# include +#endif + +#include +#include + +#include +#include +#include +#define __FAVOR_BSD /* Nasty glibc hack so we can use BSD semantics for UDP */ +#include +#undef __FAVOR_BSD + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "arp.h" +#include "common.h" +#include "dhcp.h" +#include "dhcpcd.h" +#include "dhcp-common.h" +#include "duid.h" +#include "eloop.h" +#include "ipv4.h" +#include "ipv4ll.h" +#include "script.h" + +#define DAD "Duplicate address detected" +#define DHCP_MIN_LEASE 20 + +static uint8_t *packet; + +/* Our aggregate option buffer. + * We ONLY use this when options are split, which for most purposes is + * practically never. See RFC3396 for details. */ +static uint8_t *opt_buffer; + +#define IPV4A ADDRIPV4 | ARRAY +#define IPV4R ADDRIPV4 | REQUEST + +/* We should define a maximum for the NAK exponential backoff */ +#define NAKOFF_MAX 60 + +/* Wait N nanoseconds between sending a RELEASE and dropping the address. + * This gives the kernel enough time to actually send it. */ +#define RELEASE_DELAY_S 0 +#define RELEASE_DELAY_NS 10000000 + +struct dhcp_op { + uint8_t value; + const char *name; +}; + +static const struct dhcp_op dhcp_ops[] = { + { DHCP_DISCOVER, "DISCOVER" }, + { DHCP_OFFER, "OFFER" }, + { DHCP_REQUEST, "REQUEST" }, + { DHCP_DECLINE, "DECLINE" }, + { DHCP_ACK, "ACK" }, + { DHCP_NAK, "NAK" }, + { DHCP_RELEASE, "RELEASE" }, + { DHCP_INFORM, "INFORM" }, + { 0, NULL } +}; + +static const char * const dhcp_params[] = { + "ip_address", + "subnet_cidr", + "network_number", + "filename", + "server_name", + NULL +}; + +struct udp_dhcp_packet +{ + struct ip ip; + struct udphdr udp; + struct dhcp_message dhcp; +}; + +struct dhcp_opt *dhcp_opts = NULL; +size_t dhcp_opts_len = 0; + +static const size_t udp_dhcp_len = sizeof(struct udp_dhcp_packet); + +static int dhcp_open(struct interface *); + +void +dhcp_printoptions(void) +{ + const char * const *p; + size_t i; + const struct dhcp_opt *opt; + + for (p = dhcp_params; *p; p++) + printf(" %s\n", *p); + + for (i = 0, opt = dhcp_opts; i < dhcp_opts_len; i++, opt++) + printf("%03d %s\n", opt->option, opt->var); +} + +#ifdef DEBUG_MEMORY +static void +dhcp_cleanup(void) +{ + + free(packet); + free(opt_buffer); +} +#endif + +#define get_option_raw(dhcp, opt) get_option(dhcp, opt, NULL) +static const uint8_t * +get_option(const struct dhcp_message *dhcp, uint8_t opt, int *len) +{ + const uint8_t *p = dhcp->options; + const uint8_t *e = p + sizeof(dhcp->options); + uint8_t l, ol = 0; + uint8_t o = 0; + uint8_t overl = 0; + uint8_t *bp = NULL; + const uint8_t *op = NULL; + int bl = 0; + + while (p < e) { + o = *p++; + if (o == opt) { + if (op) { + if (!opt_buffer) { + opt_buffer = malloc(sizeof(*dhcp)); + if (opt_buffer == NULL) + return NULL; + } + if (!bp) + bp = opt_buffer; + memcpy(bp, op, ol); + bp += ol; + } + ol = *p; + if (p + ol > e) { + errno = EINVAL; + return NULL; + } + op = p + 1; + bl += ol; + } + switch (o) { + case DHO_PAD: + continue; + case DHO_END: + if (overl & 1) { + /* bit 1 set means parse boot file */ + overl &= ~1; + p = dhcp->bootfile; + e = p + sizeof(dhcp->bootfile); + } else if (overl & 2) { + /* bit 2 set means parse server name */ + overl &= ~2; + p = dhcp->servername; + e = p + sizeof(dhcp->servername); + } else + goto exit; + break; + case DHO_OPTIONSOVERLOADED: + /* Ensure we only get this option once */ + if (!overl) + overl = p[1]; + break; + } + l = *p++; + p += l; + } + +exit: + if (len) + *len = bl; + if (bp) { + memcpy(bp, op, ol); + return (const uint8_t *)opt_buffer; + } + if (op) + return op; + errno = ENOENT; + return NULL; +} + +int +get_option_addr(struct in_addr *a, const struct dhcp_message *dhcp, + uint8_t option) +{ + const uint8_t *p; + int len; + + p = get_option(dhcp, option, &len); + if (!p || len < (ssize_t)sizeof(a->s_addr)) + return -1; + memcpy(&a->s_addr, p, sizeof(a->s_addr)); + return 0; +} + +static int +get_option_uint32(uint32_t *i, const struct dhcp_message *dhcp, uint8_t option) +{ + const uint8_t *p; + int len; + uint32_t d; + + p = get_option(dhcp, option, &len); + if (!p || len < (ssize_t)sizeof(d)) + return -1; + memcpy(&d, p, sizeof(d)); + if (i) + *i = ntohl(d); + return 0; +} + +static int +get_option_uint8(uint8_t *i, const struct dhcp_message *dhcp, uint8_t option) +{ + const uint8_t *p; + int len; + + p = get_option(dhcp, option, &len); + if (!p || len < (ssize_t)sizeof(*p)) + return -1; + if (i) + *i = *(p); + return 0; +} + +ssize_t +decode_rfc3442(char *out, ssize_t len, int pl, const uint8_t *p) +{ + const uint8_t *e; + ssize_t b, bytes = 0, ocets; + uint8_t cidr; + struct in_addr addr; + char *o = out; + + /* Minimum is 5 -first is CIDR and a router length of 4 */ + if (pl < 5) { + errno = EINVAL; + return -1; + } + + e = p + pl; + while (p < e) { + cidr = *p++; + if (cidr > 32) { + errno = EINVAL; + return -1; + } + ocets = (cidr + 7) / 8; + if (!out) { + p += 4 + ocets; + bytes += ((4 * 4) * 2) + 4; + continue; + } + if ((((4 * 4) * 2) + 4) > len) { + errno = ENOBUFS; + return -1; + } + if (o != out) { + *o++ = ' '; + len--; + } + /* If we have ocets then we have a destination and netmask */ + if (ocets > 0) { + addr.s_addr = 0; + memcpy(&addr.s_addr, p, ocets); + b = snprintf(o, len, "%s/%d", inet_ntoa(addr), cidr); + p += ocets; + } else + b = snprintf(o, len, "0.0.0.0/0"); + o += b; + len -= b; + + /* Finally, snag the router */ + memcpy(&addr.s_addr, p, 4); + p += 4; + b = snprintf(o, len, " %s", inet_ntoa(addr)); + o += b; + len -= b; + } + + if (out) + return o - out; + return bytes; +} + +static struct rt_head * +decode_rfc3442_rt(int dl, const uint8_t *data) +{ + const uint8_t *p = data; + const uint8_t *e; + uint8_t cidr; + size_t ocets; + struct rt_head *routes; + struct rt *rt = NULL; + + /* Minimum is 5 -first is CIDR and a router length of 4 */ + if (dl < 5) + return NULL; + + routes = malloc(sizeof(*routes)); + TAILQ_INIT(routes); + e = p + dl; + while (p < e) { + cidr = *p++; + if (cidr > 32) { + ipv4_freeroutes(routes); + errno = EINVAL; + return NULL; + } + + rt = calloc(1, sizeof(*rt)); + if (rt == NULL) { + syslog(LOG_ERR, "%s: %m", __func__); + ipv4_freeroutes(routes); + return NULL; + } + TAILQ_INSERT_TAIL(routes, rt, next); + + ocets = (cidr + 7) / 8; + /* If we have ocets then we have a destination and netmask */ + if (ocets > 0) { + memcpy(&rt->dest.s_addr, p, ocets); + p += ocets; + rt->net.s_addr = htonl(~0U << (32 - cidr)); + } + + /* Finally, snag the router */ + memcpy(&rt->gate.s_addr, p, 4); + p += 4; + } + return routes; +} + +char * +decode_rfc3361(int dl, const uint8_t *data) +{ + uint8_t enc; + unsigned int l; + char *sip = NULL; + struct in_addr addr; + char *p; + + if (dl < 2) { + errno = EINVAL; + return 0; + } + + enc = *data++; + dl--; + switch (enc) { + case 0: + if ((l = decode_rfc3397(NULL, 0, dl, data)) > 0) { + sip = malloc(l); + if (sip == NULL) + return 0; + decode_rfc3397(sip, l, dl, data); + } + break; + case 1: + if (dl == 0 || dl % 4 != 0) { + errno = EINVAL; + break; + } + addr.s_addr = INADDR_BROADCAST; + l = ((dl / sizeof(addr.s_addr)) * ((4 * 4) + 1)) + 1; + sip = p = malloc(l); + if (sip == NULL) + return 0; + while (dl != 0) { + memcpy(&addr.s_addr, data, sizeof(addr.s_addr)); + data += sizeof(addr.s_addr); + p += snprintf(p, l - (p - sip), "%s ", inet_ntoa(addr)); + dl -= sizeof(addr.s_addr); + } + *--p = '\0'; + break; + default: + errno = EINVAL; + return 0; + } + + return sip; +} + +/* Decode an RFC5969 6rd order option into a space + * separated string. Returns length of string (including + * terminating zero) or zero on error. */ +ssize_t +decode_rfc5969(char *out, ssize_t len, int pl, const uint8_t *p) +{ + uint8_t ipv4masklen, ipv6prefixlen; + uint8_t ipv6prefix[16]; + uint8_t br[4]; + int i; + ssize_t b, bytes = 0; + + if (pl < 22) { + errno = EINVAL; + return 0; + } + + ipv4masklen = *p++; + pl--; + ipv6prefixlen = *p++; + pl--; + + for (i = 0; i < 16; i++) { + ipv6prefix[i] = *p++; + pl--; + } + if (out) { + b= snprintf(out, len, + "%d %d " + "%02x%02x:%02x%02x:" + "%02x%02x:%02x%02x:" + "%02x%02x:%02x%02x:" + "%02x%02x:%02x%02x", + ipv4masklen, ipv6prefixlen, + ipv6prefix[0], ipv6prefix[1], ipv6prefix[2], ipv6prefix[3], + ipv6prefix[4], ipv6prefix[5], ipv6prefix[6], ipv6prefix[7], + ipv6prefix[8], ipv6prefix[9], ipv6prefix[10],ipv6prefix[11], + ipv6prefix[12],ipv6prefix[13],ipv6prefix[14], ipv6prefix[15] + ); + + len -= b; + out += b; + bytes += b; + } else { + bytes += 16 * 2 + 8 + 2 + 1 + 2; + } + + while (pl >= 4) { + br[0] = *p++; + br[1] = *p++; + br[2] = *p++; + br[3] = *p++; + pl -= 4; + + if (out) { + b= snprintf(out, len, " %d.%d.%d.%d", + br[0], br[1], br[2], br[3]); + len -= b; + out += b; + bytes += b; + } else { + bytes += (4 * 4); + } + } + + return bytes; +} + +char * +get_option_string(const struct dhcp_message *dhcp, uint8_t option) +{ + int len; + const uint8_t *p; + char *s; + + p = get_option(dhcp, option, &len); + if (!p || len == 0 || *p == '\0') + return NULL; + + s = malloc(sizeof(char) * (len + 1)); + if (s) { + memcpy(s, p, len); + s[len] = '\0'; + } + return s; +} + +/* This calculates the netmask that we should use for static routes. + * This IS different from the calculation used to calculate the netmask + * for an interface address. */ +static uint32_t +route_netmask(uint32_t ip_in) +{ + /* used to be unsigned long - check if error */ + uint32_t p = ntohl(ip_in); + uint32_t t; + + if (IN_CLASSA(p)) + t = ~IN_CLASSA_NET; + else { + if (IN_CLASSB(p)) + t = ~IN_CLASSB_NET; + else { + if (IN_CLASSC(p)) + t = ~IN_CLASSC_NET; + else + t = 0; + } + } + + while (t & p) + t >>= 1; + + return (htonl(~t)); +} + +/* We need to obey routing options. + * If we have a CSR then we only use that. + * Otherwise we add static routes and then routers. */ +struct rt_head * +get_option_routes(struct interface *ifp, const struct dhcp_message *dhcp) +{ + struct if_options *ifo = ifp->options; + const uint8_t *p; + const uint8_t *e; + struct rt_head *routes = NULL; + struct rt *route = NULL; + int len; + const char *csr = ""; + + /* If we have CSR's then we MUST use these only */ + if (!has_option_mask(ifo->nomask, DHO_CSR)) + p = get_option(dhcp, DHO_CSR, &len); + else + p = NULL; + /* Check for crappy MS option */ + if (!p && !has_option_mask(ifo->nomask, DHO_MSCSR)) { + p = get_option(dhcp, DHO_MSCSR, &len); + if (p) + csr = "MS "; + } + if (p) { + routes = decode_rfc3442_rt(len, p); + if (routes) { + if (!(ifo->options & DHCPCD_CSR_WARNED)) { + syslog(LOG_DEBUG, + "%s: using %sClassless Static Routes", + ifp->name, csr); + ifo->options |= DHCPCD_CSR_WARNED; + } + return routes; + } + } + + /* OK, get our static routes first. */ + routes = malloc(sizeof(*routes)); + if (routes == NULL) { + syslog(LOG_ERR, "%s: %m", __func__); + return NULL; + } + TAILQ_INIT(routes); + if (!has_option_mask(ifo->nomask, DHO_STATICROUTE)) + p = get_option(dhcp, DHO_STATICROUTE, &len); + else + p = NULL; + if (p) { + e = p + len; + while (p < e) { + route = calloc(1, sizeof(*route)); + if (route == NULL) { + syslog(LOG_ERR, "%s: %m", __func__); + ipv4_freeroutes(routes); + return NULL; + } + memcpy(&route->dest.s_addr, p, 4); + p += 4; + memcpy(&route->gate.s_addr, p, 4); + p += 4; + route->net.s_addr = route_netmask(route->dest.s_addr); + TAILQ_INSERT_TAIL(routes, route, next); + } + } + + /* Now grab our routers */ + if (!has_option_mask(ifo->nomask, DHO_ROUTER)) + p = get_option(dhcp, DHO_ROUTER, &len); + else + p = NULL; + if (p) { + e = p + len; + while (p < e) { + route = calloc(1, sizeof(*route)); + if (route == NULL) { + syslog(LOG_ERR, "%s: %m", __func__); + ipv4_freeroutes(routes); + return NULL; + } + memcpy(&route->gate.s_addr, p, 4); + p += 4; + TAILQ_INSERT_TAIL(routes, route, next); + } + } + + return routes; +} + +#define PUTADDR(_type, _val) \ + { \ + *p++ = _type; \ + *p++ = 4; \ + memcpy(p, &_val.s_addr, 4); \ + p += 4; \ + } + +int +dhcp_message_add_addr(struct dhcp_message *dhcp, + uint8_t type, struct in_addr addr) +{ + uint8_t *p; + size_t len; + + p = dhcp->options; + while (*p != DHO_END) { + p++; + p += *p + 1; + } + + len = p - (uint8_t *)dhcp; + if (len + 6 > sizeof(*dhcp)) { + errno = ENOMEM; + return -1; + } + + PUTADDR(type, addr); + *p = DHO_END; + return 0; +} + +ssize_t +make_message(struct dhcp_message **message, + const struct interface *iface, + uint8_t type) +{ + struct dhcp_message *dhcp; + uint8_t *m, *lp, *p, *auth; + uint8_t *n_params = NULL; + uint32_t ul; + uint16_t sz; + size_t len, i; + int auth_len; + const struct dhcp_opt *opt; + const struct if_options *ifo = iface->options; + const struct dhcp_state *state = D_CSTATE(iface); + const struct dhcp_lease *lease = &state->lease; + time_t up = uptime() - state->start_uptime; + const char *hostname; + const struct vivco *vivco; + + dhcp = calloc(1, sizeof (*dhcp)); + if (dhcp == NULL) + return -1; + m = (uint8_t *)dhcp; + p = dhcp->options; + + if ((type == DHCP_INFORM || type == DHCP_RELEASE || + (type == DHCP_REQUEST && + state->net.s_addr == lease->net.s_addr && + (state->new == NULL || + state->new->cookie == htonl(MAGIC_COOKIE))))) + { + dhcp->ciaddr = state->addr.s_addr; + /* In-case we haven't actually configured the address yet */ + if (type == DHCP_INFORM && state->addr.s_addr == 0) + dhcp->ciaddr = lease->addr.s_addr; + } + + dhcp->op = DHCP_BOOTREQUEST; + dhcp->hwtype = iface->family; + switch (iface->family) { + case ARPHRD_ETHER: + case ARPHRD_IEEE802: + dhcp->hwlen = iface->hwlen; + memcpy(&dhcp->chaddr, &iface->hwaddr, iface->hwlen); + break; + } + + if (ifo->options & DHCPCD_BROADCAST && + dhcp->ciaddr == 0 && + type != DHCP_DECLINE && + type != DHCP_RELEASE) + dhcp->flags = htons(BROADCAST_FLAG); + + if (type != DHCP_DECLINE && type != DHCP_RELEASE) { + if (up < 0 || up > (time_t)UINT16_MAX) + dhcp->secs = htons((uint16_t)UINT16_MAX); + else + dhcp->secs = htons(up); + } + dhcp->xid = htonl(state->xid); + dhcp->cookie = htonl(MAGIC_COOKIE); + + *p++ = DHO_MESSAGETYPE; + *p++ = 1; + *p++ = type; + + if (state->clientid) { + *p++ = DHO_CLIENTID; + memcpy(p, state->clientid, state->clientid[0] + 1); + p += state->clientid[0] + 1; + } + + if (lease->addr.s_addr && lease->cookie == htonl(MAGIC_COOKIE)) { + if (type == DHCP_DECLINE || + (type == DHCP_REQUEST && + lease->addr.s_addr != state->addr.s_addr)) + { + PUTADDR(DHO_IPADDRESS, lease->addr); + if (lease->server.s_addr) + PUTADDR(DHO_SERVERID, lease->server); + } + + if (type == DHCP_RELEASE) { + if (lease->server.s_addr) + PUTADDR(DHO_SERVERID, lease->server); + } + } + + if (type == DHCP_DECLINE) { + *p++ = DHO_MESSAGE; + len = strlen(DAD); + *p++ = len; + memcpy(p, DAD, len); + p += len; + } + + if (type == DHCP_DISCOVER && + !(options & DHCPCD_TEST) && + has_option_mask(ifo->requestmask, DHO_RAPIDCOMMIT)) + { + /* RFC 4039 Section 3 */ + *p++ = DHO_RAPIDCOMMIT; + *p++ = 0; + } + + if (type == DHCP_DISCOVER && ifo->options & DHCPCD_REQUEST) + PUTADDR(DHO_IPADDRESS, ifo->req_addr); + + if (type == DHCP_DISCOVER || + type == DHCP_INFORM || + type == DHCP_REQUEST) + { + *p++ = DHO_MAXMESSAGESIZE; + *p++ = 2; + sz = get_mtu(iface->name); + if (sz < MTU_MIN) { + if (set_mtu(iface->name, MTU_MIN) == 0) + sz = MTU_MIN; + } else if (sz > MTU_MAX) { + /* Even though our MTU could be greater than + * MTU_MAX (1500) dhcpcd does not presently + * handle DHCP packets any bigger. */ + sz = MTU_MAX; + } + sz = htons(sz); + memcpy(p, &sz, 2); + p += 2; + + if (ifo->userclass[0]) { + *p++ = DHO_USERCLASS; + memcpy(p, ifo->userclass, ifo->userclass[0] + 1); + p += ifo->userclass[0] + 1; + } + + if (ifo->vendorclassid[0]) { + *p++ = DHO_VENDORCLASSID; + memcpy(p, ifo->vendorclassid, + ifo->vendorclassid[0] + 1); + p += ifo->vendorclassid[0] + 1; + } + + + if (type != DHCP_INFORM) { + if (ifo->leasetime != 0) { + *p++ = DHO_LEASETIME; + *p++ = 4; + ul = htonl(ifo->leasetime); + memcpy(p, &ul, 4); + p += 4; + } + } + + if (ifo->hostname[0] == '\0') + hostname = get_hostname(ifo->options & + DHCPCD_HOSTNAME_SHORT ? 1 : 0); + else + hostname = ifo->hostname; + if (ifo->fqdn != FQDN_DISABLE) { + /* IETF DHC-FQDN option (81), RFC4702 */ + *p++ = DHO_FQDN; + lp = p; + *p++ = 3; + /* + * Flags: 0000NEOS + * S: 1 => Client requests Server to update + * a RR in DNS as well as PTR + * O: 1 => Server indicates to client that + * DNS has been updated + * E: 1 => Name data is DNS format + * N: 1 => Client requests Server to not + * update DNS + */ + if (hostname) + *p++ = (ifo->fqdn & 0x09) | 0x04; + else + *p++ = (FQDN_NONE & 0x09) | 0x04; + *p++ = 0; /* from server for PTR RR */ + *p++ = 0; /* from server for A RR if S=1 */ + if (hostname) { + ul = encode_rfc1035(hostname, p); + *lp += ul; + p += ul; + } + } else if (ifo->options & DHCPCD_HOSTNAME && hostname) { + *p++ = DHO_HOSTNAME; + len = strlen(hostname); + *p++ = len; + memcpy(p, hostname, len); + p += len; + } + + /* vendor is already encoded correctly, so just add it */ + if (ifo->vendor[0]) { + *p++ = DHO_VENDOR; + memcpy(p, ifo->vendor, ifo->vendor[0] + 1); + p += ifo->vendor[0] + 1; + } + + if (ifo->vivco_len) { + *p++ = DHO_VIVCO; + lp = p++; + *lp = sizeof(ul); + ul = htonl(ifo->vivco_en); + memcpy(p, &ul, sizeof(ul)); + p += sizeof(ul); + for (i = 0, vivco = ifo->vivco; + i < ifo->vivco_len; + i++, vivco++) + { + len = (p - m) + vivco->len + 1; + if (len > sizeof(*dhcp)) + goto toobig; + if (vivco->len + 2 + *lp > 255) { + syslog(LOG_ERR, + "%s: VIVCO option too big", + iface->name); + free(dhcp); + return -1; + } + *p++ = (uint8_t)vivco->len; + memcpy(p, vivco->data, vivco->len); + p += vivco->len; + *lp += (uint8_t)vivco->len + 1; + } + } + + len = (p - m) + 3; + if (len > sizeof(*dhcp)) + goto toobig; + *p++ = DHO_PARAMETERREQUESTLIST; + n_params = p; + *p++ = 0; + for (i = 0, opt = dhcp_opts; i < dhcp_opts_len; i++, opt++) { + if (!(opt->type & REQUEST || + has_option_mask(ifo->requestmask, opt->option))) + continue; + if (opt->type & NOREQ) + continue; + if (type == DHCP_INFORM && + (opt->option == DHO_RENEWALTIME || + opt->option == DHO_REBINDTIME)) + continue; + len = (p - m) + 2; + if (len > sizeof(*dhcp)) + goto toobig; + *p++ = opt->option; + } + *n_params = p - n_params - 1; + } + + /* silence GCC */ + auth_len = 0; + auth = NULL; + + if (ifo->auth.options & DHCPCD_AUTH_SEND) { + auth_len = dhcp_auth_encode(&ifo->auth, state->auth.token, + NULL, 0, 4, type, NULL, 0); + if (auth_len > 0) { + len = (p + auth_len) - m; + if (auth_len > 255 || len > sizeof(*dhcp)) + goto toobig; + *p++ = DHO_AUTHENTICATION; + *p++ = (uint8_t)auth_len; + auth = p; + p += auth_len; + } else if (auth_len == -1) + syslog(LOG_ERR, "%s: dhcp_auth_encode: %m", + iface->name); + } + + *p++ = DHO_END; + +#ifdef BOOTP_MESSAGE_LENTH_MIN + /* Some crappy DHCP servers think they have to obey the BOOTP minimum + * message length. + * They are wrong, but we should still cater for them. */ + while (p - m < BOOTP_MESSAGE_LENTH_MIN) + *p++ = DHO_PAD; +#endif + + len = p - m; + if (ifo->auth.options & DHCPCD_AUTH_SEND && auth_len > 0) + dhcp_auth_encode(&ifo->auth, state->auth.token, + m, len, 4, type, auth, auth_len); + + *message = dhcp; + return len; + +toobig: + syslog(LOG_ERR, "%s: DHCP messge too big", iface->name); + free(dhcp); + return -1; +} + +ssize_t +write_lease(const struct interface *ifp, const struct dhcp_message *dhcp) +{ + int fd; + ssize_t bytes = sizeof(*dhcp); + const uint8_t *p = dhcp->options; + const uint8_t *e = p + sizeof(dhcp->options); + uint8_t l; + uint8_t o = 0; + const struct dhcp_state *state = D_CSTATE(ifp); + + /* We don't write BOOTP leases */ + if (is_bootp(dhcp)) { + unlink(state->leasefile); + return 0; + } + + syslog(LOG_DEBUG, "%s: writing lease `%s'", + ifp->name, state->leasefile); + + fd = open(state->leasefile, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd == -1) + return -1; + + /* Only write as much as we need */ + while (p < e) { + o = *p; + if (o == DHO_END) { + bytes = p - (const uint8_t *)dhcp; + break; + } + p++; + if (o != DHO_PAD) { + l = *p++; + p += l; + } + } + bytes = write(fd, dhcp, bytes); + close(fd); + return bytes; +} + +struct dhcp_message * +read_lease(struct interface *ifp) +{ + int fd; + struct dhcp_message *dhcp; + struct dhcp_state *state = D_STATE(ifp); + ssize_t bytes; + const uint8_t *auth; + uint8_t type; + int auth_len; + + fd = open(state->leasefile, O_RDONLY); + if (fd == -1) { + if (errno != ENOENT) + syslog(LOG_ERR, "%s: open `%s': %m", + ifp->name, state->leasefile); + return NULL; + } + syslog(LOG_DEBUG, "%s: reading lease `%s'", + ifp->name, state->leasefile); + dhcp = calloc(1, sizeof(*dhcp)); + if (dhcp == NULL) { + close(fd); + return NULL; + } + bytes = read(fd, dhcp, sizeof(*dhcp)); + close(fd); + if (bytes < 0) { + free(dhcp); + return NULL; + } + + /* We may have found a BOOTP server */ + if (get_option_uint8(&type, dhcp, DHO_MESSAGETYPE) == -1) + type = 0; + /* Authenticate the message */ + auth = get_option(dhcp, DHO_AUTHENTICATION, &auth_len); + if (auth) { + if (dhcp_auth_validate(&state->auth, &ifp->options->auth, + (uint8_t *)dhcp, sizeof(*dhcp), 4, type, + auth, auth_len) == NULL) + { + syslog(LOG_DEBUG, "%s: dhcp_auth_validate: %m", + ifp->name); + free(dhcp); + return NULL; + } + syslog(LOG_DEBUG, "%s: validated using 0x%08" PRIu32, + ifp->name, state->auth.token->secretid); + } + + return dhcp; +} + +static const struct dhcp_opt * +dhcp_getoverride(const struct if_options *ifo, uint16_t o) +{ + size_t i; + const struct dhcp_opt *opt; + + for (i = 0, opt = ifo->dhcp_override; + i < ifo->dhcp_override_len; + i++, opt++) + { + if (opt->option == o) + return opt; + } + return NULL; +} + +static const uint8_t * +dhcp_getoption(unsigned int *os, unsigned int *code, unsigned int *len, + const uint8_t *od, unsigned int ol, struct dhcp_opt **oopt) +{ + size_t i; + struct dhcp_opt *opt; + + if (od) { + if (ol < 2) { + errno = EINVAL; + return NULL; + } + *os = 2; /* code + len */ + *code = (int)*od++; + *len = (int)*od++; + if (*len > ol) { + errno = EINVAL; + return NULL; + } + } + + for (i = 0, opt = dhcp_opts; i < dhcp_opts_len; i++, opt++) { + if (opt->option == *code) { + *oopt = opt; + break; + } + } + + return od; +} + +ssize_t +dhcp_env(char **env, const char *prefix, const struct dhcp_message *dhcp, + const struct interface *ifp) +{ + const struct if_options *ifo; + const uint8_t *p; + int pl; + struct in_addr addr; + struct in_addr net; + struct in_addr brd; + struct dhcp_opt *opt, *vo; + ssize_t e = 0; + char **ep; + char cidr[4]; + uint8_t overl = 0; + size_t i; + uint32_t en; + + ifo = ifp->options; + get_option_uint8(&overl, dhcp, DHO_OPTIONSOVERLOADED); + + if (!env) { + if (dhcp->yiaddr || dhcp->ciaddr) + e += 5; + if (*dhcp->bootfile && !(overl & 1)) + e++; + if (*dhcp->servername && !(overl & 2)) + e++; + for (i = 0, opt = dhcp_opts; + i < dhcp_opts_len; + i++, opt++) + { + if (has_option_mask(ifo->nomask, opt->option)) + continue; + if (dhcp_getoverride(ifo, opt->option)) + continue; + p = get_option(dhcp, opt->option, &pl); + if (!p) + continue; + e += dhcp_envoption(NULL, NULL, ifp->name, + opt, dhcp_getoption, p, pl); + } + for (i = 0, opt = ifo->dhcp_override; + i < ifo->dhcp_override_len; + i++, opt++) + { + if (has_option_mask(ifo->nomask, opt->option)) + continue; + p = get_option(dhcp, opt->option, &pl); + if (!p) + continue; + e += dhcp_envoption(NULL, NULL, ifp->name, + opt, dhcp_getoption, p, pl); + } + return e; + } + + ep = env; + if (dhcp->yiaddr || dhcp->ciaddr) { + /* Set some useful variables that we derive from the DHCP + * message but are not necessarily in the options */ + addr.s_addr = dhcp->yiaddr ? dhcp->yiaddr : dhcp->ciaddr; + setvar(&ep, prefix, "ip_address", inet_ntoa(addr)); + if (get_option_addr(&net, dhcp, DHO_SUBNETMASK) == -1) { + net.s_addr = ipv4_getnetmask(addr.s_addr); + setvar(&ep, prefix, "subnet_mask", inet_ntoa(net)); + } + snprintf(cidr, sizeof(cidr), "%d", inet_ntocidr(net)); + setvar(&ep, prefix, "subnet_cidr", cidr); + if (get_option_addr(&brd, dhcp, DHO_BROADCAST) == -1) { + brd.s_addr = addr.s_addr | ~net.s_addr; + setvar(&ep, prefix, "broadcast_address", + inet_ntoa(brd)); + } + addr.s_addr = dhcp->yiaddr & net.s_addr; + setvar(&ep, prefix, "network_number", inet_ntoa(addr)); + } + + if (*dhcp->bootfile && !(overl & 1)) + setvar(&ep, prefix, "filename", (const char *)dhcp->bootfile); + if (*dhcp->servername && !(overl & 2)) + setvar(&ep, prefix, "server_name", + (const char *)dhcp->servername); + + /* Zero our indexes */ + if (env) { + for (i = 0, opt = dhcp_opts; i < dhcp_opts_len; i++, opt++) + dhcp_zero_index(opt); + for (i = 0, opt = ifp->options->dhcp_override; + i < ifp->options->dhcp_override_len; + i++, opt++) + dhcp_zero_index(opt); + for (i = 0, opt = vivso; i < vivso_len; i++, opt++) + dhcp_zero_index(opt); + } + + for (i = 0, opt = dhcp_opts; + i < dhcp_opts_len; + i++, opt++) + { + if (has_option_mask(ifo->nomask, opt->option)) + continue; + if (dhcp_getoverride(ifo, opt->option)) + continue; + if ((p = get_option(dhcp, opt->option, &pl))) { + ep += dhcp_envoption(ep, prefix, ifp->name, + opt, dhcp_getoption, p, pl); + if (opt->option == DHO_VIVSO && + pl > (int)sizeof(uint32_t)) + { + memcpy(&en, p, sizeof(en)); + en = ntohl(en); + vo = vivso_find(en, ifp); + if (vo) { + /* Skip over en + total size */ + p += sizeof(en) + 1; + pl -= sizeof(en) + 1; + ep += dhcp_envoption(ep, prefix, + ifp->name, + vo, dhcp_getoption, p, pl); + } + } + } + } + + for (i = 0, opt = ifo->dhcp_override; + i < ifo->dhcp_override_len; + i++, opt++) + { + if (has_option_mask(ifo->nomask, opt->option)) + continue; + if ((p = get_option(dhcp, opt->option, &pl))) + ep += dhcp_envoption(ep, prefix, ifp->name, + opt, dhcp_getoption, p, pl); + } + + return ep - env; +} + +void +get_lease(struct dhcp_lease *lease, const struct dhcp_message *dhcp) +{ + struct timeval now; + + lease->cookie = dhcp->cookie; + /* BOOTP does not set yiaddr for replies when ciaddr is set. */ + if (dhcp->yiaddr) + lease->addr.s_addr = dhcp->yiaddr; + else + lease->addr.s_addr = dhcp->ciaddr; + if (get_option_addr(&lease->net, dhcp, DHO_SUBNETMASK) == -1) + lease->net.s_addr = ipv4_getnetmask(lease->addr.s_addr); + if (get_option_addr(&lease->brd, dhcp, DHO_BROADCAST) == -1) + lease->brd.s_addr = lease->addr.s_addr | ~lease->net.s_addr; + if (get_option_uint32(&lease->leasetime, dhcp, DHO_LEASETIME) == 0) { + /* Ensure that we can use the lease */ + get_monotonic(&now); + if (now.tv_sec + (time_t)lease->leasetime < now.tv_sec) + lease->leasetime = ~0U; /* Infinite lease */ + } else + lease->leasetime = ~0U; /* Default to infinite lease */ + 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; + if (get_option_addr(&lease->server, dhcp, DHO_SERVERID) != 0) + lease->server.s_addr = INADDR_ANY; +} + +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; +} + +static void +dhcp_fallback(void *arg) +{ + struct interface *iface; + + iface = (struct interface *)arg; + select_profile(iface, iface->options->fallback); + start_interface(iface); +} + +uint32_t +dhcp_xid(const struct interface *ifp) +{ + uint32_t xid; + + if (ifp->options->options & DHCPCD_XID_HWADDR && + ifp->hwlen >= sizeof(xid)) + /* The lower bits are probably more unique on the network */ + memcpy(&xid, (ifp->hwaddr + ifp->hwlen) - sizeof(xid), + sizeof(xid)); + else + xid = arc4random(); + + return xid; +} + +void +dhcp_close(struct interface *ifp) +{ + struct dhcp_state *state = D_STATE(ifp); + + if (state == NULL) + return; + + if (state->arp_fd != -1) { + eloop_event_delete(state->arp_fd); + close(state->arp_fd); + state->arp_fd = -1; + } + if (state->raw_fd != -1) { + eloop_event_delete(state->raw_fd); + close(state->raw_fd); + state->raw_fd = -1; + } + if (state->udp_fd != -1) { + /* we don't listen to events on the udp */ + close(state->udp_fd); + state->udp_fd = -1; + } + + state->interval = 0; +} + +static int +dhcp_openudp(struct interface *iface) +{ + int s; + struct sockaddr_in sin; + int n; + struct dhcp_state *state; +#ifdef SO_BINDTODEVICE + struct ifreq ifr; + char *p; +#endif + + if ((s = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) + return -1; + + n = 1; + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) == -1) + goto eexit; +#ifdef SO_BINDTODEVICE + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, iface->name, sizeof(ifr.ifr_name)); + /* We can only bind to the real device */ + p = strchr(ifr.ifr_name, ':'); + if (p) + *p = '\0'; + if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, &ifr, + sizeof(ifr)) == -1) + goto eexit; +#endif + /* As we don't use this socket for receiving, set the + * receive buffer to 1 */ + n = 1; + if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)) == -1) + goto eexit; + state = D_STATE(iface); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(DHCP_CLIENT_PORT); + sin.sin_addr.s_addr = state->addr.s_addr; + if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) == -1) + goto eexit; + + state->udp_fd = s; + set_cloexec(s); + return 0; + +eexit: + close(s); + return -1; +} + +static ssize_t +dhcp_sendpacket(const struct interface *iface, struct in_addr to, + const uint8_t *data, ssize_t len) +{ + struct sockaddr_in sin; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = to.s_addr; + sin.sin_port = htons(DHCP_SERVER_PORT); + return sendto(D_CSTATE(iface)->udp_fd, data, len, 0, + (struct sockaddr *)&sin, sizeof(sin)); +} + +static uint16_t +checksum(const void *data, uint16_t len) +{ + const uint8_t *addr = data; + uint32_t sum = 0; + + while (len > 1) { + sum += addr[0] * 256 + addr[1]; + addr += 2; + len -= 2; + } + + if (len == 1) + sum += *addr * 256; + + sum = (sum >> 16) + (sum & 0xffff); + sum += (sum >> 16); + + sum = htons(sum); + + return ~sum; +} + +static ssize_t +dhcp_makeudppacket(uint8_t **p, const uint8_t *data, size_t length, + struct in_addr source, struct in_addr dest) +{ + struct udp_dhcp_packet *udpp; + struct ip *ip; + struct udphdr *udp; + + udpp = calloc(1, sizeof(*udpp)); + if (udpp == NULL) + return -1; + ip = &udpp->ip; + udp = &udpp->udp; + + /* OK, this is important :) + * We copy the data to our packet and then create a small part of the + * ip structure and an invalid ip_len (basically udp length). + * We then fill the udp structure and put the checksum + * of the whole packet into the udp checksum. + * Finally we complete the ip structure and ip checksum. + * If we don't do the ordering like so then the udp checksum will be + * broken, so find another way of doing it! */ + + memcpy(&udpp->dhcp, data, length); + + ip->ip_p = IPPROTO_UDP; + ip->ip_src.s_addr = source.s_addr; + if (dest.s_addr == 0) + ip->ip_dst.s_addr = INADDR_BROADCAST; + else + ip->ip_dst.s_addr = dest.s_addr; + + udp->uh_sport = htons(DHCP_CLIENT_PORT); + udp->uh_dport = htons(DHCP_SERVER_PORT); + udp->uh_ulen = htons(sizeof(*udp) + length); + ip->ip_len = udp->uh_ulen; + udp->uh_sum = checksum(udpp, sizeof(*udpp)); + + ip->ip_v = IPVERSION; + ip->ip_hl = sizeof(*ip) >> 2; + ip->ip_id = arc4random() & UINT16_MAX; + ip->ip_ttl = IPDEFTTL; + ip->ip_len = htons(sizeof(*ip) + sizeof(*udp) + length); + ip->ip_sum = checksum(ip, sizeof(*ip)); + + *p = (uint8_t *)udpp; + return sizeof(*ip) + sizeof(*udp) + length; +} + +static void +send_message(struct interface *iface, int type, + void (*callback)(void *)) +{ + struct dhcp_state *state = D_STATE(iface); + struct if_options *ifo = iface->options; + struct dhcp_message *dhcp; + uint8_t *udp; + ssize_t len, r; + struct in_addr from, to; + in_addr_t a = 0; + struct timeval tv; + + if (!callback) + syslog(LOG_DEBUG, "%s: sending %s with xid 0x%x", + iface->name, get_dhcp_op(type), state->xid); + else { + if (state->interval == 0) + state->interval = 4; + else { + state->interval *= 2; + if (state->interval > 64) + state->interval = 64; + } + tv.tv_sec = state->interval + DHCP_RAND_MIN; + tv.tv_usec = arc4random() % (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U); + timernorm(&tv); + syslog(LOG_DEBUG, + "%s: sending %s (xid 0x%x), next in %0.1f seconds", + iface->name, get_dhcp_op(type), state->xid, + timeval_to_double(&tv)); + } + + /* Ensure sockets are open. */ + if (dhcp_open(iface) == -1) { + if (!(options & DHCPCD_TEST)) + dhcp_drop(iface, "FAIL"); + return; + } + + /* 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. + * Also, we should not unicast from a BOOTP lease. */ + if (state->udp_fd == -1 || + (!(ifo->options & DHCPCD_INFORM) && is_bootp(state->new))) + { + a = state->addr.s_addr; + state->addr.s_addr = 0; + } + len = make_message(&dhcp, iface, type); + if (a) + state->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 = dhcp_sendpacket(iface, to, (uint8_t *)dhcp, len); + if (r == -1) { + syslog(LOG_ERR, "%s: dhcp_sendpacket: %m", iface->name); + dhcp_close(iface); + } + } else { + len = dhcp_makeudppacket(&udp, (uint8_t *)dhcp, len, from, to); + if (len == -1) + return; + r = ipv4_sendrawpacket(iface, ETHERTYPE_IP, udp, len); + free(udp); + /* If we failed to send a raw packet this normally means + * we don't have the ability to work beneath the IP layer + * for this interface. + * As such we remove it from consideration without actually + * stopping the interface. */ + if (r == -1) { + syslog(LOG_ERR, "%s: ipv4_sendrawpacket: %m", + iface->name); + if (!(options & DHCPCD_TEST)) + dhcp_drop(iface, "FAIL"); + dhcp_close(iface); + eloop_timeout_delete(NULL, iface); + callback = NULL; + } + } + free(dhcp); + + /* Even if we fail to send a packet we should continue as we are + * as our failure timeouts will change out codepath when needed. */ + if (callback) + eloop_timeout_add_tv(&tv, callback, iface); +} + +static void +send_inform(void *arg) +{ + + send_message((struct interface *)arg, DHCP_INFORM, send_inform); +} + +static void +send_discover(void *arg) +{ + + send_message((struct interface *)arg, DHCP_DISCOVER, send_discover); +} + +static void +send_request(void *arg) +{ + + send_message((struct interface *)arg, DHCP_REQUEST, send_request); +} + +static void +send_renew(void *arg) +{ + + send_message((struct interface *)arg, DHCP_REQUEST, send_renew); +} + +static void +send_rebind(void *arg) +{ + + send_message((struct interface *)arg, DHCP_REQUEST, send_rebind); +} + +void +dhcp_discover(void *arg) +{ + struct interface *iface = arg; + struct dhcp_state *state = D_STATE(iface); + struct if_options *ifo = iface->options; + int timeout = ifo->timeout; + + /* If we're rebooting and we're not daemonised then we need + * to shorten the normal timeout to ensure we try correctly + * for a fallback or IPv4LL address. */ + if (state->state == DHS_REBOOT && !(options & DHCPCD_DAEMONISED)) { + timeout -= ifo->reboot; + if (timeout <= 0) + timeout = 2; + } + + state->state = DHS_DISCOVER; + state->xid = dhcp_xid(iface); + eloop_timeout_delete(NULL, iface); + if (ifo->fallback) + eloop_timeout_add_sec(timeout, dhcp_fallback, iface); + else if (ifo->options & DHCPCD_IPV4LL && + !IN_LINKLOCAL(htonl(state->addr.s_addr))) + { + if (IN_LINKLOCAL(htonl(state->fail.s_addr))) + eloop_timeout_add_sec(RATE_LIMIT_INTERVAL, + ipv4ll_start, iface); + else + eloop_timeout_add_sec(timeout, ipv4ll_start, iface); + } + if (ifo->options & DHCPCD_REQUEST) + syslog(LOG_INFO, "%s: soliciting a DHCP lease (requesting %s)", + iface->name, inet_ntoa(ifo->req_addr)); + else + syslog(LOG_INFO, "%s: soliciting a DHCP lease", iface->name); + send_discover(iface); +} + +static void +dhcp_request(void *arg) +{ + struct interface *ifp = arg; + struct dhcp_state *state = D_STATE(ifp); + + state->state = DHS_REQUEST; + send_request(ifp); +} + +static void +dhcp_expire(void *arg) +{ + struct interface *ifp = arg; + struct dhcp_state *state = D_STATE(ifp); + + syslog(LOG_ERR, "%s: DHCP lease expired", ifp->name); + eloop_timeout_delete(NULL, ifp); + dhcp_drop(ifp, "EXPIRE"); + unlink(state->leasefile); + + state->interval = 0; + dhcp_discover(ifp); +} + +void +dhcp_decline(struct interface *ifp) +{ + + send_message(ifp, DHCP_DECLINE, NULL); +} + +static void +dhcp_renew(void *arg) +{ + struct interface *ifp = arg; + struct dhcp_state *state = D_STATE(ifp); + struct dhcp_lease *lease = &state->lease; + + syslog(LOG_DEBUG, "%s: renewing lease of %s", + ifp->name, inet_ntoa(lease->addr)); + syslog(LOG_DEBUG, "%s: rebind in %"PRIu32" seconds," + " expire in %"PRIu32" seconds", + ifp->name, lease->rebindtime - lease->renewaltime, + lease->leasetime - lease->renewaltime); + state->state = DHS_RENEW; + state->xid = dhcp_xid(ifp); + send_renew(ifp); +} + +static void +dhcp_rebind(void *arg) +{ + struct interface *ifp = arg; + struct dhcp_state *state = D_STATE(ifp); + struct dhcp_lease *lease = &state->lease; + + syslog(LOG_WARNING, "%s: failed to renew DHCP, rebinding", + ifp->name); + syslog(LOG_DEBUG, "%s: expire in %"PRIu32" seconds", + ifp->name, lease->leasetime - lease->rebindtime); + state->state = DHS_REBIND; + eloop_timeout_delete(send_renew, ifp); + state->lease.server.s_addr = 0; + ifp->options->options &= ~ DHCPCD_CSR_WARNED; + send_rebind(ifp); +} + +void +dhcp_bind(void *arg) +{ + struct interface *iface = arg; + struct dhcp_state *state = D_STATE(iface); + struct if_options *ifo = iface->options; + struct dhcp_lease *lease = &state->lease; + struct timeval tv; + + /* We're binding an address now - ensure that sockets are closed */ + dhcp_close(iface); + state->reason = NULL; + if (clock_monotonic) + get_monotonic(&lease->boundtime); + state->xid = 0; + free(state->old); + state->old = state->new; + state->new = state->offer; + state->offer = NULL; + get_lease(lease, state->new); + if (ifo->options & DHCPCD_STATIC) { + syslog(LOG_INFO, "%s: using static address %s/%d", + iface->name, inet_ntoa(lease->addr), + inet_ntocidr(lease->net)); + lease->leasetime = ~0U; + state->reason = "STATIC"; + } else if (state->new->cookie != htonl(MAGIC_COOKIE)) { + syslog(LOG_INFO, "%s: using IPv4LL address %s", + iface->name, inet_ntoa(lease->addr)); + lease->leasetime = ~0U; + state->reason = "IPV4LL"; + } else if (ifo->options & DHCPCD_INFORM) { + if (ifo->req_addr.s_addr != 0) + lease->addr.s_addr = ifo->req_addr.s_addr; + else + lease->addr.s_addr = state->addr.s_addr; + syslog(LOG_INFO, "%s: received approval for %s", iface->name, + inet_ntoa(lease->addr)); + lease->leasetime = ~0U; + state->reason = "INFORM"; + } else { + if (gettimeofday(&tv, NULL) == 0) + lease->leasedfrom = tv.tv_sec; + else if (lease->frominfo) + state->reason = "TIMEOUT"; + if (lease->leasetime == ~0U) { + lease->renewaltime = + lease->rebindtime = + lease->leasetime; + syslog(LOG_INFO, "%s: leased %s for infinity", + iface->name, inet_ntoa(lease->addr)); + } else { + if (lease->leasetime < DHCP_MIN_LEASE) { + syslog(LOG_WARNING, + "%s: minimum lease is %d seconds", + iface->name, DHCP_MIN_LEASE); + lease->leasetime = DHCP_MIN_LEASE; + } + if (lease->rebindtime == 0) + lease->rebindtime = lease->leasetime * T2; + else if (lease->rebindtime >= lease->leasetime) { + lease->rebindtime = lease->leasetime * T2; + syslog(LOG_WARNING, + "%s: rebind time greater than lease " + "time, forcing to %"PRIu32" seconds", + iface->name, lease->rebindtime); + } + if (lease->renewaltime == 0) + lease->renewaltime = lease->leasetime * T1; + else if (lease->renewaltime > lease->rebindtime) { + lease->renewaltime = lease->leasetime * T1; + syslog(LOG_WARNING, + "%s: renewal time greater than rebind " + "time, forcing to %"PRIu32" seconds", + iface->name, lease->renewaltime); + } + syslog(lease->addr.s_addr == state->addr.s_addr ? + LOG_DEBUG : LOG_INFO, + "%s: leased %s for %"PRIu32" seconds", iface->name, + inet_ntoa(lease->addr), lease->leasetime); + } + } + if (options & DHCPCD_TEST) { + state->reason = "TEST"; + script_runreason(iface, state->reason); + exit(EXIT_SUCCESS); + } + if (state->reason == NULL) { + if (state->old) { + if (state->old->yiaddr == state->new->yiaddr && + lease->server.s_addr) + state->reason = "RENEW"; + else + state->reason = "REBIND"; + } else if (state->state == DHS_REBOOT) + state->reason = "REBOOT"; + else + state->reason = "BOUND"; + } + if (lease->leasetime == ~0U) + lease->renewaltime = lease->rebindtime = lease->leasetime; + else { + eloop_timeout_add_sec(lease->renewaltime, dhcp_renew, iface); + eloop_timeout_add_sec(lease->rebindtime, dhcp_rebind, iface); + eloop_timeout_add_sec(lease->leasetime, dhcp_expire, iface); + syslog(LOG_DEBUG, + "%s: renew in %"PRIu32" seconds, rebind in %"PRIu32 + " seconds", + iface->name, lease->renewaltime, lease->rebindtime); + } + ipv4_applyaddr(iface); + daemonise(); + state->state = DHS_BOUND; + if (ifo->options & DHCPCD_ARP) { + state->claims = 0; + arp_announce(iface); + } +} + +static void +dhcp_timeout(void *arg) +{ + struct interface *ifp = arg; + struct dhcp_state *state = D_STATE(ifp); + + dhcp_bind(ifp); + state->interval = 0; + dhcp_discover(ifp); +} + +struct dhcp_message * +dhcp_message_new(const struct in_addr *addr, const struct in_addr *mask) +{ + struct dhcp_message *dhcp; + uint8_t *p; + + dhcp = calloc(1, sizeof(*dhcp)); + if (dhcp == NULL) + return NULL; + dhcp->yiaddr = addr->s_addr; + p = dhcp->options; + if (mask && mask->s_addr != INADDR_ANY) { + *p++ = DHO_SUBNETMASK; + *p++ = sizeof(mask->s_addr); + memcpy(p, &mask->s_addr, sizeof(mask->s_addr)); + p+= sizeof(mask->s_addr); + } + *p++ = DHO_END; + return dhcp; +} + +static void +dhcp_static(struct interface *ifp) +{ + struct if_options *ifo; + struct dhcp_state *state; + + state = D_STATE(ifp); + ifo = ifp->options; + if (ifo->req_addr.s_addr == INADDR_ANY) { + syslog(LOG_INFO, + "%s: waiting for 3rd party to " + "configure IP address", + ifp->name); + state->reason = "3RDPARTY"; + script_runreason(ifp, state->reason); + return; + } + state->offer = dhcp_message_new(&ifo->req_addr, &ifo->req_mask); + if (state->offer) { + eloop_timeout_delete(NULL, ifp); + dhcp_bind(ifp); + } +} + +void +dhcp_inform(struct interface *ifp) +{ + struct dhcp_state *state; + struct if_options *ifo; + struct ipv4_addr *ap; + + state = D_STATE(ifp); + ifo = ifp->options; + if (options & DHCPCD_TEST) { + state->addr.s_addr = ifo->req_addr.s_addr; + state->net.s_addr = ifo->req_mask.s_addr; + } else { + if (ifo->req_addr.s_addr == INADDR_ANY) { + state = D_STATE(ifp); + ap = ipv4_findaddr(ifp, NULL, NULL); + if (ap == NULL) { + syslog(LOG_INFO, + "%s: waiting for 3rd party to " + "configure IP address", + ifp->name); + state->reason = "3RDPARTY"; + script_runreason(ifp, state->reason); + return; + } + state->offer = + dhcp_message_new(&ap->addr, &ap->net); + } else + state->offer = + dhcp_message_new(&ifo->req_addr, &ifo->req_mask); + if (state->offer) { + ifo->options |= DHCPCD_STATIC; + dhcp_bind(ifp); + ifo->options &= ~DHCPCD_STATIC; + } + } + + state->state = DHS_INFORM; + state->xid = dhcp_xid(ifp); + send_inform(ifp); +} + +void +dhcp_reboot_newopts(struct interface *ifp, int oldopts) +{ + struct if_options *ifo; + struct dhcp_state *state = D_STATE(ifp); + + if (state == NULL) + return; + ifo = ifp->options; + if ((ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC) && + state->addr.s_addr != ifo->req_addr.s_addr) || + (oldopts & (DHCPCD_INFORM | DHCPCD_STATIC) && + !(ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC)))) + { + dhcp_drop(ifp, "EXPIRE"); + } +} + +static void +dhcp_reboot(struct interface *ifp) +{ + struct if_options *ifo; + struct dhcp_state *state = D_STATE(ifp); + + if (state == NULL) + return; + ifo = ifp->options; + state->interval = 0; + + if (ifo->options & DHCPCD_LINK && ifp->carrier == LINK_DOWN) { + syslog(LOG_INFO, "%s: waiting for carrier", ifp->name); + return; + } + if (ifo->options & DHCPCD_STATIC) { + dhcp_static(ifp); + return; + } + if (ifo->reboot == 0 || state->offer == NULL) { + dhcp_discover(ifp); + return; + } + if (ifo->options & DHCPCD_INFORM) { + syslog(LOG_INFO, "%s: informing address of %s", + ifp->name, inet_ntoa(state->lease.addr)); + } else if (state->offer->cookie == 0) { + if (ifo->options & DHCPCD_IPV4LL) { + state->claims = 0; + arp_announce(ifp); + } else + dhcp_discover(ifp); + return; + } else { + syslog(LOG_INFO, "%s: rebinding lease of %s", + ifp->name, inet_ntoa(state->lease.addr)); + } + state->state = DHS_REBOOT; + state->xid = dhcp_xid(ifp); + state->lease.server.s_addr = 0; + eloop_timeout_delete(NULL, ifp); + if (ifo->fallback) + eloop_timeout_add_sec(ifo->reboot, dhcp_fallback, ifp); + else if (ifo->options & DHCPCD_LASTLEASE && state->lease.frominfo) + eloop_timeout_add_sec(ifo->reboot, dhcp_timeout, ifp); + else if (!(ifo->options & DHCPCD_INFORM && + options & (DHCPCD_MASTER | DHCPCD_DAEMONISED))) + eloop_timeout_add_sec(ifo->reboot, dhcp_expire, ifp); + /* Don't bother ARP checking as the server could NAK us first. */ + if (ifo->options & DHCPCD_INFORM) + dhcp_inform(ifp); + else + dhcp_request(ifp); +} + +void +dhcp_drop(struct interface *ifp, const char *reason) +{ + struct dhcp_state *state; +#ifdef RELEASE_SLOW + struct timespec ts; +#endif + + state = D_STATE(ifp); + if (state == NULL) + return; + eloop_timeouts_delete(ifp, dhcp_expire, NULL); + if (ifp->options->options & DHCPCD_RELEASE) { + unlink(state->leasefile); + if (ifp->carrier != LINK_DOWN && + state->new != NULL && + state->new->cookie == htonl(MAGIC_COOKIE)) + { + syslog(LOG_INFO, "%s: releasing lease of %s", + ifp->name, inet_ntoa(state->lease.addr)); + state->xid = dhcp_xid(ifp); + send_message(ifp, DHCP_RELEASE, NULL); +#ifdef RELEASE_SLOW + /* Give the packet a chance to go */ + ts.tv_sec = RELEASE_DELAY_S; + ts.tv_nsec = RELEASE_DELAY_NS; + nanosleep(&ts, NULL); +#endif + } + } + free(state->old); + state->old = state->new; + state->new = NULL; + state->reason = reason; + ipv4_applyaddr(ifp); + free(state->old); + state->old = NULL; + state->lease.addr.s_addr = 0; + ifp->options->options &= ~ DHCPCD_CSR_WARNED; + state->auth.token = NULL; + state->auth.replay = 0; + free(state->auth.reconf); + state->auth.reconf = NULL; +} + +static void +log_dhcp(int lvl, const char *msg, + const struct interface *iface, const struct dhcp_message *dhcp, + const struct in_addr *from) +{ + const char *tfrom; + char *a; + struct in_addr addr; + int r; + + if (strcmp(msg, "NAK:") == 0) + a = get_option_string(dhcp, DHO_MESSAGE); + else if (dhcp->yiaddr != 0) { + addr.s_addr = dhcp->yiaddr; + a = strdup(inet_ntoa(addr)); + if (a == NULL) { + syslog(LOG_ERR, "%s: %m", __func__); + return; + } + } else + a = NULL; + + tfrom = "from"; + r = get_option_addr(&addr, dhcp, DHO_SERVERID); + if (dhcp->servername[0] && r == 0) + syslog(lvl, "%s: %s %s %s %s `%s'", iface->name, msg, a, + tfrom, inet_ntoa(addr), dhcp->servername); + else { + if (r != 0) { + tfrom = "via"; + addr = *from; + } + if (a == NULL) + syslog(lvl, "%s: %s %s %s", + iface->name, msg, tfrom, inet_ntoa(addr)); + else + syslog(lvl, "%s: %s %s %s %s", + iface->name, msg, a, tfrom, inet_ntoa(addr)); + } + free(a); +} + +static int +blacklisted_ip(const struct if_options *ifo, in_addr_t addr) +{ + size_t i; + + for (i = 0; i < ifo->blacklist_len; i += 2) + if (ifo->blacklist[i] == (addr & ifo->blacklist[i + 1])) + return 1; + return 0; +} + +static int +whitelisted_ip(const struct if_options *ifo, in_addr_t addr) +{ + size_t i; + + if (ifo->whitelist_len == 0) + return -1; + for (i = 0; i < ifo->whitelist_len; i += 2) + if (ifo->whitelist[i] == (addr & ifo->whitelist[i + 1])) + return 1; + return 0; +} + +static void +dhcp_handledhcp(struct interface *iface, struct dhcp_message **dhcpp, + const struct in_addr *from) +{ + struct dhcp_state *state = D_STATE(iface); + struct if_options *ifo = iface->options; + struct dhcp_message *dhcp = *dhcpp; + struct dhcp_lease *lease = &state->lease; + uint8_t type, tmp; + const uint8_t *auth; + struct in_addr addr; + size_t i; + int auth_len; + + /* We may have found a BOOTP server */ + if (get_option_uint8(&type, dhcp, DHO_MESSAGETYPE) == -1) + type = 0; + + /* Authenticate the message */ + auth = get_option(dhcp, DHO_AUTHENTICATION, &auth_len); + if (auth) { + if (dhcp_auth_validate(&state->auth, &ifo->auth, + (uint8_t *)*dhcpp, sizeof(**dhcpp), 4, type, + auth, auth_len) == NULL) + { + syslog(LOG_DEBUG, "%s: dhcp_auth_validate: %m", + iface->name); + log_dhcp(LOG_ERR, "authentication failed", + iface, dhcp, from); + return; + } + syslog(LOG_DEBUG, "%s: validated using 0x%08" PRIu32, + iface->name, state->auth.token->secretid); + } else if (ifo->auth.options & DHCPCD_AUTH_REQUIRE) { + log_dhcp(LOG_ERR, "missing authentiation", iface, dhcp, from); + return; + } + + /* reset the message counter */ + state->interval = 0; + + if (type == DHCP_NAK) { + /* For NAK, only check if we require the ServerID */ + if (has_option_mask(ifo->requiremask, DHO_SERVERID) && + get_option_addr(&addr, dhcp, DHO_SERVERID) == -1) + { + log_dhcp(LOG_WARNING, "reject NAK", iface, dhcp, from); + return; + } + /* We should restart on a NAK */ + log_dhcp(LOG_WARNING, "NAK:", iface, dhcp, from); + if (!(options & DHCPCD_TEST)) { + dhcp_drop(iface, "NAK"); + unlink(state->leasefile); + } + dhcp_close(iface); + /* If we constantly get NAKS then we should slowly back off */ + eloop_timeout_add_sec(state->nakoff, dhcp_discover, iface); + if (state->nakoff == 0) + state->nakoff = 1; + else { + state->nakoff *= 2; + if (state->nakoff > NAKOFF_MAX) + state->nakoff = NAKOFF_MAX; + } + return; + } + + /* Ensure that all required options are present */ + for (i = 1; i < 255; i++) { + if (has_option_mask(ifo->requiremask, i) && + get_option_uint8(&tmp, dhcp, i) != 0) + { + /* If we are bootp, then ignore the need for serverid. + * To ignore bootp, require dhcp_message_type. */ + if (type == 0 && i == DHO_SERVERID) + continue; + log_dhcp(LOG_WARNING, "reject DHCP", iface, dhcp, from); + return; + } + } + + /* Ensure that the address offered is valid */ + if ((type == 0 || type == DHCP_OFFER || type == DHCP_ACK) && + (dhcp->ciaddr == INADDR_ANY || dhcp->ciaddr == INADDR_BROADCAST) && + (dhcp->yiaddr == INADDR_ANY || dhcp->yiaddr == INADDR_BROADCAST)) + { + log_dhcp(LOG_WARNING, "reject invalid address", + iface, dhcp, from); + return; + } + + if ((type == 0 || type == DHCP_OFFER) && + state->state == DHS_DISCOVER) + { + lease->frominfo = 0; + lease->addr.s_addr = dhcp->yiaddr; + lease->cookie = dhcp->cookie; + if (type == 0 || + get_option_addr(&lease->server, dhcp, DHO_SERVERID) != 0) + lease->server.s_addr = INADDR_ANY; + log_dhcp(LOG_INFO, "offered", iface, dhcp, from); + free(state->offer); + state->offer = dhcp; + *dhcpp = NULL; + if (options & DHCPCD_TEST) { + free(state->old); + state->old = state->new; + state->new = state->offer; + state->offer = NULL; + state->reason = "TEST"; + script_runreason(iface, state->reason); + exit(EXIT_SUCCESS); + } + eloop_timeout_delete(send_discover, iface); + /* We don't request BOOTP addresses */ + if (type) { + /* We used to ARP check here, but that seems to be in + * violation of RFC2131 where it only describes + * DECLINE after REQUEST. + * It also seems that some MS DHCP servers actually + * ignore DECLINE if no REQUEST, ie we decline a + * DISCOVER. */ + dhcp_request(iface); + return; + } + } + + if (type) { + if (type == DHCP_OFFER) { + log_dhcp(LOG_WARNING, "ignoring offer of", + iface, dhcp, from); + return; + } + + /* We should only be dealing with acks */ + if (type != DHCP_ACK) { + log_dhcp(LOG_ERR, "not ACK or OFFER", + iface, dhcp, from); + return; + } + + if (!(ifo->options & DHCPCD_INFORM)) + log_dhcp(LOG_DEBUG, "acknowledged", iface, dhcp, from); + else + ifo->options &= ~DHCPCD_STATIC; + } + + + /* No NAK, so reset the backoff + * We don't reset on an OFFER message because the server could + * potentially NAK the REQUEST. */ + state->nakoff = 0; + + /* BOOTP could have already assigned this above, so check we still + * have a pointer. */ + if (*dhcpp) { + free(state->offer); + state->offer = dhcp; + *dhcpp = NULL; + } + + lease->frominfo = 0; + eloop_timeout_delete(NULL, iface); + + /* We now have an offer, so close the DHCP sockets. + * This allows us to safely ARP when broken DHCP servers send an ACK + * follows by an invalid NAK. */ + dhcp_close(iface); + + if (ifo->options & DHCPCD_ARP && + state->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 (!ipv4_findaddr(iface, &addr, NULL)) { + state->claims = 0; + state->probes = 0; + state->conflicts = 0; + state->state = DHS_PROBE; + arp_probe(iface); + return; + } + } + + dhcp_bind(iface); +} + +static ssize_t +get_udp_data(const uint8_t **data, const uint8_t *udp) +{ + struct udp_dhcp_packet p; + + memcpy(&p, udp, sizeof(p)); + *data = udp + offsetof(struct udp_dhcp_packet, dhcp); + return ntohs(p.ip.ip_len) - sizeof(p.ip) - sizeof(p.udp); +} + +static int +valid_udp_packet(const uint8_t *data, size_t data_len, struct in_addr *from, + int noudpcsum) +{ + struct udp_dhcp_packet p; + uint16_t bytes, udpsum; + + if (data_len < sizeof(p.ip)) { + if (from) + from->s_addr = INADDR_ANY; + errno = EINVAL; + return -1; + } + memcpy(&p, data, MIN(data_len, sizeof(p))); + if (from) + from->s_addr = p.ip.ip_src.s_addr; + if (data_len > sizeof(p)) { + errno = EINVAL; + return -1; + } + if (checksum(&p.ip, sizeof(p.ip)) != 0) { + errno = EINVAL; + return -1; + } + + bytes = ntohs(p.ip.ip_len); + if (data_len < bytes) { + errno = EINVAL; + return -1; + } + + if (noudpcsum == 0) { + udpsum = p.udp.uh_sum; + p.udp.uh_sum = 0; + p.ip.ip_hl = 0; + p.ip.ip_v = 0; + p.ip.ip_tos = 0; + p.ip.ip_len = p.udp.uh_ulen; + p.ip.ip_id = 0; + p.ip.ip_off = 0; + p.ip.ip_ttl = 0; + p.ip.ip_sum = 0; + if (udpsum && checksum(&p, bytes) != udpsum) { + errno = EINVAL; + return -1; + } + } + + return 0; +} + +static void +dhcp_handlepacket(void *arg) +{ + struct interface *iface = arg; + struct dhcp_message *dhcp = NULL; + const uint8_t *pp; + ssize_t bytes; + struct in_addr from; + int i, partialcsum = 0; + const struct dhcp_state *state = D_CSTATE(iface); + + /* 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. */ + for(;;) { + bytes = ipv4_getrawpacket(iface, ETHERTYPE_IP, + packet, udp_dhcp_len, &partialcsum); + if (bytes == 0 || bytes == -1) + break; + if (valid_udp_packet(packet, bytes, &from, partialcsum) == -1) { + syslog(LOG_ERR, "%s: invalid UDP packet from %s", + iface->name, inet_ntoa(from)); + continue; + } + i = whitelisted_ip(iface->options, from.s_addr); + if (i == 0) { + syslog(LOG_WARNING, + "%s: non whitelisted DHCP packet from %s", + iface->name, inet_ntoa(from)); + continue; + } else if (i != 1 && + blacklisted_ip(iface->options, from.s_addr) == 1) + { + syslog(LOG_WARNING, + "%s: blacklisted DHCP packet from %s", + iface->name, inet_ntoa(from)); + continue; + } + if (iface->flags & IFF_POINTOPOINT && + state->dst.s_addr != from.s_addr) + { + syslog(LOG_WARNING, + "%s: server %s is not destination", + iface->name, inet_ntoa(from)); + } + bytes = get_udp_data(&pp, packet); + if ((size_t)bytes > sizeof(*dhcp)) { + syslog(LOG_ERR, + "%s: packet greater than DHCP size from %s", + iface->name, inet_ntoa(from)); + continue; + } + if (dhcp == NULL) { + dhcp = calloc(1, sizeof(*dhcp)); + if (dhcp == NULL) { + syslog(LOG_ERR, "%s: calloc: %m", __func__); + break; + } + } + memcpy(dhcp, pp, bytes); + if (dhcp->cookie != htonl(MAGIC_COOKIE)) { + syslog(LOG_DEBUG, "%s: bogus cookie from %s", + iface->name, inet_ntoa(from)); + continue; + } + /* Ensure it's the right transaction */ + if (state->xid != ntohl(dhcp->xid)) { + syslog(LOG_DEBUG, + "%s: wrong xid 0x%x (expecting 0x%x) from %s", + iface->name, ntohl(dhcp->xid), state->xid, + inet_ntoa(from)); + continue; + } + /* Ensure packet is for us */ + if (iface->hwlen <= sizeof(dhcp->chaddr) && + memcmp(dhcp->chaddr, iface->hwaddr, iface->hwlen)) + { + syslog(LOG_DEBUG, "%s: xid 0x%x is not for hwaddr %s", + iface->name, ntohl(dhcp->xid), + hwaddr_ntoa(dhcp->chaddr, sizeof(dhcp->chaddr))); + continue; + } + dhcp_handledhcp(iface, &dhcp, &from); + if (state->raw_fd == -1) + break; + } + free(dhcp); +} + +static int +dhcp_open(struct interface *ifp) +{ + struct dhcp_state *state; + + if (packet == NULL) { + packet = malloc(udp_dhcp_len); + if (packet == NULL) { + syslog(LOG_ERR, "%s: %m", __func__); + return -1; + } +#ifdef DEBUG_MEMORY + atexit(dhcp_cleanup); +#endif + } + + state = D_STATE(ifp); + if (state->raw_fd == -1) { + state->raw_fd = ipv4_opensocket(ifp, ETHERTYPE_IP); + if (state->raw_fd == -1) { + syslog(LOG_ERR, "%s: %s: %m", __func__, ifp->name); + return -1; + } + eloop_event_add(state->raw_fd, dhcp_handlepacket, ifp); + } + if (state->udp_fd == -1 && + state->addr.s_addr != 0 && + state->new != NULL && + (state->new->cookie == htonl(MAGIC_COOKIE) || + ifp->options->options & DHCPCD_INFORM)) + { + if (dhcp_openudp(ifp) == -1 && errno != EADDRINUSE) { + syslog(LOG_ERR, "%s: dhcp_openudp: %m", ifp->name); + return -1; + } + } + return 0; +} + +int +dhcp_dump(const char *ifname) +{ + struct interface *ifp; + struct dhcp_state *state; + + ifaces = malloc(sizeof(*ifaces)); + if (ifaces == NULL) + goto eexit; + TAILQ_INIT(ifaces); + ifp = calloc(1, sizeof(*ifp)); + if (ifp == NULL) + goto eexit; + TAILQ_INSERT_HEAD(ifaces, ifp, next); + ifp->if_data[IF_DATA_DHCP] = state = calloc(1, sizeof(*state)); + if (state == NULL) + goto eexit; + ifp->options = calloc(1, sizeof(*ifp->options)); + if (ifp->options == NULL) + goto eexit; + strlcpy(ifp->name, ifname, sizeof(ifp->name)); + snprintf(state->leasefile, sizeof(state->leasefile), + LEASEFILE, ifp->name); + strlcpy(ifp->options->script, if_options->script, + sizeof(ifp->options->script)); + state->new = read_lease(ifp); + if (state->new == NULL && errno == ENOENT) { + strlcpy(state->leasefile, ifname, sizeof(state->leasefile)); + state->new = read_lease(ifp); + } + if (state->new == NULL) { + if (errno == ENOENT) + syslog(LOG_ERR, "%s: no lease to dump", ifname); + return -1; + } + state->reason = "DUMP"; + return script_runreason(ifp, state->reason); + +eexit: + syslog(LOG_ERR, "%s: %m", __func__); + return -1; +} + +void +dhcp_free(struct interface *ifp) +{ + struct dhcp_state *state = D_STATE(ifp); + + if (state) { + free(state->old); + free(state->new); + free(state->offer); + free(state->buffer); + free(state->clientid); + free(state); + ifp->if_data[IF_DATA_DHCP] = NULL; + } +} + +static int +dhcp_init(struct interface *ifp) +{ + struct dhcp_state *state; + const struct if_options *ifo; + size_t len; + + state = D_STATE(ifp); + if (state == NULL) { + ifp->if_data[IF_DATA_DHCP] = calloc(1, sizeof(*state)); + state = D_STATE(ifp); + if (state == NULL) + return -1; + /* 0 is a valid fd, so init to -1 */ + state->raw_fd = state->udp_fd = state->arp_fd = -1; + } + + state->state = DHS_INIT; + state->reason = "PREINIT"; + state->nakoff = 0; + snprintf(state->leasefile, sizeof(state->leasefile), + LEASEFILE, ifp->name); + + ifo = ifp->options; + /* We need to drop the leasefile so that start_interface + * doesn't load it. */ + if (ifo->options & DHCPCD_REQUEST) + unlink(state->leasefile); + + free(state->clientid); + state->clientid = NULL; + + if (*ifo->clientid) { + state->clientid = malloc(ifo->clientid[0] + 1); + if (state->clientid == NULL) + goto eexit; + memcpy(state->clientid, ifo->clientid, ifo->clientid[0] + 1); + } else if (ifo->options & DHCPCD_CLIENTID) { + if (ifo->options & DHCPCD_DUID) { + state->clientid = malloc(duid_len + 6); + if (state->clientid == NULL) + goto eexit; + state->clientid[0] = duid_len + 5; + state->clientid[1] = 255; /* RFC 4361 */ + memcpy(state->clientid + 2, ifo->iaid, 4); + memcpy(state->clientid + 6, duid, duid_len); + } else { + len = ifp->hwlen + 1; + state->clientid = malloc(len + 1); + if (state->clientid == NULL) + goto eexit; + state->clientid[0] = len; + state->clientid[1] = ifp->family; + memcpy(state->clientid + 2, ifp->hwaddr, + ifp->hwlen); + } + } + + if (ifo->options & DHCPCD_DUID) + /* Don't bother logging as DUID and IAID are reported + * at device start. */ + return 0; + + if (ifo->options & DHCPCD_CLIENTID) + syslog(LOG_DEBUG, "%s: using ClientID %s", ifp->name, + hwaddr_ntoa(state->clientid + 1, state->clientid[0])); + else if (ifp->hwlen) + syslog(LOG_DEBUG, "%s: using hwaddr %s", ifp->name, + hwaddr_ntoa(ifp->hwaddr, ifp->hwlen)); + return 0; + +eexit: + syslog(LOG_ERR, "%s: Error making ClientID: %m", __func__); + return -1; +} + +void +dhcp_start(struct interface *ifp) +{ + struct if_options *ifo = ifp->options; + struct dhcp_state *state; + struct stat st; + struct timeval now; + uint32_t l; + int nolease; + + if (!(ifo->options & DHCPCD_IPV4)) + return; + + if (dhcp_init(ifp) == -1) { + syslog(LOG_ERR, "%s: dhcp_init: %m", ifp->name); + return; + } + + /* Close any pre-existing sockets as we're starting over */ + dhcp_close(ifp); + + state = D_STATE(ifp); + state->start_uptime = uptime(); + free(state->offer); + state->offer = NULL; + + if (state->arping_index < ifo->arping_len) { + arp_start(ifp); + return; + } + + if (ifo->options & DHCPCD_STATIC) { + dhcp_static(ifp); + return; + } + + if (ifo->options & DHCPCD_DHCP && dhcp_open(ifp) == -1) + return; + + if (ifo->options & DHCPCD_INFORM) { + dhcp_inform(ifp); + return; + } + if (ifp->hwlen == 0 && ifo->clientid[0] == '\0') { + syslog(LOG_WARNING, "%s: needs a clientid to configure", + ifp->name); + dhcp_drop(ifp, "FAIL"); + dhcp_close(ifp); + eloop_timeout_delete(NULL, ifp); + return; + } + /* We don't want to read the old lease if we NAK an old test */ + nolease = state->offer && options & DHCPCD_TEST; + if (!nolease) + state->offer = read_lease(ifp); + if (state->offer) { + get_lease(&state->lease, state->offer); + state->lease.frominfo = 1; + if (state->offer->cookie == 0) { + if (state->offer->yiaddr == state->addr.s_addr) { + free(state->offer); + state->offer = NULL; + } + } else if (state->lease.leasetime != ~0U && + stat(state->leasefile, &st) == 0) + { + /* Offset lease times and check expiry */ + gettimeofday(&now, NULL); + if ((time_t)state->lease.leasetime < + now.tv_sec - st.st_mtime) + { + syslog(LOG_DEBUG, + "%s: discarding expired lease", + ifp->name); + free(state->offer); + state->offer = NULL; + state->lease.addr.s_addr = 0; + } else { + l = now.tv_sec - st.st_mtime; + state->lease.leasetime -= l; + state->lease.renewaltime -= l; + state->lease.rebindtime -= l; + } + } + } + + if (!(ifo->options & DHCPCD_DHCP)) { + if (ifo->options & DHCPCD_IPV4LL) { + if (state->offer && state->offer->cookie != 0) { + free(state->offer); + state->offer = NULL; + } + ipv4ll_start(ifp); + } + return; + } + + if (state->offer == NULL) + dhcp_discover(ifp); + else if (state->offer->cookie == 0 && ifo->options & DHCPCD_IPV4LL) + ipv4ll_start(ifp); + else + dhcp_reboot(ifp); +} + +void +dhcp_handleifa(int type, struct interface *ifp, + const struct in_addr *addr, + const struct in_addr *net, + const struct in_addr *dst) +{ + struct dhcp_state *state; + struct if_options *ifo; + int i; + + state = D_STATE(ifp); + if (state == NULL) + return; + + if (type == RTM_DELADDR) { + if (state->new && + (state->new->yiaddr == addr->s_addr || + (state->new->yiaddr == INADDR_ANY && + state->new->ciaddr == addr->s_addr))) + { + syslog(LOG_INFO, "%s: removing IP address %s/%d", + ifp->name, inet_ntoa(state->lease.addr), + inet_ntocidr(state->lease.net)); + dhcp_drop(ifp, "EXPIRE"); + } + return; + } + + if (type != RTM_NEWADDR) + return; + + ifo = ifp->options; + if (ifo->options & DHCPCD_INFORM) { + if (state->state != DHS_INFORM) + dhcp_inform(ifp); + return; + } + + if (!(ifo->options & DHCPCD_STATIC)) + return; + if (ifo->req_addr.s_addr != INADDR_ANY) + return; + + free(state->old); + state->old = state->new; + state->new = dhcp_message_new(addr, net); + if (state->new == NULL) + return; + state->dst.s_addr = dst ? dst->s_addr : INADDR_ANY; + if (dst) { + for (i = 1; i < 255; i++) + if (i != DHO_ROUTER && has_option_mask(ifo->dstmask,i)) + dhcp_message_add_addr(state->new, i, *dst); + } + state->reason = "STATIC"; + ipv4_buildroutes(); + script_runreason(ifp, state->reason); + if (ifo->options & DHCPCD_INFORM) { + state->state = DHS_INFORM; + state->xid = dhcp_xid(ifp); + state->lease.server.s_addr = dst ? dst->s_addr : INADDR_ANY; + state->addr = *addr; + state->net = *net; + dhcp_inform(ifp); + } +} -- cgit v1.2.3