/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2015 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 #include #include #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 #define ELOOP_QUEUE 2 #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 "if.h" #include "ipv4.h" #include "ipv4ll.h" #include "rpc-interface.h" #include "script.h" #define DAD "Duplicate address detected" #define DHCP_MIN_LEASE 20 #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 #ifndef IPDEFTTL #define IPDEFTTL 64 /* RFC1340 */ #endif 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" }, { DHCP_FORCERENEW, "DHCP_FORCERENEW"}, { 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; }; static const size_t udp_dhcp_len = sizeof(struct udp_dhcp_packet); static int dhcp_open(struct interface *ifp); void dhcp_printoptions(const struct dhcpcd_ctx *ctx, const struct dhcp_opt *opts, size_t opts_len) { const char * const *p; size_t i, j; const struct dhcp_opt *opt, *opt2; int cols; for (p = dhcp_params; *p; p++) printf(" %s\n", *p); for (i = 0, opt = ctx->dhcp_opts; i < ctx->dhcp_opts_len; i++, opt++) { for (j = 0, opt2 = opts; j < opts_len; j++, opt2++) if (opt->option == opt2->option) break; if (j == opts_len) { cols = printf("%03d %s", opt->option, opt->var); dhcp_print_option_encoding(opt, cols); } } for (i = 0, opt = opts; i < opts_len; i++, opt++) { cols = printf("%03d %s", opt->option, opt->var); dhcp_print_option_encoding(opt, cols); } } #define get_option_raw(ctx, dhcp, opt) get_option(ctx, dhcp, opt, NULL) static const uint8_t * get_option(struct dhcpcd_ctx *ctx, const struct dhcp_message *dhcp, unsigned int opt, size_t *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; size_t bl = 0; /* Check we have the magic cookie */ if (dhcp->cookie != htonl(MAGIC_COOKIE)) { errno = ENOTSUP; return NULL; } /* DHCP options are in TLV format with T and L each being a single * bytes. In general, here we have p -> T, ol=p+1 -> L, op -> V. * We must make sure there is enough room to read both T and L. */ while (p + 1 < e) { o = *p++; if (o == opt) { if (op) { if (!ctx->opt_buffer) { ctx->opt_buffer = malloc(DHCP_OPTION_LEN + BOOTFILE_LEN + SERVERNAME_LEN); if (ctx->opt_buffer == NULL) return NULL; } if (!bp) bp = ctx->opt_buffer; memcpy(bp, op, ol); bp += ol; } ol = (p + *p < e) ? *p : e - (p + 1); 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 = (uint8_t)(overl & ~1); p = dhcp->bootfile; e = p + sizeof(dhcp->bootfile); } else if (overl & 2) { /* bit 2 set means parse server name */ overl = (uint8_t)(overl & ~2); p = dhcp->servername; e = p + sizeof(dhcp->servername); } else goto exit; break; case DHO_OPTIONSOVERLOADED: /* Ensure we only get this option once by setting * the last bit as well as the value. * This is valid because only the first two bits * actually mean anything in RFC2132 Section 9.3 */ if (!overl) overl = 0x80 | p[1]; break; } l = *p++; p += l; } exit: if (len) *len = bl; if (bp) { memcpy(bp, op, ol); return (const uint8_t *)ctx->opt_buffer; } if (op) return op; errno = ENOENT; return NULL; } int get_option_addr(struct dhcpcd_ctx *ctx, struct in_addr *a, const struct dhcp_message *dhcp, uint8_t option) { const uint8_t *p; size_t len; p = get_option(ctx, 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(struct dhcpcd_ctx *ctx, uint32_t *i, const struct dhcp_message *dhcp, uint8_t option) { const uint8_t *p; size_t len; uint32_t d; p = get_option(ctx, 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(struct dhcpcd_ctx *ctx, uint8_t *i, const struct dhcp_message *dhcp, uint8_t option) { const uint8_t *p; size_t len; p = get_option(ctx, dhcp, option, &len); if (!p || len < (ssize_t)sizeof(*p)) return -1; if (i) *i = *(p); return 0; } ssize_t decode_rfc3442(char *out, size_t len, const uint8_t *p, size_t pl) { const uint8_t *e; size_t bytes = 0, ocets; int b; 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 = (size_t)(cidr + 7) / NBBY; if (p + 4 + ocets > e) { errno = ERANGE; return -1; } 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 -= (size_t)b; /* Finally, snag the router */ memcpy(&addr.s_addr, p, 4); p += 4; b = snprintf(o, len, " %s", inet_ntoa(addr)); o += b; len -= (size_t)b; } if (out) return o - out; return (ssize_t)bytes; } static struct rt_head * decode_rfc3442_rt(struct dhcpcd_ctx *ctx, const uint8_t *data, size_t dl) { 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; } ocets = (size_t)(cidr + 7) / NBBY; if (p + 4 + ocets > e) { ipv4_freeroutes(routes); errno = ERANGE; return NULL; } rt = calloc(1, sizeof(*rt)); if (rt == NULL) { logger(ctx, LOG_ERR, "%s: %m", __func__); ipv4_freeroutes(routes); return NULL; } TAILQ_INSERT_TAIL(routes, rt, next); /* 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(const uint8_t *data, size_t dl) { uint8_t enc; size_t l; ssize_t r; char *sip = NULL; struct in_addr addr; char *p; if (dl < 2) { errno = EINVAL; return 0; } enc = *data++; dl--; switch (enc) { case 0: if ((r = decode_rfc3397(NULL, 0, data, dl)) > 0) { l = (size_t)r; sip = malloc(l); if (sip == NULL) return 0; decode_rfc3397(sip, l, data, dl); } 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 - (size_t)(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, size_t len, const uint8_t *p, size_t pl) { 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 -= (size_t)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 -= (size_t)b; out += b; bytes += b; } else { bytes += (4 * 4); } } return bytes; } static char * get_option_string(struct dhcpcd_ctx *ctx, const struct dhcp_message *dhcp, uint8_t option) { size_t len; const uint8_t *p; char *s; p = get_option(ctx, 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; size_t 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(ifp->ctx, dhcp, DHO_CSR, &len); else p = NULL; /* Check for crappy MS option */ if (!p && !has_option_mask(ifo->nomask, DHO_MSCSR)) { p = get_option(ifp->ctx, dhcp, DHO_MSCSR, &len); if (p) csr = "MS "; } if (p) { routes = decode_rfc3442_rt(ifp->ctx, p, len); if (routes) { const struct dhcp_state *state; state = D_CSTATE(ifp); if (!(ifo->options & DHCPCD_CSR_WARNED) && !(state->added & STATE_FAKE)) { logger(ifp->ctx, 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) { logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); return NULL; } TAILQ_INIT(routes); if (!has_option_mask(ifo->nomask, DHO_STATICROUTE)) p = get_option(ifp->ctx, dhcp, DHO_STATICROUTE, &len); else p = NULL; /* RFC 2131 Section 5.8 states length MUST be in multiples of 8 */ if (p && len % 8 == 0) { e = p + len; while (p < e) { if ((route = calloc(1, sizeof(*route))) == NULL) { logger(ifp->ctx, 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; /* RFC 2131 Section 5.8 states default route is * illegal */ if (route->dest.s_addr == htonl(INADDR_ANY)) { errno = EINVAL; free(route); continue; } 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(ifp->ctx, dhcp, DHO_ROUTER, &len); else p = NULL; if (p) { e = p + len; while (p < e) { if ((route = calloc(1, sizeof(*route))) == NULL) { logger(ifp->ctx, 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 = (size_t)(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 *ifp, uint8_t type) { struct dhcp_message *dhcp; uint8_t *m, *lp, *p, *auth; uint8_t *n_params = NULL, auth_len; uint32_t ul; uint16_t sz; size_t len, i; const struct dhcp_opt *opt; struct if_options *ifo = ifp->options; const struct dhcp_state *state = D_CSTATE(ifp); const struct dhcp_lease *lease = &state->lease; time_t up = uptime() - state->start_uptime; char hbuf[HOSTNAME_MAX_LEN + 1]; 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 = (uint8_t)ifp->family; switch (ifp->family) { case ARPHRD_ETHER: case ARPHRD_IEEE802: dhcp->hwlen = (uint8_t)ifp->hwlen; memcpy(&dhcp->chaddr, &ifp->hwaddr, ifp->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((uint16_t)up); } dhcp->xid = htonl(state->xid); dhcp->cookie = htonl(MAGIC_COOKIE); if (!(ifo->options & DHCPCD_BOOTP)) { *p++ = DHO_MESSAGETYPE; *p++ = 1; *p++ = type; } if (state->clientid) { *p++ = DHO_CLIENTID; memcpy(p, state->clientid, (size_t)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++ = (uint8_t)len; memcpy(p, DAD, len); p += len; } if (type == DHCP_DISCOVER && !(ifp->ctx->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); /* RFC 2563 Auto Configure */ if (type == DHCP_DISCOVER && ifo->options & DHCPCD_IPV4LL) { *p++ = DHO_AUTOCONFIGURE; *p++ = 1; *p++ = 1; } if (type == DHCP_DISCOVER || type == DHCP_INFORM || type == DHCP_REQUEST) { if (!(ifo->options & DHCPCD_BOOTP)) { int mtu; *p++ = DHO_MAXMESSAGESIZE; *p++ = 2; mtu = if_getmtu(ifp->name); if (mtu < MTU_MIN) { if (if_setmtu(ifp->name, MTU_MIN) == 0) sz = MTU_MIN; } else if (mtu > MTU_MAX) { /* Even though our MTU could be greater than * MTU_MAX (1500) dhcpcd does not presently * handle DHCP packets any bigger. */ mtu = MTU_MAX; } sz = htons((uint16_t)mtu); memcpy(p, &sz, 2); p += 2; } if (ifo->userclass[0]) { *p++ = DHO_USERCLASS; memcpy(p, ifo->userclass, (size_t)ifo->userclass[0] + 1); p += ifo->userclass[0] + 1; } if (ifo->vendorclassid[0]) { *p++ = DHO_VENDORCLASSID; memcpy(p, ifo->vendorclassid, (size_t)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(hbuf, sizeof(hbuf), ifo->options & DHCPCD_HOSTNAME_SHORT ? 1 : 0); else hostname = ifo->hostname; /* * RFC4702 3.1 States that if we send the Client FQDN option * then we MUST NOT also send the Host Name option. * Technically we could, but that is not RFC conformant and * also seems to break some DHCP server implemetations such as * Windows. On the other hand, ISC dhcpd is just as non RFC * conformant by not accepting a partially qualified FQDN. */ 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++ = (uint8_t)((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) { i = encode_rfc1035(hostname, p); *lp = (uint8_t)(*lp + i); p += i; } } else if (ifo->options & DHCPCD_HOSTNAME && hostname) { *p++ = DHO_HOSTNAME; len = strlen(hostname); *p++ = (uint8_t)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, (size_t)ifo->vendor[0] + 1); p += ifo->vendor[0] + 1; } if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) != DHCPCD_AUTH_SENDREQUIRE) { /* We support HMAC-MD5 */ *p++ = DHO_FORCERENEW_NONCE; *p++ = 1; *p++ = AUTH_ALG_HMAC_MD5; } 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 = (size_t)(p - m) + vivco->len + 1; if (len > sizeof(*dhcp)) goto toobig; if (vivco->len + 2 + *lp > 255) { logger(ifp->ctx, LOG_ERR, "%s: VIVCO option too big", ifp->name); free(dhcp); return -1; } *p++ = (uint8_t)vivco->len; memcpy(p, vivco->data, vivco->len); p += vivco->len; *lp = (uint8_t)(*lp + vivco->len + 1); } } len = (size_t)((p - m) + 3); if (len > sizeof(*dhcp)) goto toobig; *p++ = DHO_PARAMETERREQUESTLIST; n_params = p; *p++ = 0; for (i = 0, opt = ifp->ctx->dhcp_opts; i < ifp->ctx->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 = (size_t)((p - m) + 2); if (len > sizeof(*dhcp)) goto toobig; *p++ = (uint8_t)opt->option; } for (i = 0, opt = ifo->dhcp_override; i < ifo->dhcp_override_len; i++, opt++) { /* Check if added above */ for (lp = n_params + 1; lp < p; lp++) if (*lp == (uint8_t)opt->option) break; if (lp < p) continue; 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 = (size_t)((p - m) + 2); if (len > sizeof(*dhcp)) goto toobig; *p++ = (uint8_t)opt->option; } *n_params = (uint8_t)(p - n_params - 1); } /* silence GCC */ auth_len = 0; auth = NULL; if (ifo->auth.options & DHCPCD_AUTH_SEND) { ssize_t alen = dhcp_auth_encode(&ifo->auth, state->auth.token, NULL, 0, 4, type, NULL, 0); if (alen != -1 && alen > UINT8_MAX) { errno = ERANGE; alen = -1; } if (alen == -1) logger(ifp->ctx, LOG_ERR, "%s: dhcp_auth_encode: %m", ifp->name); else if (alen != 0) { auth_len = (uint8_t)alen; len = (size_t)((p + alen) - m); if (len > sizeof(*dhcp)) goto toobig; *p++ = DHO_AUTHENTICATION; *p++ = auth_len; auth = p; p += auth_len; } } *p++ = DHO_END; /* Pad out to the BOOTP minimum message length. * Some DHCP servers incorrectly require this. */ while (p - m < BOOTP_MESSAGE_LENTH_MIN) *p++ = DHO_PAD; len = (size_t)(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 (ssize_t)len; toobig: logger(ifp->ctx, LOG_ERR, "%s: DHCP messge too big", ifp->name); free(dhcp); return -1; } static ssize_t write_lease(const struct interface *ifp, const struct dhcp_message *dhcp) { int fd; size_t len; ssize_t bytes; const uint8_t *e, *p; uint8_t l; uint8_t o = 0; const struct dhcp_state *state = D_CSTATE(ifp); uint8_t write_buffer[sizeof(*dhcp) + sizeof(state->server_info) + 1]; uint8_t *w; /* We don't write BOOTP leases */ if (IS_BOOTP(ifp, dhcp)) { unlink(state->leasefile); return 0; } logger(ifp->ctx, LOG_DEBUG, "%s: writing lease `%s'", ifp->name, state->leasefile); fd = open(state->leasefile, O_WRONLY | O_CREAT | O_TRUNC, 0600); if (fd == -1) return -1; /* Only write as much as we need */ p = dhcp->options; e = p + sizeof(dhcp->options); len = sizeof(*dhcp); while (p < e) { o = *p; if (o == DHO_END) { len = (size_t)(p - (const uint8_t *)dhcp); break; } p++; if (o != DHO_PAD) { l = *p++; p += l; } } memcpy(write_buffer, dhcp, len); w = write_buffer + len; /* Copy in server info if this is available. */ if (state->server_info.gw_hwlen != 0) { *w++ = DHO_END; memcpy(w, &state->server_info, sizeof(state->server_info)); len += sizeof(state->server_info) + 1; } bytes = write(fd, write_buffer, len); close(fd); return bytes; } static struct dhcp_message * read_lease(struct interface *ifp) { int fd; struct dhcp_message *dhcp; struct dhcp_state *state = D_STATE(ifp); uint8_t read_buffer[sizeof(*dhcp) + sizeof(state->server_info) + 1]; const uint8_t *options_startp = read_buffer + offsetof(struct dhcp_message, options); const uint8_t *options_endp = options_startp + sizeof(dhcp->options); uint8_t option_len; uint8_t option_type = 0; ssize_t bytes; const uint8_t *auth; uint8_t type; size_t auth_len; memset(&state->server_info, 0, sizeof(state->server_info)); fd = open(state->leasefile, O_RDONLY); if (fd == -1) { if (errno != ENOENT) logger(ifp->ctx, LOG_ERR, "%s: open `%s': %m", ifp->name, state->leasefile); return NULL; } logger(ifp->ctx, LOG_DEBUG, "%s: reading lease `%s'", ifp->name, state->leasefile); bytes = read(fd, read_buffer, sizeof(read_buffer)); close(fd); /* Lease file should at minimum contain all fields before options. */ if (read_buffer + bytes < options_startp) return NULL; dhcp = calloc(1, sizeof(*dhcp)); if (dhcp == NULL) { return NULL; } if (options_endp > read_buffer + bytes) options_endp = read_buffer + bytes; while (options_startp < options_endp) { option_type = *options_startp++; if (option_type == DHO_END) break; if (option_type != DHO_PAD) { option_len = *options_startp++; options_startp += option_len; } } memcpy(dhcp, read_buffer, options_startp - read_buffer); /* We may have found a BOOTP server */ if (get_option_uint8(ifp->ctx, &type, dhcp, DHO_MESSAGETYPE) == -1) type = 0; /* Authenticate the message */ auth = get_option(ifp->ctx, 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) { logger(ifp->ctx, LOG_DEBUG, "%s: dhcp_auth_validate: %m", ifp->name); free(dhcp); return NULL; } if (state->auth.token) logger(ifp->ctx, LOG_DEBUG, "%s: validated using 0x%08" PRIu32, ifp->name, state->auth.token->secretid); else logger(ifp->ctx, LOG_DEBUG, "%s: accepted reconfigure key", ifp->name); } /* * DHCP server information is stored after the DHO_END character * in the lease file. The first byte of the server information * is the length of the gateway hardware address. */ options_endp = read_buffer + bytes; if (options_startp >= options_endp || options_startp + sizeof(state->server_info) > options_endp) return dhcp; logger(ifp->ctx, LOG_DEBUG, "%s: found server info in lease '%s'", ifp->name, state->leasefile); memcpy(&state->server_info, options_startp, sizeof(state->server_info)); if (state->server_info.gw_hwlen != ifp->hwlen) { logger(ifp->ctx, LOG_ERR, "%s: lease file %s has incompatible" "MAC address length %d (expected %zd)", ifp->name, state->leasefile, state->server_info.gw_hwlen, ifp->hwlen); memset(&state->server_info, 0, sizeof(state->server_info)); } return dhcp; } static const struct dhcp_opt * dhcp_getoverride(const struct if_options *ifo, unsigned int 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(struct dhcpcd_ctx *ctx, size_t *os, unsigned int *code, size_t *len, const uint8_t *od, size_t 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 = (unsigned int)*od++; *len = (size_t)*od++; if (*len > ol - *os) { errno = EINVAL; return NULL; } } *oopt = NULL; for (i = 0, opt = ctx->dhcp_opts; i < ctx->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; struct in_addr addr; struct in_addr net; struct in_addr brd; struct dhcp_opt *opt, *vo; size_t e, i, pl; char **ep; char cidr[4], safe[(BOOTFILE_LEN * 4) + 1]; uint8_t overl = 0; uint32_t en; e = 0; ifo = ifp->options; get_option_uint8(ifp->ctx, &overl, dhcp, DHO_OPTIONSOVERLOADED); if (env == NULL) { if (dhcp->yiaddr || dhcp->ciaddr) e += 5; if (*dhcp->bootfile && !(overl & 1)) e++; if (*dhcp->servername && !(overl & 2)) e++; for (i = 0, opt = ifp->ctx->dhcp_opts; i < ifp->ctx->dhcp_opts_len; i++, opt++) { if (has_option_mask(ifo->nomask, opt->option)) continue; if (dhcp_getoverride(ifo, opt->option)) continue; p = get_option(ifp->ctx, dhcp, opt->option, &pl); if (!p) continue; e += dhcp_envoption(ifp->ctx, 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(ifp->ctx, dhcp, opt->option, &pl); if (!p) continue; e += dhcp_envoption(ifp->ctx, NULL, NULL, ifp->name, opt, dhcp_getoption, p, pl); } return (ssize_t)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(ifp->ctx, &ep, prefix, "ip_address", inet_ntoa(addr)); if (get_option_addr(ifp->ctx, &net, dhcp, DHO_SUBNETMASK) == -1) { net.s_addr = ipv4_getnetmask(addr.s_addr); setvar(ifp->ctx, &ep, prefix, "subnet_mask", inet_ntoa(net)); } snprintf(cidr, sizeof(cidr), "%d", inet_ntocidr(net)); setvar(ifp->ctx, &ep, prefix, "subnet_cidr", cidr); if (get_option_addr(ifp->ctx, &brd, dhcp, DHO_BROADCAST) == -1) { brd.s_addr = addr.s_addr | ~net.s_addr; setvar(ifp->ctx, &ep, prefix, "broadcast_address", inet_ntoa(brd)); } addr.s_addr = dhcp->yiaddr & net.s_addr; setvar(ifp->ctx, &ep, prefix, "network_number", inet_ntoa(addr)); } if (*dhcp->bootfile && !(overl & 1)) { print_string(safe, sizeof(safe), STRING, dhcp->bootfile, sizeof(dhcp->bootfile)); setvar(ifp->ctx, &ep, prefix, "filename", safe); } if (*dhcp->servername && !(overl & 2)) { print_string(safe, sizeof(safe), STRING | DOMAIN, dhcp->servername, sizeof(dhcp->servername)); setvar(ifp->ctx, &ep, prefix, "server_name", safe); } /* Zero our indexes */ if (env) { for (i = 0, opt = ifp->ctx->dhcp_opts; i < ifp->ctx->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 = ifp->ctx->vivso; i < ifp->ctx->vivso_len; i++, opt++) dhcp_zero_index(opt); } for (i = 0, opt = ifp->ctx->dhcp_opts; i < ifp->ctx->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(ifp->ctx, dhcp, opt->option, &pl))) { ep += dhcp_envoption(ifp->ctx, 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(ifp->ctx, 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(ifp->ctx, dhcp, opt->option, &pl))) ep += dhcp_envoption(ifp->ctx, ep, prefix, ifp->name, opt, dhcp_getoption, p, pl); } return ep - env; } static void get_lease(struct dhcpcd_ctx *ctx, struct dhcp_lease *lease, const struct dhcp_message *dhcp) { 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(ctx, &lease->net, dhcp, DHO_SUBNETMASK) == -1) lease->net.s_addr = ipv4_getnetmask(lease->addr.s_addr); if (get_option_addr(ctx, &lease->brd, dhcp, DHO_BROADCAST) == -1) lease->brd.s_addr = lease->addr.s_addr | ~lease->net.s_addr; if (get_option_uint32(ctx, &lease->leasetime, dhcp, DHO_LEASETIME) != 0) lease->leasetime = ~0U; /* Default to infinite lease */ if (get_option_uint32(ctx, &lease->renewaltime, dhcp, DHO_RENEWALTIME) != 0) lease->renewaltime = 0; if (get_option_uint32(ctx, &lease->rebindtime, dhcp, DHO_REBINDTIME) != 0) lease->rebindtime = 0; if (get_option_addr(ctx, &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; dhcpcd_selectprofile(iface, iface->options->fallback); dhcpcd_startinterface(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->raw_fd != -1) { eloop_event_delete(ifp->ctx->eloop, state->raw_fd, 0); close(state->raw_fd); state->raw_fd = -1; } state->interval = 0; } static int dhcp_openudp(struct interface *ifp) { int s; struct sockaddr_in sin; int n; struct dhcp_state *state; #ifdef SO_BINDTODEVICE struct ifreq ifr; char *p; #endif #ifdef SOCK_CLOEXEC if ((s = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP)) == -1) return -1; #else if ((s = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) return -1; if ((n = fcntl(s, F_GETFD, 0)) == -1 || fcntl(s, F_SETFD, n | FD_CLOEXEC) == -1) { close(s); return -1; } #endif n = 1; if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) == -1) goto eexit; #ifdef SO_BINDTODEVICE if (ifp) { memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifp->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 memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(DHCP_CLIENT_PORT); if (ifp) { state = D_STATE(ifp); sin.sin_addr.s_addr = state->addr.s_addr; } else state = NULL; /* appease gcc */ if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) == -1) goto eexit; return s; eexit: close(s); return -1; } static uint16_t checksum(const void *data, unsigned int len) { const uint8_t *addr = data; uint32_t sum = 0; while (len > 1) { sum += (uint32_t)(addr[0] * 256 + addr[1]); addr += 2; len -= 2; } if (len == 1) sum += (uint32_t)(*addr * 256); sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); return (uint16_t)~htons((uint16_t)sum); } static struct udp_dhcp_packet * dhcp_makeudppacket(size_t *sz, 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 NULL; 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((uint16_t)(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 = (uint16_t)arc4random_uniform(UINT16_MAX); ip->ip_ttl = IPDEFTTL; ip->ip_len = htons((uint16_t)(sizeof(*ip) + sizeof(*udp) + length)); ip->ip_sum = checksum(ip, sizeof(*ip)); *sz = sizeof(*ip) + sizeof(*udp) + length; return udpp; } static void send_message(struct interface *ifp, uint8_t type, void (*callback)(void *)) { struct dhcp_state *state = D_STATE(ifp); struct if_options *ifo = ifp->options; struct dhcp_message *dhcp; struct udp_dhcp_packet *udp; size_t len; ssize_t r; struct in_addr from, to; in_addr_t a = INADDR_ANY; struct timespec tv; int s; #ifdef IN_IFF_NOTUSEABLE struct ipv4_addr *ia; #endif if (!callback) logger(ifp->ctx, LOG_INFO, "%s: sending %s with xid 0x%x", ifp->name, ifo->options & DHCPCD_BOOTP ? "BOOTP" : get_dhcp_op(type), state->xid); else { if (state->interval == 0) state->interval = DHCP_BASE; else { state->interval *= 2; if (state->interval > DHCP_MAX) state->interval = DHCP_MAX; } tv.tv_sec = state->interval + DHCP_RAND_MIN; tv.tv_nsec = (suseconds_t)arc4random_uniform( (DHCP_RAND_MAX - DHCP_RAND_MIN) * NSEC_PER_SEC); timespecnorm(&tv); logger(ifp->ctx, LOG_INFO, "%s: sending %s (xid 0x%x), next in %0.1f seconds", ifp->name, ifo->options & DHCPCD_BOOTP ? "BOOTP" : get_dhcp_op(type), state->xid, timespec_to_double(&tv)); } if (dhcp_open(ifp) == -1) return; if (state->added && !(state->added & STATE_FAKE) && state->addr.s_addr != INADDR_ANY && state->new != NULL && #ifdef IN_IFF_NOTUSEABLE ((ia = ipv4_iffindaddr(ifp, &state->addr, NULL)) && !(ia->addr_flags & IN_IFF_NOTUSEABLE)) && #endif (state->new->cookie == htonl(MAGIC_COOKIE) || ifp->options->options & DHCPCD_INFORM)) { s = dhcp_openudp(ifp); if (s == -1 && errno != EADDRINUSE) logger(ifp->ctx, LOG_ERR, "%s: dhcp_openudp: %m", ifp->name); } else s = -1; /* 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 (s == -1 || (!(ifo->options & DHCPCD_INFORM) && IS_BOOTP(ifp, state->new))) { a = state->addr.s_addr; state->addr.s_addr = INADDR_ANY; } r = make_message(&dhcp, ifp, type); if (r == -1) goto fail; len = (size_t)r; 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 = INADDR_ANY; if (to.s_addr && to.s_addr != INADDR_BROADCAST) { 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); r = sendto(s, (uint8_t *)dhcp, len, 0, (struct sockaddr *)&sin, sizeof(sin)); if (r == -1) logger(ifp->ctx, LOG_ERR, "%s: dhcp_sendpacket: %m", ifp->name); } else { size_t ulen; r = 0; udp = dhcp_makeudppacket(&ulen, (uint8_t *)dhcp, len, from, to); if (udp == NULL) { logger(ifp->ctx, LOG_ERR, "dhcp_makeudppacket: %m"); } else { r = if_sendrawpacket(ifp, ETHERTYPE_IP, (uint8_t *)udp, ulen, NULL); 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) { logger(ifp->ctx, LOG_ERR, "%s: if_sendrawpacket: %m", ifp->name); switch(errno) { case ENETDOWN: case ENETRESET: case ENETUNREACH: break; default: if (!(ifp->ctx->options & DHCPCD_TEST)) dhcp_drop(ifp, "FAIL"); dhcp_free(ifp); eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); callback = NULL; } } } free(dhcp); fail: if (s != -1) close(s); /* 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(ifp->ctx->eloop, &tv, callback, ifp); } 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 *ifp = arg; struct dhcp_state *state = D_STATE(ifp); struct if_options *ifo = ifp->options; rpc_signal_status("Discover"); state->state = DHS_DISCOVER; state->xid = dhcp_xid(ifp); state->nak_receive_count = 0; state->failed_address_offer_count = 0; eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); if (ifo->fallback) eloop_timeout_add_sec(ifp->ctx->eloop, ifo->reboot, dhcp_fallback, ifp); else if (ifo->options & DHCPCD_IPV4LL && !IN_LINKLOCAL(htonl(state->addr.s_addr))) eloop_timeout_add_sec(ifp->ctx->eloop, ifo->reboot, ipv4ll_start, ifp); if (ifo->options & DHCPCD_REQUEST) logger(ifp->ctx, LOG_INFO, "%s: soliciting a DHCP lease (requesting %s)", ifp->name, inet_ntoa(ifo->req_addr)); else logger(ifp->ctx, LOG_INFO, "%s: soliciting a %s lease", ifp->name, ifo->options & DHCPCD_BOOTP ? "BOOTP" : "DHCP"); send_discover(ifp); } static void dhcp_request(void *arg) { struct interface *ifp = arg; struct dhcp_state *state = D_STATE(ifp); logger(ifp->ctx, LOG_INFO, "%s: requesting lease of %s", ifp->name, inet_ntoa(state->lease.addr)); rpc_signal_status("Request"); state->state = DHS_REQUEST; state->nak_receive_count = 0; send_request(ifp); } static void dhcp_expire(void *arg) { struct interface *ifp = arg; struct dhcp_state *state = D_STATE(ifp); logger(ifp->ctx, LOG_ERR, "%s: DHCP lease expired", ifp->name); eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); dhcp_drop(ifp, "EXPIRE"); unlink(state->leasefile); state->interval = 0; dhcp_discover(ifp); } static 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; rpc_signal_status("Renew"); logger(ifp->ctx, LOG_INFO, "%s: renewing lease of %s", ifp->name, inet_ntoa(lease->addr)); logger(ifp->ctx, 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); state->nak_receive_count = 0; send_renew(ifp); } #ifndef IN_IFF_TENTATIVE static void dhcp_arp_announced(struct arp_state *astate) { arp_close(astate->iface); } #endif static void dhcp_rebind(void *arg) { struct interface *ifp = arg; struct dhcp_state *state = D_STATE(ifp); struct dhcp_lease *lease = &state->lease; rpc_signal_status("Rebind"); logger(ifp->ctx, LOG_WARNING, "%s: failed to renew DHCP, rebinding", ifp->name); logger(ifp->ctx, LOG_DEBUG, "%s: expire in %"PRIu32" seconds", ifp->name, lease->leasetime - lease->rebindtime); state->state = DHS_REBIND; eloop_timeout_delete(ifp->ctx->eloop, send_renew, ifp); state->lease.server.s_addr = 0; state->nak_receive_count = 0; ifp->options->options &= ~(DHCPCD_CSR_WARNED | DHCPCD_ROUTER_HOST_ROUTE_WARNED); send_rebind(ifp); } static void init_option_iterator(const struct dhcp_message *message, struct dhcp_option_iterator *iterator) { iterator->message = message; iterator->ptr = message->options; iterator->end = iterator->ptr + sizeof(message->options); iterator->extra_option_locations = 0; iterator->extra_option_locations_set = 0; } static int iterate_next_option(struct dhcp_option_iterator *iterator, uint8_t *option, uint8_t *length, const uint8_t **value) { uint8_t option_code; uint8_t option_len; /* Process special DHO_PAD and DHO_END opcodes. */ while (iterator->ptr < iterator->end) { if (*iterator->ptr == DHO_PAD) { iterator->ptr++; continue; } if (*iterator->ptr != DHO_END) break; if (iterator->extra_option_locations & OPTION_OVERLOADED_BOOT_FILE) { iterator->extra_option_locations &= ~OPTION_OVERLOADED_BOOT_FILE; iterator->ptr = iterator->message->bootfile; iterator->end = iterator->ptr + sizeof(iterator->message->bootfile); } else if (iterator->extra_option_locations & OPTION_OVERLOADED_SERVER_NAME) { iterator->extra_option_locations &= ~OPTION_OVERLOADED_SERVER_NAME; iterator->ptr = iterator->message->servername; iterator->end = iterator->ptr + sizeof(iterator->message->servername); } else return 0; } if (iterator->ptr + 2 > iterator->end) return 0; option_code = *iterator->ptr++; option_len = *iterator->ptr++; if (iterator->ptr + option_len > iterator->end) return 0; if (option_code == DHO_OPTIONSOVERLOADED && option_len > 0 && !iterator->extra_option_locations_set) { iterator->extra_option_locations = *iterator->ptr; iterator->extra_option_locations_set = 1; } if (option) *option = option_code; if (length) *length = option_len; if (value) *value = iterator->ptr; iterator->ptr += option_len; return 1; } static void merge_option_values(const struct dhcp_message *src, struct dhcp_message *dst, uint8_t *copy_options) { uint8_t supplied_options[OPTION_MASK_SIZE]; struct dhcp_option_iterator dst_iterator; struct dhcp_option_iterator src_iterator; uint8_t option; const uint8_t *option_value; uint8_t option_length; uint8_t *out; const uint8_t *out_end; int added_options = 0; /* Traverse the destination message for options already supplied. */ memset(&supplied_options, 0, sizeof(supplied_options)); init_option_iterator(dst, &dst_iterator); while (iterate_next_option(&dst_iterator, &option, NULL, NULL)) { add_option_mask(supplied_options, option); } /* We will start merging options at the end of the last block * the iterator traversed to. The const cast below is safe since * this points to data within the (non-const) dst message. */ out = (uint8_t *) dst_iterator.ptr; out_end = dst_iterator.end; init_option_iterator(src, &src_iterator); while (iterate_next_option(&src_iterator, &option, &option_length, &option_value)) { if (has_option_mask(supplied_options, option) || !has_option_mask(copy_options, option)) continue; /* We need space for this option, plus a trailing DHO_END. */ if (out + option_length + 3 > out_end) { syslog(LOG_ERR, "%s: unable to fit option %d (length %d)", __func__, option, option_length); continue; } *out++ = option; *out++ = option_length; memcpy(out, option_value, option_length); out += option_length; added_options++; } if (added_options) { *out++ = DHO_END; syslog(LOG_INFO, "carrying over %d options from original offer", added_options); } } void dhcp_bind(struct interface *ifp, struct arp_state *astate) { struct dhcp_state *state = D_STATE(ifp); struct if_options *ifo = ifp->options; struct dhcp_lease *lease = &state->lease; uint8_t ipv4ll = 0; if (state->state == DHS_BOUND) goto applyaddr; state->reason = NULL; free(state->old); state->old = state->new; state->new = state->offer; state->offer = NULL; get_lease(ifp->ctx, lease, state->new); if (ifo->options & DHCPCD_STATIC) { logger(ifp->ctx, LOG_INFO, "%s: using static address %s/%d", ifp->name, inet_ntoa(lease->addr), inet_ntocidr(lease->net)); lease->leasetime = ~0U; state->reason = "STATIC"; } else if (state->new->cookie != htonl(MAGIC_COOKIE)) { logger(ifp->ctx, LOG_INFO, "%s: using IPv4LL address %s", ifp->name, inet_ntoa(lease->addr)); lease->leasetime = ~0U; state->reason = "IPV4LL"; ipv4ll = 1; } 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; logger(ifp->ctx, LOG_INFO, "%s: received approval for %s", ifp->name, inet_ntoa(lease->addr)); lease->leasetime = ~0U; state->reason = "INFORM"; } else { if (lease->frominfo) state->reason = "TIMEOUT"; if (lease->leasetime == ~0U) { lease->renewaltime = lease->rebindtime = lease->leasetime; logger(ifp->ctx, LOG_INFO, "%s: leased %s for infinity", ifp->name, inet_ntoa(lease->addr)); } else { if (lease->leasetime < DHCP_MIN_LEASE) { logger(ifp->ctx, LOG_WARNING, "%s: minimum lease is %d seconds", ifp->name, DHCP_MIN_LEASE); lease->leasetime = DHCP_MIN_LEASE; } if (lease->rebindtime == 0) lease->rebindtime = (uint32_t)(lease->leasetime * T2); else if (lease->rebindtime >= lease->leasetime) { lease->rebindtime = (uint32_t)(lease->leasetime * T2); logger(ifp->ctx, LOG_WARNING, "%s: rebind time greater than lease " "time, forcing to %"PRIu32" seconds", ifp->name, lease->rebindtime); } if (lease->renewaltime == 0) lease->renewaltime = (uint32_t)(lease->leasetime * T1); else if (lease->renewaltime > lease->rebindtime) { lease->renewaltime = (uint32_t)(lease->leasetime * T1); logger(ifp->ctx, LOG_WARNING, "%s: renewal time greater than rebind " "time, forcing to %"PRIu32" seconds", ifp->name, lease->renewaltime); } logger(ifp->ctx, lease->addr.s_addr == state->addr.s_addr && !(state->added & STATE_FAKE) ? LOG_DEBUG : LOG_INFO, "%s: leased %s for %"PRIu32" seconds", ifp->name, inet_ntoa(lease->addr), lease->leasetime); } } if (ifp->ctx->options & DHCPCD_TEST) { state->reason = "TEST"; script_runreason(ifp, state->reason); eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS); return; } if (state->reason == NULL) { if (state->old && !(state->added & STATE_FAKE)) { 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 (state->old && state->old->yiaddr == state->new->yiaddr && (state->state == DHS_REBOOT || state->state == DHS_RENEW || state->state == DHS_REBIND)) { /* Some DHCP servers respond to REQUEST with a subset * of the original requested parameters. If they were not * supplied in the response to a renewal, we should assume * that it's reasonable to transfer them forward from the * original offer. */ merge_option_values(state->old, state->new, ifo->requestmask); } if (lease->leasetime == ~0U) lease->renewaltime = lease->rebindtime = lease->leasetime; else { eloop_timeout_add_sec(ifp->ctx->eloop, (time_t)lease->renewaltime, dhcp_renew, ifp); eloop_timeout_add_sec(ifp->ctx->eloop, (time_t)lease->rebindtime, dhcp_rebind, ifp); eloop_timeout_add_sec(ifp->ctx->eloop, (time_t)lease->leasetime, dhcp_expire, ifp); logger(ifp->ctx, LOG_DEBUG, "%s: renew in %"PRIu32" seconds, rebind in %"PRIu32 " seconds", ifp->name, lease->renewaltime, lease->rebindtime); } if (!(ifo->options & DHCPCD_STATIC) && state->new->cookie != htonl(MAGIC_COOKIE)) state->state = DHS_IPV4LL_BOUND; else state->state = DHS_BOUND; if (!state->lease.frominfo && !(ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC))) if (write_lease(ifp, state->new) == -1) logger(ifp->ctx, LOG_ERR, "%s: write_lease: %m", __func__); applyaddr: ipv4_applyaddr(ifp); if (ifo->options & DHCPCD_ARP && !(ifp->ctx->options & DHCPCD_FORKED)) { #ifdef IN_IFF_TENTATIVE if (astate) arp_free_but(astate); else if (!ipv4ll) arp_close(ifp); #else if (state->added) { if (astate == NULL) { astate = arp_new(ifp, &state->addr); astate->announced_cb = dhcp_arp_announced; } if (astate) { arp_announce(astate); if (!ipv4ll) arp_free_but(astate); } } else if (!ipv4ll) arp_close(ifp); #endif } } static void dhcp_timeout(void *arg) { struct interface *ifp = arg; struct dhcp_state *state = D_STATE(ifp); dhcp_bind(ifp, NULL); 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) { logger(ifp->ctx, 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(ifp->ctx->eloop, NULL, ifp); dhcp_bind(ifp, NULL); } } 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; logger(ifp->ctx, LOG_INFO, "%s: informing peers of local address", ifp->name); if (ifp->ctx->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_iffindaddr(ifp, NULL, NULL); if (ap == NULL) { logger(ifp->ctx, 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, NULL); ifo->options &= ~DHCPCD_STATIC; } } rpc_signal_status("Inform"); state->state = DHS_INFORM; state->xid = dhcp_xid(ifp); send_inform(ifp); } void dhcp_reboot_newopts(struct interface *ifp, unsigned long long 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 start_unicast_arp(struct interface *ifp); static void dhcp_reboot(struct interface *ifp) { struct if_options *ifo; struct dhcp_state *state = D_STATE(ifp); if (state == NULL) return; rpc_signal_status("Reboot"); ifo = ifp->options; state->state = DHS_REBOOT; state->interval = 0; if (ifo->options & DHCPCD_LINK && ifp->carrier == LINK_DOWN) { logger(ifp->ctx, LOG_INFO, "%s: waiting for carrier", ifp->name); return; } if (ifo->options & DHCPCD_STATIC) { dhcp_static(ifp); return; } if (ifo->options & DHCPCD_UNICAST_ARP) { start_unicast_arp(ifp); } if (ifo->options & DHCPCD_INFORM) { logger(ifp->ctx, LOG_INFO, "%s: informing address of %s", ifp->name, inet_ntoa(state->lease.addr)); dhcp_inform(ifp); return; } if (ifo->reboot == 0 || state->offer == NULL) { dhcp_discover(ifp); return; } if (state->offer->cookie == 0) return; logger(ifp->ctx, LOG_INFO, "%s: rebinding lease of %s", ifp->name, inet_ntoa(state->lease.addr)); state->xid = dhcp_xid(ifp); state->lease.server.s_addr = 0; eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); /* Need to add this before dhcp_expire and friends. */ if (!ifo->fallback && ifo->options & DHCPCD_IPV4LL && !IN_LINKLOCAL(htonl(state->addr.s_addr))) eloop_timeout_add_sec(ifp->ctx->eloop, ifo->reboot, ipv4ll_start, ifp); if (ifo->options & DHCPCD_LASTLEASE && state->lease.frominfo) eloop_timeout_add_sec(ifp->ctx->eloop, ifo->reboot, dhcp_timeout, ifp); else if (!(ifo->options & DHCPCD_INFORM)) eloop_timeout_add_sec(ifp->ctx->eloop, ifo->reboot, dhcp_expire, ifp); /* Don't bother ARP checking as the server could NAK us first. * Don't call dhcp_request as that would change the state */ send_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); /* dhcp_start may just have been called and we don't yet have a state * but we do have a timeout, so punt it. */ if (state == NULL) { eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); return; } /* Don't reset DHCP state if we have an IPv4LL address and link is up, * unless the interface is departing. */ if (state->state != DHS_IPV4LL_BOUND || ifp->carrier != LINK_UP || ifp->options->options & DHCPCD_DEPARTED) { eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); dhcp_auth_reset(&state->auth); dhcp_close(ifp); } if (ifp->options->options & DHCPCD_RELEASE || strcmp(reason, "RELEASE") == 0) { unlink(state->leasefile); if (ifp->carrier != LINK_DOWN && state->new != NULL && state->new->cookie == htonl(MAGIC_COOKIE)) { logger(ifp->ctx, 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 | DHCPCD_ROUTER_HOST_ROUTE_WARNED); } static void log_dhcp1(int lvl, const char *msg, const struct interface *ifp, const struct dhcp_message *dhcp, const struct in_addr *from, int ad) { const char *tfrom; char *a, sname[sizeof(dhcp->servername) * 4]; struct in_addr addr; int r; if (strcmp(msg, "NAK:") == 0) { a = get_option_string(ifp->ctx, dhcp, DHO_MESSAGE); if (a) { char *tmp; size_t al, tmpl; al = strlen(a); tmpl = (al * 4) + 1; tmp = malloc(tmpl); if (tmp == NULL) { logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); free(a); return; } print_string(tmp, tmpl, STRING, (uint8_t *)a, al); free(a); a = tmp; } } else if (ad && dhcp->yiaddr != 0) { addr.s_addr = dhcp->yiaddr; a = strdup(inet_ntoa(addr)); if (a == NULL) { logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); return; } } else a = NULL; tfrom = "from"; r = get_option_addr(ifp->ctx, &addr, dhcp, DHO_SERVERID); if (dhcp->servername[0] && r == 0) { print_string(sname, sizeof(sname), STRING, dhcp->servername, strlen((const char *)dhcp->servername)); if (a == NULL) logger(ifp->ctx, lvl, "%s: %s %s %s `%s'", ifp->name, msg, tfrom, inet_ntoa(addr), sname); else logger(ifp->ctx, lvl, "%s: %s %s %s %s `%s'", ifp->name, msg, a, tfrom, inet_ntoa(addr), sname); } else { if (r != 0) { tfrom = "via"; addr = *from; } if (a == NULL) logger(ifp->ctx, lvl, "%s: %s %s %s", ifp->name, msg, tfrom, inet_ntoa(addr)); else logger(ifp->ctx, lvl, "%s: %s %s %s %s", ifp->name, msg, a, tfrom, inet_ntoa(addr)); } free(a); } static void log_dhcp(int lvl, const char *msg, const struct interface *ifp, const struct dhcp_message *dhcp, const struct in_addr *from) { log_dhcp1(lvl, msg, ifp, dhcp, from, 1); } 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 save_gateway_addr(struct interface *ifp, const uint8_t *gw_hwaddr) { struct dhcp_state *state = D_STATE(ifp); memcpy(state->server_info.gw_hwaddr, gw_hwaddr, ifp->hwlen); state->server_info.gw_hwlen = ifp->hwlen; } static void dhcp_probe_gw_timeout(struct arp_state *astate) { struct dhcp_state *state = D_STATE(astate->iface); /* Ignore unicast ARP failures. */ if (astate->dest_hwlen) return; /* Probegw failure, allow ourselves to fail only once this way */ logger(astate->iface->ctx, LOG_ERR, "%s: Probe gateway %s timed out ", astate->iface->name, inet_ntoa(astate->addr)); astate->iface->options->options &= ~DHCPCD_ARPGW; unlink(state->leasefile); if (!state->lease.frominfo) dhcp_decline(astate->iface); #ifdef IN_IFF_DUPLICATED ia = ipv4_iffindaddr(astate->iface, &astate->addr, NULL); if (ia) ipv4_deladdr(astate->iface, &ia->addr, &ia->net); #endif eloop_timeout_delete(astate->iface->ctx->eloop, NULL, astate->iface); eloop_timeout_add_sec(astate->iface->ctx->eloop, DHCP_RAND_MAX, dhcp_discover, astate->iface); } static void dhcp_probe_gw_response(struct arp_state *astate, const struct arp_msg *amsg) { /* Verify this is a response for the gateway probe. */ if (astate->src_addr.s_addr != 0 && amsg && amsg->tip.s_addr == astate->src_addr.s_addr && amsg->sip.s_addr == astate->addr.s_addr) { if (astate->dest_hwlen) { /* Response to unicast ARP. */ rpc_notify_unicast_arp(astate->iface); } else { /* Response to arpgw request. */ save_gateway_addr(astate->iface, amsg->sha); dhcp_close(astate->iface); eloop_timeout_delete(astate->iface->ctx->eloop, NULL, astate->iface); #ifdef IN_IFF_TENTATIVE ipv4_finaliseaddr(astate->iface); #else dhcp_bind(astate->iface, NULL); #endif } arp_close(astate->iface); } } static int dhcp_probe_gw(struct interface *ifp) { struct dhcp_state *state = D_STATE(ifp); struct arp_state *astate; struct in_addr gateway_addr; if (get_option_addr(ifp->ctx, &gateway_addr, state->offer, DHO_ROUTER) == 0) { astate = arp_new(ifp, &gateway_addr); if (astate) { astate->src_addr.s_addr = state->offer->yiaddr; astate->probed_cb = dhcp_probe_gw_timeout; astate->conflicted_cb = dhcp_probe_gw_response; arp_probe(astate); return 1; } } return 0; } static void start_unicast_arp(struct interface *ifp) { struct dhcp_state *state = D_STATE(ifp); struct in_addr gwa; struct in_addr src_addr; struct arp_state *astate; if (!state->offer) return; if (!state->lease.frominfo) return; if (state->server_info.gw_hwlen != ifp->hwlen) return; if (get_option_addr(ifp->ctx, &gwa, state->offer, DHO_ROUTER)) return; astate = arp_new(ifp, &gwa); if (!astate) return; if (state->offer->yiaddr) astate->src_addr.s_addr = state->offer->yiaddr; else astate->src_addr.s_addr = state->offer->ciaddr; astate->probed_cb = dhcp_probe_gw_timeout; astate->conflicted_cb = dhcp_probe_gw_response; astate->dest_hwlen = state->server_info.gw_hwlen; memcpy(astate->dest_hwaddr, state->server_info.gw_hwaddr, state->server_info.gw_hwlen); arp_probe(astate); /* Invalidate our gateway address until the next successful PROBEGW. */ state->server_info.gw_hwlen = 0; } static void dhcp_arp_probed(struct arp_state *astate) { struct dhcp_state *state; struct if_options *ifo; /* We didn't find a profile for this * address or hwaddr, so move to the next * arping profile */ state = D_STATE(astate->iface); ifo = astate->iface->options; if (state->arping_index < ifo->arping_len) { if (++state->arping_index < ifo->arping_len) { astate->addr.s_addr = ifo->arping[state->arping_index - 1]; arp_probe(astate); } dhcpcd_startinterface(astate->iface); return; } /* Probe the gateway specified in the lease offer. */ if ((ifo->options & DHCPCD_ARPGW) && (dhcp_probe_gw(astate->iface))) { return; } dhcp_close(astate->iface); eloop_timeout_delete(astate->iface->ctx->eloop, NULL, astate->iface); #ifdef IN_IFF_TENTATIVE ipv4_finaliseaddr(astate->iface); arp_close(astate->iface); #else dhcp_bind(astate->iface, astate); #endif } static void dhcp_arp_conflicted(struct arp_state *astate, const struct arp_msg *amsg) { struct dhcp_state *state; struct if_options *ifo; state = D_STATE(astate->iface); ifo = astate->iface->options; if (state->arping_index && state->arping_index <= ifo->arping_len && amsg && (amsg->sip.s_addr == ifo->arping[state->arping_index - 1] || (amsg->sip.s_addr == 0 && amsg->tip.s_addr == ifo->arping[state->arping_index - 1]))) { char buf[HWADDR_LEN * 3]; astate->failed.s_addr = ifo->arping[state->arping_index - 1]; arp_report_conflicted(astate, amsg); hwaddr_ntoa(amsg->sha, astate->iface->hwlen, buf, sizeof(buf)); if (dhcpcd_selectprofile(astate->iface, buf) == -1 && dhcpcd_selectprofile(astate->iface, inet_ntoa(astate->failed)) == -1) { /* We didn't find a profile for this * address or hwaddr, so move to the next * arping profile */ dhcp_arp_probed(astate); return; } dhcp_close(astate->iface); arp_close(astate->iface); eloop_timeout_delete(astate->iface->ctx->eloop, NULL, astate->iface); dhcpcd_startinterface(astate->iface); } /* RFC 2131 3.1.5, Client-server interaction * NULL amsg means IN_IFF_DUPLICATED */ if (amsg == NULL || (state->offer && (amsg->sip.s_addr == state->offer->yiaddr || (amsg->sip.s_addr == 0 && amsg->tip.s_addr == state->offer->yiaddr)))) { #ifdef IN_IFF_DUPLICATED struct ipv4_addr *ia; #endif if (amsg) { astate->failed.s_addr = state->offer->yiaddr; state->failed.s_addr = state->offer->yiaddr; } else { astate->failed = astate->addr; state->failed = astate->addr; } arp_report_conflicted(astate, amsg); unlink(state->leasefile); if (!state->lease.frominfo) dhcp_decline(astate->iface); #ifdef IN_IFF_DUPLICATED ia = ipv4_iffindaddr(astate->iface, &astate->addr, NULL); if (ia) ipv4_deladdr(astate->iface, &ia->addr, &ia->net); #endif arp_close(astate->iface); eloop_timeout_delete(astate->iface->ctx->eloop, NULL, astate->iface); eloop_timeout_add_sec(astate->iface->ctx->eloop, DHCP_RAND_MAX, dhcp_discover, astate->iface); } } static void handle_nak(void *arg) { struct interface *ifp = arg; struct dhcp_state *state = D_STATE(ifp); logger(ifp->ctx, LOG_INFO, "%s: Handling deferred NAK", ifp->name); if (!(ifp->ctx->options & DHCPCD_TEST)) { dhcp_drop(ifp, "NAK"); unlink(state->leasefile); } /* If we constantly get NAKS then we should slowly back off */ eloop_timeout_add_sec(ifp->ctx->eloop, state->nakoff, dhcp_discover, ifp); if (state->nakoff == 0) state->nakoff = 1; else { state->nakoff *= 2; if (state->nakoff > NAKOFF_MAX) state->nakoff = NAKOFF_MAX; } } static void dhcp_handledhcp(struct interface *ifp, struct dhcp_message **dhcpp, const struct in_addr *from) { struct dhcp_state *state = D_STATE(ifp); struct if_options *ifo = ifp->options; struct dhcp_message *dhcp = *dhcpp; struct dhcp_lease *lease = &state->lease; uint8_t type, tmp; const uint8_t *auth; struct in_addr addr; unsigned int i; size_t auth_len; char *msg; struct arp_state *astate; struct ipv4_addr *ia; /* We may have found a BOOTP server */ if (get_option_uint8(ifp->ctx, &type, dhcp, DHO_MESSAGETYPE) == -1) type = 0; else if (ifo->options & DHCPCD_BOOTP) { logger(ifp->ctx, LOG_DEBUG, "%s: ignoring DHCP reply (excpecting BOOTP)", ifp->name); return; } logger(ifp->ctx, LOG_INFO, "%s: received %s with xid 0x%x", ifp->name, get_dhcp_op(type), state->xid); /* Authenticate the message */ auth = get_option(ifp->ctx, 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) { logger(ifp->ctx, LOG_DEBUG, "%s: dhcp_auth_validate: %m", ifp->name); log_dhcp1(LOG_ERR, "authentication failed", ifp, dhcp, from, 0); return; } if (state->auth.token) logger(ifp->ctx, LOG_DEBUG, "%s: validated using 0x%08" PRIu32, ifp->name, state->auth.token->secretid); else logger(ifp->ctx, LOG_DEBUG, "%s: accepted reconfigure key", ifp->name); } else if (ifo->auth.options & DHCPCD_AUTH_REQUIRE) { log_dhcp1(LOG_ERR, "no authentication", ifp, dhcp, from, 0); return; } else if (ifo->auth.options & DHCPCD_AUTH_SEND) log_dhcp1(LOG_WARNING, "no authentication", ifp, dhcp, from, 0); /* RFC 3203 */ if (type == DHCP_FORCERENEW) { if (from->s_addr == INADDR_ANY || from->s_addr == INADDR_BROADCAST) { log_dhcp(LOG_ERR, "discarding Force Renew", ifp, dhcp, from); return; } if (auth == NULL) { log_dhcp(LOG_ERR, "unauthenticated Force Renew", ifp, dhcp, from); return; } if (state->state != DHS_BOUND && state->state != DHS_INFORM) { log_dhcp(LOG_DEBUG, "not bound, ignoring Force Renew", ifp, dhcp, from); return; } log_dhcp(LOG_ERR, "Force Renew from", ifp, dhcp, from); /* The rebind and expire timings are still the same, we just * enter the renew state early */ if (state->state == DHS_BOUND) { eloop_timeout_delete(ifp->ctx->eloop, dhcp_renew, ifp); dhcp_renew(ifp); } else { eloop_timeout_delete(ifp->ctx->eloop, send_inform, ifp); dhcp_inform(ifp); } return; } if (state->state == DHS_BOUND) { /* Before we supported FORCERENEW we closed off the raw * port so we effectively ignored all messages. * As such we'll not log by default here. */ //log_dhcp(LOG_DEBUG, "bound, ignoring", iface, dhcp, from); return; } /* Ensure it's the right transaction */ if (state->xid != ntohl(dhcp->xid)) { logger(ifp->ctx, LOG_DEBUG, "%s: wrong xid 0x%x (expecting 0x%x) from %s", ifp->name, ntohl(dhcp->xid), state->xid, inet_ntoa(*from)); return; } /* reset the message counter */ state->interval = 0; /* Ensure that no reject options are present */ for (i = 1; i < 255; i++) { if (has_option_mask(ifo->rejectmask, i) && get_option_uint8(ifp->ctx, &tmp, dhcp, (uint8_t)i) == 0) { log_dhcp(LOG_WARNING, "reject DHCP", ifp, dhcp, from); return; } } if (type == DHCP_NAK) { if ((msg = get_option_string(ifp->ctx, dhcp, DHO_MESSAGE))) { logger(ifp->ctx, LOG_WARNING, "%s: message: %s", ifp->name, msg); free(msg); } if (state->state == DHS_INFORM) /* INFORM should not be NAKed */ return; log_dhcp(LOG_WARNING, "NAK (deferred):", ifp, dhcp, from); rpc_signal_status("NakDefer"); if (state->nak_receive_count == 0) eloop_timeout_add_sec(ifp->ctx->eloop, DHCP_BASE, handle_nak, ifp); state->nak_receive_count++; return; } /* Ensure that all required options are present */ for (i = 1; i < 255; i++) { if (has_option_mask(ifo->requiremask, i) && get_option_uint8(ifp->ctx, &tmp, dhcp, (uint8_t)i) != 0) { /* If we are BOOTP, then ignore the need for serverid. * To ignore BOOTP, require dhcp_message_type. * However, nothing really stops BOOTP from providing * DHCP style options as well so the above isn't * always true. */ if (type == 0 && i == DHO_SERVERID) continue; log_dhcp(LOG_WARNING, "reject DHCP", ifp, dhcp, from); return; } } /* DHCP Auto-Configure, RFC 2563 */ if (type == DHCP_OFFER && dhcp->yiaddr == 0) { log_dhcp(LOG_WARNING, "no address given", ifp, dhcp, from); if ((msg = get_option_string(ifp->ctx, dhcp, DHO_MESSAGE))) { logger(ifp->ctx, LOG_WARNING, "%s: message: %s", ifp->name, msg); free(msg); } if ((state->state == DHS_DISCOVER || state->state == DHS_IPV4LL_BOUND) && get_option_uint8(ifp->ctx, &tmp, dhcp, DHO_AUTOCONFIGURE) == 0) { switch (tmp) { case 0: log_dhcp(LOG_WARNING, "IPv4LL disabled from", ifp, dhcp, from); dhcp_drop(ifp, "EXPIRE"); arp_close(ifp); eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); eloop_timeout_add_sec(ifp->ctx->eloop, DHCP_MAX, dhcp_discover, ifp); break; case 1: log_dhcp(LOG_WARNING, "IPv4LL enabled from", ifp, dhcp, from); eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); if (IN_LINKLOCAL(htonl(state->addr.s_addr))) eloop_timeout_add_sec(ifp->ctx->eloop, DHCP_MAX, dhcp_discover, ifp); else ipv4ll_start(ifp); break; default: logger(ifp->ctx, LOG_ERR, "%s: unknown auto configuration option %d", ifp->name, tmp); break; } } 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)) { rpc_signal_status("IgnoreInvalidOffer"); log_dhcp(LOG_WARNING, "reject invalid address", ifp, dhcp, from); return; } #ifdef IN_IFF_DUPLICATED ia = ipv4_iffindaddr(ifp, &lease->addr, NULL); if (ia && ia->addr_flags & IN_IFF_DUPLICATED) { log_dhcp(LOG_WARNING, "declined duplicate address", ifp, dhcp, from); if (type) dhcp_decline(ifp); ipv4_deladdr(ifp, &ia->addr, &ia->net); eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); eloop_timeout_add_sec(ifp->ctx->eloop, DHCP_RAND_MAX, dhcp_discover, ifp); return; } #endif if ((type == 0 || type == DHCP_OFFER) && (state->state == DHS_DISCOVER || state->state == DHS_IPV4LL_BOUND)) { if (dhcp->yiaddr == state->failed.s_addr && state->failed_address_offer_count == 0) { log_dhcp(LOG_WARNING, "reject previously declined address", ifp, dhcp, from); rpc_signal_status("IgnoreFailedOffer"); state->failed_address_offer_count++; return; } lease->frominfo = 0; lease->addr.s_addr = dhcp->yiaddr; lease->cookie = dhcp->cookie; if (type == 0 || get_option_addr(ifp->ctx, &lease->server, dhcp, DHO_SERVERID) != 0) lease->server.s_addr = INADDR_ANY; log_dhcp(LOG_INFO, "offered", ifp, dhcp, from); free(state->offer); state->offer = dhcp; *dhcpp = NULL; if (ifp->ctx->options & DHCPCD_TEST) { free(state->old); state->old = state->new; state->new = state->offer; state->offer = NULL; state->reason = "TEST"; script_runreason(ifp, state->reason); eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS); return; } eloop_timeout_delete(ifp->ctx->eloop, send_discover, ifp); eloop_timeout_delete(ifp->ctx->eloop, handle_nak, ifp); /* 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(ifp); return; } } if (type) { if (type == DHCP_OFFER) { log_dhcp(LOG_WARNING, "ignoring offer of", ifp, dhcp, from); rpc_signal_status("IgnoreAdditionalOffer"); return; } /* We should only be dealing with acks */ if (type != DHCP_ACK) { log_dhcp(LOG_ERR, "not ACK or OFFER", ifp, dhcp, from); rpc_signal_status("IgnoreNonOffer"); return; } if (!(ifo->options & DHCPCD_INFORM)) log_dhcp(LOG_INFO, "acknowledged", ifp, 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(ifp->ctx->eloop, NULL, ifp); astate = NULL; #ifdef IN_IFF_TENTATIVE addr.s_addr = state->offer->yiaddr; astate = arp_new(ifp, &addr); if (astate) { astate->probed_cb = dhcp_arp_probed; astate->conflicted_cb = dhcp_arp_conflicted; /* No need to start the probe as we'll * listen to the kernel stating DAD or not and * that action look look for our ARP state for * what to do. */ } #else if ((ifo->options & DHCPCD_ARP || state->nak_receive_count > 0 || dhcp->yiaddr == state->failed.s_addr) && state->addr.s_addr != state->offer->yiaddr) { addr.s_addr = state->offer->yiaddr; /* If the interface already has the address configured * then we can't ARP for duplicate detection. */ ia = ipv4_findaddr(ifp->ctx, &addr); if (ia == NULL) { astate = arp_new(ifp, &addr); if (astate) { astate->probed_cb = dhcp_arp_probed; astate->conflicted_cb = dhcp_arp_conflicted; arp_probe(astate); rpc_signal_status("ArpSelf"); } return; } } #endif if ((ifo->options & DHCPCD_ARPGW) && (dhcp_probe_gw(ifp))) { rpc_signal_status("ArpGateway"); return; } dhcp_bind(ifp, astate); } static size_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)) { syslog(LOG_WARNING, "packet short than an ip header " "(len=%zd)", data_len); 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)) { syslog(LOG_WARNING, "packet too long (%zd bytes)", data_len); errno = EINVAL; return -1; } if (checksum(&p.ip, sizeof(p.ip)) != 0) { syslog(LOG_WARNING, "packet failed ip header checksum"); errno = EINVAL; return -1; } bytes = ntohs(p.ip.ip_len); if (data_len < bytes) { syslog(LOG_WARNING, "packet appears truncated " "(len=%zd, ip_len=%zd)", 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) { syslog(LOG_WARNING, "packet failed udp checksum"); errno = EINVAL; return -1; } } return 0; } static void dhcp_handlepacket(void *arg) { struct interface *ifp = arg; struct dhcp_message *dhcp = NULL; const uint8_t *pp; size_t bytes; struct in_addr from; int i, flags; const struct dhcp_state *state = D_CSTATE(ifp); /* Need this API due to BPF */ flags = 0; while (!(flags & RAW_EOF)) { bytes = (size_t)if_readrawpacket(ifp, ETHERTYPE_IP, ifp->ctx->packet, udp_dhcp_len, &flags); if ((ssize_t)bytes == -1) { logger(ifp->ctx, LOG_ERR, "%s: dhcp if_readrawpacket: %m", ifp->name); dhcp_close(ifp); arp_close(ifp); break; } if (valid_udp_packet(ifp->ctx->packet, bytes, &from, flags & RAW_PARTIALCSUM) == -1) { logger(ifp->ctx, LOG_ERR, "%s: invalid UDP packet from %s", ifp->name, inet_ntoa(from)); continue; } i = whitelisted_ip(ifp->options, from.s_addr); if (i == 0) { logger(ifp->ctx, LOG_WARNING, "%s: non whitelisted DHCP packet from %s", ifp->name, inet_ntoa(from)); continue; } else if (i != 1 && blacklisted_ip(ifp->options, from.s_addr) == 1) { logger(ifp->ctx, LOG_WARNING, "%s: blacklisted DHCP packet from %s", ifp->name, inet_ntoa(from)); continue; } if (ifp->flags & IFF_POINTOPOINT && state->dst.s_addr != from.s_addr) { logger(ifp->ctx, LOG_WARNING, "%s: server %s is not destination", ifp->name, inet_ntoa(from)); } bytes = get_udp_data(&pp, ifp->ctx->packet); if (bytes > sizeof(*dhcp)) { logger(ifp->ctx, LOG_ERR, "%s: packet greater than DHCP size from %s", ifp->name, inet_ntoa(from)); continue; } if (dhcp == NULL) { dhcp = calloc(1, sizeof(*dhcp)); if (dhcp == NULL) { logger(ifp->ctx, LOG_ERR, "%s: calloc: %m", __func__); break; } } memcpy(dhcp, pp, bytes); if (dhcp->cookie != htonl(MAGIC_COOKIE)) { logger(ifp->ctx, LOG_DEBUG, "%s: bogus cookie from %s", ifp->name, inet_ntoa(from)); continue; } /* Ensure packet is for us */ if (ifp->hwlen <= sizeof(dhcp->chaddr) && memcmp(dhcp->chaddr, ifp->hwaddr, ifp->hwlen)) { char buf[sizeof(dhcp->chaddr) * 3]; logger(ifp->ctx, LOG_DEBUG, "%s: xid 0x%x is for hwaddr %s", ifp->name, ntohl(dhcp->xid), hwaddr_ntoa(dhcp->chaddr, sizeof(dhcp->chaddr), buf, sizeof(buf))); continue; } dhcp_handledhcp(ifp, &dhcp, &from); if (state->raw_fd == -1) break; } free(dhcp); } static void dhcp_handleudp(void *arg) { struct dhcpcd_ctx *ctx; uint8_t buffer[sizeof(struct dhcp_message)]; ctx = arg; /* Just read what's in the UDP fd and discard it as we always read * from the raw fd */ if (read(ctx->udp_fd, buffer, sizeof(buffer)) == -1) { logger(ctx, LOG_ERR, "%s: %m", __func__); eloop_event_delete(ctx->eloop, ctx->udp_fd, 0); close(ctx->udp_fd); ctx->udp_fd = -1; } } static int dhcp_open(struct interface *ifp) { struct dhcp_state *state; if (ifp->ctx->packet == NULL) { ifp->ctx->packet = malloc(udp_dhcp_len); if (ifp->ctx->packet == NULL) { logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); return -1; } } state = D_STATE(ifp); if (state->raw_fd == -1) { state->raw_fd = if_openrawsocket(ifp, ETHERTYPE_IP); if (state->raw_fd == -1) { if (errno == ENOENT) { logger(ifp->ctx, LOG_ERR, "%s not found", if_pfname); /* May as well disable IPv4 entirely at * this point as we really need it. */ ifp->options->options &= ~DHCPCD_IPV4; } else logger(ifp->ctx, LOG_ERR, "%s: %s: %m", __func__, ifp->name); return -1; } eloop_event_add(ifp->ctx->eloop, state->raw_fd, dhcp_handlepacket, ifp, NULL, NULL); } return 0; } int dhcp_dump(struct interface *ifp) { struct dhcp_state *state; ifp->if_data[IF_DATA_DHCP] = state = calloc(1, sizeof(*state)); if (state == NULL) goto eexit; state->raw_fd = state->arp_fd = -1; TAILQ_INIT(&state->arp_states); dhcp_set_leasefile(state->leasefile, sizeof(state->leasefile), AF_INET, ifp, ""); state->new = read_lease(ifp); if (state->new == NULL) { logger(ifp->ctx, LOG_ERR, "%s: %s: %m", *ifp->name ? ifp->name : state->leasefile, __func__); return -1; } state->reason = "DUMP"; return script_runreason(ifp, state->reason); eexit: logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); return -1; } void dhcp_free(struct interface *ifp) { struct dhcp_state *state = D_STATE(ifp); struct dhcpcd_ctx *ctx; dhcp_close(ifp); arp_close(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; } ctx = ifp->ctx; /* If we don't have any more DHCP enabled interfaces, * close the global socket and release resources */ if (ctx->ifaces) { TAILQ_FOREACH(ifp, ctx->ifaces, next) { if (D_STATE(ifp)) break; } } if (ifp == NULL) { if (ctx->udp_fd != -1) { eloop_event_delete(ctx->eloop, ctx->udp_fd, 0); close(ctx->udp_fd); ctx->udp_fd = -1; } free(ctx->packet); free(ctx->opt_buffer); ctx->packet = NULL; ctx->opt_buffer = NULL; } } static int dhcp_init(struct interface *ifp) { struct dhcp_state *state; const struct if_options *ifo; uint8_t len; char buf[(sizeof(ifo->clientid) - 1) * 3]; 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->arp_fd = -1; TAILQ_INIT(&state->arp_states); /* Now is a good time to find IPv4 routes */ if_initrt(ifp); } state->state = DHS_INIT; state->reason = "PREINIT"; state->nakoff = 0; dhcp_set_leasefile(state->leasefile, sizeof(state->leasefile), AF_INET, ifp, ""); ifo = ifp->options; /* We need to drop the leasefile so that dhcp_start * doesn't load it. */ if (ifo->options & DHCPCD_REQUEST) unlink(state->leasefile); free(state->clientid); state->clientid = NULL; if (*ifo->clientid) { state->clientid = malloc((size_t)(ifo->clientid[0] + 1)); if (state->clientid == NULL) goto eexit; memcpy(state->clientid, ifo->clientid, (size_t)(ifo->clientid[0]) + 1); } else if (ifo->options & DHCPCD_CLIENTID) { if (ifo->options & DHCPCD_DUID) { state->clientid = malloc(ifp->ctx->duid_len + 6); if (state->clientid == NULL) goto eexit; state->clientid[0] =(uint8_t)(ifp->ctx->duid_len + 5); state->clientid[1] = 255; /* RFC 4361 */ memcpy(state->clientid + 2, ifo->iaid, 4); memcpy(state->clientid + 6, ifp->ctx->duid, ifp->ctx->duid_len); } else { len = (uint8_t)(ifp->hwlen + 1); state->clientid = malloc((size_t)len + 1); if (state->clientid == NULL) goto eexit; state->clientid[0] = len; state->clientid[1] = (uint8_t)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) logger(ifp->ctx, LOG_DEBUG, "%s: using ClientID %s", ifp->name, hwaddr_ntoa(state->clientid + 1, state->clientid[0], buf, sizeof(buf))); else if (ifp->hwlen) logger(ifp->ctx, LOG_DEBUG, "%s: using hwaddr %s", ifp->name, hwaddr_ntoa(ifp->hwaddr, ifp->hwlen, buf, sizeof(buf))); return 0; eexit: logger(ifp->ctx, LOG_ERR, "%s: error making ClientID: %m", __func__); return -1; } static void dhcp_start1(void *arg) { struct interface *ifp = arg; struct if_options *ifo = ifp->options; struct dhcp_state *state; struct stat st; uint32_t l; int nolease; if (!(ifo->options & DHCPCD_IPV4)) return; /* Listen on *.*.*.*:bootpc so that the kernel never sends an * ICMP port unreachable message back to the DHCP server */ if (ifp->ctx->udp_fd == -1) { ifp->ctx->udp_fd = dhcp_openudp(NULL); if (ifp->ctx->udp_fd == -1) { /* Don't log an error if some other process * is handling this. */ if (errno != EADDRINUSE) logger(ifp->ctx, LOG_ERR, "%s: dhcp_openudp: %m", __func__); } else eloop_event_add(ifp->ctx->eloop, ifp->ctx->udp_fd, dhcp_handleudp, ifp->ctx, NULL, NULL); } if (dhcp_init(ifp) == -1) { logger(ifp->ctx, LOG_ERR, "%s: dhcp_init: %m", ifp->name); return; } state = D_STATE(ifp); state->start_uptime = uptime(); free(state->offer); state->offer = NULL; if (state->arping_index < ifo->arping_len) { struct arp_state *astate; astate = arp_new(ifp, NULL); if (astate) { astate->probed_cb = dhcp_arp_probed; astate->conflicted_cb = dhcp_arp_conflicted; dhcp_arp_probed(astate); } 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') { logger(ifp->ctx, LOG_WARNING, "%s: needs a clientid to configure", ifp->name); dhcp_drop(ifp, "FAIL"); eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); return; } /* We don't want to read the old lease if we NAK an old test */ nolease = state->offer && ifp->ctx->options & DHCPCD_TEST; if (!nolease) { state->offer = read_lease(ifp); /* Check the saved lease matches the type we want */ if (state->offer) { #ifdef IN_IFF_DUPLICATED struct in_addr addr; struct ipv4_addr *ia; addr.s_addr = state->offer->yiaddr; ia = ipv4_iffindaddr(ifp, &addr, NULL); #endif if ((IS_BOOTP(ifp, state->offer) && !(ifo->options & DHCPCD_BOOTP)) || #ifdef IN_IFF_DUPLICATED (ia && ia->addr_flags & IN_IFF_DUPLICATED) || #endif (!IS_BOOTP(ifp, state->offer) && ifo->options & DHCPCD_BOOTP)) { free(state->offer); state->offer = NULL; } } } if (state->offer) { get_lease(ifp->ctx, &state->lease, state->offer); state->lease.frominfo = 1; if (state->new == NULL && ipv4_iffindaddr(ifp, &state->lease.addr, &state->lease.net)) { /* We still have the IP address from the last lease. * Fake add the address and routes from it so the lease * can be cleaned up. */ state->new = malloc(sizeof(*state->new)); if (state->new) { memcpy(state->new, state->offer, sizeof(*state->new)); state->addr = state->lease.addr; state->net = state->lease.net; state->added |= STATE_ADDED | STATE_FAKE; ipv4_buildroutes(ifp->ctx); } else logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); } 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) { time_t now; /* Offset lease times and check expiry */ now = time(NULL); if (now == -1 || (time_t)state->lease.leasetime < now - st.st_mtime) { logger(ifp->ctx, LOG_DEBUG, "%s: discarding expired lease", ifp->name); free(state->offer); state->offer = NULL; state->lease.addr.s_addr = 0; /* Technically we should discard the lease * as it's expired, just as DHCPv6 addresses * would be by the kernel. * However, this may violate POLA so * we currently leave it be. * If we get a totally different lease from * the DHCP server we'll drop it anyway, as * we will on any other event which would * trigger a lease drop. * This should only happen if dhcpcd stops * running and the lease expires before * dhcpcd starts again. */ #if 0 if (state->new) dhcp_drop(ifp, "EXPIRE"); #endif } else { l = (uint32_t)(now - 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 || state->offer->cookie == 0) dhcp_discover(ifp); else dhcp_reboot(ifp); } void dhcp_start(struct interface *ifp) { struct timespec tv; if (!(ifp->options->options & DHCPCD_IPV4)) return; /* No point in delaying a static configuration */ tv.tv_sec = DHCP_MIN_DELAY; tv.tv_nsec = (suseconds_t)arc4random_uniform( (DHCP_MAX_DELAY - DHCP_MIN_DELAY) * NSEC_PER_SEC); timespecnorm(&tv); logger(ifp->ctx, LOG_DEBUG, "%s: delaying IPv4 for %0.1f seconds", ifp->name, timespec_to_double(&tv)); eloop_timeout_add_tv(ifp->ctx->eloop, &tv, dhcp_start1, ifp); } void dhcp_handleifa(int cmd, struct interface *ifp, const struct in_addr *addr, const struct in_addr *net, const struct in_addr *dst, __unused int flags) { struct dhcp_state *state; struct if_options *ifo; uint8_t i; state = D_STATE(ifp); if (state == NULL) return; if (cmd == RTM_DELADDR) { if (state->addr.s_addr == addr->s_addr && state->net.s_addr == net->s_addr) { logger(ifp->ctx, LOG_INFO, "%s: removing IP address %s/%d", ifp->name, inet_ntoa(state->addr), inet_ntocidr(state->net)); dhcp_drop(ifp, "EXPIRE"); } return; } if (cmd != 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(ifp->ctx); 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); } }