From 1d5d236a813dc87b987cfe054ffd7feb9be23715 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Thu, 23 Oct 2014 19:07:35 +0000 Subject: [PATCH] Rework the ARP code so that we can have multiple ARP states. Split the ARP resolution code to dhcp or ipv4ll where it belongs. This allows us to probe IPv4LL while still DISCOVERing a DHCP lease. --- arp.c | 308 ++++++++++++++++++----------------------------- arp.h | 29 ++++- dhcp.c | 220 +++++++++++++++++++++++++-------- dhcp.h | 24 ++-- dhcpcd.c | 25 +--- dhcpcd.conf.5.in | 5 +- if-options.h | 3 +- ipv4.c | 21 +++- ipv4.h | 1 + ipv4ll.c | 208 +++++++++++++++++++------------- 10 files changed, 479 insertions(+), 365 deletions(-) diff --git a/arp.c b/arp.c index 1782d37c..929e032d 100644 --- a/arp.c +++ b/arp.c @@ -39,7 +39,7 @@ #include #include -#define ELOOP_QUEUE 2 +#define ELOOP_QUEUE 5 #include "config.h" #include "arp.h" #include "ipv4.h" @@ -94,33 +94,6 @@ eexit: return -1; } -static void -arp_failure(struct interface *ifp) -{ - const struct dhcp_state *state = D_CSTATE(ifp); - - /* If we failed without a magic cookie then we need to try - * and defend our IPv4LL address. */ - if ((state->offer != NULL && - state->offer->cookie != htonl(MAGIC_COOKIE)) || - (state->new != NULL && - state->new->cookie != htonl(MAGIC_COOKIE))) - { - ipv4ll_handle_failure(ifp); - return; - } - - unlink(state->leasefile); - if (!state->lease.frominfo) - dhcp_decline(ifp); - eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); - if (state->lease.frominfo) - dhcpcd_startinterface(ifp); - else - eloop_timeout_add_sec(ifp->ctx->eloop, - DHCP_ARP_FAIL, dhcpcd_startinterface, ifp); -} - static void arp_packet(void *arg) { @@ -128,15 +101,11 @@ arp_packet(void *arg) const struct interface *ifn; uint8_t arp_buffer[ARP_LEN]; struct arphdr ar; - uint32_t reply_s; - uint32_t reply_t; - uint8_t *hw_s, *hw_t; + struct arp_msg arm; ssize_t bytes; struct dhcp_state *state; - struct if_options *opts = ifp->options; - const char *hwaddr; - struct in_addr ina, fail; - char hwbuf[HWADDR_LEN * 3]; + struct arp_state *astate, *astaten; + unsigned char *hw_s, *hw_t; int flags; state = D_STATE(ifp); @@ -154,10 +123,13 @@ arp_packet(void *arg) if ((size_t)bytes < sizeof(ar)) continue; memcpy(&ar, arp_buffer, sizeof(ar)); + /* Families must match */ + if (ar.ar_hrd != htons(ifp->family)) + continue; /* Protocol must be IP. */ if (ar.ar_pro != htons(ETHERTYPE_IP)) continue; - if (ar.ar_pln != sizeof(reply_s)) + if (ar.ar_pln != sizeof(arm.sip.s_addr)) continue; /* Only these types are recognised */ if (ar.ar_op != htons(ARPOP_REPLY) && @@ -178,79 +150,25 @@ arp_packet(void *arg) } if (ifn) continue; - /* Copy out the IP addresses */ - memcpy(&reply_s, hw_s + ar.ar_hln, ar.ar_pln); - memcpy(&reply_t, hw_t + ar.ar_hln, ar.ar_pln); - - /* Check for arping */ - if (state->arping_index && - state->arping_index <= opts->arping_len && - (reply_s == opts->arping[state->arping_index - 1] || - (reply_s == 0 && - reply_t == opts->arping[state->arping_index - 1]))) - { - ina.s_addr = reply_s; - hwaddr = hwaddr_ntoa((unsigned char *)hw_s, - (size_t)ar.ar_hln, hwbuf, sizeof(hwbuf)); - syslog(LOG_INFO, - "%s: found %s on hardware address %s", - ifp->name, inet_ntoa(ina), hwaddr); - if (dhcpcd_selectprofile(ifp, hwaddr) == -1 && - dhcpcd_selectprofile(ifp, inet_ntoa(ina)) == -1) - { - state->probes = 0; - /* We didn't find a profile for this - * address or hwaddr, so move to the next - * arping profile */ - if (state->arping_index < - ifp->options->arping_len) - { - arp_probe(ifp); - return; - } - } - dhcp_close(ifp); - eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); - dhcpcd_startinterface(ifp); - return; - } - - fail.s_addr = 0; - /* RFC 2131 3.1.5, Client-server interaction - * RFC 3927 2.2.1, Probe Conflict Detection */ - if (state->offer && - (reply_s == state->offer->yiaddr || - (reply_s == 0 && reply_t == state->offer->yiaddr))) - fail.s_addr = state->offer->yiaddr; - - /* RFC 3927 2.5, Conflict Defense */ - if (IN_LINKLOCAL(htonl(state->addr.s_addr)) && - reply_s == state->addr.s_addr) - fail.s_addr = state->addr.s_addr; - - if (fail.s_addr) { - state->fail = fail; - syslog(LOG_ERR, "%s: hardware address %s claims %s", - ifp->name, - hwaddr_ntoa((unsigned char *)hw_s, - (size_t)ar.ar_hln, hwbuf, sizeof(hwbuf)), - inet_ntoa(state->fail)); - errno = EEXIST; - arp_failure(ifp); - return; + /* Copy out the HW and IP addresses */ + memcpy(&arm.sha, hw_s, ar.ar_hln); + memcpy(&arm.sip.s_addr, hw_s + ar.ar_hln, ar.ar_pln); + memcpy(&arm.tha, hw_t, ar.ar_hln); + memcpy(&arm.tip.s_addr, hw_t + ar.ar_hln, ar.ar_pln); + + /* Run the conflicts */ + TAILQ_FOREACH_SAFE(astate, &state->arp_states, next, astaten) { + astate->conflicted_cb(astate, &arm); } } } -void -arp_announce(void *arg) +static void +arp_open(struct interface *ifp) { - struct interface *ifp = arg; - struct dhcp_state *state = D_STATE(ifp); - struct timeval tv; + struct dhcp_state *state; - if (state->new == NULL) - return; + state = D_STATE(ifp); if (state->arp_fd == -1) { state->arp_fd = if_openrawsocket(ifp, ETHERTYPE_ARP); if (state->arp_fd == -1) { @@ -260,131 +178,132 @@ arp_announce(void *arg) eloop_event_add(ifp->ctx->eloop, state->arp_fd, arp_packet, ifp, NULL, NULL); } - if (++state->claims < ANNOUNCE_NUM) +} + +static void +arp_announced(void *arg) +{ + struct arp_state *astate = arg; + + if (astate->announced_cb) { + astate->announced_cb(astate); + return; + } + + /* Nothing more to do, so free us */ + arp_free(astate); +} + +static void +arp_announce1(void *arg) +{ + struct arp_state *astate = arg; + struct interface *ifp = astate->iface; + + if (++astate->claims < ANNOUNCE_NUM) syslog(LOG_DEBUG, "%s: sending ARP announce (%d of %d), " "next in %d.0 seconds", - ifp->name, state->claims, ANNOUNCE_NUM, ANNOUNCE_WAIT); + ifp->name, astate->claims, ANNOUNCE_NUM, ANNOUNCE_WAIT); else syslog(LOG_DEBUG, "%s: sending ARP announce (%d of %d)", - ifp->name, state->claims, ANNOUNCE_NUM); + ifp->name, astate->claims, ANNOUNCE_NUM); if (arp_send(ifp, ARPOP_REQUEST, - state->new->yiaddr, state->new->yiaddr) == -1) + astate->addr.s_addr, astate->addr.s_addr) == -1) syslog(LOG_ERR, "send_arp: %m"); - if (state->claims < ANNOUNCE_NUM) { - eloop_timeout_add_sec(ifp->ctx->eloop, - ANNOUNCE_WAIT, arp_announce, ifp); - return; - } - if (state->new->cookie != htonl(MAGIC_COOKIE)) { - /* Reset the conflict counter when we finish announcing. */ - eloop_timeout_add_sec(ifp->ctx->eloop, - ANNOUNCE_WAIT, ipv4ll_claimed, ifp); - /* Check if doing DHCP */ - if (!(ifp->options->options & DHCPCD_DHCP)) - return; - /* We should pretend to be at the end - * of the DHCP negotation cycle unless we rebooted */ - if (state->interval) - state->interval = 64 + DHCP_RAND_MIN; - else - state->interval = ANNOUNCE_WAIT; - state->probes = 0; - state->claims = 0; - tv.tv_sec = state->interval; - tv.tv_usec = (suseconds_t)arc4random_uniform( - (DHCP_RAND_MAX - DHCP_RAND_MIN) * 1000000); - timernorm(&tv); - eloop_timeout_add_tv(ifp->ctx->eloop, &tv, dhcp_discover, ifp); - } else { - eloop_event_delete(ifp->ctx->eloop, state->arp_fd, 0); - close(state->arp_fd); - state->arp_fd = -1; - } + eloop_timeout_add_sec(ifp->ctx->eloop, ANNOUNCE_WAIT, + astate->claims < ANNOUNCE_NUM ? arp_announce1 : arp_announced, + astate); } void -arp_probe(void *arg) +arp_announce(struct arp_state *astate) { - struct interface *ifp = arg; - struct dhcp_state *state = D_STATE(ifp); - struct in_addr addr; - struct timeval tv; - int arping = 0; - if (state->arp_fd == -1) { - state->arp_fd = if_openrawsocket(ifp, ETHERTYPE_ARP); - if (state->arp_fd == -1) { - syslog(LOG_ERR, "%s: %s: %m", __func__, ifp->name); - return; - } - eloop_event_add(ifp->ctx->eloop, - state->arp_fd, arp_packet, ifp, NULL, NULL); - } + arp_open(astate->iface); + astate->claims = 0; + arp_announce1(astate); +} - if (state->arping_index < ifp->options->arping_len) { - addr.s_addr = ifp->options->arping[state->arping_index]; - arping = 1; - } else if (state->offer) { - if (state->offer->yiaddr) - addr.s_addr = state->offer->yiaddr; - else - addr.s_addr = state->offer->ciaddr; - } else - addr.s_addr = state->addr.s_addr; - - if (state->probes == 0) { - if (arping) - syslog(LOG_DEBUG, "%s: searching for %s", - ifp->name, inet_ntoa(addr)); - else - syslog(LOG_DEBUG, "%s: checking for %s", - ifp->name, inet_ntoa(addr)); - } - if (++state->probes < PROBE_NUM) { +static void +arp_probed(void *arg) +{ + struct arp_state *astate = arg; + + astate->probed_cb(astate); +} + +static void +arp_probe1(void *arg) +{ + struct arp_state *astate = arg; + struct interface *ifp = astate->iface; + struct timeval tv; + + if (++astate->probes < PROBE_NUM) { tv.tv_sec = PROBE_MIN; tv.tv_usec = (suseconds_t)arc4random_uniform( (PROBE_MAX - PROBE_MIN) * 1000000); timernorm(&tv); - eloop_timeout_add_tv(ifp->ctx->eloop, &tv, arp_probe, ifp); + eloop_timeout_add_tv(ifp->ctx->eloop, &tv, arp_probe1, astate); } else { tv.tv_sec = ANNOUNCE_WAIT; tv.tv_usec = 0; - if (arping) { - state->probes = 0; - if (++state->arping_index < ifp->options->arping_len) - eloop_timeout_add_tv(ifp->ctx->eloop, - &tv, arp_probe, ifp); - else - eloop_timeout_add_tv(ifp->ctx->eloop, - &tv, dhcpcd_startinterface, ifp); - } else - eloop_timeout_add_tv(ifp->ctx->eloop, - &tv, dhcp_bind, ifp); + eloop_timeout_add_tv(ifp->ctx->eloop, &tv, arp_probed, astate); } syslog(LOG_DEBUG, "%s: sending ARP probe (%d of %d), next in %0.1f seconds", - ifp->name, state->probes ? state->probes : PROBE_NUM, PROBE_NUM, + ifp->name, astate->probes ? astate->probes : PROBE_NUM, PROBE_NUM, timeval_to_double(&tv)); - if (arp_send(ifp, ARPOP_REQUEST, 0, addr.s_addr) == -1) + if (arp_send(ifp, ARPOP_REQUEST, 0, astate->addr.s_addr) == -1) syslog(LOG_ERR, "send_arp: %m"); } void -arp_start(struct interface *ifp) +arp_probe(struct arp_state *astate) { - struct dhcp_state *state = D_STATE(ifp); - state->probes = 0; - state->arping_index = 0; - arp_probe(ifp); + arp_open(astate->iface); + astate->probes = 0; + syslog(LOG_DEBUG, "%s: probing for %s", + astate->iface->name, inet_ntoa(astate->addr)); + arp_probe1(astate); +} + + +struct arp_state * +arp_new(struct interface *ifp) { + struct arp_state *astate; + struct dhcp_state *state; + + astate = calloc(1, sizeof(*astate)); + if (astate == NULL) { + syslog(LOG_ERR, "%s: %s: %m", ifp->name, __func__); + return NULL; + } + + astate->iface = ifp; + state = D_STATE(ifp); + TAILQ_INSERT_TAIL(&state->arp_states, astate, next); + return astate; +} + +void +arp_free(struct arp_state *astate) +{ + struct dhcp_state *state; + + state = D_STATE(astate->iface); + TAILQ_REMOVE(&state->arp_states, astate, next); + free(astate); } void arp_close(struct interface *ifp) { struct dhcp_state *state = D_STATE(ifp); + struct arp_state *astate; if (state == NULL) return; @@ -394,5 +313,10 @@ arp_close(struct interface *ifp) close(state->arp_fd); state->arp_fd = -1; } -} + while ((astate = TAILQ_FIRST(&state->arp_states))) { + TAILQ_REMOVE(&state->arp_states, astate, next); + eloop_timeout_delete(ifp->ctx->eloop, NULL, astate); + free(astate); + } +} diff --git a/arp.h b/arp.h index 22ab72ea..a87dd4f4 100644 --- a/arp.h +++ b/arp.h @@ -42,8 +42,31 @@ #include "dhcpcd.h" -void arp_announce(void *); -void arp_probe(void *); -void arp_start(struct interface *); +struct arp_msg { + uint16_t op; + unsigned char sha[HWADDR_LEN]; + struct in_addr sip; + unsigned char tha[HWADDR_LEN]; + struct in_addr tip; +}; + +struct arp_state { + TAILQ_ENTRY(arp_state) next; + struct interface *iface; + + void (*probed_cb)(struct arp_state *); + void (*announced_cb)(struct arp_state *); + void (*conflicted_cb)(struct arp_state *, const struct arp_msg *); + + struct in_addr addr; + int probes; + int claims; +}; +TAILQ_HEAD(arp_statehead, arp_state); + +void arp_announce(struct arp_state *); +void arp_probe(struct arp_state *); +struct arp_state *arp_new(struct interface *); +void arp_free(struct arp_state *); void arp_close(struct interface *); #endif diff --git a/dhcp.c b/dhcp.c index 81ca1d84..560f4e2c 100644 --- a/dhcp.c +++ b/dhcp.c @@ -1415,11 +1415,7 @@ dhcp_close(struct interface *ifp) if (state == NULL) return; - if (state->arp_fd != -1) { - eloop_event_delete(ifp->ctx->eloop, state->arp_fd, 0); - close(state->arp_fd); - state->arp_fd = -1; - } + arp_close(ifp); if (state->raw_fd != -1) { eloop_event_delete(ifp->ctx->eloop, state->raw_fd, 0); close(state->raw_fd); @@ -1735,16 +1731,13 @@ dhcp_discover(void *arg) struct if_options *ifo = ifp->options; time_t 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 && - !(ifp->ctx->options & DHCPCD_DAEMONISED)) - { + /* If we're rebooting then we need to shorten the normal timeout + * to ensure we try for a fallback or IPv4LL address. */ + if (state->state == DHS_REBOOT) { if (ifo->reboot >= timeout) timeout = 2; else - timeout -= ifo->reboot; + timeout = ifo->reboot; } state->state = DHS_DISCOVER; @@ -1755,12 +1748,8 @@ dhcp_discover(void *arg) timeout, dhcp_fallback, ifp); else if (ifo->options & DHCPCD_IPV4LL && !IN_LINKLOCAL(htonl(state->addr.s_addr))) - { - if (IN_LINKLOCAL(htonl(state->fail.s_addr))) - timeout = RATE_LIMIT_INTERVAL; eloop_timeout_add_sec(ifp->ctx->eloop, timeout, ipv4ll_start, ifp); - } if (ifo->options & DHCPCD_REQUEST) syslog(LOG_INFO, "%s: soliciting a DHCP lease (requesting %s)", ifp->name, inet_ntoa(ifo->req_addr)); @@ -1819,6 +1808,13 @@ dhcp_renew(void *arg) send_renew(ifp); } +static void +dhcp_arp_announced(struct arp_state *astate) +{ + + arp_close(astate->iface); +} + static void dhcp_rebind(void *arg) { @@ -1838,9 +1834,8 @@ dhcp_rebind(void *arg) } void -dhcp_bind(void *arg) +dhcp_bind(struct interface *ifp, struct arp_state *astate) { - struct interface *ifp = arg; struct dhcp_state *state = D_STATE(ifp); struct if_options *ifo = ifp->options; struct dhcp_lease *lease = &state->lease; @@ -1850,7 +1845,6 @@ dhcp_bind(void *arg) if (state->state == DHS_BOUND) goto applyaddr; state->reason = NULL; - state->xid = 0; free(state->old); state->old = state->new; state->new = state->offer; @@ -1963,18 +1957,21 @@ dhcp_bind(void *arg) applyaddr: ipv4_applyaddr(ifp); - if (dhcpcd_daemonise(ifp->ctx) == 0) { - if (!ipv4ll) { - /* We bound a non IPv4LL address so reset the - * conflict counter */ - state->conflicts = 0; + if (dhcpcd_daemonise(ifp->ctx)) + return; + if (ifo->options & DHCPCD_ARP) { + if (state->added) { + if (astate == NULL) { + /* We don't care about what happens + * to the ARP announcement */ + astate = arp_new(ifp); + astate->announced_cb = + dhcp_arp_announced; + } + if (astate) + arp_announce(astate); + } else if (!ipv4ll) arp_close(ifp); - } - if (ifo->options & DHCPCD_ARP) { - state->claims = 0; - if (state->added) - arp_announce(ifp); - } } } @@ -1984,7 +1981,7 @@ dhcp_timeout(void *arg) struct interface *ifp = arg; struct dhcp_state *state = D_STATE(ifp); - dhcp_bind(ifp); + dhcp_bind(ifp, NULL); state->interval = 0; dhcp_discover(ifp); } @@ -2030,7 +2027,7 @@ dhcp_static(struct interface *ifp) 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); + dhcp_bind(ifp, NULL); } } @@ -2066,7 +2063,7 @@ dhcp_inform(struct interface *ifp) dhcp_message_new(&ifo->req_addr, &ifo->req_mask); if (state->offer) { ifo->options |= DHCPCD_STATIC; - dhcp_bind(ifp); + dhcp_bind(ifp, NULL); ifo->options &= ~DHCPCD_STATIC; } } @@ -2103,6 +2100,7 @@ dhcp_reboot(struct interface *ifp) if (state == NULL) return; ifo = ifp->options; + state->state = DHS_REBOOT; state->interval = 0; if (ifo->options & DHCPCD_LINK && ifp->carrier == LINK_DOWN) { @@ -2121,18 +2119,11 @@ dhcp_reboot(struct interface *ifp) 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; - if (state->added) - 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(ifp->ctx->eloop, NULL, ifp); @@ -2172,7 +2163,6 @@ dhcp_drop(struct interface *ifp, const char *reason) return; dhcp_auth_reset(&state->auth); dhcp_close(ifp); - arp_close(ifp); if (ifp->options->options & DHCPCD_RELEASE) { unlink(state->leasefile); if (ifp->carrier != LINK_DOWN && @@ -2299,6 +2289,118 @@ whitelisted_ip(const struct if_options *ifo, in_addr_t addr) return 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; + } + dhcp_close(astate->iface); + eloop_timeout_delete(astate->iface->ctx->eloop, NULL, astate->iface); + dhcp_bind(astate->iface, astate); +} + +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->sip.s_addr == ifo->arping[state->arping_index - 1] || + (amsg->sip.s_addr == 0 && + amsg->tip.s_addr == ifo->arping[state->arping_index - 1]))) + { + struct in_addr addr; + char buf[HWADDR_LEN * 3]; + + addr.s_addr = ifo->arping[state->arping_index - 1]; + syslog(LOG_INFO, + "%s: found %s on hardware address %s", + astate->iface->name, inet_ntoa(addr), + hwaddr_ntoa(amsg->sha, astate->iface->hwlen, + buf, sizeof(buf))); + if (dhcpcd_selectprofile(astate->iface, buf) == -1 && + dhcpcd_selectprofile(astate->iface, inet_ntoa(addr)) == -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); + eloop_timeout_delete(astate->iface->ctx->eloop, NULL, + astate->iface); + dhcpcd_startinterface(astate->iface); + } + + if (state->offer == NULL) + return; + + /* RFC 2131 3.1.5, Client-server interaction */ + if (amsg->sip.s_addr == state->offer->yiaddr || + (amsg->sip.s_addr == 0 && amsg->tip.s_addr == state->offer->yiaddr)) + { + struct in_addr fail; + char buf[HWADDR_LEN * 3]; + + fail.s_addr = state->offer->yiaddr; + syslog(LOG_ERR, "%s: hardware address %s claims %s", + astate->iface->name, + hwaddr_ntoa(amsg->sha, astate->iface->hwlen, + buf, sizeof(buf)), + inet_ntoa(fail)); + + arp_free(astate); + unlink(state->leasefile); + if (!state->lease.frominfo) + dhcp_decline(astate->iface); + eloop_timeout_delete(astate->iface->ctx->eloop, NULL, + astate->iface); + if (state->lease.frominfo) + dhcpcd_startinterface(astate->iface); + else + eloop_timeout_add_sec(astate->iface->ctx->eloop, + DHCP_ARP_FAIL, + dhcpcd_startinterface, astate->iface); + } +} + +void +dhcp_probe(struct interface *ifp) +{ + const struct dhcp_state *state; + struct arp_state *astate; + + astate = arp_new(ifp); + if (astate) { + state = D_CSTATE(ifp); + astate->addr = state->addr; + astate->probed_cb = dhcp_arp_probed; + astate->conflicted_cb = dhcp_arp_conflicted; + astate->announced_cb = dhcp_arp_announced; + arp_probe(astate); + } +} + static void dhcp_handledhcp(struct interface *iface, struct dhcp_message **dhcpp, const struct in_addr *from) @@ -2581,15 +2683,20 @@ dhcp_handledhcp(struct interface *iface, struct dhcp_message **dhcpp, * then we can't ARP for duplicate detection. */ addr.s_addr = state->offer->yiaddr; if (!ipv4_iffindaddr(iface, &addr, NULL)) { - state->claims = 0; - state->probes = 0; - state->conflicts = 0; - arp_probe(iface); + struct arp_state *astate; + + astate = arp_new(iface); + if (astate) { + astate->addr = addr; + astate->probed_cb = dhcp_arp_probed; + astate->conflicted_cb = dhcp_arp_conflicted; + arp_probe(astate); + } return; } } - dhcp_bind(iface); + dhcp_bind(iface, NULL); } static size_t @@ -2794,6 +2901,8 @@ dhcp_dump(struct interface *ifp) 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); snprintf(state->leasefile, sizeof(state->leasefile), LEASEFILE, ifp->name); state->new = read_lease(ifp); @@ -2820,6 +2929,7 @@ dhcp_free(struct interface *ifp) struct dhcp_state *state = D_STATE(ifp); struct dhcpcd_ctx *ctx; + dhcp_close(ifp); if (state) { free(state->old); free(state->new); @@ -2869,6 +2979,7 @@ dhcp_init(struct interface *ifp) return -1; /* 0 is a valid fd, so init to -1 */ state->raw_fd = state->arp_fd = -1; + TAILQ_INIT(&state->arp_states); } state->state = DHS_INIT; @@ -2973,7 +3084,14 @@ dhcp_start1(void *arg) state->offer = NULL; if (state->arping_index < ifo->arping_len) { - arp_start(ifp); + struct arp_state *astate; + + astate = arp_new(ifp); + if (astate) { + astate->probed_cb = dhcp_arp_probed; + astate->conflicted_cb = dhcp_arp_conflicted; + dhcp_arp_probed(astate); + } return; } @@ -3042,9 +3160,13 @@ dhcp_start1(void *arg) return; } - if (state->offer == NULL || state->offer->cookie == 0) + if (state->offer == NULL || state->offer->cookie == 0) { + /* If we don't have an address yet, enter the reboot + * state to ensure at least fallback in short order. */ + if (state->addr.s_addr == INADDR_ANY) + state->state = DHS_REBOOT; dhcp_discover(ifp); - else + } else dhcp_reboot(ifp); } diff --git a/dhcp.h b/dhcp.h index 48e1bba0..2ebe4f98 100644 --- a/dhcp.h +++ b/dhcp.h @@ -34,6 +34,7 @@ #include #include +#include "arp.h" #include "auth.h" #include "dhcp-common.h" @@ -213,17 +214,6 @@ struct dhcp_state { uint32_t xid; int socket; - /* ARP */ - int probes; - int claims; - struct in_addr fail; - size_t arping_index; - - /* IPv4LL */ - char randomstate[128]; - int conflicts; - time_t defend; - int raw_fd; int arp_fd; size_t buffer_size, buffer_len, buffer_pos; @@ -240,6 +230,15 @@ struct dhcp_state { unsigned char *clientid; struct authstate auth; + struct arp_statehead arp_states; + + size_t arping_index; + + struct arp_state *arp_ipv4ll; + unsigned int conflicts; + time_t defend; + char randomstate[128]; + struct in_addr failed; }; #define D_STATE(ifp) \ @@ -286,7 +285,8 @@ void dhcp_stop(struct interface *); void dhcp_decline(struct interface *); void dhcp_discover(void *); void dhcp_inform(struct interface *); -void dhcp_bind(void *); +void dhcp_probe(struct interface *); +void dhcp_bind(struct interface *, struct arp_state *); void dhcp_reboot_newopts(struct interface *, unsigned long long); void dhcp_close(struct interface *); void dhcp_free(struct interface *); diff --git a/dhcpcd.c b/dhcpcd.c index b62546f7..8f9b56c9 100644 --- a/dhcpcd.c +++ b/dhcpcd.c @@ -185,28 +185,15 @@ static void handle_exit_timeout(void *arg) { struct dhcpcd_ctx *ctx; - int timeout; ctx = arg; syslog(LOG_ERR, "timed out"); - if (!(ctx->options & DHCPCD_IPV4) || - !(ctx->options & DHCPCD_TIMEOUT_IPV4LL)) - { - if (ctx->options & DHCPCD_MASTER) { - /* We've timed out, so remove the waitip requirements. - * If the user doesn't like this they can always set - * an infinite timeout. */ - ctx->options &= - ~(DHCPCD_WAITIP | DHCPCD_WAITIP4 | DHCPCD_WAITIP6); - dhcpcd_daemonise(ctx); - } else - eloop_exit(ctx->eloop, EXIT_FAILURE); + if (!ctx->options & DHCPCD_MASTER) { + eloop_exit(ctx->eloop, EXIT_FAILURE); return; } - ctx->options &= ~DHCPCD_TIMEOUT_IPV4LL; - timeout = (PROBE_NUM * PROBE_MAX) + (PROBE_WAIT * 2) + DHCP_MAX_DELAY; - syslog(LOG_WARNING, "allowing %d seconds for IPv4LL timeout", timeout); - eloop_timeout_add_sec(ctx->eloop, timeout, handle_exit_timeout, ctx); + ctx->options |= DHCPCD_NOWAITIP; + dhcpcd_daemonise(ctx); } int @@ -257,7 +244,7 @@ dhcpcd_daemonise(struct dhcpcd_ctx *ctx) int sidpipe[2], fd; if (ctx->options & DHCPCD_DAEMONISE && - !(ctx->options & DHCPCD_DAEMONISED)) + !(ctx->options & (DHCPCD_DAEMONISED | DHCPCD_NOWAITIP))) { if (!dhcpcd_ipwaited(ctx)) return 0; @@ -1709,8 +1696,6 @@ main(int argc, char **argv) if (dhcpcd_daemonise(&ctx)) goto exit_success; } else if (t > 0) { - if (ctx.options & DHCPCD_IPV4LL) - ctx.options |= DHCPCD_TIMEOUT_IPV4LL; eloop_timeout_add_sec(ctx.eloop, t, handle_exit_timeout, &ctx); } diff --git a/dhcpcd.conf.5.in b/dhcpcd.conf.5.in index fee17318..53857854 100644 --- a/dhcpcd.conf.5.in +++ b/dhcpcd.conf.5.in @@ -455,8 +455,9 @@ Suppress any dhcpcd output to the console, except for errors. .It Ic reboot Ar seconds Allow .Ar reboot -seconds before moving to the DISCOVER phase if we have an old lease to use. -The default is 5 seconds. +seconds before moving to the DISCOVER phase if we have an old lease to use +and moving from DISCOVER to IPv4LL if no reply. +The default is 10 seconds. A setting of 0 seconds causes .Nm dhcpcd to skip the REBOOT phase and go straight into DISCOVER. diff --git a/if-options.h b/if-options.h index 82e3e95a..63b659e5 100644 --- a/if-options.h +++ b/if-options.h @@ -63,6 +63,7 @@ #define DHCPCD_LASTLEASE (1ULL << 7) #define DHCPCD_INFORM (1ULL << 8) #define DHCPCD_REQUEST (1ULL << 9) + #define DHCPCD_IPV4LL (1ULL << 10) #define DHCPCD_DUID (1ULL << 11) #define DHCPCD_PERSISTENT (1ULL << 12) @@ -76,7 +77,7 @@ #define DHCPCD_QUIET (1ULL << 21) #define DHCPCD_BACKGROUND (1ULL << 22) #define DHCPCD_VENDORRAW (1ULL << 23) -#define DHCPCD_TIMEOUT_IPV4LL (1ULL << 24) +#define DHCPCD_NOWAITIP (1ULL << 24) /* To force daemonise */ #define DHCPCD_WAITIP (1ULL << 25) #define DHCPCD_SLAACPRIVATE (1ULL << 26) #define DHCPCD_CSR_WARNED (1ULL << 27) diff --git a/ipv4.c b/ipv4.c index 1f4a5d92..7d2b99fc 100644 --- a/ipv4.c +++ b/ipv4.c @@ -132,6 +132,22 @@ ipv4_iffindaddr(struct interface *ifp, return NULL; } +struct ipv4_addr * +ipv4_iffindlladdr(struct interface *ifp) +{ + struct ipv4_state *state; + struct ipv4_addr *ap; + + state = IPV4_STATE(ifp); + if (state) { + TAILQ_FOREACH(ap, &state->addrs, next) { + if (IN_LINKLOCAL(htonl(ap->addr.s_addr))) + return ap; + } + } + return NULL; +} + struct ipv4_addr * ipv4_findaddr(struct dhcpcd_ctx *ctx, const struct in_addr *addr) { @@ -767,10 +783,7 @@ ipv4_applyaddr(void *arg) if (ifn->options->options & DHCPCD_ARP) { - nstate->claims = 0; - nstate->probes = 0; - nstate->conflicts = 0; - arp_probe(ifn); + dhcp_bind(ifn, NULL); } else { ipv4_addaddr(ifn, &nstate->lease); diff --git a/ipv4.h b/ipv4.h index 5a4a78ed..5dbfc9cd 100644 --- a/ipv4.h +++ b/ipv4.h @@ -72,6 +72,7 @@ int ipv4_routedeleted(struct dhcpcd_ctx *, const struct rt *); struct ipv4_addr *ipv4_iffindaddr(struct interface *, const struct in_addr *, const struct in_addr *); +struct ipv4_addr *ipv4_iffindlladdr(struct interface *); struct ipv4_addr *ipv4_findaddr(struct dhcpcd_ctx *, const struct in_addr *); void ipv4_handleifa(struct dhcpcd_ctx *, int, struct if_head *, const char *, const struct in_addr *, const struct in_addr *, const struct in_addr *); diff --git a/ipv4ll.c b/ipv4ll.c index 93a09661..2ce623c5 100644 --- a/ipv4ll.c +++ b/ipv4ll.c @@ -32,7 +32,7 @@ #include #include -#define ELOOP_QUEUE 2 +#define ELOOP_QUEUE 6 #include "config.h" #include "arp.h" #include "common.h" @@ -70,11 +70,11 @@ ipv4ll_make_lease(uint32_t addr) return dhcp; } -static struct dhcp_message * -ipv4ll_find_lease(const struct interface *ifp) +static in_addr_t +ipv4ll_pick_addr(const struct arp_state *astate) { - uint32_t addr; - struct interface *ifp2; + in_addr_t addr; + struct interface *ifp; const struct dhcp_state *state; for (;;) { @@ -83,32 +83,112 @@ ipv4ll_find_lease(const struct interface *ifp) * See ipv4ll_start for why we don't use arc4_random. */ addr = ntohl(LINKLOCAL_ADDR | ((random() % 0xFD00) + 0x0100)); - state = D_CSTATE(ifp); + state = D_CSTATE(astate->iface); /* No point using a failed address */ - if (addr == state->fail.s_addr) + if (addr == state->failed.s_addr) continue; /* Ensure we don't have the address on another interface */ - TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) { - state = D_CSTATE(ifp2); + TAILQ_FOREACH(ifp, astate->iface->ctx->ifaces, next) { + state = D_CSTATE(ifp); if (state && state->addr.s_addr == addr) break; } /* Yay, this should be a unique and workable IPv4LL address */ - if (ifp2 == NULL) + if (ifp == NULL) break; } - return ipv4ll_make_lease(addr); + return addr; } -void -ipv4ll_claimed(void *arg) +static void +ipv4ll_probed(struct arp_state *astate) { - struct interface *ifp = arg; - struct dhcp_state *state = D_STATE(ifp); + struct dhcp_state *state = D_STATE(astate->iface); + + free(state->offer); + state->offer = ipv4ll_make_lease(astate->addr.s_addr); + if (state->offer == NULL) { + syslog(LOG_ERR, "%s: %m", __func__); + return; + } + dhcp_bind(astate->iface, astate); +} + +static void +ipv4ll_announced(struct arp_state *astate) +{ + struct dhcp_state *state = D_STATE(astate->iface); state->conflicts = 0; + /* Need to keep the arp state so we can defend our IP. */ +} + +static void +ipv4ll_probe(void *arg) +{ + + arp_probe(arg); +} + +static void +ipv4ll_conflicted(struct arp_state *astate, const struct arp_msg *amsg) +{ + struct dhcp_state *state = D_STATE(astate->iface); + uint32_t fail; + char buf[HWADDR_LEN * 3]; + + if (state->offer == NULL) + return; + + fail = 0; + /* RFC 3927 2.2.1, Probe Conflict Detection */ + if (amsg->sip.s_addr == astate->addr.s_addr || + (amsg->sip.s_addr == 0 && amsg->tip.s_addr == astate->addr.s_addr)) + fail = astate->addr.s_addr; + + /* RFC 3927 2.5, Conflict Defense */ + if (IN_LINKLOCAL(htonl(state->addr.s_addr)) && + amsg->sip.s_addr == state->addr.s_addr) + fail = state->addr.s_addr; + + if (fail == 0) + return; + + state->failed.s_addr = fail; + syslog(LOG_ERR, "%s: hardware address %s claims %s", + astate->iface->name, + hwaddr_ntoa(amsg->sha, astate->iface->hwlen, buf, sizeof(buf)), + inet_ntoa(state->failed)); + + if (state->failed.s_addr == state->addr.s_addr) { + time_t up; + + /* RFC 3927 Section 2.5 */ + up = uptime(); + if (state->defend + DEFEND_INTERVAL > up) { + syslog(LOG_WARNING, + "%s: IPv4LL %d second defence failed for %s", + astate->iface->name, DEFEND_INTERVAL, + inet_ntoa(state->addr)); + dhcp_drop(astate->iface, "EXPIRE"); + } else { + syslog(LOG_DEBUG, "%s: defended IPv4LL address %s", + astate->iface->name, inet_ntoa(state->addr)); + state->defend = up; + return; + } + } + + if (++state->conflicts == MAX_CONFLICTS) + syslog(LOG_ERR, "%s: failed to acquire an IPv4LL address", + astate->iface->name); + astate->addr.s_addr = ipv4ll_pick_addr(astate); + eloop_timeout_add_sec(astate->iface->ctx->eloop, + state->conflicts > MAX_CONFLICTS ? + RATE_LIMIT_INTERVAL : PROBE_WAIT, + ipv4ll_probe, astate); } void @@ -116,7 +196,11 @@ ipv4ll_start(void *arg) { struct interface *ifp = arg; struct dhcp_state *state = D_STATE(ifp); - uint32_t addr; + struct arp_state *astate; + struct ipv4_addr *ap; + + if (state->arp_ipv4ll) + return; /* RFC 3927 Section 2.1 states that the random number generator * SHOULD be seeded with a value derived from persistent information @@ -134,80 +218,40 @@ ipv4ll_start(void *arg) initstate(seed, state->randomstate, sizeof(state->randomstate)); } - eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); - state->probes = 0; - state->claims = 0; - if (state->addr.s_addr) { - if (IN_LINKLOCAL(htonl(state->addr.s_addr))) { - arp_announce(ifp); - return; - } + if ((astate = arp_new(ifp)) == NULL) + return; + + state->arp_ipv4ll = astate; + astate->probed_cb = ipv4ll_probed; + astate->announced_cb = ipv4ll_announced; + astate->conflicted_cb = ipv4ll_conflicted; + + if (IN_LINKLOCAL(htonl(state->addr.s_addr))) { + astate->addr = state->addr; + arp_announce(astate); + return; } - if (state->offer == NULL) - addr = 0; - else { - addr = state->offer->yiaddr; + if (state->offer) { + astate->addr.s_addr = state->offer->yiaddr; free(state->offer); + ap = ipv4_iffindaddr(ifp, &astate->addr, NULL); + } else + ap = ipv4_iffindlladdr(ifp); + if (ap) { + astate->addr = ap->addr; + ipv4ll_probed(astate); + return; } - state->state = DHS_INIT_IPV4LL; setstate(state->randomstate); /* We maybe rebooting an IPv4LL address. */ - if (!IN_LINKLOCAL(htonl(addr))) { + if (!IN_LINKLOCAL(htonl(astate->addr.s_addr))) { syslog(LOG_INFO, "%s: probing for an IPv4LL address", ifp->name); - addr = 0; - } - if (addr == 0) - state->offer = ipv4ll_find_lease(ifp); - else - state->offer = ipv4ll_make_lease(addr); - if (state->offer == NULL) { - syslog(LOG_ERR, "%s: %m", __func__); - return; - } - state->lease.frominfo = 0; - arp_probe(ifp); -} - -void -ipv4ll_handle_failure(void *arg) -{ - struct interface *ifp = arg; - struct dhcp_state *state = D_STATE(ifp); - time_t up; - - if (state->fail.s_addr == state->addr.s_addr) { - /* RFC 3927 Section 2.5 */ - up = uptime(); - if (state->defend + DEFEND_INTERVAL > up) { - syslog(LOG_WARNING, - "%s: IPv4LL %d second defence failed for %s", - ifp->name, DEFEND_INTERVAL, inet_ntoa(state->addr)); - dhcp_drop(ifp, "EXPIRE"); - } else { - syslog(LOG_DEBUG, "%s: defended IPv4LL address %s", - ifp->name, inet_ntoa(state->addr)); - state->defend = up; - return; - } - } - - free(state->offer); - state->offer = NULL; - eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); - if (++state->conflicts >= MAX_CONFLICTS) { - syslog(LOG_ERR, "%s: failed to acquire an IPv4LL address", - ifp->name); - if (ifp->options->options & DHCPCD_DHCP) { - state->interval = RATE_LIMIT_INTERVAL / 2; - dhcp_discover(ifp); - } else - eloop_timeout_add_sec(ifp->ctx->eloop, - RATE_LIMIT_INTERVAL, ipv4ll_start, ifp); - } else { - eloop_timeout_add_sec(ifp->ctx->eloop, - PROBE_WAIT, ipv4ll_start, ifp); + astate->addr.s_addr = INADDR_ANY; } + if (astate->addr.s_addr == INADDR_ANY) + astate->addr.s_addr = ipv4ll_pick_addr(astate); + arp_probe(astate); } -- 2.47.2