dhcpcd-discuss

Re: Multihoming support in dhcpcd (source-dest routing)

Roy Marples

Sat Mar 07 01:16:36 2015

Hi

On Monday 09 Feb 2015 01:03:43 Baptiste Jonglez wrote:
> On Sun, Feb 08, 2015 at 11:47:26AM +0000, Roy Marples wrote:
> > > What would be the best way to implement the above ideas?  It looks
> > > possible to do it by directly calling "ip" in hooks (although I haven't
> > > found the necessary variables), but I'm not sure it's the cleanest
> > > approach.  Maybe it could be integrated directly into dhcpcd?
> >
> > dhcpcd manages the routing table itself internally so manipulating it
> > via the hooks, while possible, could lead to interesting errors.
> > For ipv6, look at ipv6.c ipv6_buildroutes() function which is called
> > each time dhcpcd has an IPv6 event such as receiving a RA or DHCPv6
> > message (or they expire).
> >
> > Question though - if we only have one default route, marked with a from
> > address, can other global addresses still use the route if on a
> > different prefix? If the answer is yes then we probably won't need a
> > config option for it, otherwise we will.
>
> The answer is no, but it's possible to have both a "regular" default route
> and source-specific default routes.
>
> Actually, we *do* want to have both kind of default routes: if only a
> source-specific default route is present, applications that don't specify
> the source address (no bind() before connect()) will fail.
>
> > There's a similar function in ipv4.c for IPv4 routing.
> >
> > Can you do a patch for this if needed? My time to spend in dhcpcd is
> > fairly limited right now to bug fixing only.
>
> I had actually started working on a patch, see attachment.
>
> It only handles IPv6 on Linux.  The idea is to keep the regular default
> routes, and to add additional default routes specific to local addresses.
> This way, the logic used by dhcpcd to select the default route (metric) is
> kept unchanged when applications don't bind to a specific source address.
>
> Here is the routing table obtained when using this patch and connecting to
> two different networks (through eth0 and wlan1), each of them configured
> through RAs:
>
> default from 2001:db8::1:4150:4cbe:cfa7:860e via fe80::7444:1ff:fe84:a4f3
> dev eth0  metric 202  mtu 1450 default from 2001:db8::4:8067:815:15b7:eee0
> via fe80::7444:1ff:fe84:a4f3 dev wlan1  metric 306  mtu 1450
> 2001:db8::1::/64 dev eth0  proto kernel  metric 202  mtu 1450
> 2001:db8::4::/64 dev wlan1  proto kernel  metric 306  mtu 1450
> fe80::/64 dev eth0  proto kernel  metric 256
> fe80::/64 dev wlan1  proto kernel  metric 256
> default via fe80::7444:1ff:fe84:a4f3 dev eth0  metric 202  mtu 1450
> default via fe80::7444:1ff:fe84:a4f3 dev wlan1  metric 306  mtu 1450
>
> Note that the router is the same for both networks, that's why the
> link-local address is the same.
>
> It works mostly fine, but there seems to be an issue with the routing
> cache of Linux.  When a route to X is cached as going through eth0, then
> all packets to X will go through eth0, regardless of their source address.
> It's not an issue related to dhcpcd, though, but the patch needs some more
> testing anyway.

It's probably going through eth0 because it has the lower metric.
Does it work if you make the metrics the same?

> What do you think of the patch itself?  It may be a bit too intrusive,
> because it changes the rt6 structure, but it looks like the simplest way
> to add a source network prefix to a route.

I see no problem with it.
As I said earlier, I was making a lot of changes to how routing is handled
within dhcpcd. This has now been done and here's a patch against the latest
code in fossil. I've #ifdef'ed everything as it only applies in the Linux case
and it seems it needs the IPV6_MULTIPLE_TABLES kernel option enabled (wasn't
on in my kernel) so I've turned it into an option in dhcpcd.conf(5).

How does this look? Does it work for you?

Roy
Index: dhcpcd.conf.5.in
=================================================================--- dhcpcd.conf.5.in
+++ dhcpcd.conf.5.in
@@ -20,11 +20,11 @@
 .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd January 20, 2015
+.Dd March 7, 2015
 .Dt DHCPCD.CONF 5
 .Os
 .Sh NAME
 .Nm dhcpcd.conf
 .Nd dhcpcd configuration file
@@ -500,10 +500,14 @@
 .It Ic script Ar script
 Use
 .Ar script
 instead of the default
 .Pa @SCRIPT@ .
+.It Ic source_routing
+For each autoconf address in a IPv6 RA,
+generate a default route from the source address to the router.
+Only available on Linux, requires IPV6_MULTIPLE_TABLES enabled in the kernel.
 .It Ic ssid Ar ssid
 Subsequent options are only parsed for this wireless
 .Ar ssid .
 .It Ic slaac Op Ar hwaddr | Ar private
 Selects the interface identifier used for SLAAC generated IPv6 addresses.

Index: if-linux.c
=================================================================--- if-linux.c
+++ if-linux.c
@@ -467,10 +467,14 @@
 	rtm = (struct rtmsg *)NLMSG_DATA(nlm);
 	if (rtm->rtm_table != RT_TABLE_MAIN || rtm->rtm_family != AF_INET6)
 		return -1;

 	memset(rt, 0, sizeof(*rt));
+	ipv6_mask(&rt->net, rtm->rtm_dst_len);
+#ifdef HAVE_SOURCE_ROUTING
+	ipv6_mask(&rt->src_net, rtm->rtm_src_len);
+#endif
 	rta = (struct rtattr *)RTM_RTA(rtm);
 	len = RTM_PAYLOAD(nlm);
 	while (RTA_OK(rta, len)) {
 		switch (rta->rta_type) {
 		case RTA_DST:
@@ -479,10 +483,16 @@
 			break;
 		case RTA_GATEWAY:
 			memcpy(&rt->gate.s6_addr, RTA_DATA(rta),
 			    sizeof(rt->gate.s6_addr));
 			break;
+#ifdef HAVE_SOURCE_ROUTING
+		case RTA_SRC:
+			memcpy(&rt->src.s6_addr, RTA_DATA(rta),
+			    sizeof(rt->src.s6_addr));
+			break;
+#endif
 		case RTA_OIF:
 			rt->iface = if_findindex(ctx,
 			    *(unsigned int *)RTA_DATA(rta));
 			break;
 		case RTA_PRIORITY:
@@ -490,11 +500,10 @@
 			break;
 		}
 		rta = RTA_NEXT(rta, len);
 	}

-	ipv6_mask(&rt->net, rtm->rtm_dst_len);
 	return 0;
 }
 #endif

 /* Work out the maximum pid size */
@@ -1538,10 +1547,20 @@
 	}

 	nlm.rt.rtm_dst_len = ipv6_prefixlen(&rt->net);
 	add_attr_l(&nlm.hdr, sizeof(nlm), RTA_DST,
 	    &rt->dest.s6_addr, sizeof(rt->dest.s6_addr));
+
+#ifdef HAVE_SOURCE_ROUTING
+	if (!IN6_IS_ADDR_UNSPECIFIED(&rt->src) &&
+	    !IN6_IS_ADDR_UNSPECIFIED(&rt->src_net))
+	{
+		nlm.rt.rtm_src_len = ipv6_prefixlen(&rt->src_net);
+		add_attr_l(&nlm.hdr, sizeof(nlm), RTA_SRC,
+		    &rt->src.s6_addr, sizeof(rt->src.s6_addr));
+	}
+#endif

 	if (cmd == RTM_ADD && !IN6_IS_ADDR_UNSPECIFIED(&rt->gate))
 		add_attr_l(&nlm.hdr, sizeof(nlm), RTA_GATEWAY,
 		    &rt->gate.s6_addr, sizeof(rt->gate.s6_addr));


Index: if-options.c
=================================================================--- if-options.c
+++ if-options.c
@@ -95,10 +95,11 @@
 #define O_GATEWAY		O_BASE + 36
 #define O_PFXDLGMIX		O_BASE + 37
 #define O_IPV6RA_AUTOCONF	O_BASE + 38
 #define O_IPV6RA_NOAUTOCONF	O_BASE + 39
 #define O_REJECT		O_BASE + 40
+#define O_SOURCE_ROUTING	O_BASE + 41

 const struct option cf_options[] = {
 	{"background",      no_argument,       NULL, 'b'},
 	{"script",          required_argument, NULL, 'c'},
 	{"debug",           no_argument,       NULL, 'd'},
@@ -185,10 +186,11 @@
 	{"controlgroup",    required_argument, NULL, O_CONTROLGRP},
 	{"slaac",           required_argument, NULL, O_SLAAC},
 	{"gateway",         no_argument,       NULL, O_GATEWAY},
 	{"ia_pd_mix",       no_argument,       NULL, O_PFXDLGMIX},
 	{"reject",          required_argument, NULL, O_REJECT},
+	{"source_routing",  no_argument,       NULL, O_SOURCE_ROUTING},
 	{NULL,              0,                 NULL, '\0'}
 };

 static char *
 add_environ(struct if_options *ifo, const char *value, int uniq)
@@ -1923,10 +1925,13 @@
 			ifo->options &= ~DHCPCD_SLAACPRIVATE;
 		break;
 	case O_PFXDLGMIX:
 		ifo->options |= DHCPCD_PFXDLGMIX;
 		break;
+	case O_SOURCE_ROUTING:
+		ifo->options |= DHCPCD_SOURCE_ROUTING;
+		break;
 	default:
 		return 0;
 	}

 	return 1;

Index: if-options.h
=================================================================--- if-options.h
+++ if-options.h
@@ -106,10 +106,11 @@
 #define DHCPCD_NOPFXDLG			(1ULL << 51)
 #define DHCPCD_PFXDLGONLY		(1ULL << 52)
 #define DHCPCD_PFXDLGMIX		(1ULL << 53)
 #define DHCPCD_IPV6RA_AUTOCONF		(1ULL << 54)
 #define DHCPCD_ROUTER_HOST_ROUTE_WARNED	(1ULL << 55)
+#define DHCPCD_SOURCE_ROUTING		(1ULL << 56)

 extern const struct option cf_options[];

 struct if_sla {
 	char ifname[IF_NAMESIZE];

Index: if.h
=================================================================--- if.h
+++ if.h
@@ -33,14 +33,20 @@
 #include <netinet/in.h>

 /* Some systems have route metrics.
  * OpenBSD route priority is not this. */
 #ifndef HAVE_ROUTE_METRIC
-# if defined(__linux__)
+# ifdef __linux__
 #  define HAVE_ROUTE_METRIC 1
 # endif
 #endif
+
+#ifndef HAVE_SOURCE_ROUTING
+# ifdef __linux__
+#  define HAVE_SOURCE_ROUTING
+# endif
+#endif

 #include "config.h"
 #include "dhcpcd.h"
 #include "ipv4.h"
 #include "ipv6.h"

Index: ipv6.c
=================================================================--- ipv6.c
+++ ipv6.c
@@ -1701,10 +1701,14 @@
 		if (IN6_ARE_ADDR_EQUAL(&rt->dest, &r->dest) &&
 #ifdef HAVE_ROUTE_METRIC
 		    (r->iface == NULL || rt->iface == NULL ||
 		    rt->iface->metric == r->iface->metric) &&
 #endif
+#ifdef HAVE_SOURCE_ROUTING
+		    IN6_ARE_ADDR_EQUAL(&rt->src, &r->src) &&
+		    IN6_ARE_ADDR_EQUAL(&rt->src_net, &r->src_net) &&
+#endif
 		    IN6_ARE_ADDR_EQUAL(&rt->net, &r->net))
 			return rt;
 	}
 	return NULL;
 }
@@ -1722,13 +1726,28 @@
 	if (IN6_ARE_ADDR_EQUAL(&rt->gate, &in6addr_any))
 		syslog(LOG_INFO, "%s: %s route to %s/%d", ifname, cmd,
 		    dest, ipv6_prefixlen(&rt->net));
 	else if (IN6_ARE_ADDR_EQUAL(&rt->dest, &in6addr_any) &&
 	    IN6_ARE_ADDR_EQUAL(&rt->net, &in6addr_any))
-		syslog(LOG_INFO, "%s: %s default route via %s", ifname, cmd,
-		    gate);
-	else
+	{
+		if (IN6_ARE_ADDR_EQUAL(&rt->src, &in6addr_any))
+			syslog(LOG_INFO, "%s: %s default route via %s",
+			    ifname, cmd, gate);
+#ifdef HAVE_SOURCE_ROUTING
+		else {
+			char srcbuf[INET6_ADDRSTRLEN];
+			const char *src;
+
+			src = inet_ntop(AF_INET6, &rt->src, srcbuf,
+			    INET6_ADDRSTRLEN);
+			syslog(LOG_INFO,
+			    "%s: %s default route from %s/%d via %s",
+			    ifname, cmd, src, ipv6_prefixlen(&rt->src_net),
+			    gate);
+		}
+#endif
+	} else
 		syslog(LOG_INFO, "%s: %s%s route to %s/%d via %s", ifname, cmd,
 		    rt->flags & RTF_REJECT ? " reject" : "",
 		    dest, ipv6_prefixlen(&rt->net), gate);
 }

@@ -1744,10 +1763,14 @@
 		    rt->iface == r->iface &&
 		    (!flags || rt->metric == r->metric) &&
 #else
 		    (!flags || rt->iface == r->iface) &&
 #endif
+#ifdef HAVE_SOURCE_ROUTING
+		    IN6_ARE_ADDR_EQUAL(&rt->src, &r->src) &&
+		    IN6_ARE_ADDR_EQUAL(&rt->src_net, &r->src_net) &&
+#endif
 		    IN6_ARE_ADDR_EQUAL(&rt->net, &r->net))
 			return r;
 	}
 	return NULL;
 }
@@ -1910,24 +1933,39 @@
 	if (addr->flags & IPV6_AF_DELEGATEDPFX) {
 		r->flags |= RTF_REJECT;
 		r->gate = in6addr_loopback;
 	} else
 		r->gate = in6addr_any;
+
+#ifdef HAVE_SOURCE_ROUTING
+	r->src = in6addr_any;
+	r->src_net = in6addr_any;
+#endif
 	return r;
 }

 static struct rt6 *
-make_router(const struct ra *rap)
+#ifdef HAVE_SOURCE_ROUTING
+make_router(const struct ra *rap, const struct ipv6_addr *src)
+#else
+make_router(const struct ra *rap, __unused const struct ipv6_addr *src)
+#endif
 {
 	struct rt6 *r;

 	r = make_route(rap->iface, rap);
 	if (r == NULL)
 		return NULL;
 	r->dest = in6addr_any;
 	r->net = in6addr_any;
 	r->gate = rap->from;
+#ifdef HAVE_SOURCE_ROUTING
+	if (src) {
+		r->src = src->addr;
+		ipv6_mask(&r->src_net, 128);
+	}
+#endif
 	return r;
 }

 #define RT_IS_DEFAULT(rtp) \
 	(IN6_ARE_ADDR_EQUAL(&((rtp)->dest), &in6addr_any) &&		      \
@@ -1943,21 +1981,34 @@
 	TAILQ_FOREACH(rap, ctx->ra_routers, next) {
 		if (rap->expired != expired)
 			continue;
 		if (rap->iface->options->options & DHCPCD_IPV6RA_OWN) {
 			TAILQ_FOREACH(addr, &rap->addrs, next) {
-				rt = make_prefix(rap->iface, rap, addr);
-				if (rt)
+				if ((rt = make_prefix(rap->iface, rap, addr)))
 					TAILQ_INSERT_TAIL(dnr, rt, next);
 			}
 		}
 		if (rap->lifetime && rap->iface->options->options &
 		    (DHCPCD_IPV6RA_OWN | DHCPCD_IPV6RA_OWN_DEFAULT))
 		{
-			rt = make_router(rap);
-			if (rt)
+			/* Add a regular default route */
+			if ((rt = make_router(rap, NULL)))
 				TAILQ_INSERT_TAIL(dnr, rt, next);
+
+#ifdef HAVE_SOURCE_ROUTING
+			if (rap->iface->options->options &
+			    DHCPCD_SOURCE_ROUTING)
+			{
+				/* Add as many source-specific default routes as
+				 * we have addreses */
+				TAILQ_FOREACH(addr, &rap->addrs, next) {
+					if (!IN6_IS_ADDR_UNSPECIFIED(&addr->addr) &&
+					    (rt = make_router(rap, addr)))
+						TAILQ_INSERT_TAIL(dnr, rt, next);
+				}
+			}
+#endif
 		}
 	}
 }

 static void

Index: ipv6.h
=================================================================--- ipv6.h
+++ ipv6.h
@@ -142,10 +142,14 @@
 struct rt6 {
 	TAILQ_ENTRY(rt6) next;
 	struct in6_addr dest;
 	struct in6_addr net;
 	struct in6_addr gate;
+#ifdef HAVE_SOURCE_ROUTING
+	struct in6_addr src;
+	struct in6_addr src_net;
+#endif
 	const struct interface *iface;
 	unsigned int flags;
 #ifdef HAVE_ROUTE_METRIC
 	unsigned int metric;
 #endif

Attachment: signature.asc
Description: This is a digitally signed message part.


Follow-Ups:
Re: Multihoming support in dhcpcd (source-dest routing)Baptiste Jonglez
domain versus search keywords in resolvconfJoachim Achtzehnter
References:
Multihoming support in dhcpcd (source-dest routing)Baptiste Jonglez
Re: Multihoming support in dhcpcd (source-dest routing)Roy Marples
Re: Multihoming support in dhcpcd (source-dest routing)Baptiste Jonglez
Archive administrator: postmaster@marples.name