net: mpls: Fix notifications when deleting a device

commit 7d4741eacdefa5f0475431645b56baf00784df1f upstream.

There are various problems related to netlink notifications for mpls route
changes in response to interfaces being deleted:
* delete interface of only nexthop
	DELROUTE notification is missing RTA_OIF attribute
* delete interface of non-last nexthop
	NEWROUTE notification is missing entirely
* delete interface of last nexthop
	DELROUTE notification is missing nexthop

All of these problems stem from the fact that existing routes are modified
in-place before sending a notification. Restructure mpls_ifdown() to avoid
changing the route in the DELROUTE cases and to create a copy in the
NEWROUTE case.

Fixes: f8efb73c97 ("mpls: multipath route support")
Signed-off-by: Benjamin Poirier <bpoirier@nvidia.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Benjamin Poirier 2021-11-29 15:15:05 +09:00 committed by Greg Kroah-Hartman
parent 57af54a560
commit 68ae929717

View File

@ -1438,22 +1438,52 @@ static void mpls_dev_destroy_rcu(struct rcu_head *head)
kfree(mdev); kfree(mdev);
} }
static void mpls_ifdown(struct net_device *dev, int event) static int mpls_ifdown(struct net_device *dev, int event)
{ {
struct mpls_route __rcu **platform_label; struct mpls_route __rcu **platform_label;
struct net *net = dev_net(dev); struct net *net = dev_net(dev);
u8 alive, deleted;
unsigned index; unsigned index;
platform_label = rtnl_dereference(net->mpls.platform_label); platform_label = rtnl_dereference(net->mpls.platform_label);
for (index = 0; index < net->mpls.platform_labels; index++) { for (index = 0; index < net->mpls.platform_labels; index++) {
struct mpls_route *rt = rtnl_dereference(platform_label[index]); struct mpls_route *rt = rtnl_dereference(platform_label[index]);
bool nh_del = false;
u8 alive = 0;
if (!rt) if (!rt)
continue; continue;
alive = 0; if (event == NETDEV_UNREGISTER) {
deleted = 0; u8 deleted = 0;
for_nexthops(rt) {
struct net_device *nh_dev =
rtnl_dereference(nh->nh_dev);
if (!nh_dev || nh_dev == dev)
deleted++;
if (nh_dev == dev)
nh_del = true;
} endfor_nexthops(rt);
/* if there are no more nexthops, delete the route */
if (deleted == rt->rt_nhn) {
mpls_route_update(net, index, NULL, NULL);
continue;
}
if (nh_del) {
size_t size = sizeof(*rt) + rt->rt_nhn *
rt->rt_nh_size;
struct mpls_route *orig = rt;
rt = kmalloc(size, GFP_KERNEL);
if (!rt)
return -ENOMEM;
memcpy(rt, orig, size);
}
}
change_nexthops(rt) { change_nexthops(rt) {
unsigned int nh_flags = nh->nh_flags; unsigned int nh_flags = nh->nh_flags;
@ -1477,16 +1507,15 @@ static void mpls_ifdown(struct net_device *dev, int event)
next: next:
if (!(nh_flags & (RTNH_F_DEAD | RTNH_F_LINKDOWN))) if (!(nh_flags & (RTNH_F_DEAD | RTNH_F_LINKDOWN)))
alive++; alive++;
if (!rtnl_dereference(nh->nh_dev))
deleted++;
} endfor_nexthops(rt); } endfor_nexthops(rt);
WRITE_ONCE(rt->rt_nhn_alive, alive); WRITE_ONCE(rt->rt_nhn_alive, alive);
/* if there are no more nexthops, delete the route */ if (nh_del)
if (event == NETDEV_UNREGISTER && deleted == rt->rt_nhn) mpls_route_update(net, index, rt, NULL);
mpls_route_update(net, index, NULL, NULL);
} }
return 0;
} }
static void mpls_ifup(struct net_device *dev, unsigned int flags) static void mpls_ifup(struct net_device *dev, unsigned int flags)
@ -1554,8 +1583,12 @@ static int mpls_dev_notify(struct notifier_block *this, unsigned long event,
return NOTIFY_OK; return NOTIFY_OK;
switch (event) { switch (event) {
int err;
case NETDEV_DOWN: case NETDEV_DOWN:
mpls_ifdown(dev, event); err = mpls_ifdown(dev, event);
if (err)
return notifier_from_errno(err);
break; break;
case NETDEV_UP: case NETDEV_UP:
flags = dev_get_flags(dev); flags = dev_get_flags(dev);
@ -1566,13 +1599,18 @@ static int mpls_dev_notify(struct notifier_block *this, unsigned long event,
break; break;
case NETDEV_CHANGE: case NETDEV_CHANGE:
flags = dev_get_flags(dev); flags = dev_get_flags(dev);
if (flags & (IFF_RUNNING | IFF_LOWER_UP)) if (flags & (IFF_RUNNING | IFF_LOWER_UP)) {
mpls_ifup(dev, RTNH_F_DEAD | RTNH_F_LINKDOWN); mpls_ifup(dev, RTNH_F_DEAD | RTNH_F_LINKDOWN);
else } else {
mpls_ifdown(dev, event); err = mpls_ifdown(dev, event);
if (err)
return notifier_from_errno(err);
}
break; break;
case NETDEV_UNREGISTER: case NETDEV_UNREGISTER:
mpls_ifdown(dev, event); err = mpls_ifdown(dev, event);
if (err)
return notifier_from_errno(err);
mdev = mpls_dev_get(dev); mdev = mpls_dev_get(dev);
if (mdev) { if (mdev) {
mpls_dev_sysctl_unregister(dev, mdev); mpls_dev_sysctl_unregister(dev, mdev);
@ -1583,8 +1621,6 @@ static int mpls_dev_notify(struct notifier_block *this, unsigned long event,
case NETDEV_CHANGENAME: case NETDEV_CHANGENAME:
mdev = mpls_dev_get(dev); mdev = mpls_dev_get(dev);
if (mdev) { if (mdev) {
int err;
mpls_dev_sysctl_unregister(dev, mdev); mpls_dev_sysctl_unregister(dev, mdev);
err = mpls_dev_sysctl_register(dev, mdev); err = mpls_dev_sysctl_register(dev, mdev);
if (err) if (err)