Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 68ae9297 authored by Benjamin Poirier's avatar Benjamin Poirier Committed by Greg Kroah-Hartman
Browse files

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: f8efb73c ("mpls: multipath route support")
Signed-off-by: default avatarBenjamin Poirier <bpoirier@nvidia.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 57af54a5
Loading
Loading
Loading
Loading
+52 −16
Original line number Diff line number Diff line
@@ -1438,22 +1438,52 @@ static void mpls_dev_destroy_rcu(struct rcu_head *head)
	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 net *net = dev_net(dev);
	u8 alive, deleted;
	unsigned index;

	platform_label = rtnl_dereference(net->mpls.platform_label);
	for (index = 0; index < net->mpls.platform_labels; index++) {
		struct mpls_route *rt = rtnl_dereference(platform_label[index]);
		bool nh_del = false;
		u8 alive = 0;

		if (!rt)
			continue;

		alive = 0;
		deleted = 0;
		if (event == NETDEV_UNREGISTER) {
			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) {
			unsigned int nh_flags = nh->nh_flags;

@@ -1477,16 +1507,15 @@ static void mpls_ifdown(struct net_device *dev, int event)
next:
			if (!(nh_flags & (RTNH_F_DEAD | RTNH_F_LINKDOWN)))
				alive++;
			if (!rtnl_dereference(nh->nh_dev))
				deleted++;
		} endfor_nexthops(rt);

		WRITE_ONCE(rt->rt_nhn_alive, alive);

		/* if there are no more nexthops, delete the route */
		if (event == NETDEV_UNREGISTER && deleted == rt->rt_nhn)
			mpls_route_update(net, index, NULL, NULL);
		if (nh_del)
			mpls_route_update(net, index, rt, NULL);
	}

	return 0;
}

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;

	switch (event) {
		int err;

	case NETDEV_DOWN:
		mpls_ifdown(dev, event);
		err = mpls_ifdown(dev, event);
		if (err)
			return notifier_from_errno(err);
		break;
	case NETDEV_UP:
		flags = dev_get_flags(dev);
@@ -1566,13 +1599,18 @@ static int mpls_dev_notify(struct notifier_block *this, unsigned long event,
		break;
	case NETDEV_CHANGE:
		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);
		else
			mpls_ifdown(dev, event);
		} else {
			err = mpls_ifdown(dev, event);
			if (err)
				return notifier_from_errno(err);
		}
		break;
	case NETDEV_UNREGISTER:
		mpls_ifdown(dev, event);
		err = mpls_ifdown(dev, event);
		if (err)
			return notifier_from_errno(err);
		mdev = mpls_dev_get(dev);
		if (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:
		mdev = mpls_dev_get(dev);
		if (mdev) {
			int err;

			mpls_dev_sysctl_unregister(dev, mdev);
			err = mpls_dev_sysctl_register(dev, mdev);
			if (err)