From 634443d4e8a2d85c9931040dbb01870e808aacc1 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Thu, 18 Apr 2019 14:54:47 +0100 Subject: [PATCH] ARP: Refactor so that ACD is available for all inet addresses Callbacks have also been improved so they are more descriptive. --- src/arp.c | 240 ++++++++++++++++++++++------------------ src/arp.h | 17 +-- src/dhcp.c | 249 +++++++++++++++++++----------------------- src/ipv4.c | 16 +-- src/ipv4.h | 3 + src/ipv4ll.c | 303 ++++++++++++++++++++++++++++----------------------- src/ipv4ll.h | 6 +- 7 files changed, 430 insertions(+), 404 deletions(-) diff --git a/src/arp.c b/src/arp.c index cbcefa8d..faf8be1c 100644 --- a/src/arp.c +++ b/src/arp.c @@ -62,8 +62,9 @@ /* Assert the correct structure size for on wire */ __CTASSERT(sizeof(struct arphdr) == 8); -ssize_t -arp_request(const struct interface *ifp, in_addr_t sip, in_addr_t tip) +static ssize_t +arp_request(const struct interface *ifp, + const struct in_addr *sip, const struct in_addr *tip) { uint8_t arp_buffer[ARP_LEN]; struct arphdr ar; @@ -74,7 +75,7 @@ arp_request(const struct interface *ifp, in_addr_t sip, in_addr_t tip) ar.ar_hrd = htons(ifp->family); ar.ar_pro = htons(ETHERTYPE_IP); ar.ar_hln = ifp->hwlen; - ar.ar_pln = sizeof(sip); + ar.ar_pln = sizeof(tip->s_addr); ar.ar_op = htons(ARPOP_REQUEST); p = arp_buffer; @@ -93,9 +94,12 @@ arp_request(const struct interface *ifp, in_addr_t sip, in_addr_t tip) APPEND(&ar, sizeof(ar)); APPEND(ifp->hwaddr, ifp->hwlen); - APPEND(&sip, sizeof(sip)); + if (sip != NULL) + APPEND(&sip->s_addr, sizeof(sip->s_addr)); + else + ZERO(sizeof(tip->s_addr)); ZERO(ifp->hwlen); - APPEND(&tip, sizeof(tip)); + APPEND(&tip->s_addr, sizeof(tip->s_addr)); state = ARP_CSTATE(ifp); return bpf_send(ifp, state->bpf_fd, ETHERTYPE_ARP, arp_buffer, len); @@ -105,6 +109,77 @@ eexit: return -1; } +static void +arp_report_conflicted(const struct arp_state *astate, + const struct arp_msg *amsg) +{ + char buf[HWADDR_LEN * 3]; + + if (amsg == NULL) { + logerrx("%s: DAD detected %s", + astate->iface->name, inet_ntoa(astate->addr)); + return; + } + + logerrx("%s: hardware address %s claims %s", + astate->iface->name, + hwaddr_ntoa(amsg->sha, astate->iface->hwlen, buf, sizeof(buf)), + inet_ntoa(astate->addr)); +} + + +static void +arp_found(struct arp_state *astate, const struct arp_msg *amsg) +{ + struct interface *ifp; + struct ivp4_addr *ia; +#ifndef KERNEL_RFC5227 + struct timespec now, defend; +#endif + + arp_report_conflicted(astate, amsg); + ifp = astate->iface; + +#pragma GCC diagnostic push /* GCC is clearly wrong about this warning. */ +#pragma GCC diagnostic ignored "-Wincompatible-pointer-types" + /* If we haven't added the address we're doing a probe. */ + ia = ipv4_iffindaddr(ifp, &astate->addr, NULL); +#pragma GCC diagnostic pop + if (ia == NULL) { + if (astate->found_cb != NULL) + astate->found_cb(astate, amsg); + return; + } + +#ifndef KERNEL_RFC5227 + /* RFC 3927 Section 2.5 says a defence should + * broadcast an ARP announcement. + * Because the kernel will also unicast a reply to the + * hardware address which requested the IP address + * the other IPv4LL client will receieve two ARP + * messages. + * If another conflict happens within DEFEND_INTERVAL + * then we must drop our address and negotiate a new one. */ + defend.tv_sec = astate->defend.tv_sec + DEFEND_INTERVAL; + defend.tv_nsec = astate->defend.tv_nsec; + clock_gettime(CLOCK_MONOTONIC, &now); + if (timespeccmp(&defend, &now, >)) + logwarnx("%s: %d second defence failed for %s", + ifp->name, DEFEND_INTERVAL, inet_ntoa(astate->addr)); + else if (arp_request(ifp, &astate->addr, &astate->addr) == -1) + logerr(__func__); + else { + logdebugx("%s: defended address %s", + ifp->name, inet_ntoa(astate->addr)); + astate->defend = now; + return; + } +#endif + + if (astate->defend_failed_cb != NULL) + astate->defend_failed_cb(astate); +} + static void arp_packet(struct interface *ifp, uint8_t *data, size_t len) { @@ -164,14 +239,13 @@ arp_packet(struct interface *ifp, uint8_t *data, size_t len) memcpy(&arm.tha, hw_t, ar.ar_hln); memcpy(&arm.tip.s_addr, hw_t + ar.ar_hln, ar.ar_pln); - /* Run the conflicts */ + /* Match the ARP probe to our states */ state = ARP_CSTATE(ifp); TAILQ_FOREACH_SAFE(astate, &state->arp_states, next, astaten) { - if (arm.sip.s_addr != astate->addr.s_addr && - arm.tip.s_addr != astate->addr.s_addr) - continue; - if (astate->conflicted_cb) - astate->conflicted_cb(astate, &arm); + if (IN_ARE_ADDR_EQUAL(&arm.sip, &astate->addr) || + (IN_IS_ADDR_UNSPECIFIED(&arm.sip) && + IN_ARE_ADDR_EQUAL(&arm.tip, &astate->addr))) + arp_found(astate, &arm); } } @@ -243,7 +317,7 @@ arp_read(void *arg) } } -int +static int arp_open(struct interface *ifp) { struct iarp_state *state; @@ -265,7 +339,8 @@ arp_probed(void *arg) { struct arp_state *astate = arg; - astate->probed_cb(astate); + timespecclear(&astate->defend); + astate->not_found_cb(astate); } static void @@ -290,7 +365,7 @@ arp_probe1(void *arg) ifp->name, inet_ntoa(astate->addr), astate->probes ? astate->probes : PROBE_NUM, PROBE_NUM, timespec_to_double(&tv)); - if (arp_request(ifp, 0, astate->addr.s_addr) == -1) + if (arp_request(ifp, NULL, &astate->addr) == -1) logerr(__func__); } @@ -314,6 +389,23 @@ arp_probe(struct arp_state *astate) } #endif /* ARP */ +static struct arp_state * +arp_find(struct interface *ifp, const struct in_addr *addr) +{ + struct iarp_state *state; + struct arp_state *astate; + + if ((state = ARP_STATE(ifp)) == NULL) + goto out; + TAILQ_FOREACH(astate, &state->arp_states, next) { + if (astate->addr.s_addr == addr->s_addr && astate->iface == ifp) + return astate; + } +out: + errno = ESRCH; + return NULL; +} + static void arp_announced(void *arg) { @@ -342,7 +434,7 @@ arp_announce1(void *arg) logdebugx("%s: ARP announcing %s (%d of %d)", ifp->name, inet_ntoa(astate->addr), astate->claims, ANNOUNCE_NUM); - if (arp_request(ifp, astate->addr.s_addr, astate->addr.s_addr) == -1) + if (arp_request(ifp, &astate->addr, &astate->addr) == -1) logerr(__func__); eloop_timeout_add_sec(ifp->ctx->eloop, ANNOUNCE_WAIT, astate->claims < ANNOUNCE_NUM ? arp_announce1 : arp_announced, @@ -400,12 +492,26 @@ arp_announce(struct arp_state *astate) arp_announce1(astate); } +void +arp_ifannounceaddr(struct interface *ifp, const struct in_addr *ia) +{ + struct arp_state *astate; + + astate = arp_find(ifp, ia); + if (astate == NULL) { + astate = arp_new(ifp, ia); + if (astate == NULL) + return; + astate->announced_cb = arp_free; + } + arp_announce(astate); +} + void arp_announceaddr(struct dhcpcd_ctx *ctx, const struct in_addr *ia) { struct interface *ifp; struct ipv4_addr *iaf; - struct arp_state *astate; TAILQ_FOREACH(ifp, ctx->ifaces, next) { iaf = ipv4_iffindaddr(ifp, ia, NULL); @@ -419,54 +525,7 @@ arp_announceaddr(struct dhcpcd_ctx *ctx, const struct in_addr *ia) if (ifp == NULL) return; - astate = arp_find(ifp, ia); - if (astate != NULL) - arp_announce(astate); -} - -void -arp_ifannounceaddr(struct interface *ifp, const struct in_addr *ia) -{ - struct arp_state *astate; - - astate = arp_new(ifp, ia); - if (astate != NULL) - arp_announce(astate); -} - -void -arp_report_conflicted(const struct arp_state *astate, - const struct arp_msg *amsg) -{ - - if (amsg != NULL) { - char buf[HWADDR_LEN * 3]; - - logerrx("%s: hardware address %s claims %s", - astate->iface->name, - hwaddr_ntoa(amsg->sha, astate->iface->hwlen, - buf, sizeof(buf)), - inet_ntoa(astate->failed)); - } else - logerrx("%s: DAD detected %s", - astate->iface->name, inet_ntoa(astate->failed)); -} - -struct arp_state * -arp_find(struct interface *ifp, const struct in_addr *addr) -{ - struct iarp_state *state; - struct arp_state *astate; - - if ((state = ARP_STATE(ifp)) == NULL) - goto out; - TAILQ_FOREACH(astate, &state->arp_states, next) { - if (astate->addr.s_addr == addr->s_addr && astate->iface == ifp) - return astate; - } -out: - errno = ESRCH; - return NULL; + arp_ifannounceaddr(ifp, ia); } struct arp_state * @@ -532,61 +591,28 @@ arp_free(struct arp_state *astate) arp_tryfree(ifp); } -static void -arp_free_but1(struct interface *ifp, struct arp_state *astate) -{ - struct iarp_state *state; - - if ((state = ARP_STATE(ifp)) != NULL) { - struct arp_state *p, *n; - - TAILQ_FOREACH_SAFE(p, &state->arp_states, next, n) { - if (p != astate) - arp_free(p); - } - } -} - void -arp_free_but(struct arp_state *astate) +arp_freeaddr(struct interface *ifp, const struct in_addr *ia) { + struct arp_state *astate; - arp_free_but1(astate->iface, astate); + astate = arp_find(ifp, ia); + arp_free(astate); } void arp_drop(struct interface *ifp) -{ - - arp_free_but1(ifp, NULL); - arp_close(ifp); -} - -void -arp_handleifa(int cmd, struct ipv4_addr *addr) { struct iarp_state *state; - struct arp_state *astate, *asn; + struct arp_state *astate; - state = ARP_STATE(addr->iface); + state = ARP_STATE(ifp); if (state == NULL) return; - TAILQ_FOREACH_SAFE(astate, &state->arp_states, next, asn) { - if (astate->addr.s_addr != addr->addr.s_addr) - continue; - if (cmd == RTM_DELADDR) - arp_free(astate); -#ifdef IN_IFF_DUPLICATED - if (cmd != RTM_NEWADDR) - continue; - if (addr->addr_flags & IN_IFF_DUPLICATED) { - if (astate->conflicted_cb) - astate->conflicted_cb(astate, NULL); - } else if (!(addr->addr_flags & IN_IFF_NOTUSEABLE)) { - if (astate->probed_cb) - astate->probed_cb(astate); - } -#endif + while ((astate = TAILQ_FIRST(&state->arp_states)) != NULL) { + arp_free(astate); } + + /* No need to close because the last free will close */ } diff --git a/src/arp.h b/src/arp.h index 1c0be043..d29b06b8 100644 --- a/src/arp.h +++ b/src/arp.h @@ -63,15 +63,16 @@ struct arp_state { TAILQ_ENTRY(arp_state) next; struct interface *iface; - void (*probed_cb)(struct arp_state *); + void (*found_cb)(struct arp_state *, const struct arp_msg *); + void (*not_found_cb)(struct arp_state *); void (*announced_cb)(struct arp_state *); - void (*conflicted_cb)(struct arp_state *, const struct arp_msg *); + void (*defend_failed_cb)(struct arp_state *); void (*free_cb)(struct arp_state *); struct in_addr addr; int probes; int claims; - struct in_addr failed; + struct timespec defend; }; TAILQ_HEAD(arp_statehead, arp_state); @@ -87,20 +88,14 @@ struct iarp_state { ((const struct iarp_state *)(ifp)->if_data[IF_DATA_ARP]) #ifdef ARP -int arp_open(struct interface *); -ssize_t arp_request(const struct interface *, in_addr_t, in_addr_t); -void arp_probe(struct arp_state *); -void arp_report_conflicted(const struct arp_state *, const struct arp_msg *); struct arp_state *arp_new(struct interface *, const struct in_addr *); -struct arp_state *arp_find(struct interface *, const struct in_addr *); +void arp_probe(struct arp_state *); void arp_announce(struct arp_state *); void arp_announceaddr(struct dhcpcd_ctx *, const struct in_addr *); void arp_ifannounceaddr(struct interface *, const struct in_addr *); void arp_cancel(struct arp_state *); void arp_free(struct arp_state *); -void arp_free_but(struct arp_state *); +void arp_freeaddr(struct interface *, const struct in_addr *); void arp_drop(struct interface *); - -void arp_handleifa(int, struct ipv4_addr *); #endif /* ARP */ #endif /* ARP_H */ diff --git a/src/dhcp.c b/src/dhcp.c index f7cdefc9..f20c73fa 100644 --- a/src/dhcp.c +++ b/src/dhcp.c @@ -124,8 +124,9 @@ static const char * const dhcp_params[] = { }; static int dhcp_openbpf(struct interface *); +static void dhcp_start1(void *); #ifdef ARP -static void dhcp_arp_conflicted(struct arp_state *, const struct arp_msg *); +static void dhcp_arp_found(struct arp_state *, const struct arp_msg *); #endif static void dhcp_handledhcp(struct interface *, struct bootp *, size_t, const struct in_addr *); @@ -1921,35 +1922,6 @@ dhcp_request(void *arg) send_request(ifp); } -static int -dhcp_leaseextend(struct interface *ifp) -{ - -#ifdef ARP - if (ifp->options->options & DHCPCD_ARP) { - const struct dhcp_state *state; - struct arp_state *astate; - - state = D_CSTATE(ifp); - if ((astate = arp_new(ifp, &state->lease.addr)) == NULL) - return -1; - astate->conflicted_cb = dhcp_arp_conflicted; - -#ifndef KERNEL_RFC5227 - if (arp_open(ifp) == -1) - return -1; -#endif - - logwarnx("%s: extending lease until DaD failure or DHCP", - ifp->name); - return 0; - } -#endif - - logwarnx("%s: extending lease", ifp->name); - return 0; -} - static void dhcp_expire1(struct interface *ifp) { @@ -1968,12 +1940,12 @@ dhcp_expire(void *arg) { struct interface *ifp = arg; - logerrx("%s: DHCP lease expired", ifp->name); if (ifp->options->options & DHCPCD_LASTLEASE_EXTEND) { - if (dhcp_leaseextend(ifp) == 0) - return; - logerr(__func__); + logwarnx("%s: DHCP lease expired, extending lease", ifp->name); + return; } + + logerrx("%s: DHCP lease expired", ifp->name); dhcp_expire1(ifp); } @@ -2039,41 +2011,18 @@ dhcp_rebind(void *arg) send_rebind(ifp); } -#ifdef ARP static void -dhcp_arp_probed(struct arp_state *astate) +dhcp_finish_dad(struct interface *ifp, struct in_addr *ia) { - struct interface *ifp; - struct dhcp_state *state; - struct if_options *ifo; + struct dhcp_state *state = D_STATE(ifp); - ifp = astate->iface; - state = D_STATE(ifp); - ifo = ifp->options; -#ifdef ARPING - if (ifo->arping_len && state->arping_index < ifo->arping_len) { - /* We didn't find a profile for this - * address or hwaddr, so move to the next - * arping profile */ - if (++state->arping_index < ifo->arping_len) { - astate->addr.s_addr = - ifo->arping[state->arping_index]; - arp_probe(astate); - return; - } - arp_free(astate); - dhcpcd_startinterface(ifp); + if (state->state != DHS_PROBE) return; - } -#endif - - /* Already bound so DAD has worked */ - if (state->state == DHS_BOUND) + if (state->offer == NULL || state->offer->yiaddr != ia->s_addr) return; - logdebugx("%s: DAD completed for %s", - ifp->name, inet_ntoa(astate->addr)); - if (!(ifo->options & DHCPCD_INFORM)) + logdebugx("%s: DAD completed for %s", ifp->name, inet_ntoa(*ia)); + if (!(ifp->options->options & DHCPCD_INFORM)) dhcp_bind(ifp); #ifndef IN_IFF_TENTATIVE else { @@ -2100,23 +2049,79 @@ dhcp_arp_probed(struct arp_state *astate) ipv4ll_drop(ifp); #endif - if (ifo->options & DHCPCD_INFORM) + if (ifp->options->options & DHCPCD_INFORM) dhcp_inform(ifp); } + +static void +dhcp_addr_duplicated(struct interface *ifp, struct in_addr *ia) +{ + struct dhcp_state *state = D_STATE(ifp); +#ifdef IN_IFF_DUPLICATED + struct ipv4_addr *iap; +#endif + + if ((state->offer == NULL || state->offer->yiaddr != ia->s_addr) && + !IN_ARE_ADDR_EQUAL(ia, &state->lease.addr)) + return; + + /* RFC 2131 3.1.5, Client-server interaction */ + logerrx("%s: DAD detected %s", ifp->name, inet_ntoa(*ia)); + unlink(state->leasefile); + if (!(ifp->options->options & DHCPCD_STATIC) && !state->lease.frominfo) + dhcp_decline(ifp); +#ifdef IN_IFF_DUPLICATED + if ((iap = ipv4_iffindaddr(ifp, ia, NULL)) != NULL) + ipv4_deladdr(iap, 0); +#endif + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + eloop_timeout_add_sec(ifp->ctx->eloop, + DHCP_RAND_MAX, dhcp_discover, ifp); +} + +#ifdef ARP static void -dhcp_arp_conflicted(struct arp_state *astate, const struct arp_msg *amsg) +dhcp_arp_not_found(struct arp_state *astate) { struct interface *ifp; struct dhcp_state *state; -#ifdef ARPING struct if_options *ifo; -#endif ifp = astate->iface; state = D_STATE(ifp); + ifo = ifp->options; +#ifdef ARPING + if (ifo->arping_len && state->arping_index < ifo->arping_len) { + /* We didn't find a profile for this + * address or hwaddr, so move to the next + * arping profile */ + if (++state->arping_index < ifo->arping_len) { + astate->addr.s_addr = + ifo->arping[state->arping_index]; + arp_probe(astate); + return; + } + arp_free(astate); + dhcpcd_startinterface(ifp); + return; + } +#endif + dhcp_finish_dad(ifp, &astate->addr); +} + +static void +dhcp_arp_found(struct arp_state *astate, const struct arp_msg *amsg) +{ #ifdef ARPING + struct interface *ifp; + struct dhcp_state *state; + struct if_options *ifo; + + ifp = astate->iface; + state = D_STATE(ifp); + ifo = ifp->options; if (state->arping_index != -1 && state->arping_index < ifo->arping_len && @@ -2125,17 +2130,14 @@ dhcp_arp_conflicted(struct arp_state *astate, const struct arp_msg *amsg) { char buf[HWADDR_LEN * 3]; - astate->failed.s_addr = ifo->arping[state->arping_index]; - arp_report_conflicted(astate, amsg); hwaddr_ntoa(amsg->sha, ifp->hwlen, buf, sizeof(buf)); if (dhcpcd_selectprofile(ifp, buf) == -1 && - dhcpcd_selectprofile(ifp, - inet_ntoa(astate->failed)) == -1) + dhcpcd_selectprofile(ifp, inet_ntoa(amsg->sip)) == -1) { /* We didn't find a profile for this * address or hwaddr, so move to the next * arping profile */ - dhcp_arp_probed(astate); + dhcp_arp_not_found(astate); return; } arp_free(astate); @@ -2145,65 +2147,18 @@ dhcp_arp_conflicted(struct arp_state *astate, const struct arp_msg *amsg) } #endif - /* 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; - else - astate->failed = astate->addr; - arp_report_conflicted(astate, amsg); - unlink(state->leasefile); -#ifdef ARP - if (!(ifp->options->options & DHCPCD_STATIC) && - !state->lease.frominfo) - dhcp_decline(ifp); -#endif -#ifdef IN_IFF_DUPLICATED - if ((ia = ipv4_iffindaddr(ifp, &astate->addr, NULL)) != NULL) - ipv4_deladdr(ia, 1); -#endif - arp_free(astate); - eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); - eloop_timeout_add_sec(ifp->ctx->eloop, - DHCP_RAND_MAX, dhcp_discover, ifp); - return; - } - - /* Bound address */ - if (amsg && state->addr && - amsg->sip.s_addr == state->addr->addr.s_addr) - { - astate->failed = state->addr->addr; - arp_report_conflicted(astate, amsg); - if (state->state == DHS_BOUND) { - /* For now, just report the duplicated address */ - } else { - arp_free(astate); - dhcp_expire1(ifp); - } - return; - } + dhcp_addr_duplicated(astate->iface, &astate->addr); } +#ifdef KERNEL_RFC5227 static void dhcp_arp_announced(struct arp_state *state) { -// TODO: DHCP addresses handle ACD? -//#ifdef KERNEL_RFC5227 arp_free(state); -//#endif } -#endif +#endif /* KERNEL_RFC5227 */ +#endif /* ARP */ void dhcp_bind(struct interface *ifp) @@ -2357,12 +2312,6 @@ dhcp_lastlease(void *arg) if (ifp->ctx->options & DHCPCD_FORKED) return; state->interval = 0; - if (ifp->options->options & DHCPCD_LASTLEASE_EXTEND && - dhcp_leaseextend(ifp) == -1) - { - logerr("%s: %s", ifp->name, __func__); - dhcp_expire(ifp); - } dhcp_discover(ifp); } @@ -2395,17 +2344,32 @@ dhcp_message_new(struct bootp **bootp, } #ifdef ARP +#ifndef KERNEL_RFC5227 +static void +dhcp_arp_defend_failed(struct arp_state *astate) +{ + + dhcp_drop(astate->iface, "EXPIRED"); + dhcp_start1(astate->iface); +} +#endif + static struct arp_state * dhcp_arp_new(struct interface *ifp, struct in_addr *addr) { struct arp_state *astate; + astate = arp_new(ifp, addr); if (astate == NULL) return NULL; - astate->probed_cb = dhcp_arp_probed; - astate->conflicted_cb = dhcp_arp_conflicted; + astate->found_cb = dhcp_arp_found; + astate->not_found_cb = dhcp_arp_not_found; +#ifdef KERNEL_RFC5227 astate->announced_cb = dhcp_arp_announced; +#else + astate->defend_failed_cb = dhcp_arp_defend_failed; +#endif return astate; } @@ -2415,7 +2379,6 @@ dhcp_arp_address(struct interface *ifp) struct dhcp_state *state; struct in_addr addr; struct ipv4_addr *ia; - struct arp_state *astate; eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); @@ -2425,10 +2388,6 @@ dhcp_arp_address(struct interface *ifp) /* If the interface already has the address configured * then we can't ARP for duplicate detection. */ ia = ipv4_iffindaddr(ifp, &addr, NULL); - astate = dhcp_arp_new(ifp, &addr); - if (astate == NULL) - return -1; - #ifdef IN_IFF_TENTATIVE if (ia == NULL || ia->addr_flags & IN_IFF_NOTUSEABLE) { state->state = DHS_PROBE; @@ -2445,8 +2404,13 @@ dhcp_arp_address(struct interface *ifp) } #else if (ifp->options->options & DHCPCD_ARP && ia == NULL) { + struct arp_state *astate; struct dhcp_lease l; + astate = dhcp_arp_new(ifp, &addr); + if (astate == NULL) + return -1; + state->state = DHS_PROBE; get_lease(ifp, &l, state->offer, state->offer_len); loginfox("%s: probing address %s/%d", @@ -2703,9 +2667,14 @@ dhcp_drop(struct interface *ifp, const char *reason) return; } +#ifdef ARP + if (state->addr != NULL) + arp_freeaddr(ifp, &state->addr->addr); +#endif #ifdef ARPING state->arping_index = -1; #endif + if (ifp->options->options & DHCPCD_RELEASE && !(ifp->options->options & DHCPCD_INFORM)) { @@ -3764,7 +3733,7 @@ dhcp_start1(void *arg) astate = dhcp_arp_new(ifp, NULL); if (astate) - dhcp_arp_probed(astate); + dhcp_arp_not_found(astate); return; } #endif @@ -4016,8 +3985,10 @@ dhcp_handleifa(int cmd, struct ipv4_addr *ia, pid_t pid) return; #ifdef IN_IFF_NOTUSEABLE - if (ia->addr_flags & IN_IFF_NOTUSEABLE) - return; + if (!(ia->addr_flags & IN_IFF_NOTUSEABLE)) + dhcp_finish_dad(ifp, &ia->addr); + else if (ia->addr_flags & IN_IFF_DUPLICATED) + dhcp_addr_duplicated(ifp, &ia->addr); #endif ifo = ifp->options; diff --git a/src/ipv4.c b/src/ipv4.c index 7a39c2c5..74f49439 100644 --- a/src/ipv4.c +++ b/src/ipv4.c @@ -468,11 +468,6 @@ ipv4_deladdr(struct ipv4_addr *addr, int keeparp) int r; struct ipv4_state *state; struct ipv4_addr *ap; -#ifdef ARP - struct arp_state *astate; -#else - UNUSED(keeparp); -#endif logdebugx("%s: deleting IP address %s", addr->iface->name, addr->saddr); @@ -484,8 +479,8 @@ ipv4_deladdr(struct ipv4_addr *addr, int keeparp) logerr("%s: %s", addr->iface->name, __func__); #ifdef ARP - if (!keeparp && (astate = arp_find(addr->iface, &addr->addr)) != NULL) - arp_free(astate); + if (!keeparp) + arp_freeaddr(addr->iface, &addr->addr); #endif state = IPV4_STATE(addr->iface); @@ -523,6 +518,7 @@ delete_address(struct interface *ifp) ifo->options & DHCPCD_INFORM || (ifo->options & DHCPCD_STATIC && ifo->req_addr.s_addr == 0)) return 0; + arp_freeaddr(ifp, &state->addr->addr); r = ipv4_deladdr(state->addr, 0); return r; } @@ -897,10 +893,10 @@ ipv4_handleifa(struct dhcpcd_ctx *ctx, } if (addr->s_addr != INADDR_ANY && addr->s_addr != INADDR_BROADCAST) { -#ifdef ARP - arp_handleifa(cmd, ia); -#endif dhcp_handleifa(cmd, ia, pid); +#ifdef IPV4LL + ipv4ll_handleifa(cmd, ia, pid); +#endif } if (cmd == RTM_DELADDR) diff --git a/src/ipv4.h b/src/ipv4.h index 496b8ecd..1038a5f5 100644 --- a/src/ipv4.h +++ b/src/ipv4.h @@ -72,6 +72,9 @@ (IN_IFF_TENTATIVE | IN_IFF_DUPLICATED | IN_IFF_DETACHED) #endif +#define IN_ARE_ADDR_EQUAL(a, b) ((a)->s_addr == (b)->s_addr) +#define IN_IS_ADDR_UNSPECIFIED(a) ((a)->s_addr == INADDR_ANY) + struct ipv4_addr { TAILQ_ENTRY(ipv4_addr) next; struct in_addr addr; diff --git a/src/ipv4ll.c b/src/ipv4ll.c index 51cd4f5b..81919bef 100644 --- a/src/ipv4ll.c +++ b/src/ipv4ll.c @@ -47,7 +47,6 @@ #include "sa.h" #include "script.h" -#ifdef IPV4LL static const struct in_addr inaddr_llmask = { .s_addr = HTONL(LINKLOCAL_MASK) }; @@ -55,18 +54,21 @@ static const struct in_addr inaddr_llbcast = { .s_addr = HTONL(LINKLOCAL_BCAST) }; +static void ipv4ll_start1(struct interface *, struct arp_state *); + static in_addr_t -ipv4ll_pickaddr(struct arp_state *astate) +ipv4ll_pickaddr(struct interface *ifp) { struct in_addr addr; - struct ipv4ll_state *istate; + struct ipv4ll_state *state; - istate = IPV4LL_STATE(astate->iface); - setstate(istate->randomstate); + state = IPV4LL_STATE(ifp); + setstate(state->randomstate); do { long r; +again: /* RFC 3927 Section 2.1 states that the first 256 and * last 256 addresses are reserved for future use. * See ipv4ll_start for why we don't use arc4random. */ @@ -76,13 +78,13 @@ ipv4ll_pickaddr(struct arp_state *astate) ((uint32_t)(r % 0xFD00) + 0x0100)); /* No point using a failed address */ - if (addr.s_addr == astate->failed.s_addr) - continue; + if (IN_ARE_ADDR_EQUAL(&addr, &state->pickedaddr)) + goto again; /* Ensure we don't have the address on another interface */ - } while (ipv4_findaddr(astate->iface->ctx, &addr) != NULL); + } while (ipv4_findaddr(ifp->ctx, &addr) != NULL); /* Restore the original random state */ - setstate(istate->arp->iface->ctx->randomstate); + setstate(ifp->ctx->randomstate); return addr.s_addr; } @@ -171,29 +173,46 @@ ipv4ll_env(char **env, const char *prefix, const struct interface *ifp) } static void -ipv4ll_probed(struct arp_state *astate) +ipv4ll_announced(struct arp_state *astate) +{ + struct ipv4ll_state *state = IPV4LL_STATE(astate->iface); + + state->conflicts = 0; +#ifdef KERNEL_RFC5227 + arp_free(astate); +#endif +} + +static void +ipv4ll_arpfree(struct arp_state *astate) { - struct interface *ifp; struct ipv4ll_state *state; - struct ipv4_addr *ia; - assert(astate != NULL); - assert(astate->iface != NULL); + state = IPV4LL_STATE(astate->iface); + if (state != NULL && state->arp == astate) + state->arp = NULL; +} + +static void +ipv4ll_not_found(struct interface *ifp) +{ + struct ipv4ll_state *state; + struct ipv4_addr *ia; + struct arp_state *astate; - ifp = astate->iface; state = IPV4LL_STATE(ifp); assert(state != NULL); - ia = ipv4_iffindaddr(ifp, &astate->addr, &inaddr_llmask); + ia = ipv4_iffindaddr(ifp, &state->pickedaddr, &inaddr_llmask); #ifdef IN_IFF_NOTREADY if (ia == NULL || ia->addr_flags & IN_IFF_NOTREADY) #endif loginfox("%s: using IPv4LL address %s", - ifp->name, inet_ntoa(astate->addr)); + ifp->name, inet_ntoa(state->pickedaddr)); if (ia == NULL) { if (ifp->ctx->options & DHCPCD_TEST) goto test; - ia = ipv4_addaddr(ifp, &astate->addr, + ia = ipv4_addaddr(ifp, &state->pickedaddr, &inaddr_llmask, &inaddr_llbcast); } if (ia == NULL) @@ -201,153 +220,117 @@ ipv4ll_probed(struct arp_state *astate) #ifdef IN_IFF_NOTREADY if (ia->addr_flags & IN_IFF_NOTREADY) return; - logdebugx("%s: DAD completed for %s", - ifp->name, inet_ntoa(astate->addr)); + logdebugx("%s: DAD completed for %s", ifp->name, ia->saddr); #endif test: state->addr = ia; + state->down = false; if (ifp->ctx->options & DHCPCD_TEST) { script_runreason(ifp, "TEST"); eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS); return; } - timespecclear(&state->defend); if_initrt(ifp->ctx, AF_INET); rt_build(ifp->ctx, AF_INET); - arp_announce(astate); +#ifdef KERNEL_RFC5227 + astate = arp_new(ifp, &ia->addr); + if (astate != NULL) { + astate->announced_cb = ipv4ll_announced; + astate->free_cb = ipv4ll_arpfree; + arp_announce(astate); + } +#else + arp_annnounce(state->arp); +#endif script_runreason(ifp, "IPV4LL"); dhcpcd_daemonise(ifp->ctx); } static void -ipv4ll_announced(struct arp_state *astate) +ipv4ll_startifp(void *arg) { - struct ipv4ll_state *state = IPV4LL_STATE(astate->iface); + struct interface *ifp = arg; + struct ipv4ll_state *state; - state->conflicts = 0; - /* Need to keep the arp state so we can defend our IP. */ + state = IPV4LL_STATE(ifp); + ipv4ll_start1(ifp, state->arp); } static void -ipv4ll_probe(void *arg) +ipv4ll_found(struct interface *ifp) { + struct ipv4ll_state *state = IPV4LL_STATE(ifp); -#ifdef IN_IFF_TENTATIVE - ipv4ll_probed(arg); -#else - arp_probe(arg); -#endif + arp_cancel(state->arp); + if (++state->conflicts == MAX_CONFLICTS) + logerr("%s: failed to acquire an IPv4LL address", + ifp->name); + eloop_timeout_add_sec(ifp->ctx->eloop, + state->conflicts >= MAX_CONFLICTS ? + RATE_LIMIT_INTERVAL : PROBE_WAIT, + ipv4ll_startifp, ifp); +} + +static void +ipv4ll_defend_failed(struct interface *ifp) +{ + struct ipv4ll_state *state = IPV4LL_STATE(ifp); + + ipv4_deladdr(state->addr, 1); + state->down = true; + state->addr = NULL; + if_initrt(ifp->ctx, AF_INET); + rt_build(ifp->ctx, AF_INET); + script_runreason(ifp, "IPV4LL"); + ipv4ll_start1(ifp, state->arp); } +#ifndef KERNEL_RFC5227 static void -ipv4ll_conflicted(struct arp_state *astate, const struct arp_msg *amsg) +ipv4ll_not_found_arp(struct arp_state *astate) { struct interface *ifp; struct ipv4ll_state *state; -#ifdef IN_IFF_DUPLICATED struct ipv4_addr *ia; -#endif assert(astate != NULL); assert(astate->iface != NULL); + ifp = astate->iface; state = IPV4LL_STATE(ifp); assert(state != NULL); + assert(state->arp == astate); + ipv4ll_not_found_arp(state); +} - /* - * NULL amsg means kernel detected DAD. - * We always fail on matching sip. - * We only fail on matching tip and we haven't added that address yet. - */ - if (amsg == NULL || - amsg->sip.s_addr == astate->addr.s_addr || - (amsg->sip.s_addr == 0 && amsg->tip.s_addr == astate->addr.s_addr - && ipv4_iffindaddr(ifp, &amsg->tip, NULL) == NULL)) - astate->failed = astate->addr; - else - return; - - arp_report_conflicted(astate, amsg); - - if (state->addr != NULL && - astate->failed.s_addr == state->addr->addr.s_addr) - { -#ifdef KERNEL_RFC5227 - logwarnx("%s: IPv4LL defence failed for %s", - ifp->name, state->addr->saddr); -#else - struct timespec now, defend; - - /* RFC 3927 Section 2.5 says a defence should - * broadcast an ARP announcement. - * Because the kernel will also unicast a reply to the - * hardware address which requested the IP address - * the other IPv4LL client will receieve two ARP - * messages. - * If another conflict happens within DEFEND_INTERVAL - * then we must drop our address and negotiate a new one. */ - defend.tv_sec = state->defend.tv_sec + DEFEND_INTERVAL; - defend.tv_nsec = state->defend.tv_nsec; - clock_gettime(CLOCK_MONOTONIC, &now); - if (timespeccmp(&defend, &now, >)) - logwarnx("%s: IPv4LL %d second defence failed for %s", - ifp->name, DEFEND_INTERVAL, state->addr->saddr); - else if (arp_request(ifp, - state->addr->addr.s_addr, state->addr->addr.s_addr) == -1) - logerr(__func__); - else { - logdebugx("%s: defended IPv4LL address %s", - ifp->name, state->addr->saddr); - state->defend = now; - return; - } -#endif - ipv4_deladdr(state->addr, 1); - state->down = 1; - state->addr = NULL; - if_initrt(ifp->ctx, AF_INET); - rt_build(ifp->ctx, AF_INET); - script_runreason(ifp, "IPV4LL"); - } - -#ifdef IN_IFF_DUPLICATED - ia = ipv4_iffindaddr(ifp, &astate->addr, NULL); - if (ia != NULL && ia->addr_flags & IN_IFF_DUPLICATED) - ipv4_deladdr(ia, 1); -#endif +static void +ipv4ll_found_arp(struct arp_state *astate, __unused const struct arp_msg *amsg) +{ + struct interface *ifp = astate->iface; + struct ipv4ll_state *state = IPV4LL_STATE(ifp); - arp_cancel(astate); - if (++state->conflicts == MAX_CONFLICTS) - logerr("%s: failed to acquire an IPv4LL address", - ifp->name); - state->pickedaddr.s_addr = ipv4ll_pickaddr(astate); - astate->addr = state->pickedaddr; - eloop_timeout_add_sec(ifp->ctx->eloop, - state->conflicts >= MAX_CONFLICTS ? - RATE_LIMIT_INTERVAL : PROBE_WAIT, - ipv4ll_probe, astate); + assert(state->arp == astate); + ipv4ll_found(ifp); } static void -ipv4ll_arpfree(struct arp_state *astate) +ipv4ll_defend_failed_arp(struct arp_state *astate) { - struct ipv4ll_state *state; + struct ipv4ll_state *state = IPV4LL_STATE(astate->ifp); - state = IPV4LL_STATE(astate->iface); - if (state != NULL && state->arp == astate) - state->arp = NULL; + assert(state->arp == astate); + ipv4ll_defend_failed1(astate->iface); } +#endif -void -ipv4ll_start(void *arg) +static void +ipv4ll_start1(struct interface *ifp, struct arp_state *astate) { - struct interface *ifp; struct ipv4ll_state *state; - struct arp_state *astate; struct ipv4_addr *ia; + bool repick; - assert(arg != NULL); - ifp = arg; + assert(ifp != NULL); if ((state = IPV4LL_STATE(ifp)) == NULL) { ifp->if_data[IF_DATA_IPV4LL] = calloc(1, sizeof(*state)); if ((state = IPV4LL_STATE(ifp)) == NULL) { @@ -383,16 +366,25 @@ ipv4ll_start(void *arg) state->seeded = true; } - if (state->arp != NULL) - return; - if ((astate = arp_new(ifp, NULL)) == NULL) - return; +#ifndef KERNEL_RFC5227 + if (astate == NULL) { + if (state->arp != NULL) + return; + if ((astate = arp_new(ifp, NULL)) == NULL) + return; + astate->found_cb = ipv4ll_found_arp; + astate->not_found_cb = ipv4ll_not_found_arp; + astate->announced_cb = ipv4ll_announced_arp; + astate->defend_failed_cb = ipv4ll_defend_failed_arp; + astate->free_cb = ipv4ll_arpfree; + state->arp = astate; + } else + assert(state->arp == astate); +#else + UNUSED(astate); +#endif - state->arp = astate; - astate->probed_cb = ipv4ll_probed; - astate->announced_cb = ipv4ll_announced; - astate->conflicted_cb = ipv4ll_conflicted; - astate->free_cb = ipv4ll_arpfree; + state->down = true; /* Find the previosuly used address. */ if (state->pickedaddr.s_addr != INADDR_ANY) @@ -404,15 +396,22 @@ ipv4ll_start(void *arg) if (ia == NULL) ia = ipv4_iffindlladdr(ifp); + repick = false; #ifdef IN_IFF_TENTATIVE if (ia != NULL && ia->addr_flags & IN_IFF_DUPLICATED) { + state->pickedaddr = ia->addr; /* So it's not picked again. */ + repick = true; ipv4_deladdr(ia, 0); ia = NULL; } #endif + state->addr = ia; if (ia != NULL) { - state->pickedaddr = astate->addr = ia->addr; + state->pickedaddr = ia->addr; +#ifndef KERNEL_RFC5227 + astate->addr = ia->addr; +#endif #ifdef IN_IFF_TENTATIVE if (ia->addr_flags & (IN_IFF_TENTATIVE | IN_IFF_DETACHED)) { loginfox("%s: waiting for DAD to complete on %s", @@ -421,21 +420,30 @@ ipv4ll_start(void *arg) } loginfox("%s: using IPv4LL address %s", ifp->name, ia->saddr); #endif - ipv4ll_probed(astate); + ipv4ll_not_found(ifp); return; } loginfox("%s: probing for an IPv4LL address", ifp->name); - if (state->pickedaddr.s_addr == INADDR_ANY) - state->pickedaddr.s_addr = ipv4ll_pickaddr(astate); + if (repick || state->pickedaddr.s_addr == INADDR_ANY) + state->pickedaddr.s_addr = ipv4ll_pickaddr(ifp); +#ifndef KERNEL_RFC5227 astate->addr = state->pickedaddr; +#endif #ifdef IN_IFF_TENTATIVE - ipv4ll_probed(astate); + ipv4ll_not_found(ifp); #else arp_probe(astate); #endif } +void +ipv4ll_start(void *arg) +{ + + ipv4ll_start1(arg, NULL); +} + static void ipv4ll_freearp(struct interface *ifp) { @@ -447,7 +455,6 @@ ipv4ll_freearp(struct interface *ifp) eloop_timeout_delete(ifp->ctx->eloop, NULL, state->arp); arp_free(state->arp); - state->arp = NULL; } void @@ -461,9 +468,7 @@ ipv4ll_drop(struct interface *ifp) ipv4ll_freearp(ifp); -#ifndef IN_IFF_TENATIVE if ((ifp->options->options & DHCPCD_NODROP) == DHCPCD_NODROP) -#endif return; state = IPV4LL_STATE(ifp); @@ -543,4 +548,34 @@ ipv4ll_recvrt(__unused int cmd, const struct rt *rt) return 0; } #endif + +void +ipv4ll_handleifa(int cmd, struct ipv4_addr *ia, pid_t pid) +{ + struct interface *ifp; + struct ipv4ll_state *state; + + ifp = ia->iface; + state = IPV4LL_STATE(ifp); + if (state == NULL || state->addr == NULL || + !IN_ARE_ADDR_EQUAL(&state->addr->addr, &ia->addr)) + return; + + if (cmd == RTM_DELADDR) { + loginfox("%s: pid %d deleted IP address %s", + ifp->name, pid, ia->saddr); + ipv4ll_defend_failed(ifp); + } + +#ifdef IN_IFF_DUPLICATED + if (cmd != RTM_NEWADDR) + return; + if (!(ia->addr_flags & IN_IFF_NOTUSEABLE)) + ipv4ll_not_found(ifp); + else if (ia->addr_flags & IN_IFF_DUPLICATED) { + logerrx("%s: DAD detected %s", ifp->name, ia->saddr); + ipv4_deladdr(state->addr, 1); + ipv4ll_found(ifp); + } #endif +} diff --git a/src/ipv4ll.h b/src/ipv4ll.h index 23fad1b1..6b95c180 100644 --- a/src/ipv4ll.h +++ b/src/ipv4ll.h @@ -43,11 +43,10 @@ struct ipv4ll_state { struct in_addr pickedaddr; struct ipv4_addr *addr; struct arp_state *arp; - unsigned int conflicts; - struct timespec defend; char randomstate[128]; bool seeded; - uint8_t down; + bool down; + size_t conflicts; }; #define IPV4LL_STATE(ifp) \ @@ -64,6 +63,7 @@ ssize_t ipv4ll_env(char **, const char *, const struct interface *); void ipv4ll_start(void *); void ipv4ll_claimed(void *); void ipv4ll_handle_failure(void *); +void ipv4ll_handleifa(int, struct ipv4_addr *, pid_t pid); #ifdef HAVE_ROUTE_METRIC int ipv4ll_recvrt(int, const struct rt *); #endif -- 2.47.3