net/ipv4: Add support for specifying metric of connected routes
authorDavid Ahern <dsahern@gmail.com>
Sun, 27 May 2018 15:09:57 +0000 (08:09 -0700)
committerDavid S. Miller <davem@davemloft.net>
Tue, 29 May 2018 14:12:45 +0000 (10:12 -0400)
Add support for IFA_RT_PRIORITY to ipv4 addresses.

If the metric is changed on an existing address then the new route
is inserted before removing the old one. Since the metric is one
of the route keys, the prefix route can not be replaced.

Signed-off-by: David Ahern <dsahern@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/inetdevice.h
include/net/route.h
net/ipv4/devinet.c
net/ipv4/fib_frontend.c

index e16fe7d44a7124d32e83a40f1c1579b06b8ff356..27650f1bff3d7ad8595935de659d658f35de6dd8 100644 (file)
@@ -139,6 +139,7 @@ struct in_ifaddr {
        __be32                  ifa_local;
        __be32                  ifa_address;
        __be32                  ifa_mask;
+       __u32                   ifa_rt_priority;
        __be32                  ifa_broadcast;
        unsigned char           ifa_scope;
        unsigned char           ifa_prefixlen;
index dbb032d5921b4b4816ac8a469107e52c857f172c..bb53cdba38dc28a3a4fb0d90cbf8927fb2ba8b49 100644 (file)
@@ -225,6 +225,7 @@ struct rtable *rt_dst_alloc(struct net_device *dev,
 struct in_ifaddr;
 void fib_add_ifaddr(struct in_ifaddr *);
 void fib_del_ifaddr(struct in_ifaddr *, struct in_ifaddr *);
+void fib_modify_prefix_metric(struct in_ifaddr *ifa, u32 new_metric);
 
 void rt_add_uncached_list(struct rtable *rt);
 void rt_del_uncached_list(struct rtable *rt);
index 40f001782c1b37314ecd197ab65cb3adc68f50c7..d7585ab1a77a0e9d0f942960811a7c61c70ae129 100644 (file)
@@ -99,6 +99,7 @@ static const struct nla_policy ifa_ipv4_policy[IFA_MAX+1] = {
        [IFA_LABEL]             = { .type = NLA_STRING, .len = IFNAMSIZ - 1 },
        [IFA_CACHEINFO]         = { .len = sizeof(struct ifa_cacheinfo) },
        [IFA_FLAGS]             = { .type = NLA_U32 },
+       [IFA_RT_PRIORITY]       = { .type = NLA_U32 },
 };
 
 #define IN4_ADDR_HSIZE_SHIFT   8
@@ -835,6 +836,9 @@ static struct in_ifaddr *rtm_to_ifaddr(struct net *net, struct nlmsghdr *nlh,
        else
                memcpy(ifa->ifa_label, dev->name, IFNAMSIZ);
 
+       if (tb[IFA_RT_PRIORITY])
+               ifa->ifa_rt_priority = nla_get_u32(tb[IFA_RT_PRIORITY]);
+
        if (tb[IFA_CACHEINFO]) {
                struct ifa_cacheinfo *ci;
 
@@ -906,12 +910,20 @@ static int inet_rtm_newaddr(struct sk_buff *skb, struct nlmsghdr *nlh,
                return __inet_insert_ifa(ifa, nlh, NETLINK_CB(skb).portid,
                                         extack);
        } else {
+               u32 new_metric = ifa->ifa_rt_priority;
+
                inet_free_ifa(ifa);
 
                if (nlh->nlmsg_flags & NLM_F_EXCL ||
                    !(nlh->nlmsg_flags & NLM_F_REPLACE))
                        return -EEXIST;
                ifa = ifa_existing;
+
+               if (ifa->ifa_rt_priority != new_metric) {
+                       fib_modify_prefix_metric(ifa, new_metric);
+                       ifa->ifa_rt_priority = new_metric;
+               }
+
                set_ifa_lifetime(ifa, valid_lft, prefered_lft);
                cancel_delayed_work(&check_lifetime_work);
                queue_delayed_work(system_power_efficient_wq,
@@ -1549,6 +1561,7 @@ static size_t inet_nlmsg_size(void)
               + nla_total_size(4) /* IFA_BROADCAST */
               + nla_total_size(IFNAMSIZ) /* IFA_LABEL */
               + nla_total_size(4)  /* IFA_FLAGS */
+              + nla_total_size(4)  /* IFA_RT_PRIORITY */
               + nla_total_size(sizeof(struct ifa_cacheinfo)); /* IFA_CACHEINFO */
 }
 
@@ -1618,6 +1631,8 @@ static int inet_fill_ifaddr(struct sk_buff *skb, struct in_ifaddr *ifa,
            (ifa->ifa_label[0] &&
             nla_put_string(skb, IFA_LABEL, ifa->ifa_label)) ||
            nla_put_u32(skb, IFA_FLAGS, ifa->ifa_flags) ||
+           (ifa->ifa_rt_priority &&
+            nla_put_u32(skb, IFA_RT_PRIORITY, ifa->ifa_rt_priority)) ||
            put_cacheinfo(skb, ifa->ifa_cstamp, ifa->ifa_tstamp,
                          preferred, valid))
                goto nla_put_failure;
index b69e2824c7615b7d64146ec7eb6d8b3a5a890280..63aa39b3af03cb70c50b16418bf95a5c736f6cae 100644 (file)
@@ -847,7 +847,8 @@ out_err:
  * to fib engine. It is legal, because all events occur
  * only when netlink is already locked.
  */
-static void fib_magic(int cmd, int type, __be32 dst, int dst_len, struct in_ifaddr *ifa)
+static void fib_magic(int cmd, int type, __be32 dst, int dst_len,
+                     struct in_ifaddr *ifa, u32 rt_priority)
 {
        struct net *net = dev_net(ifa->ifa_dev->dev);
        u32 tb_id = l3mdev_fib_table(ifa->ifa_dev->dev);
@@ -857,6 +858,7 @@ static void fib_magic(int cmd, int type, __be32 dst, int dst_len, struct in_ifad
                .fc_type = type,
                .fc_dst = dst,
                .fc_dst_len = dst_len,
+               .fc_priority = rt_priority,
                .fc_prefsrc = ifa->ifa_local,
                .fc_oif = ifa->ifa_dev->dev->ifindex,
                .fc_nlflags = NLM_F_CREATE | NLM_F_APPEND,
@@ -902,31 +904,57 @@ void fib_add_ifaddr(struct in_ifaddr *ifa)
                }
        }
 
-       fib_magic(RTM_NEWROUTE, RTN_LOCAL, addr, 32, prim);
+       fib_magic(RTM_NEWROUTE, RTN_LOCAL, addr, 32, prim, 0);
 
        if (!(dev->flags & IFF_UP))
                return;
 
        /* Add broadcast address, if it is explicitly assigned. */
        if (ifa->ifa_broadcast && ifa->ifa_broadcast != htonl(0xFFFFFFFF))
-               fib_magic(RTM_NEWROUTE, RTN_BROADCAST, ifa->ifa_broadcast, 32, prim);
+               fib_magic(RTM_NEWROUTE, RTN_BROADCAST, ifa->ifa_broadcast, 32,
+                         prim, 0);
 
        if (!ipv4_is_zeronet(prefix) && !(ifa->ifa_flags & IFA_F_SECONDARY) &&
            (prefix != addr || ifa->ifa_prefixlen < 32)) {
                if (!(ifa->ifa_flags & IFA_F_NOPREFIXROUTE))
                        fib_magic(RTM_NEWROUTE,
                                  dev->flags & IFF_LOOPBACK ? RTN_LOCAL : RTN_UNICAST,
-                                 prefix, ifa->ifa_prefixlen, prim);
+                                 prefix, ifa->ifa_prefixlen, prim,
+                                 ifa->ifa_rt_priority);
 
                /* Add network specific broadcasts, when it takes a sense */
                if (ifa->ifa_prefixlen < 31) {
-                       fib_magic(RTM_NEWROUTE, RTN_BROADCAST, prefix, 32, prim);
+                       fib_magic(RTM_NEWROUTE, RTN_BROADCAST, prefix, 32,
+                                 prim, 0);
                        fib_magic(RTM_NEWROUTE, RTN_BROADCAST, prefix | ~mask,
-                                 32, prim);
+                                 32, prim, 0);
                }
        }
 }
 
+void fib_modify_prefix_metric(struct in_ifaddr *ifa, u32 new_metric)
+{
+       __be32 prefix = ifa->ifa_address & ifa->ifa_mask;
+       struct in_device *in_dev = ifa->ifa_dev;
+       struct net_device *dev = in_dev->dev;
+
+       if (!(dev->flags & IFF_UP) ||
+           ifa->ifa_flags & (IFA_F_SECONDARY | IFA_F_NOPREFIXROUTE) ||
+           ipv4_is_zeronet(prefix) ||
+           prefix == ifa->ifa_local || ifa->ifa_prefixlen == 32)
+               return;
+
+       /* add the new */
+       fib_magic(RTM_NEWROUTE,
+                 dev->flags & IFF_LOOPBACK ? RTN_LOCAL : RTN_UNICAST,
+                 prefix, ifa->ifa_prefixlen, ifa, new_metric);
+
+       /* delete the old */
+       fib_magic(RTM_DELROUTE,
+                 dev->flags & IFF_LOOPBACK ? RTN_LOCAL : RTN_UNICAST,
+                 prefix, ifa->ifa_prefixlen, ifa, ifa->ifa_rt_priority);
+}
+
 /* Delete primary or secondary address.
  * Optionally, on secondary address promotion consider the addresses
  * from subnet iprim as deleted, even if they are in device list.
@@ -968,7 +996,7 @@ void fib_del_ifaddr(struct in_ifaddr *ifa, struct in_ifaddr *iprim)
                if (!(ifa->ifa_flags & IFA_F_NOPREFIXROUTE))
                        fib_magic(RTM_DELROUTE,
                                  dev->flags & IFF_LOOPBACK ? RTN_LOCAL : RTN_UNICAST,
-                                 any, ifa->ifa_prefixlen, prim);
+                                 any, ifa->ifa_prefixlen, prim, 0);
                subnet = 1;
        }
 
@@ -1052,17 +1080,20 @@ void fib_del_ifaddr(struct in_ifaddr *ifa, struct in_ifaddr *iprim)
 
 no_promotions:
        if (!(ok & BRD_OK))
-               fib_magic(RTM_DELROUTE, RTN_BROADCAST, ifa->ifa_broadcast, 32, prim);
+               fib_magic(RTM_DELROUTE, RTN_BROADCAST, ifa->ifa_broadcast, 32,
+                         prim, 0);
        if (subnet && ifa->ifa_prefixlen < 31) {
                if (!(ok & BRD1_OK))
-                       fib_magic(RTM_DELROUTE, RTN_BROADCAST, brd, 32, prim);
+                       fib_magic(RTM_DELROUTE, RTN_BROADCAST, brd, 32,
+                                 prim, 0);
                if (!(ok & BRD0_OK))
-                       fib_magic(RTM_DELROUTE, RTN_BROADCAST, any, 32, prim);
+                       fib_magic(RTM_DELROUTE, RTN_BROADCAST, any, 32,
+                                 prim, 0);
        }
        if (!(ok & LOCAL_OK)) {
                unsigned int addr_type;
 
-               fib_magic(RTM_DELROUTE, RTN_LOCAL, ifa->ifa_local, 32, prim);
+               fib_magic(RTM_DELROUTE, RTN_LOCAL, ifa->ifa_local, 32, prim, 0);
 
                /* Check, that this local address finally disappeared. */
                addr_type = inet_addr_type_dev_table(dev_net(dev), dev,