net: allow user to set metric on default route learned via Router Advertisement
authorPraveen Chaudhary <praveen5582@gmail.com>
Mon, 25 Jan 2021 21:44:30 +0000 (13:44 -0800)
committerJakub Kicinski <kuba@kernel.org>
Wed, 27 Jan 2021 02:39:45 +0000 (18:39 -0800)
For IPv4, default route is learned via DHCPv4 and user is allowed to change
metric using config etc/network/interfaces. But for IPv6, default route can
be learned via RA, for which, currently a fixed metric value 1024 is used.

Ideally, user should be able to configure metric on default route for IPv6
similar to IPv4. This patch adds sysctl for the same.

Logs:

For IPv4:

Config in etc/network/interfaces:
auto eth0
iface eth0 inet dhcp
    metric 4261413864

IPv4 Kernel Route Table:
$ ip route list
default via 172.21.47.1 dev eth0 metric 4261413864

FRR Table, if a static route is configured:
[In real scenario, it is useful to prefer BGP learned default route over DHCPv4 default route.]
Codes: K - kernel route, C - connected, S - static, R - RIP,
       O - OSPF, I - IS-IS, B - BGP, P - PIM, E - EIGRP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP,
       > - selected route, * - FIB route

S>* 0.0.0.0/0 [20/0] is directly connected, eth0, 00:00:03
K   0.0.0.0/0 [254/1000] via 172.21.47.1, eth0, 6d08h51m

i.e. User can prefer Default Router learned via Routing Protocol in IPv4.
Similar behavior is not possible for IPv6, without this fix.

After fix [for IPv6]:
sudo sysctl -w net.ipv6.conf.eth0.net.ipv6.conf.eth0.ra_defrtr_metric=1996489705

IP monitor: [When IPv6 RA is received]
default via fe80::xx16:xxxx:feb3:ce8e dev eth0 proto ra metric 1996489705  pref high

Kernel IPv6 routing table
$ ip -6 route list
default via fe80::be16:65ff:feb3:ce8e dev eth0 proto ra metric 1996489705 expires 21sec hoplimit 64 pref high

FRR Table, if a static route is configured:
[In real scenario, it is useful to prefer BGP learned default route over IPv6 RA default route.]
Codes: K - kernel route, C - connected, S - static, R - RIPng,
       O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table,
       v - VNC, V - VNC-Direct, A - Babel, D - SHARP,
       > - selected route, * - FIB route

S>* ::/0 [20/0] is directly connected, eth0, 00:00:06
K   ::/0 [119/1001] via fe80::xx16:xxxx:feb3:ce8e, eth0, 6d07h43m

If the metric is changed later, the effect will be seen only when next IPv6
RA is received, because the default route must be fully controlled by RA msg.
Below metric is changed from 1996489705 to 1996489704.

$ sudo sysctl -w net.ipv6.conf.eth0.ra_defrtr_metric=1996489704
net.ipv6.conf.eth0.ra_defrtr_metric = 1996489704

IP monitor:
[On next IPv6 RA msg, Kernel deletes prev route and installs new route with updated metric]

Deleted default via fe80::xx16:xxxx:feb3:ce8e dev eth0 proto ra metric 1996489705 expires 3sec hoplimit 64 pref high
default via fe80::xx16:xxxx:feb3:ce8e dev eth0 proto ra metric 1996489704 pref high

Signed-off-by: Praveen Chaudhary <pchaudhary@linkedin.com>
Signed-off-by: Zhenggen Xu <zxu@linkedin.com>
Reviewed-by: David Ahern <dsahern@kernel.org>
Link: https://lore.kernel.org/r/20210125214430.24079-1-pchaudhary@linkedin.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Documentation/networking/ip-sysctl.rst
include/linux/ipv6.h
include/net/ip6_route.h
include/uapi/linux/ipv6.h
include/uapi/linux/sysctl.h
net/ipv6/addrconf.c
net/ipv6/ndisc.c
net/ipv6/route.c

index dd2b12a32b73903b1fe6a422214c7d832bb30d05..0e51ddd9a2f1335940deb594ae686a1cd6f8d2a2 100644 (file)
@@ -1871,6 +1871,16 @@ accept_ra_defrtr - BOOLEAN
                - enabled if accept_ra is enabled.
                - disabled if accept_ra is disabled.
 
+ra_defrtr_metric - UNSIGNED INTEGER
+       Route metric for default route learned in Router Advertisement. This value
+       will be assigned as metric for the default route learned via IPv6 Router
+       Advertisement. Takes affect only if accept_ra_defrtr is enabled.
+
+       Possible values:
+               1 to 0xFFFFFFFF
+
+               Default: IP6_RT_PRIO_USER i.e. 1024.
+
 accept_ra_from_local - BOOLEAN
        Accept RA with source-address that is found on local machine
        if the RA is otherwise proper and able to be accepted.
index dda61d150a13833e2f198da2124908758103c56b..9d1f29f0c5129d5b9d5dea2a1d4fcae598be96b5 100644 (file)
@@ -31,6 +31,7 @@ struct ipv6_devconf {
        __s32           max_desync_factor;
        __s32           max_addresses;
        __s32           accept_ra_defrtr;
+       __u32           ra_defrtr_metric;
        __s32           accept_ra_min_hop_limit;
        __s32           accept_ra_pinfo;
        __s32           ignore_routes_with_linkdown;
index 2a5277758379e5da10d33faaf4637ccef070fbc6..f51a118bfce8b0c8f120338381081a39010a6dc4 100644 (file)
@@ -174,7 +174,8 @@ struct fib6_info *rt6_get_dflt_router(struct net *net,
                                     struct net_device *dev);
 struct fib6_info *rt6_add_dflt_router(struct net *net,
                                     const struct in6_addr *gwaddr,
-                                    struct net_device *dev, unsigned int pref);
+                                    struct net_device *dev, unsigned int pref,
+                                    u32 defrtr_usr_metric);
 
 void rt6_purge_dflt_routers(struct net *net);
 
index 13e8751bf24a0373fedb61b7b56d56c488477817..70603775fe91de74ec22848eee3b845416881a14 100644 (file)
@@ -189,6 +189,7 @@ enum {
        DEVCONF_ACCEPT_RA_RT_INFO_MIN_PLEN,
        DEVCONF_NDISC_TCLASS,
        DEVCONF_RPL_SEG_ENABLED,
+       DEVCONF_RA_DEFRTR_METRIC,
        DEVCONF_MAX
 };
 
index 458179df9b2719cd7ed69fc9174a1ac42acfe141..1e05d3caa712ff9abcb0a1d827e69507808c6228 100644 (file)
@@ -571,6 +571,7 @@ enum {
        NET_IPV6_ACCEPT_SOURCE_ROUTE=25,
        NET_IPV6_ACCEPT_RA_FROM_LOCAL=26,
        NET_IPV6_ACCEPT_RA_RT_INFO_MIN_PLEN=27,
+       NET_IPV6_RA_DEFRTR_METRIC=28,
        __NET_IPV6_MAX
 };
 
index 9edc5bb2d531aeffef448ef049ed8a745fe61544..f2337fb756ac73b9fccd70af47dfd99cbe801feb 100644 (file)
@@ -205,6 +205,7 @@ static struct ipv6_devconf ipv6_devconf __read_mostly = {
        .max_desync_factor      = MAX_DESYNC_FACTOR,
        .max_addresses          = IPV6_MAX_ADDRESSES,
        .accept_ra_defrtr       = 1,
+       .ra_defrtr_metric       = IP6_RT_PRIO_USER,
        .accept_ra_from_local   = 0,
        .accept_ra_min_hop_limit= 1,
        .accept_ra_pinfo        = 1,
@@ -260,6 +261,7 @@ static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
        .max_desync_factor      = MAX_DESYNC_FACTOR,
        .max_addresses          = IPV6_MAX_ADDRESSES,
        .accept_ra_defrtr       = 1,
+       .ra_defrtr_metric       = IP6_RT_PRIO_USER,
        .accept_ra_from_local   = 0,
        .accept_ra_min_hop_limit= 1,
        .accept_ra_pinfo        = 1,
@@ -5476,6 +5478,7 @@ static inline void ipv6_store_devconf(struct ipv6_devconf *cnf,
        array[DEVCONF_MAX_DESYNC_FACTOR] = cnf->max_desync_factor;
        array[DEVCONF_MAX_ADDRESSES] = cnf->max_addresses;
        array[DEVCONF_ACCEPT_RA_DEFRTR] = cnf->accept_ra_defrtr;
+       array[DEVCONF_RA_DEFRTR_METRIC] = cnf->ra_defrtr_metric;
        array[DEVCONF_ACCEPT_RA_MIN_HOP_LIMIT] = cnf->accept_ra_min_hop_limit;
        array[DEVCONF_ACCEPT_RA_PINFO] = cnf->accept_ra_pinfo;
 #ifdef CONFIG_IPV6_ROUTER_PREF
@@ -6668,6 +6671,14 @@ static const struct ctl_table addrconf_sysctl[] = {
                .mode           = 0644,
                .proc_handler   = proc_dointvec,
        },
+       {
+               .procname       = "ra_defrtr_metric",
+               .data           = &ipv6_devconf.ra_defrtr_metric,
+               .maxlen         = sizeof(u32),
+               .mode           = 0644,
+               .proc_handler   = proc_douintvec_minmax,
+               .extra1         = (void *)SYSCTL_ONE,
+       },
        {
                .procname       = "accept_ra_min_hop_limit",
                .data           = &ipv6_devconf.accept_ra_min_hop_limit,
index 76717478f1733fb753c067efaa5dd210320e0261..c467c6419893cddf845448c0e34eec37455a55ff 100644 (file)
@@ -1173,6 +1173,7 @@ static void ndisc_router_discovery(struct sk_buff *skb)
        struct neighbour *neigh = NULL;
        struct inet6_dev *in6_dev;
        struct fib6_info *rt = NULL;
+       u32 defrtr_usr_metric;
        struct net *net;
        int lifetime;
        struct ndisc_options ndopts;
@@ -1303,18 +1304,21 @@ static void ndisc_router_discovery(struct sk_buff *skb)
                        return;
                }
        }
-       if (rt && lifetime == 0) {
+       /* Set default route metric as specified by user */
+       defrtr_usr_metric = in6_dev->cnf.ra_defrtr_metric;
+       /* delete the route if lifetime is 0 or if metric needs change */
+       if (rt && (lifetime == 0 || rt->fib6_metric != defrtr_usr_metric)) {
                ip6_del_rt(net, rt, false);
                rt = NULL;
        }
 
-       ND_PRINTK(3, info, "RA: rt: %p  lifetime: %d, for dev: %s\n",
-                 rt, lifetime, skb->dev->name);
+       ND_PRINTK(3, info, "RA: rt: %p  lifetime: %d, metric: %d, for dev: %s\n",
+                 rt, lifetime, defrtr_usr_metric, skb->dev->name);
        if (!rt && lifetime) {
                ND_PRINTK(3, info, "RA: adding default router\n");
 
                rt = rt6_add_dflt_router(net, &ipv6_hdr(skb)->saddr,
-                                        skb->dev, pref);
+                                        skb->dev, pref, defrtr_usr_metric);
                if (!rt) {
                        ND_PRINTK(0, err,
                                  "RA: %s failed to add default route\n",
index 188e114b29b4a522117883f2cfa0de273be87cdd..41d8f801b75fe55632267b89cd0e486440ca76d3 100644 (file)
@@ -4252,11 +4252,12 @@ struct fib6_info *rt6_get_dflt_router(struct net *net,
 struct fib6_info *rt6_add_dflt_router(struct net *net,
                                     const struct in6_addr *gwaddr,
                                     struct net_device *dev,
-                                    unsigned int pref)
+                                    unsigned int pref,
+                                    u32 defrtr_usr_metric)
 {
        struct fib6_config cfg = {
                .fc_table       = l3mdev_fib_table(dev) ? : RT6_TABLE_DFLT,
-               .fc_metric      = IP6_RT_PRIO_USER,
+               .fc_metric      = defrtr_usr_metric,
                .fc_ifindex     = dev->ifindex,
                .fc_flags       = RTF_GATEWAY | RTF_ADDRCONF | RTF_DEFAULT |
                                  RTF_UP | RTF_EXPIRES | RTF_PREF(pref),