changeset 4168:e8510a89cdb2 draft

dhcp: arp announce existing address before reboot This updates upstream ARP tables to send their ACK to the interface we want to receive the message on and not one which is not in use but has the same IP address. Doing this means we no longer need to open DHCP sockets for interfaces we're not interested in.
author Roy Marples <roy@marples.name>
date Tue, 24 Oct 2017 00:13:09 +0100
parents 4d4937553032
children 07bfee79bd7a
files src/arp.c src/arp.h src/dhcp.c
diffstat 3 files changed, 80 insertions(+), 38 deletions(-) [+]
line wrap: on
line diff
--- a/src/arp.c	Mon Oct 23 10:55:28 2017 +0100
+++ b/src/arp.c	Tue Oct 24 00:13:09 2017 +0100
@@ -373,10 +373,16 @@
 arp_announceaddr(struct dhcpcd_ctx *ctx, struct in_addr *ia)
 {
 	struct interface *ifp;
+	struct ipv4_addr *iaf;
 	struct arp_state *astate;
 
 	TAILQ_FOREACH(ifp, ctx->ifaces, next) {
-		if (ipv4_iffindaddr(ifp, ia, NULL))
+		iaf = ipv4_iffindaddr(ifp, ia, NULL);
+#ifdef IN_IFF_NOTUSEABLE
+		if (iaf && !(iaf->addr_flags & IN_IFF_NOTUSEABLE))
+#else
+		if (iaf)
+#endif
 			break;
 	}
 	if (ifp == NULL)
@@ -388,6 +394,16 @@
 }
 
 void
+arp_ifannounceaddr(struct interface *ifp, 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)
 {
--- a/src/arp.h	Mon Oct 23 10:55:28 2017 +0100
+++ b/src/arp.h	Tue Oct 24 00:13:09 2017 +0100
@@ -95,6 +95,7 @@
 struct arp_state *arp_find(struct interface *, const struct in_addr *);
 void arp_announce(struct arp_state *);
 void arp_announceaddr(struct dhcpcd_ctx *, struct in_addr *);
+void arp_ifannounceaddr(struct interface *, struct in_addr *);
 void arp_cancel(struct arp_state *);
 void arp_free(struct arp_state *);
 void arp_free_but(struct arp_state *);
--- a/src/dhcp.c	Mon Oct 23 10:55:28 2017 +0100
+++ b/src/dhcp.c	Tue Oct 24 00:13:09 2017 +0100
@@ -2463,11 +2463,43 @@
 	}
 }
 
+#ifdef ARP
+static int
+dhcp_activeaddr(const struct interface *ifp, const struct in_addr *addr)
+{
+	const struct interface *ifp1;
+	const struct dhcp_state *state;
+
+	TAILQ_FOREACH(ifp1, ifp->ctx->ifaces, next) {
+		if (ifp1 == ifp)
+			continue;
+		if ((state = D_CSTATE(ifp1)) == NULL)
+			continue;
+		switch(state->state) {
+		case DHS_REBOOT:
+		case DHS_RENEW:
+		case DHS_REBIND:
+		case DHS_BOUND:
+		case DHS_INFORM:
+			break;
+		default:
+			continue;
+		}
+		if (state->lease.addr.s_addr == addr->s_addr)
+			return 1;
+	}
+	return 0;
+}
+#endif
+
 static void
 dhcp_reboot(struct interface *ifp)
 {
 	struct if_options *ifo;
 	struct dhcp_state *state = D_STATE(ifp);
+#ifdef ARP
+	struct ipv4_addr *ia;
+#endif
 
 	if (state == NULL || state->state == DHS_NONE)
 		return;
@@ -2498,6 +2530,20 @@
 
 	loginfox("%s: rebinding lease of %s",
 	    ifp->name, inet_ntoa(state->lease.addr));
+
+#ifdef ARP
+	/* If the address exists on the interface and no other interface
+	 * is currently using it then announce it to ensure this
+	 * interface gets the reply. */
+	ia = ipv4_iffindaddr(ifp, &state->lease.addr, NULL);
+	if (ia != NULL &&
+#ifdef IN_IFF_NOTUSEABLE
+	    !(ia->addr_flags & IN_IFF_NOTUSEABLE) &&
+#endif
+	    dhcp_activeaddr(ifp, &state->lease.addr) == 0)
+		arp_ifannounceaddr(ifp, &state->lease.addr);
+#endif
+
 	dhcp_new_xid(ifp);
 	state->lease.server.s_addr = 0;
 	eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
@@ -3287,53 +3333,32 @@
 	}
 }
 
-
 static int
-dhcp_openbpf1(struct interface *ifp, bool logerror)
+dhcp_openbpf(struct interface *ifp)
 {
 	struct dhcp_state *state;
 
 	state = D_STATE(ifp);
+	if (state->bpf_fd != -1)
+		return 0;
+
+	state->bpf_fd = bpf_open(ifp, bpf_bootp);
 	if (state->bpf_fd == -1) {
-		state->bpf_fd = bpf_open(ifp, bpf_bootp);
-		if (state->bpf_fd == -1) {
-			if (errno == ENOENT) {
-				logerrx("%s not found", bpf_name);
-				/* May as well disable IPv4 entirely at
-				 * this point as we really need it. */
-				ifp->options->options &= ~DHCPCD_IPV4;
-			} else if (logerror)
-				logerr("%s: %s", __func__, ifp->name);
-			return -1;
-		}
-		eloop_event_add(ifp->ctx->eloop,
-		    state->bpf_fd, dhcp_readpacket, ifp);
+		if (errno == ENOENT) {
+			logerrx("%s not found", bpf_name);
+			/* May as well disable IPv4 entirely at
+			 * this point as we really need it. */
+			ifp->options->options &= ~DHCPCD_IPV4;
+		} else
+			logerr("%s: %s", __func__, ifp->name);
+		return -1;
 	}
+
+	eloop_event_add(ifp->ctx->eloop,
+	    state->bpf_fd, dhcp_readpacket, ifp);
 	return 0;
 }
 
-/* This blows chunks.
- * Because we may (although unlikely) get the same IP address
- * across different interfaces we need to open a BPF socket
- * on ALL interfaces as thanks to ARP magic we might get the
- * DHCP reply on a different interface. */
-static int
-dhcp_openbpf(struct interface *ifp)
-{
-	struct interface *ifn;
-	int r, o;
-
-	r = -1;
-	TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) {
-		if (dhcp_initstate(ifn) == -1)
-			continue;
-		o = dhcp_openbpf1(ifn, ifn == ifp);
-		if (ifn == ifp)
-			r = o;
-	}
-	return r;
-}
-
 int
 dhcp_dump(struct interface *ifp)
 {