changeset 4235:27bad70c0d9c draft

link: detect buffer overflow / desync and relearn interface state It's possible for the internal kernel buffer that reports network events to overflow. On Linux and NetBSD* this is handled by ENOBUFS being returned by recv(2). On OpenBSD there is a special route(4) message RTM_DESYNC. All other OS's don't seem to report this error, so dhcpcd cannot detect it. * I will commit a patch to NetBSD soon for this and will request a pullup to NetBSD-8.
author Roy Marples <roy@marples.name>
date Mon, 19 Mar 2018 15:39:05 +0000
parents 56c1ab3b654e
children f6be46401658
files src/dhcpcd.c src/dhcpcd.h src/if-bsd.c src/if.c src/if.h src/ipv4.c src/ipv4.h src/ipv6.c src/ipv6.h src/ipv6nd.c
diffstat 10 files changed, 188 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- a/src/dhcpcd.c	Thu Mar 15 20:31:14 2018 +0000
+++ b/src/dhcpcd.c	Mon Mar 19 15:39:05 2018 +0000
@@ -955,20 +955,6 @@
 	}
 }
 
-static void
-dhcpcd_handlelink(void *arg)
-{
-	struct dhcpcd_ctx *ctx;
-
-	ctx = arg;
-	if (if_handlelink(ctx) == -1) {
-		logerr(__func__);
-		eloop_event_delete(ctx->eloop, ctx->link_fd);
-		close(ctx->link_fd);
-		ctx->link_fd = -1;
-	}
-}
-
 int
 dhcpcd_handleinterface(void *arg, int action, const char *ifname)
 {
@@ -1041,6 +1027,83 @@
 	return 1;
 }
 
+static void
+dhcpcd_handlelink(void *arg)
+{
+	struct dhcpcd_ctx *ctx = arg;
+
+	if (if_handlelink(ctx) == -1) {
+		if (errno == ENOBUFS) {
+			dhcpcd_linkoverflow(ctx);
+			return;
+		}
+		logerr(__func__);
+	}
+}
+
+static void
+dhcpcd_checkcarrier(void *arg)
+{
+	struct interface *ifp = arg;
+
+	dhcpcd_handlecarrier(ifp->ctx, LINK_UNKNOWN, ifp->flags, ifp->name);
+}
+
+void
+dhcpcd_linkoverflow(struct dhcpcd_ctx *ctx)
+{
+	struct if_head *ifaces;
+	struct ifaddrs *ifaddrs;
+	struct interface *ifp, *ifn, *ifp1;
+
+	loginfox("route socket overflowed - learning interface state");
+
+	/* Close the existing socket and open a new one.
+	 * This is easier than draining the kernel buffer of an
+	 * in-determinate size. */
+	eloop_event_delete(ctx->eloop, ctx->link_fd);
+	close(ctx->link_fd);
+	if_closesockets_os(ctx);
+	if (if_opensockets_os(ctx) == -1) {
+		logerr("%s: if_opensockets", __func__);
+		eloop_exit(ctx->eloop, EXIT_FAILURE);
+		return;
+	}
+	eloop_event_add(ctx->eloop, ctx->link_fd, dhcpcd_handlelink, ctx);
+
+	/* Work out the current interfaces. */
+	ifaces = if_discover(ctx, &ifaddrs, ctx->ifc, ctx->ifv);
+
+	/* Punt departed interfaces */
+	TAILQ_FOREACH_SAFE(ifp, ctx->ifaces, next, ifn) {
+		if (if_find(ifaces, ifp->name) != NULL)
+			continue;
+		dhcpcd_handleinterface(ctx, -1, ifp->name);
+	}
+
+	/* Add new interfaces */
+	TAILQ_FOREACH_SAFE(ifp, ifaces, next, ifn) {
+		ifp1 = if_find(ctx->ifaces, ifp->name);
+		if (ifp1 != NULL) {
+			/* If the interface already exists,
+			 * check carrier state. */
+			eloop_timeout_add_sec(ctx->eloop, 0,
+			    dhcpcd_checkcarrier, ifp1);
+			continue;
+		}
+		TAILQ_REMOVE(ifaces, ifp, next);
+		TAILQ_INSERT_TAIL(ctx->ifaces, ifp, next);
+		if (ifp->active)
+			eloop_timeout_add_sec(ctx->eloop, 0,
+			    dhcpcd_prestartinterface, ifp);
+	}
+
+	/* Update address state. */
+	if_markaddrsstale(ctx->ifaces);
+	if_learnaddrs(ctx, ctx->ifaces, &ifaddrs);
+	if_deletestaleaddrs(ctx->ifaces);
+}
+
 void
 dhcpcd_handlehwaddr(struct dhcpcd_ctx *ctx, const char *ifname,
     const void *hwaddr, uint8_t hwlen)
--- a/src/dhcpcd.h	Thu Mar 15 20:31:14 2018 +0000
+++ b/src/dhcpcd.h	Mon Mar 19 15:39:05 2018 +0000
@@ -230,6 +230,7 @@
 int dhcpcd_afwaiting(const struct dhcpcd_ctx *);
 pid_t dhcpcd_daemonise(struct dhcpcd_ctx *);
 
+void dhcpcd_linkoverflow(struct dhcpcd_ctx *);
 int dhcpcd_handleargs(struct dhcpcd_ctx *, struct fd_list *, int, char **);
 void dhcpcd_handlecarrier(struct dhcpcd_ctx *, int, unsigned int, const char *);
 int dhcpcd_handleinterface(void *, int, const char *);
--- a/src/if-bsd.c	Thu Mar 15 20:31:14 2018 +0000
+++ b/src/if-bsd.c	Mon Mar 19 15:39:05 2018 +0000
@@ -1210,6 +1210,11 @@
 	case RTM_NEWADDR:
 		if_ifa(ctx, (const void *)rtm);
 		break;
+#ifdef RTM_DESYNC
+	case RTM_DESYNC:
+		dhcpcd_linkoverflow(ctx);
+		break;
+#endif
 	}
 }
 
@@ -1223,7 +1228,8 @@
 	msg.msg_iov = ctx->iov;
 	msg.msg_iovlen = 1;
 
-	if ((len = recvmsg_realloc(ctx->link_fd, &msg, 0)) == -1)
+	len = recvmsg_realloc(ctx->link_fd, &msg, 0);
+	if (len == -1)
 		return -1;
 	if (len != 0)
 		if_dispatch(ctx, ctx->iov[0].iov_base);
@@ -1480,9 +1486,11 @@
 		char ifname[IFNAMSIZ + 8];
 
 		strlcpy(ifname, ifp->name, sizeof(ifname));
-		if (ioctl(s, SIOCSRTRFLUSH_IN6, (void *)&ifname) == -1)
+		if (ioctl(s, SIOCSRTRFLUSH_IN6, (void *)&ifname) == -1 &&
+		    errno != ENOTSUP)
 			logwarn("SIOCSRTRFLUSH_IN6");
-		if (ioctl(s, SIOCSPFXFLUSH_IN6, (void *)&ifname) == -1)
+		if (ioctl(s, SIOCSPFXFLUSH_IN6, (void *)&ifname) == -1 &&
+		    errno != ENOTSUP)
 			logwarn("SIOCSPFXFLUSH_IN6");
 	}
 #endif
--- a/src/if.c	Thu Mar 15 20:31:14 2018 +0000
+++ b/src/if.c	Mon Mar 19 15:39:05 2018 +0000
@@ -191,6 +191,21 @@
 }
 
 void
+if_markaddrsstale(struct if_head *ifs)
+{
+	struct interface *ifp;
+
+	TAILQ_FOREACH(ifp, ifs, next) {
+#ifdef INET
+		ipv4_markaddrsstale(ifp);
+#endif
+#ifdef INET6
+		ipv6_markaddrsstale(ifp, 0);
+#endif
+	}
+}
+
+void
 if_learnaddrs(struct dhcpcd_ctx *ctx, struct if_head *ifs,
     struct ifaddrs **ifaddrs)
 {
@@ -268,6 +283,21 @@
 	*ifaddrs = NULL;
 }
 
+void
+if_deletestaleaddrs(struct if_head *ifs)
+{
+	struct interface *ifp;
+
+	TAILQ_FOREACH(ifp, ifs, next) {
+#ifdef INET
+		ipv4_deletestaleaddrs(ifp);
+#endif
+#ifdef INET6
+		ipv6_deletestaleaddrs(ifp);
+#endif
+	}
+}
+
 bool
 if_valid_hwaddr(const uint8_t *hwaddr, size_t hwlen)
 {
--- a/src/if.h	Thu Mar 15 20:31:14 2018 +0000
+++ b/src/if.h	Mon Mar 19 15:39:05 2018 +0000
@@ -116,7 +116,9 @@
 bool if_valid_hwaddr(const uint8_t *, size_t);
 struct if_head *if_discover(struct dhcpcd_ctx *, struct ifaddrs **,
     int, char * const *);
+void if_markaddrsstale(struct if_head *);
 void if_learnaddrs(struct dhcpcd_ctx *, struct if_head *, struct ifaddrs **);
+void if_deletestaleaddrs(struct if_head *);
 struct interface *if_find(struct if_head *, const char *);
 struct interface *if_findindex(struct if_head *, unsigned int);
 struct interface *if_loopback(struct dhcpcd_ctx *);
--- a/src/ipv4.c	Thu Mar 15 20:31:14 2018 +0000
+++ b/src/ipv4.c	Mon Mar 19 15:39:05 2018 +0000
@@ -762,6 +762,39 @@
 }
 
 void
+ipv4_markaddrsstale(struct interface *ifp)
+{
+	struct ipv4_state *state;
+	struct ipv4_addr *ia;
+
+	state = IPV4_STATE(ifp);
+	if (state == NULL)
+		return;
+
+	TAILQ_FOREACH(ia, &state->addrs, next) {
+		ia->flags |= IPV4_AF_STALE;
+	}
+}
+
+void
+ipv4_deletestaleaddrs(struct interface *ifp)
+{
+	struct ipv4_state *state;
+	struct ipv4_addr *ia, *ia1;
+
+	state = IPV4_STATE(ifp);
+	if (state == NULL)
+		return;
+
+	TAILQ_FOREACH_SAFE(ia, &state->addrs, next, ia1) {
+		if (ia->flags & IPV4_AF_STALE)
+			ipv4_handleifa(ifp->ctx, RTM_DELADDR,
+			    ifp->ctx->ifaces, ifp->name,
+			    &ia->addr, &ia->mask, &ia->brd, 0, 0);
+	}
+}
+
+void
 ipv4_handleifa(struct dhcpcd_ctx *ctx,
     int cmd, struct if_head *ifs, const char *ifname,
     const struct in_addr *addr, const struct in_addr *mask,
@@ -796,6 +829,7 @@
 			ia->iface = ifp;
 			ia->addr = *addr;
 			ia->mask = *mask;
+			ia->flags = 0;
 			ia_is_new = true;
 #ifdef ALIAS_ADDR
 			strlcpy(ia->alias, ifname, sizeof(ia->alias));
@@ -817,6 +851,7 @@
 		else
 			ia->brd.s_addr = INADDR_ANY;
 		ia->addr_flags = addrflags;
+		ia->flags &= ~IPV4_AF_STALE;
 		break;
 	case RTM_DELADDR:
 		if (ia == NULL)
--- a/src/ipv4.h	Thu Mar 15 20:31:14 2018 +0000
+++ b/src/ipv4.h	Mon Mar 19 15:39:05 2018 +0000
@@ -79,6 +79,7 @@
 	struct in_addr brd;
 	struct interface *iface;
 	int addr_flags;
+	unsigned int flags;
 	char saddr[INET_ADDRSTRLEN + 3];
 #ifdef ALIAS_ADDR
 	char alias[IF_NAMESIZE];
@@ -86,6 +87,8 @@
 };
 TAILQ_HEAD(ipv4_addrhead, ipv4_addr);
 
+#define	IPV4_AF_STALE		(1U << 0)
+
 #define	IPV4_ADDR_EQ(a1, a2)	((a1) && (a1)->addr.s_addr == (a2)->addr.s_addr)
 #define	IPV4_MASK1_EQ(a1, a2)	((a1) && (a1)->mask.s_addr == (a2)->mask.s_addr)
 #define	IPV4_MASK_EQ(a1, a2)	(IPV4_ADDR_EQ(a1, a2) && IPV4_MASK1_EQ(a1, a2))
@@ -129,6 +132,8 @@
 struct ipv4_addr *ipv4_findaddr(struct dhcpcd_ctx *, const struct in_addr *);
 struct ipv4_addr *ipv4_findmaskaddr(struct dhcpcd_ctx *,
     const struct in_addr *);
+void ipv4_markaddrsstale(struct interface *);
+void ipv4_deletestaleaddrs(struct interface *);
 void ipv4_handleifa(struct dhcpcd_ctx *, int, struct if_head *, const char *,
     const struct in_addr *, const struct in_addr *, const struct in_addr *,
     int, pid_t);
--- a/src/ipv6.c	Thu Mar 15 20:31:14 2018 +0000
+++ b/src/ipv6.c	Mon Mar 19 15:39:05 2018 +0000
@@ -1134,6 +1134,7 @@
 			TAILQ_INSERT_TAIL(&state->addrs, ia, next);
 		}
 		ia->addr_flags = addrflags;
+		ia->flags &= ~IPV6_AF_STALE;
 #ifdef IPV6_MANAGETEMPADDR
 		if (ia->addr_flags & IN6_IFF_TEMPORARY)
 			ia->flags |= IPV6_AF_TEMPORARY;
@@ -1973,18 +1974,39 @@
 }
 
 void
-ipv6_settempstale(struct interface *ifp)
+ipv6_markaddrsstale(struct interface *ifp, unsigned int flags)
 {
 	struct ipv6_state *state;
 	struct ipv6_addr *ia;
 
 	state = IPV6_STATE(ifp);
+	if (state == NULL)
+		return;
+
 	TAILQ_FOREACH(ia, &state->addrs, next) {
-		if (ia->flags & IPV6_AF_TEMPORARY)
+		if (flags == 0 || ia->flags & flags)
 			ia->flags |= IPV6_AF_STALE;
 	}
 }
 
+void
+ipv6_deletestaleaddrs(struct interface *ifp)
+{
+	struct ipv6_state *state;
+	struct ipv6_addr *ia, *ia1;
+
+	state = IPV6_STATE(ifp);
+	if (state == NULL)
+		return;
+
+	TAILQ_FOREACH_SAFE(ia, &state->addrs, next, ia1) {
+		if (ia->flags & IPV6_AF_STALE)
+			ipv6_handleifa(ifp->ctx, RTM_DELADDR,
+			    ifp->ctx->ifaces, ifp->name,
+			    &ia->addr, ia->prefix_len, 0, 0);
+	}
+}
+
 struct ipv6_addr *
 ipv6_settemptime(struct ipv6_addr *ia, int flags)
 {
--- a/src/ipv6.h	Thu Mar 15 20:31:14 2018 +0000
+++ b/src/ipv6.h	Mon Mar 19 15:39:05 2018 +0000
@@ -231,6 +231,8 @@
 int ipv6_userprefix( const struct in6_addr *, short prefix_len,
     uint64_t user_number, struct in6_addr *result, short result_len);
 void ipv6_checkaddrflags(void *);
+void ipv6_markaddrsstale(struct interface *, unsigned int);
+void ipv6_deletestaleaddrs(struct interface *);
 int ipv6_addaddr(struct ipv6_addr *, const struct timespec *);
 ssize_t ipv6_addaddrs(struct ipv6_addrhead *addrs);
 void ipv6_deleteaddr(struct ipv6_addr *);
@@ -260,7 +262,6 @@
 
 #ifdef IPV6_MANAGETEMPADDR
 void ipv6_gentempifid(struct interface *);
-void ipv6_settempstale(struct interface *);
 struct ipv6_addr *ipv6_createtempaddr(struct ipv6_addr *,
     const struct timespec *);
 struct ipv6_addr *ipv6_settemptime(struct ipv6_addr *, int);
--- a/src/ipv6nd.c	Thu Mar 15 20:31:14 2018 +0000
+++ b/src/ipv6nd.c	Mon Mar 19 15:39:05 2018 +0000
@@ -870,7 +870,7 @@
 		rap->expired = 0;
 	rap->hasdns = 0;
 
-	ipv6_settempstale(ifp);
+	ipv6_markaddrsstale(ifp, IPV6_AF_TEMPORARY);
 	TAILQ_FOREACH(ap, &rap->addrs, next) {
 		ap->flags |= IPV6_AF_STALE;
 	}