| From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 |
| From: Lorenzo Colitti <lorenzo@google.com> |
| Date: Wed, 26 Mar 2014 19:35:41 +0900 |
| Subject: ANDROID: net: ipv6: autoconf routes into per-device tables |
| |
| 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. |
| |
| [CPNOTE: 20/07/21] Lee: Asked Lorenzo for status via the bug |
| |
| Signed-off-by: Lorenzo Colitti <lorenzo@google.com> |
| [AmitP: Refactored original changes to align with |
| the changes introduced by upstream commits |
| 830218c1add1 ("net: ipv6: Fix processing of RAs in presence of VRF"), |
| 8d1c802b2815 ("net/ipv6: Flip FIB entries to fib6_info"). |
| |
| Also folded following android-4.9 commit changes into this patch |
| be65fb01da4d ("ANDROID: net: ipv6: remove unused variable ifindex in")] |
| Bug: 120445791 |
| Change-Id: I82d16e3737d9cdfa6489e649e247894d0d60cbb1 |
| Signed-off-by: Amit Pundir <amit.pundir@linaro.org> |
| --- |
| include/linux/ipv6.h | 1 + |
| include/net/addrconf.h | 12 ++++++++++ |
| net/ipv6/addrconf.c | 33 +++++++++++++++++++++++++-- |
| net/ipv6/route.c | 51 ++++++++++++------------------------------ |
| 4 files changed, 58 insertions(+), 39 deletions(-) |
| |
| diff --git a/include/linux/ipv6.h b/include/linux/ipv6.h |
| --- a/include/linux/ipv6.h |
| +++ b/include/linux/ipv6.h |
| @@ -43,6 +43,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; |
| __s32 accept_ra_from_local; |
| diff --git a/include/net/addrconf.h b/include/net/addrconf.h |
| --- a/include/net/addrconf.h |
| +++ b/include/net/addrconf.h |
| @@ -269,6 +269,18 @@ static inline bool ipv6_is_mld(struct sk_buff *skb, int nexthdr, int offset) |
| void addrconf_prefix_rcv(struct net_device *dev, |
| u8 *opt, int len, bool sllao); |
| |
| +/* 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). |
| + */ |
| +u32 addrconf_rt_table(const struct net_device *dev, u32 default_table); |
| + |
| /* |
| * anycast prototypes (anycast.c) |
| */ |
| diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c |
| --- a/net/ipv6/addrconf.c |
| +++ b/net/ipv6/addrconf.c |
| @@ -218,6 +218,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, |
| @@ -278,6 +279,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, |
| @@ -2389,6 +2391,26 @@ static void ipv6_gen_rnd_iid(struct in6_addr *addr) |
| goto regen; |
| } |
| |
| +u32 addrconf_rt_table(const struct net_device *dev, u32 default_table) |
| +{ |
| + struct inet6_dev *idev = in6_dev_get(dev); |
| + int sysctl; |
| + u32 table; |
| + |
| + if (!idev) |
| + return default_table; |
| + 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. |
| */ |
| @@ -2399,7 +2421,7 @@ addrconf_prefix_route(struct in6_addr *pfx, int plen, u32 metric, |
| u32 flags, gfp_t gfp_flags) |
| { |
| struct fib6_config cfg = { |
| - .fc_table = l3mdev_fib_table(dev) ? : RT6_TABLE_PREFIX, |
| + .fc_table = l3mdev_fib_table(dev) ? : addrconf_rt_table(dev, RT6_TABLE_PREFIX), |
| .fc_metric = metric ? : IP6_RT_PRIO_ADDRCONF, |
| .fc_ifindex = dev->ifindex, |
| .fc_expires = expires, |
| @@ -2434,7 +2456,7 @@ static struct fib6_info *addrconf_get_prefix_route(const struct in6_addr *pfx, |
| struct fib6_node *fn; |
| struct fib6_info *rt = NULL; |
| struct fib6_table *table; |
| - u32 tb_id = l3mdev_fib_table(dev) ? : RT6_TABLE_PREFIX; |
| + u32 tb_id = l3mdev_fib_table(dev) ? : addrconf_rt_table(dev, RT6_TABLE_PREFIX); |
| |
| table = fib6_get_table(dev_net(dev), tb_id); |
| if (!table) |
| @@ -6780,6 +6802,13 @@ static const struct ctl_table addrconf_sysctl[] = { |
| }, |
| #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, |
| diff --git a/net/ipv6/route.c b/net/ipv6/route.c |
| --- a/net/ipv6/route.c |
| +++ b/net/ipv6/route.c |
| @@ -4275,7 +4275,7 @@ static struct fib6_info *rt6_get_route_info(struct net *net, |
| const struct in6_addr *gwaddr, |
| struct net_device *dev) |
| { |
| - u32 tb_id = l3mdev_fib_table(dev) ? : RT6_TABLE_INFO; |
| + u32 tb_id = l3mdev_fib_table(dev) ? : addrconf_rt_table(dev, RT6_TABLE_INFO); |
| int ifindex = dev->ifindex; |
| struct fib6_node *fn; |
| struct fib6_info *rt = NULL; |
| @@ -4329,7 +4329,7 @@ static struct fib6_info *rt6_add_route_info(struct net *net, |
| .fc_nlinfo.nl_net = net, |
| }; |
| |
| - cfg.fc_table = l3mdev_fib_table(dev) ? : RT6_TABLE_INFO; |
| + cfg.fc_table = l3mdev_fib_table(dev) ? : addrconf_rt_table(dev, RT6_TABLE_INFO); |
| cfg.fc_dst = *prefix; |
| cfg.fc_gateway = *gwaddr; |
| |
| @@ -4347,7 +4347,7 @@ struct fib6_info *rt6_get_dflt_router(struct net *net, |
| const struct in6_addr *addr, |
| struct net_device *dev) |
| { |
| - u32 tb_id = l3mdev_fib_table(dev) ? : RT6_TABLE_DFLT; |
| + u32 tb_id = l3mdev_fib_table(dev) ? : addrconf_rt_table(dev, RT6_TABLE_DFLT); |
| struct fib6_info *rt; |
| struct fib6_table *table; |
| |
| @@ -4382,7 +4382,7 @@ struct fib6_info *rt6_add_dflt_router(struct net *net, |
| u32 defrtr_usr_metric) |
| { |
| struct fib6_config cfg = { |
| - .fc_table = l3mdev_fib_table(dev) ? : RT6_TABLE_DFLT, |
| + .fc_table = l3mdev_fib_table(dev) ? : addrconf_rt_table(dev, RT6_TABLE_DFLT), |
| .fc_metric = defrtr_usr_metric, |
| .fc_ifindex = dev->ifindex, |
| .fc_flags = RTF_GATEWAY | RTF_ADDRCONF | RTF_DEFAULT | |
| @@ -4407,47 +4407,24 @@ struct fib6_info *rt6_add_dflt_router(struct net *net, |
| return rt6_get_dflt_router(net, gwaddr, dev); |
| } |
| |
| -static void __rt6_purge_dflt_routers(struct net *net, |
| - struct fib6_table *table) |
| +static int rt6_addrconf_purge(struct fib6_info *rt, void *arg) |
| { |
| - struct fib6_info *rt; |
| - |
| -restart: |
| - rcu_read_lock(); |
| - for_each_fib6_node_rt_rcu(&table->tb6_root) { |
| - struct net_device *dev = fib6_info_nh_dev(rt); |
| - struct inet6_dev *idev = dev ? __in6_dev_get(dev) : NULL; |
| + struct net_device *dev = fib6_info_nh_dev(rt); |
| + struct inet6_dev *idev = dev ? __in6_dev_get(dev) : NULL; |
| |
| - if (rt->fib6_flags & (RTF_DEFAULT | RTF_ADDRCONF) && |
| - (!idev || idev->cnf.accept_ra != 2) && |
| - fib6_info_hold_safe(rt)) { |
| - rcu_read_unlock(); |
| - ip6_del_rt(net, rt, false); |
| - goto restart; |
| - } |
| + if (rt->fib6_flags & (RTF_DEFAULT | RTF_ADDRCONF) && |
| + (!idev || idev->cnf.accept_ra != 2)) { |
| + /* Delete this route. See fib6_clean_tree() */ |
| + return -1; |
| } |
| - rcu_read_unlock(); |
| |
| - table->flags &= ~RT6_TABLE_HAS_DFLT_ROUTER; |
| + /* Continue walking */ |
| + return 0; |
| } |
| |
| void rt6_purge_dflt_routers(struct net *net) |
| { |
| - struct fib6_table *table; |
| - struct hlist_head *head; |
| - unsigned int h; |
| - |
| - rcu_read_lock(); |
| - |
| - for (h = 0; h < FIB6_TABLE_HASHSZ; h++) { |
| - head = &net->ipv6.fib_table_hash[h]; |
| - hlist_for_each_entry_rcu(table, head, tb6_hlist) { |
| - if (table->flags & RT6_TABLE_HAS_DFLT_ROUTER) |
| - __rt6_purge_dflt_routers(net, table); |
| - } |
| - } |
| - |
| - rcu_read_unlock(); |
| + fib6_clean_all(net, rt6_addrconf_purge, NULL); |
| } |
| |
| static void rtmsg_to_fib6_config(struct net *net, |