net/ipv4: Add support for specifying metric of connected routes

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>
This commit is contained in:
David Ahern 2018-05-27 08:09:57 -07:00 committed by David S. Miller
parent 620dee9415
commit af4d768ad2
4 changed files with 59 additions and 11 deletions

View File

@ -139,6 +139,7 @@ struct in_ifaddr {
__be32 ifa_local; __be32 ifa_local;
__be32 ifa_address; __be32 ifa_address;
__be32 ifa_mask; __be32 ifa_mask;
__u32 ifa_rt_priority;
__be32 ifa_broadcast; __be32 ifa_broadcast;
unsigned char ifa_scope; unsigned char ifa_scope;
unsigned char ifa_prefixlen; unsigned char ifa_prefixlen;

View File

@ -225,6 +225,7 @@ struct rtable *rt_dst_alloc(struct net_device *dev,
struct in_ifaddr; struct in_ifaddr;
void fib_add_ifaddr(struct in_ifaddr *); void fib_add_ifaddr(struct in_ifaddr *);
void fib_del_ifaddr(struct in_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_add_uncached_list(struct rtable *rt);
void rt_del_uncached_list(struct rtable *rt); void rt_del_uncached_list(struct rtable *rt);

View 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_LABEL] = { .type = NLA_STRING, .len = IFNAMSIZ - 1 },
[IFA_CACHEINFO] = { .len = sizeof(struct ifa_cacheinfo) }, [IFA_CACHEINFO] = { .len = sizeof(struct ifa_cacheinfo) },
[IFA_FLAGS] = { .type = NLA_U32 }, [IFA_FLAGS] = { .type = NLA_U32 },
[IFA_RT_PRIORITY] = { .type = NLA_U32 },
}; };
#define IN4_ADDR_HSIZE_SHIFT 8 #define IN4_ADDR_HSIZE_SHIFT 8
@ -835,6 +836,9 @@ static struct in_ifaddr *rtm_to_ifaddr(struct net *net, struct nlmsghdr *nlh,
else else
memcpy(ifa->ifa_label, dev->name, IFNAMSIZ); 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]) { if (tb[IFA_CACHEINFO]) {
struct ifa_cacheinfo *ci; 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, return __inet_insert_ifa(ifa, nlh, NETLINK_CB(skb).portid,
extack); extack);
} else { } else {
u32 new_metric = ifa->ifa_rt_priority;
inet_free_ifa(ifa); inet_free_ifa(ifa);
if (nlh->nlmsg_flags & NLM_F_EXCL || if (nlh->nlmsg_flags & NLM_F_EXCL ||
!(nlh->nlmsg_flags & NLM_F_REPLACE)) !(nlh->nlmsg_flags & NLM_F_REPLACE))
return -EEXIST; return -EEXIST;
ifa = ifa_existing; 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); set_ifa_lifetime(ifa, valid_lft, prefered_lft);
cancel_delayed_work(&check_lifetime_work); cancel_delayed_work(&check_lifetime_work);
queue_delayed_work(system_power_efficient_wq, 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(4) /* IFA_BROADCAST */
+ nla_total_size(IFNAMSIZ) /* IFA_LABEL */ + nla_total_size(IFNAMSIZ) /* IFA_LABEL */
+ nla_total_size(4) /* IFA_FLAGS */ + nla_total_size(4) /* IFA_FLAGS */
+ nla_total_size(4) /* IFA_RT_PRIORITY */
+ nla_total_size(sizeof(struct ifa_cacheinfo)); /* IFA_CACHEINFO */ + 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] && (ifa->ifa_label[0] &&
nla_put_string(skb, IFA_LABEL, ifa->ifa_label)) || nla_put_string(skb, IFA_LABEL, ifa->ifa_label)) ||
nla_put_u32(skb, IFA_FLAGS, ifa->ifa_flags) || 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, put_cacheinfo(skb, ifa->ifa_cstamp, ifa->ifa_tstamp,
preferred, valid)) preferred, valid))
goto nla_put_failure; goto nla_put_failure;

View File

@ -847,7 +847,8 @@ out_err:
* to fib engine. It is legal, because all events occur * to fib engine. It is legal, because all events occur
* only when netlink is already locked. * 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); struct net *net = dev_net(ifa->ifa_dev->dev);
u32 tb_id = l3mdev_fib_table(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_type = type,
.fc_dst = dst, .fc_dst = dst,
.fc_dst_len = dst_len, .fc_dst_len = dst_len,
.fc_priority = rt_priority,
.fc_prefsrc = ifa->ifa_local, .fc_prefsrc = ifa->ifa_local,
.fc_oif = ifa->ifa_dev->dev->ifindex, .fc_oif = ifa->ifa_dev->dev->ifindex,
.fc_nlflags = NLM_F_CREATE | NLM_F_APPEND, .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)) if (!(dev->flags & IFF_UP))
return; return;
/* Add broadcast address, if it is explicitly assigned. */ /* Add broadcast address, if it is explicitly assigned. */
if (ifa->ifa_broadcast && ifa->ifa_broadcast != htonl(0xFFFFFFFF)) 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) && if (!ipv4_is_zeronet(prefix) && !(ifa->ifa_flags & IFA_F_SECONDARY) &&
(prefix != addr || ifa->ifa_prefixlen < 32)) { (prefix != addr || ifa->ifa_prefixlen < 32)) {
if (!(ifa->ifa_flags & IFA_F_NOPREFIXROUTE)) if (!(ifa->ifa_flags & IFA_F_NOPREFIXROUTE))
fib_magic(RTM_NEWROUTE, fib_magic(RTM_NEWROUTE,
dev->flags & IFF_LOOPBACK ? RTN_LOCAL : RTN_UNICAST, 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 */ /* Add network specific broadcasts, when it takes a sense */
if (ifa->ifa_prefixlen < 31) { 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, 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. /* Delete primary or secondary address.
* Optionally, on secondary address promotion consider the addresses * Optionally, on secondary address promotion consider the addresses
* from subnet iprim as deleted, even if they are in device list. * 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)) if (!(ifa->ifa_flags & IFA_F_NOPREFIXROUTE))
fib_magic(RTM_DELROUTE, fib_magic(RTM_DELROUTE,
dev->flags & IFF_LOOPBACK ? RTN_LOCAL : RTN_UNICAST, dev->flags & IFF_LOOPBACK ? RTN_LOCAL : RTN_UNICAST,
any, ifa->ifa_prefixlen, prim); any, ifa->ifa_prefixlen, prim, 0);
subnet = 1; subnet = 1;
} }
@ -1052,17 +1080,20 @@ void fib_del_ifaddr(struct in_ifaddr *ifa, struct in_ifaddr *iprim)
no_promotions: no_promotions:
if (!(ok & BRD_OK)) 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 (subnet && ifa->ifa_prefixlen < 31) {
if (!(ok & BRD1_OK)) 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)) 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)) { if (!(ok & LOCAL_OK)) {
unsigned int addr_type; 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. */ /* Check, that this local address finally disappeared. */
addr_type = inet_addr_type_dev_table(dev_net(dev), dev, addr_type = inet_addr_type_dev_table(dev_net(dev), dev,