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.
Archive administrator: postmaster@marples.name