changeset 972:e61044370846 draft

Remove remembering routes per interface and have a global routing table so we can change routes depending on interface state. This is very useful for the BSD's where there is no route metric.
author Roy Marples <roy@marples.name>
date Fri, 12 Sep 2008 18:08:07 +0000
parents 71c60fc4d1eb
children 5b59480a3a9b
files configure.c dhcpcd.h if-bsd.c if-linux.c net.h
diffstat 5 files changed, 201 insertions(+), 123 deletions(-) [+]
line wrap: on
line diff
--- a/configure.c	Thu Sep 11 22:55:27 2008 +0000
+++ b/configure.c	Fri Sep 12 18:08:07 2008 +0000
@@ -49,6 +49,17 @@
 
 #define DEFAULT_PATH	"PATH=/usr/bin:/usr/sbin:/bin:/sbin"
 
+#ifndef HAVE_ROUTE_METRIC
+# ifdef __linux__
+#  define HAVE_ROUTE_METRIC 1
+# endif
+# ifndef HAVE_ROUTE_METRIC
+#  define HAVE_ROUTE_METRIC 0
+# endif
+#endif
+
+static struct rt *routes = NULL;
+
 static int
 exec_script(char *const *argv, char *const *env)
 {
@@ -183,8 +194,77 @@
 	return status;
 }
 
+static struct rt *
+find_route(struct rt *rts, const struct rt *r, struct rt **lrt,
+	   const struct rt *srt)
+{
+	struct rt *rt;
+
+	if (lrt)
+		*lrt = NULL;
+	for (rt = rts; rt; rt = rt->next) {
+		if (rt->dest.s_addr == r->dest.s_addr &&
+#if HAVE_ROUTE_METRIC
+		    (srt || (!rt->iface || rt->iface->metric == r->iface->metric)) &&
+#endif
+                    (!srt || srt != rt) &&
+		    rt->net.s_addr == r->net.s_addr)
+			return rt;
+		if (lrt)
+			*lrt = rt;
+	}
+	return NULL;
+}
+
 static int
-delete_route(const struct interface *iface, struct rt *rt, int metric)
+n_route(struct rt *rt, const struct interface *iface)
+{
+	char *addr;
+
+	/* Don't set default routes if not asked to */
+	if (rt->dest.s_addr == 0 &&
+	    rt->net.s_addr == 0 &&
+	    !(iface->state->options->options & DHCPCD_GATEWAY))
+		return -1;
+
+	addr = xstrdup(inet_ntoa(rt->dest));
+	syslog(LOG_DEBUG, "%s: adding route to %s/%d via %s",
+			iface->name, addr,
+			inet_ntocidr(rt->net), inet_ntoa(rt->gate));
+	free(addr);
+	if (!add_route(iface, &rt->dest, &rt->net, &rt->gate, iface->metric))
+		return 0;
+	if (errno != EEXIST)
+		syslog(LOG_ERR, "add_route: %m");
+	return -1;
+}
+
+static int
+c_route(struct rt *ort, struct rt *nrt, const struct interface *iface)
+{
+	char *addr;
+
+	/* Don't set default routes if not asked to */
+	if (nrt->dest.s_addr == 0 &&
+	    nrt->net.s_addr == 0 &&
+	    !(iface->state->options->options & DHCPCD_GATEWAY))
+		return -1;
+
+	addr = xstrdup(inet_ntoa(nrt->dest));
+	syslog(LOG_DEBUG, "%s: changing route to %s/%d via %s",
+			iface->name, addr,
+			inet_ntocidr(nrt->net), inet_ntoa(nrt->gate));
+	free(addr);
+	del_route(ort->iface, &ort->dest, &ort->net, &ort->gate, ort->iface->metric);
+	if (!add_route(iface, &nrt->dest, &nrt->net, &nrt->gate, iface->metric))
+		return 0;
+	syslog(LOG_ERR, "add_route: %m");
+	return -1;
+}
+
+
+static int
+d_route(struct rt *rt, const struct interface *iface, int metric)
 {
 	char *addr;
 	int retval;
@@ -199,119 +279,109 @@
 	return retval;
 }
 
-static int
-delete_routes(struct interface *iface)
+static void
+remove_routes(const struct interface *iface)
 {
-	struct rt *rt;
-	struct rt *rtn;
-	int retval = 0;
+	struct rt *rt, *dor, *dnr = NULL, *irt, *lirt, *irts, *trt, *rtn, *lrt;
+	const struct interface *ifp;
+
+	if (!iface->state->old)
+		return;
+
+	if (iface->state->new)
+		dnr = get_option_routes(iface->state->new);
 
-	rt = iface->routes;
-	while (rt) {
-		rtn = rt->next;
-		retval += delete_route(iface, rt, iface->metric);
-		free(rt);
-		rt = rtn;
+	dor = get_option_routes(iface->state->old);
+	for (rt = dor; rt && (rtn = rt->next, 1); rt = rtn) {
+		rt->iface = iface;
+		/* Do we still have the route? */
+		if (dnr && find_route(dnr, rt, NULL, NULL))
+			continue;
+		/* Check if we manage the route */
+		if (!(trt = find_route(routes, rt, &lrt, NULL)))
+			continue;
+		if (trt->iface != iface)
+			continue;
+		irt = NULL;
+		irts = NULL;
+		/* We may have an alternative route */
+		if (!find_route(routes, rt, NULL, trt)) {
+			/* Do we have a replacement route? */
+			for (ifp = ifaces; ifp; ifp = ifp->next) {
+				if (ifp == iface || !ifp->state->new)
+					continue;
+				irts = get_option_routes(ifp->state->new);
+				if ((irt = find_route(irts, rt, &lirt, NULL)))
+					break;
+				free_routes(irts);
+				irts = NULL;
+			}
+		}
+		if (irt) {
+			c_route(trt, irt, ifp);
+			trt->gate.s_addr = irt->gate.s_addr;
+			trt->iface = ifp;
+		} else {
+			d_route(trt, trt->iface,  trt->iface->metric);
+			if (lrt)
+				lrt->next = trt->next;
+			else
+				routes = trt->next; 
+			free(trt);
+		}
+		free_routes(irts);
 	}
-	iface->routes = NULL;
-
-	return retval;
-}
-
-static int
-in_routes(const struct rt *routes, const struct rt *rt)
-{
-	while (routes) {
-		if (routes->dest.s_addr == rt->dest.s_addr &&
-				routes->net.s_addr == rt->net.s_addr &&
-				routes->gate.s_addr == rt->gate.s_addr)
-			return 0;
-		routes = routes->next;
-	}
-	return -1;
+	free_routes(dor);
+	return;
 }
 
-static int
-configure_routes(struct interface *iface, const struct dhcp_message *dhcp)
+static void
+build_routes(void)
 {
-	const struct if_options *ifo = iface->state->options;
-	struct rt *rt, *ort;
-	struct rt *rtn = NULL, *nr = NULL;
-	int remember;
-	int retval = 0;
-	char *addr;
-
-	ort = get_option_routes(dhcp);
-
-#ifdef IPV4LL_ALWAYSROUTE
-	if (ifo->options & DHCPCD_IPV4LL &&
-	    IN_PRIVATE(ntohl(dhcp->yiaddr)))
-	{
-		for (rt = ort; rt; rt = rt->next) {
-			/* Check if we have already got a link locale route
-			 * dished out by the DHCP server */
-			if (rt->dest.s_addr == htonl(LINKLOCAL_ADDR) &&
-			    rt->net.s_addr == htonl(LINKLOCAL_MASK))
-				break;
-			rtn = rt;
-		}
-
-		if (!rt) {
-			rt = xmalloc(sizeof(*rt));
-			rt->dest.s_addr = htonl(LINKLOCAL_ADDR);
-			rt->net.s_addr = htonl(LINKLOCAL_MASK);
-			rt->gate.s_addr = 0;
-			rt->next = NULL;
-			if (rtn)
-				rtn->next = rt;
-			else
-				ort = rt;
-		}
-	}
-#endif
+	struct rt *nrs = NULL, *dnr, *or, *rt, *rtn, *rtl;
+	const struct interface *ifp;
 
-	/* Now remove old routes we no longer use. */
-	for (rt = iface->routes; rt; rt = rt->next)
-		if (in_routes(ort, rt) != 0)
-			delete_route(iface, rt, iface->metric);
-
-	for (rt = ort; rt; rt = rt->next) {
-		/* Don't set default routes if not asked to */
-		if (rt->dest.s_addr == 0 &&
-		    rt->net.s_addr == 0 &&
-		    !(ifo->options & DHCPCD_GATEWAY))
+	for (ifp = ifaces; ifp; ifp = ifp->next) {
+		if (!ifp->state->new)
 			continue;
-
-		addr = xstrdup(inet_ntoa(rt->dest));
-		syslog(LOG_DEBUG, "%s: adding route to %s/%d via %s",
-		       iface->name, addr,
-		       inet_ntocidr(rt->net), inet_ntoa(rt->gate));
-		free(addr);
-		remember = add_route(iface, &rt->dest,
-				     &rt->net, &rt->gate, iface->metric);
-		retval += remember;
-
-		/* If we failed to add the route, we may have already added it
-		   ourselves. If so, remember it again. */
-		if (remember < 0) {
-			if (errno != EEXIST)
-				syslog(LOG_ERR, "add_route: %m");
-			if (in_routes(iface->routes, rt) == 0)
-				remember = 1;
+		dnr = get_option_routes(ifp->state->new);
+		for (rt = dnr; rt && (rtn = rt->next, 1); rt = rtn) {
+			rt->iface = ifp;
+			/* Is this route already in our table? */
+			if ((find_route(nrs, rt, NULL, NULL)))
+				continue;
+			/* Do we already manage it? */
+			if ((or = find_route(routes, rt, &rtl, NULL))) {
+				if (or->iface == ifp) {
+					if (rtl)
+						rtl->next = or->next;
+					else
+						routes = or->next;
+					rt = or;
+				} else {
+					if (c_route(or, rt, ifp) == 0) {
+						if (rtl)
+							rtl->next = or->next;
+						else
+							routes = or->next;
+						free(or);
+					} else
+						continue;
+				}
+			} else {
+				if (n_route(rt, ifp))
+					continue;
+			}
+			if (dnr == rt)
+				dnr = rtn;
+			rt->iface = ifp;
+			rt->next = nrs;
+			nrs = rt;
 		}
-		if (remember >= 0) {
-			rtn = xmalloc(sizeof(*rtn));
-			rtn->dest.s_addr = rt->dest.s_addr;
-			rtn->net.s_addr = rt->net.s_addr;
-			rtn->gate.s_addr = rt->gate.s_addr;
-			rtn->next = nr;
-			nr = rtn;
-		}
+		free_routes(dnr);
 	}
-	free_routes(ort);
-	free_routes(iface->routes);
-	iface->routes = nr;
-	return retval;
+	free_routes(routes);
+	routes = nrs;
 }
 
 static int
@@ -338,7 +408,7 @@
 	struct in_addr addr;
 	struct in_addr net;
 	struct in_addr brd;
-#ifdef __linux__
+#if HAVE_ROUTE_METRIC
 	struct in_addr dest;
 	struct in_addr gate;
 #endif
@@ -357,14 +427,14 @@
 			net.s_addr = get_netmask(addr.s_addr);
 		if (get_option_addr(&brd.s_addr, dhcp, DHO_BROADCAST) == -1)
 			brd.s_addr = addr.s_addr | ~net.s_addr;
-#ifdef __linux__
+#if HAVE_ROUTE_METRIC
 		dest.s_addr = addr.s_addr & net.s_addr;
 		gate.s_addr = 0;
 #endif
 	} else {
 		/* Only reset things if we had set them before */
 		if (iface->addr.s_addr != 0) {
-			delete_routes(iface);
+			remove_routes(iface);
 			delete_address(iface);
 		}
 
@@ -390,8 +460,8 @@
 	    iface->addr.s_addr != 0)
 		delete_address(iface);
 
-#ifdef __linux__
-	/* On linux, we need to change the subnet route to have our metric. */
+#if HAVE_ROUTE_METRIC
+	/* We need to change the subnet route to have our metric. */
 	if (iface->metric > 0 && 
 	    (net.s_addr != iface->net.s_addr ||
 	     dest.s_addr != (iface->addr.s_addr & iface->net.s_addr)))
@@ -405,12 +475,10 @@
 
 	iface->addr.s_addr = addr.s_addr;
 	iface->net.s_addr = net.s_addr;
-	configure_routes(iface, dhcp);
-
+	build_routes();
 	if (!iface->state->lease.frominfo)
 		if (write_lease(iface, dhcp) == -1)
 			syslog(LOG_ERR, "write_lease: %m");
-
 	run_script(iface, reason);
 	return 0;
 }
--- a/dhcpcd.h	Thu Sep 11 22:55:27 2008 +0000
+++ b/dhcpcd.h	Fri Sep 12 18:08:07 2008 +0000
@@ -94,7 +94,6 @@
 
 	struct in_addr addr;
 	struct in_addr net;
-	struct rt *routes;
 
 	char leasefile[PATH_MAX];
 	time_t start_uptime;
--- a/if-bsd.c	Thu Sep 11 22:55:27 2008 +0000
+++ b/if-bsd.c	Fri Sep 12 18:08:07 2008 +0000
@@ -116,7 +116,7 @@
 	struct rtm 
 	{
 		struct rt_msghdr hdr;
-		char buffer[sizeof(su) * 3];
+		char buffer[sizeof(su) * 5];
 	} rtm;
 	char *bp = rtm.buffer;
 	size_t l;
@@ -140,6 +140,7 @@
 
 	/* This order is important */
 	rtm.hdr.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK;
+	rtm.hdr.rtm_addrs |= RTA_IFP | RTA_IFA;
 
 #define ADDADDR(_addr) \
 	memset (&su, 0, sizeof(su)); \
@@ -172,6 +173,20 @@
 	}
 
 	ADDADDR(netmask);
+	/* Make us a link layer socket for IFP */
+	memset(&su, 0, sizeof(su));
+	su.sdl.sdl_len = sizeof(su.sdl);
+	su.sdl.sdl_family = AF_LINK;
+	su.sdl.sdl_nlen = strlen(iface->name);
+	memcpy(&su.sdl.sdl_data, iface->name, (size_t)su.sdl.sdl_nlen);
+	su.sdl.sdl_alen = iface->hwlen;
+	memcpy(((unsigned char *)&su.sdl.sdl_data) + su.sdl.sdl_nlen,
+	       iface->hwaddr, (size_t)su.sdl.sdl_alen);
+
+	l = SA_SIZE(&(su.sa));
+	memcpy(bp, &su, l);
+	bp += l;
+	ADDADDR(&iface->addr); /* IFA */
 #undef ADDADDR
 
 	rtm.hdr.rtm_msglen = l = bp - (char *)&rtm;
--- a/if-linux.c	Thu Sep 11 22:55:27 2008 +0000
+++ b/if-linux.c	Fri Sep 12 18:08:07 2008 +0000
@@ -365,7 +365,10 @@
 	else {
 		nlm->hdr.nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL;
 		/* We only change route metrics for kernel routes */
-		nlm->rt.rtm_protocol = action ? RTPROT_BOOT : RTPROT_KERNEL;
+		if (action == 0 && netmask->s_addr == iface->net.s_addr)
+			nlm->rt.rtm_protocol = RTPROT_KERNEL;
+		else
+			nlm->rt.rtm_protocol = RTPROT_BOOT;
 		if (gateway->s_addr == INADDR_ANY)
 			nlm->rt.rtm_scope = RT_SCOPE_LINK;
 		else
--- a/net.h	Thu Sep 11 22:55:27 2008 +0000
+++ b/net.h	Fri Sep 12 18:08:07 2008 +0000
@@ -79,18 +79,11 @@
 # define IN_LINKLOCAL(addr) ((addr & IN_CLASSB_NET) == LINKLOCAL_ADDR)
 #endif
 
-/* There is an argument that this should be converted to an STAIL using
- * queue(3). However, that isn't readily available on all libc's that
- * dhcpcd works on. The only benefit of STAILQ over this is the ability to
- * quickly loop backwards through the list - currently we reverse the list
- * and then move through it forwards. This isn't that much of a big deal
- * though as the norm is to just have one default route, and an IPV4LL route.
- * You can (and do) get more routes in the DHCP message, but not enough to
- * really warrant a change to STAIL queue for performance reasons. */
 struct rt {
 	struct in_addr dest;
 	struct in_addr net;
 	struct in_addr gate;
+	const struct interface *iface;
 	struct rt *next;
 };