Re: Multihoming support in dhcpcd (source-dest routing)
Baptiste Jonglez
Mon Feb 09 00:03:47 2015
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.
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.
Thanks,
Baptiste
diff --git a/if-linux.c b/if-linux.c
index 1333520..4258589 100644
--- a/if-linux.c
+++ b/if-linux.c
@@ -1448,6 +1448,13 @@ if_route6(const struct rt6 *rt, int action)
add_attr_l(&nlm.hdr, sizeof(nlm), RTA_DST,
&rt->dest.s6_addr, sizeof(rt->dest.s6_addr));
+ 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));
+ }
+
if (action >= 0 && !IN6_IS_ADDR_UNSPECIFIED(&rt->gate))
add_attr_l(&nlm.hdr, sizeof(nlm), RTA_GATEWAY,
&rt->gate.s6_addr, sizeof(rt->gate.s6_addr));
diff --git a/ipv6.c b/ipv6.c
index fcb79dd..54b4387 100644
--- a/ipv6.c
+++ b/ipv6.c
@@ -1696,7 +1696,9 @@ find_route6(struct rt6_head *rts, const struct rt6 *r)
(r->iface == NULL || rt->iface == NULL ||
rt->iface->metric == r->iface->metric) &&
#endif
- IN6_ARE_ADDR_EQUAL(&rt->net, &r->net))
+ IN6_ARE_ADDR_EQUAL(&rt->net, &r->net) &&
+ IN6_ARE_ADDR_EQUAL(&rt->src, &r->src) &&
+ IN6_ARE_ADDR_EQUAL(&rt->src_net, &r->src_net))
return rt;
}
return NULL;
@@ -1706,19 +1708,28 @@ static void
desc_route(const char *cmd, const struct rt6 *rt)
{
char destbuf[INET6_ADDRSTRLEN];
+ char srcbuf[INET6_ADDRSTRLEN];
char gatebuf[INET6_ADDRSTRLEN];
- const char *ifname, *dest, *gate;
+ const char *ifname, *dest, *src, *gate;
ifname = rt->iface ? rt->iface->name : "(no iface)";
dest = inet_ntop(AF_INET6, &rt->dest, destbuf, INET6_ADDRSTRLEN);
+ src = inet_ntop(AF_INET6, &rt->src, srcbuf, INET6_ADDRSTRLEN);
gate = inet_ntop(AF_INET6, &rt->gate, gatebuf, INET6_ADDRSTRLEN);
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);
+ IN6_ARE_ADDR_EQUAL(&rt->net, &in6addr_any)) {
+ if (IN6_ARE_ADDR_EQUAL(&rt->src, &in6addr_any) ||
+ IN6_ARE_ADDR_EQUAL(&rt->src_net, &in6addr_any))
+ syslog(LOG_INFO, "%s: %s default route via %s",
+ ifname, cmd, gate);
+ else
+ syslog(LOG_INFO, "%s: %s default route from %s/%d via %s",
+ ifname, cmd, src, ipv6_prefixlen(&rt->src_net),
+ gate);
+ }
else
syslog(LOG_INFO, "%s: %s%s route to %s/%d via %s", ifname, cmd,
rt->flags & RTF_REJECT ? " reject" : "",
@@ -1825,6 +1836,8 @@ make_prefix(const struct interface * ifp, const struct ra *rap,
return NULL;
r->dest = addr->prefix;
ipv6_mask(&r->net, addr->prefix_len);
+ r->src = in6addr_any;
+ r->src_net = in6addr_any;
if (addr->flags & IPV6_AF_DELEGATEDPFX) {
r->flags |= RTF_REJECT;
r->gate = in6addr_loopback;
@@ -1834,7 +1847,7 @@ make_prefix(const struct interface * ifp, const struct ra *rap,
}
static struct rt6 *
-make_router(const struct ra *rap)
+make_router(const struct ra *rap, const struct ipv6_addr *addr)
{
struct rt6 *r;
@@ -1843,6 +1856,10 @@ make_router(const struct ra *rap)
return NULL;
r->dest = in6addr_any;
r->net = in6addr_any;
+ if (addr) {
+ r->src = addr->addr;
+ ipv6_mask(&r->src_net, 128);
+ }
r->gate = rap->from;
return r;
}
@@ -1910,9 +1927,17 @@ ipv6_build_ra_routes(struct ipv6_ctx *ctx, struct rt6_head *dnr, int expired)
if (rap->lifetime && rap->iface->options->options &
(DHCPCD_IPV6RA_OWN | DHCPCD_IPV6RA_OWN_DEFAULT))
{
- rt = make_router(rap);
+ /* Add a regular default route. */
+ rt = make_router(rap, NULL);
if (rt)
TAILQ_INSERT_TAIL(dnr, rt, next);
+ /* Add as much source-specific default routes as
+ we have addresses. */
+ TAILQ_FOREACH(addr, &rap->addrs, next) {
+ rt = make_router(rap, addr);
+ if (rt)
+ TAILQ_INSERT_TAIL(dnr, rt, next);
+ }
}
}
}
diff --git a/ipv6.h b/ipv6.h
index a846185..a02791e 100644
--- a/ipv6.h
+++ b/ipv6.h
@@ -136,6 +136,10 @@ struct rt6 {
TAILQ_ENTRY(rt6) next;
struct in6_addr dest;
struct in6_addr net;
+ /* Used for src-dest routing ("from 2001:db8:42::/64" in iproute2) */
+ struct in6_addr src;
+ struct in6_addr src_net;
+ /* Gateway */
struct in6_addr gate;
const struct interface *iface;
unsigned int flags;
Attachment:
pgpkmdqFuiJbA.pgp
Description: PGP signature
Archive administrator: postmaster@marples.name