Skip to content

Commit

Permalink
net: ipv6: autoconf routes into per-device tables
Browse files Browse the repository at this point in the history
Currently, IPv6 router discovery always puts routes into
RT6_TABLE_MAIN. This causes problems for connection managers
that want to support multiple simultaneous network connections
and want control over which one is used by default (e.g., wifi
and wired).

To work around this connection managers typically take the routes
they prefer and copy them to static routes with low metrics in
the main table. This puts the burden on the connection manager
to watch netlink to see if the routes have changed, delete the
routes when their lifetime expires, etc.

Instead, this patch adds a per-interface sysctl to have the
kernel put autoconf routes into different tables. This allows
each interface to have its own autoconf table, and choosing the
default interface (or using different interfaces at the same
time for different types of traffic) can be done using
appropriate ip rules.

The sysctl behaves as follows:

- = 0: default. Put routes into RT6_TABLE_MAIN as before.
- > 0: manual. Put routes into the specified table.
- < 0: automatic. Add the absolute value of the sysctl to the
       device's ifindex, and use that table.

The automatic mode is most useful in conjunction with
net.ipv6.conf.default.accept_ra_rt_table. A connection manager
or distribution could set it to, say, -100 on boot, and
thereafter just use IP rules.

Change-Id: I093d39fb06ec413905dc0d0d5792c1bc5d5c73a9
Signed-off-by: Lorenzo Colitti <[email protected]>

Conflicts:
	net/ipv6/addrconf.c
	net/ipv6/route.c

Slightly incomplete cherry-pick.
  • Loading branch information
lcolitti authored and Mustaavalkosta committed May 7, 2015
1 parent 93b75af commit b309f7d
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 42 deletions.
2 changes: 2 additions & 0 deletions include/linux/ipv6.h
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ struct ipv6_devconf {
__s32 accept_ra_rt_info_max_plen;
#endif
#endif
__s32 accept_ra_rt_table;
__s32 proxy_ndp;
__s32 accept_source_route;
#ifdef CONFIG_IPV6_OPTIMISTIC_DAD
Expand Down Expand Up @@ -213,6 +214,7 @@ enum {
DEVCONF_DISABLE_IPV6,
DEVCONF_ACCEPT_DAD,
DEVCONF_FORCE_TLLAO,
DEVCONF_ACCEPT_RA_RT_TABLE,
DEVCONF_MAX
};

Expand Down
2 changes: 2 additions & 0 deletions include/net/addrconf.h
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ extern int __ipv6_dev_ac_dec(struct inet6_dev *idev, const struct in6_addr *addr
extern int ipv6_chk_acast_addr(struct net *net, struct net_device *dev,
const struct in6_addr *addr);

u32 addrconf_rt_table(const struct net_device *dev, u32 default_table);


/* Device notifier */
extern int register_inet6addr_notifier(struct notifier_block *nb);
Expand Down
37 changes: 36 additions & 1 deletion net/ipv6/addrconf.c
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ static struct ipv6_devconf ipv6_devconf __read_mostly = {
.accept_ra_rt_info_max_plen = 0,
#endif
#endif
.accept_ra_rt_table = 0,
.proxy_ndp = 0,
.accept_source_route = 0, /* we do not accept RH0 by default. */
.disable_ipv6 = 0,
Expand Down Expand Up @@ -226,6 +227,7 @@ static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
.accept_ra_rt_info_max_plen = 0,
#endif
#endif
.accept_ra_rt_table = 0,
.proxy_ndp = 0,
.accept_source_route = 0, /* we do not accept RH0 by default. */
.disable_ipv6 = 0,
Expand Down Expand Up @@ -1687,6 +1689,31 @@ static int __ipv6_try_regen_rndid(struct inet6_dev *idev, struct in6_addr *tmpad
}
#endif

u32 addrconf_rt_table(const struct net_device *dev, u32 default_table) {
/* Determines into what table to put autoconf PIO/RIO/default routes
* learned on this device.
*
* - If 0, use the same table for every device. This puts routes into
* one of RT_TABLE_{PREFIX,INFO,DFLT} depending on the type of route
* (but note that these three are currently all equal to
* RT6_TABLE_MAIN).
* - If > 0, use the specified table.
* - If < 0, put routes into table dev->ifindex + (-rt_table).
*/
struct inet6_dev *idev = in6_dev_get(dev);
u32 table;
int sysctl = idev->cnf.accept_ra_rt_table;
if (sysctl == 0) {
table = default_table;
} else if (sysctl > 0) {
table = (u32) sysctl;
} else {
table = (unsigned) dev->ifindex + (-sysctl);
}
in6_dev_put(idev);
return table;
}

/*
* Add prefix route.
*/
Expand All @@ -1696,7 +1723,7 @@ addrconf_prefix_route(struct in6_addr *pfx, int plen, struct net_device *dev,
unsigned long expires, u32 flags)
{
struct fib6_config cfg = {
.fc_table = RT6_TABLE_PREFIX,
.fc_table = addrconf_rt_table(dev, RT6_TABLE_PREFIX),
.fc_metric = IP6_RT_PRIO_ADDRCONF,
.fc_ifindex = dev->ifindex,
.fc_expires = expires,
Expand Down Expand Up @@ -3872,6 +3899,7 @@ static inline void ipv6_store_devconf(struct ipv6_devconf *cnf,
array[DEVCONF_ACCEPT_RA_RT_INFO_MAX_PLEN] = cnf->accept_ra_rt_info_max_plen;
#endif
#endif
array[DEVCONF_ACCEPT_RA_RT_TABLE] = cnf->accept_ra_rt_table;
array[DEVCONF_PROXY_NDP] = cnf->proxy_ndp;
array[DEVCONF_ACCEPT_SOURCE_ROUTE] = cnf->accept_source_route;
#ifdef CONFIG_IPV6_OPTIMISTIC_DAD
Expand Down Expand Up @@ -4482,6 +4510,13 @@ static struct addrconf_sysctl_table
},
#endif
#endif
{
.procname = "accept_ra_rt_table",
.data = &ipv6_devconf.accept_ra_rt_table,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
},
{
.procname = "proxy_ndp",
.data = &ipv6_devconf.proxy_ndp,
Expand Down
70 changes: 29 additions & 41 deletions net/ipv6/route.c
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,13 @@ static void ip6_link_failure(struct sk_buff *skb);
static void ip6_rt_update_pmtu(struct dst_entry *dst, u32 mtu);

#ifdef CONFIG_IPV6_ROUTE_INFO
static struct rt6_info *rt6_add_route_info(struct net *net,
static struct rt6_info *rt6_add_route_info(struct net_device *dev,
const struct in6_addr *prefix, int prefixlen,
const struct in6_addr *gwaddr, int ifindex,
const struct in6_addr *gwaddr,
unsigned pref);
static struct rt6_info *rt6_get_route_info(struct net *net,
static struct rt6_info *rt6_get_route_info(struct net_device *dev,
const struct in6_addr *prefix, int prefixlen,
const struct in6_addr *gwaddr, int ifindex);
const struct in6_addr *gwaddr);
#endif

static u32 *ipv6_cow_metrics(struct dst_entry *dst, unsigned long old)
Expand Down Expand Up @@ -533,7 +533,6 @@ static struct rt6_info *rt6_select(struct fib6_node *fn, int oif, int strict)
int rt6_route_rcv(struct net_device *dev, u8 *opt, int len,
const struct in6_addr *gwaddr)
{
struct net *net = dev_net(dev);
struct route_info *rinfo = (struct route_info *) opt;
struct in6_addr prefix_buf, *prefix;
unsigned int pref;
Expand Down Expand Up @@ -575,17 +574,15 @@ int rt6_route_rcv(struct net_device *dev, u8 *opt, int len,
prefix = &prefix_buf;
}

rt = rt6_get_route_info(net, prefix, rinfo->prefix_len, gwaddr,
dev->ifindex);
rt = rt6_get_route_info(dev, prefix, rinfo->prefix_len, gwaddr);

if (rt && !lifetime) {
ip6_del_rt(rt);
rt = NULL;
}

if (!rt && lifetime)
rt = rt6_add_route_info(net, prefix, rinfo->prefix_len, gwaddr, dev->ifindex,
pref);
rt = rt6_add_route_info(dev, prefix, rinfo->prefix_len, gwaddr, pref);
else if (rt)
rt->rt6i_flags = RTF_ROUTEINFO |
(rt->rt6i_flags & ~RTF_PREF_MASK) | RTF_PREF(pref);
Expand Down Expand Up @@ -1768,15 +1765,16 @@ static struct rt6_info * ip6_rt_copy(struct rt6_info *ort)
}

#ifdef CONFIG_IPV6_ROUTE_INFO
static struct rt6_info *rt6_get_route_info(struct net *net,
static struct rt6_info *rt6_get_route_info(struct net_device *dev,
const struct in6_addr *prefix, int prefixlen,
const struct in6_addr *gwaddr, int ifindex)
const struct in6_addr *gwaddr)
{
struct fib6_node *fn;
struct rt6_info *rt = NULL;
struct fib6_table *table;

table = fib6_get_table(net, RT6_TABLE_INFO);
table = fib6_get_table(dev_net(dev),
addrconf_rt_table(dev, RT6_TABLE_INFO));
if (table == NULL)
return NULL;

Expand All @@ -1786,7 +1784,7 @@ static struct rt6_info *rt6_get_route_info(struct net *net,
goto out;

for (rt = fn->leaf; rt; rt = rt->dst.rt6_next) {
if (rt->rt6i_dev->ifindex != ifindex)
if (rt->dst.dev->ifindex != dev->ifindex)
continue;
if ((rt->rt6i_flags & (RTF_ROUTEINFO|RTF_GATEWAY)) != (RTF_ROUTEINFO|RTF_GATEWAY))
continue;
Expand All @@ -1800,21 +1798,21 @@ static struct rt6_info *rt6_get_route_info(struct net *net,
return rt;
}

static struct rt6_info *rt6_add_route_info(struct net *net,
static struct rt6_info *rt6_add_route_info(struct net_device *dev,
const struct in6_addr *prefix, int prefixlen,
const struct in6_addr *gwaddr, int ifindex,
const struct in6_addr *gwaddr,
unsigned pref)
{
struct fib6_config cfg = {
.fc_table = RT6_TABLE_INFO,
.fc_table = addrconf_rt_table(dev, RT6_TABLE_INFO),
.fc_metric = IP6_RT_PRIO_USER,
.fc_ifindex = ifindex,
.fc_ifindex = dev->ifindex,
.fc_dst_len = prefixlen,
.fc_flags = RTF_GATEWAY | RTF_ADDRCONF | RTF_ROUTEINFO |
RTF_UP | RTF_PREF(pref),
.fc_nlinfo.pid = 0,
.fc_nlinfo.nlh = NULL,
.fc_nlinfo.nl_net = net,
.fc_nlinfo.nl_net = dev_net(dev),
};

ipv6_addr_copy(&cfg.fc_dst, prefix);
Expand All @@ -1826,7 +1824,7 @@ static struct rt6_info *rt6_add_route_info(struct net *net,

ip6_route_add(&cfg);

return rt6_get_route_info(net, prefix, prefixlen, gwaddr, ifindex);
return rt6_get_route_info(dev, prefix, prefixlen, gwaddr);
}
#endif

Expand All @@ -1835,7 +1833,8 @@ struct rt6_info *rt6_get_dflt_router(const struct in6_addr *addr, struct net_dev
struct rt6_info *rt;
struct fib6_table *table;

table = fib6_get_table(dev_net(dev), RT6_TABLE_DFLT);
table = fib6_get_table(dev_net(dev),
addrconf_rt_table(dev, RT6_TABLE_MAIN));
if (table == NULL)
return NULL;

Expand All @@ -1857,7 +1856,7 @@ struct rt6_info *rt6_add_dflt_router(const struct in6_addr *gwaddr,
unsigned int pref)
{
struct fib6_config cfg = {
.fc_table = RT6_TABLE_DFLT,
.fc_table = addrconf_rt_table(dev, RT6_TABLE_DFLT),
.fc_metric = IP6_RT_PRIO_USER,
.fc_ifindex = dev->ifindex,
.fc_flags = RTF_GATEWAY | RTF_ADDRCONF | RTF_DEFAULT |
Expand All @@ -1874,28 +1873,17 @@ struct rt6_info *rt6_add_dflt_router(const struct in6_addr *gwaddr,
return rt6_get_dflt_router(gwaddr, dev);
}

void rt6_purge_dflt_routers(struct net *net)
{
struct rt6_info *rt;
struct fib6_table *table;

/* NOTE: Keep consistent with rt6_get_dflt_router */
table = fib6_get_table(net, RT6_TABLE_DFLT);
if (table == NULL)
return;
int rt6_addrconf_purge(struct rt6_info *rt, void *arg) {
if (rt->rt6i_flags & (RTF_DEFAULT | RTF_ADDRCONF) &&
(!rt->rt6i_idev || rt->rt6i_idev->cnf.accept_ra != 2))
return -1;
return 0;
}

restart:
read_lock_bh(&table->tb6_lock);
for (rt = table->tb6_root.leaf; rt; rt = rt->dst.rt6_next) {
if (rt->rt6i_flags & (RTF_DEFAULT | RTF_ADDRCONF) &&
(!rt->rt6i_idev || rt->rt6i_idev->cnf.accept_ra != 2)) {
dst_hold(&rt->dst);
read_unlock_bh(&table->tb6_lock);
ip6_del_rt(rt);
goto restart;
}
}
read_unlock_bh(&table->tb6_lock);
void rt6_purge_dflt_routers(struct net *net)
{
fib6_clean_all(net, rt6_addrconf_purge, 0, NULL);
}

static void rtmsg_to_fib6_config(struct net *net,
Expand Down

0 comments on commit b309f7d

Please sign in to comment.