changeset 4453:00fb50fd1a45 draft

ARP: Refactor so that ACD is available for all inet addresses Callbacks have also been improved so they are more descriptive.
author Roy Marples <roy@marples.name>
date Thu, 18 Apr 2019 14:54:47 +0100
parents 41a77e94b627
children 1037d1e35cd8
files src/arp.c src/arp.h src/dhcp.c src/ipv4.c src/ipv4.h src/ipv4ll.c src/ipv4ll.h
diffstat 7 files changed, 481 insertions(+), 455 deletions(-) [+]
line wrap: on
line diff
--- a/src/arp.c	Thu Apr 18 14:54:29 2019 +0100
+++ b/src/arp.c	Thu Apr 18 14:54:47 2019 +0100
@@ -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 @@
 	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 @@
 
 	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);
@@ -106,6 +110,77 @@
 }
 
 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)
 {
 	const struct interface *ifn;
@@ -164,14 +239,13 @@
 	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 @@
 	}
 }
 
-int
+static int
 arp_open(struct interface *ifp)
 {
 	struct iarp_state *state;
@@ -265,7 +339,8 @@
 {
 	struct arp_state *astate = arg;
 
-	astate->probed_cb(astate);
+	timespecclear(&astate->defend);
+	astate->not_found_cb(astate);
 }
 
 static void
@@ -290,7 +365,7 @@
 	    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 @@
 }
 #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 @@
 		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,
@@ -401,11 +493,25 @@
 }
 
 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 @@
 	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_tryfree(ifp);
 }
 
-static void
-arp_free_but1(struct interface *ifp, struct arp_state *astate)
+void
+arp_freeaddr(struct interface *ifp, const struct in_addr *ia)
 {
-	struct iarp_state *state;
-
-	if ((state = ARP_STATE(ifp)) != NULL) {
-		struct arp_state *p, *n;
+	struct arp_state *astate;
 
-		TAILQ_FOREACH_SAFE(p, &state->arp_states, next, n) {
-			if (p != astate)
-				arp_free(p);
-		}
-	}
-}
-
-void
-arp_free_but(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);
-}
+	struct iarp_state *state;
+	struct arp_state *astate;
 
-void
-arp_handleifa(int cmd, struct ipv4_addr *addr)
-{
-	struct iarp_state *state;
-	struct arp_state *astate, *asn;
-
-	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 */
 }
--- a/src/arp.h	Thu Apr 18 14:54:29 2019 +0100
+++ b/src/arp.h	Thu Apr 18 14:54:47 2019 +0100
@@ -63,15 +63,16 @@
 	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 @@
 	((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);
+struct arp_state *arp_new(struct interface *, const struct in_addr *);
 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_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 */
--- a/src/dhcp.c	Thu Apr 18 14:54:29 2019 +0100
+++ b/src/dhcp.c	Thu Apr 18 14:54:47 2019 +0100
@@ -124,8 +124,9 @@
 };
 
 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 @@
 	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 @@
 {
 	struct interface *ifp = arg;
 
+	if (ifp->options->options & DHCPCD_LASTLEASE_EXTEND) {
+		logwarnx("%s: DHCP lease expired, extending lease", ifp->name);
+		return;
+	}
+
 	logerrx("%s: DHCP lease expired", ifp->name);
-	if (ifp->options->options & DHCPCD_LASTLEASE_EXTEND) {
-		if (dhcp_leaseextend(ifp) == 0)
-			return;
-		logerr(__func__);
-	}
 	dhcp_expire1(ifp);
 }
 
@@ -2039,41 +2011,18 @@
 	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;
-
-	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);
+	struct dhcp_state *state = D_STATE(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 @@
 	ipv4ll_drop(ifp);
 #endif
 
-	if (ifo->options & DHCPCD_INFORM)
+	if (ifp->options->options & DHCPCD_INFORM)
 		dhcp_inform(ifp);
 }
 
+
 static void
-dhcp_arp_conflicted(struct arp_state *astate, const struct arp_msg *amsg)
+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_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 @@
 	{
 		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 @@
 	}
 #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 @@
 	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 @@
 }
 
 #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 @@
 	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 @@
 	/* 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 @@
 	}
 #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 @@
 		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 @@
 
 		astate = dhcp_arp_new(ifp, NULL);
 		if (astate)
-			dhcp_arp_probed(astate);
+			dhcp_arp_not_found(astate);
 		return;
 	}
 #endif
@@ -4016,8 +3985,10 @@
 		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;
--- a/src/ipv4.c	Thu Apr 18 14:54:29 2019 +0100
+++ b/src/ipv4.c	Thu Apr 18 14:54:47 2019 +0100
@@ -468,11 +468,6 @@
 	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 @@
 		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 @@
 	    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 @@
 	}
 
 	if (addr->s_addr != INADDR_ANY && addr->s_addr != INADDR_BROADCAST) {
-#ifdef ARP
-		arp_handleifa(cmd, ia);
+		dhcp_handleifa(cmd, ia, pid);
+#ifdef IPV4LL
+		ipv4ll_handleifa(cmd, ia, pid);
 #endif
-		dhcp_handleifa(cmd, ia, pid);
 	}
 
 	if (cmd == RTM_DELADDR)
--- a/src/ipv4.h	Thu Apr 18 14:54:29 2019 +0100
+++ b/src/ipv4.h	Thu Apr 18 14:54:47 2019 +0100
@@ -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;
--- a/src/ipv4ll.c	Thu Apr 18 14:54:29 2019 +0100
+++ b/src/ipv4ll.c	Thu Apr 18 14:54:47 2019 +0100
@@ -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 @@
 	.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 @@
 		    ((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,161 +173,14 @@
 }
 
 static void
-ipv4ll_probed(struct arp_state *astate)
-{
-	struct interface *ifp;
-	struct ipv4ll_state *state;
-	struct ipv4_addr *ia;
-
-	assert(astate != NULL);
-	assert(astate->iface != NULL);
-
-	ifp = astate->iface;
-	state = IPV4LL_STATE(ifp);
-	assert(state != NULL);
-
-	ia = ipv4_iffindaddr(ifp, &astate->addr, &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));
-	if (ia == NULL) {
-		if (ifp->ctx->options & DHCPCD_TEST)
-			goto test;
-		ia = ipv4_addaddr(ifp, &astate->addr,
-		    &inaddr_llmask, &inaddr_llbcast);
-	}
-	if (ia == NULL)
-		return;
-#ifdef IN_IFF_NOTREADY
-	if (ia->addr_flags & IN_IFF_NOTREADY)
-		return;
-	logdebugx("%s: DAD completed for %s",
-	    ifp->name, inet_ntoa(astate->addr));
-#endif
-test:
-	state->addr = ia;
-	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);
-	script_runreason(ifp, "IPV4LL");
-	dhcpcd_daemonise(ifp->ctx);
-}
-
-static void
 ipv4ll_announced(struct arp_state *astate)
 {
 	struct ipv4ll_state *state = IPV4LL_STATE(astate->iface);
 
 	state->conflicts = 0;
-	/* Need to keep the arp state so we can defend our IP. */
-}
-
-static void
-ipv4ll_probe(void *arg)
-{
-
-#ifdef IN_IFF_TENTATIVE
-	ipv4ll_probed(arg);
-#else
-	arp_probe(arg);
-#endif
-}
-
-static void
-ipv4ll_conflicted(struct arp_state *astate, const struct arp_msg *amsg)
-{
-	struct interface *ifp;
-	struct ipv4ll_state *state;
-#ifdef IN_IFF_DUPLICATED
-	struct ipv4_addr *ia;
+#ifdef KERNEL_RFC5227
+	arp_free(astate);
 #endif
-
-	assert(astate != NULL);
-	assert(astate->iface != NULL);
-	ifp = astate->iface;
-	state = IPV4LL_STATE(ifp);
-	assert(state != NULL);
-
-	/*
-	 * 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
-
-	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);
 }
 
 static void
@@ -338,16 +193,144 @@
 		state->arp = NULL;
 }
 
-void
-ipv4ll_start(void *arg)
+static void
+ipv4ll_not_found(struct interface *ifp)
+{
+	struct ipv4ll_state *state;
+	struct ipv4_addr *ia;
+	struct arp_state *astate;
+
+	state = IPV4LL_STATE(ifp);
+	assert(state != NULL);
+
+	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(state->pickedaddr));
+	if (ia == NULL) {
+		if (ifp->ctx->options & DHCPCD_TEST)
+			goto test;
+		ia = ipv4_addaddr(ifp, &state->pickedaddr,
+		    &inaddr_llmask, &inaddr_llbcast);
+	}
+	if (ia == NULL)
+		return;
+#ifdef IN_IFF_NOTREADY
+	if (ia->addr_flags & IN_IFF_NOTREADY)
+		return;
+	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;
+	}
+	if_initrt(ifp->ctx, AF_INET);
+	rt_build(ifp->ctx, AF_INET);
+#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_startifp(void *arg)
+{
+	struct interface *ifp = arg;
+	struct ipv4ll_state *state;
+
+	state = IPV4LL_STATE(ifp);
+	ipv4ll_start1(ifp, state->arp);
+}
+
+static void
+ipv4ll_found(struct interface *ifp)
+{
+	struct ipv4ll_state *state = IPV4LL_STATE(ifp);
+
+	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_not_found_arp(struct arp_state *astate)
 {
 	struct interface *ifp;
 	struct ipv4ll_state *state;
-	struct arp_state *astate;
 	struct ipv4_addr *ia;
 
-	assert(arg != NULL);
-	ifp = arg;
+	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);
+}
+
+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);
+
+	assert(state->arp == astate);
+	ipv4ll_found(ifp);
+}
+
+static void
+ipv4ll_defend_failed_arp(struct arp_state *astate)
+{
+	struct ipv4ll_state *state = IPV4LL_STATE(astate->ifp);
+
+	assert(state->arp == astate);
+	ipv4ll_defend_failed1(astate->iface);
+}
+#endif
+
+static void
+ipv4ll_start1(struct interface *ifp, struct arp_state *astate)
+{
+	struct ipv4ll_state *state;
+	struct ipv4_addr *ia;
+	bool repick;
+
+	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 @@
 		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 @@
 	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 @@
 		}
 		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 @@
 
 	eloop_timeout_delete(ifp->ctx->eloop, NULL, state->arp);
 	arp_free(state->arp);
-	state->arp = NULL;
 }
 
 void
@@ -461,9 +468,7 @@
 
 	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 @@
 	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
+}
--- a/src/ipv4ll.h	Thu Apr 18 14:54:29 2019 +0100
+++ b/src/ipv4ll.h	Thu Apr 18 14:54:47 2019 +0100
@@ -43,11 +43,10 @@
 	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 @@
 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