Commit 79472fe8 authored by Roopa Prabhu's avatar Roopa Prabhu Committed by David S. Miller
Browse files

vxlan: few locking fixes in nexthop event handler



- remove fdb from nh_list before the rcu grace period
- protect fdb->vdev with rcu
- hold spin lock before destroying fdb

Fixes: c7cdbe2e ("vxlan: support for nexthop notifiers")
Signed-off-by: default avatarRoopa Prabhu <roopa@cumulusnetworks.com>
Reviewed-by: default avatarNikolay Aleksandrov <nikolay@cumulusnetworks.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 72b48682
Loading
Loading
Loading
Loading
+25 −7
Original line number Diff line number Diff line
@@ -81,7 +81,7 @@ struct vxlan_fdb {
	u16		  flags;	/* see ndm_flags and below */
	struct list_head  nh_list;
	struct nexthop __rcu *nh;
	struct vxlan_dev  *vdev;
	struct vxlan_dev  __rcu *vdev;
};

#define NTF_VXLAN_ADDED_BY_USER 0x100
@@ -837,7 +837,7 @@ static struct vxlan_fdb *vxlan_fdb_alloc(struct vxlan_dev *vxlan, const u8 *mac,
	f->updated = f->used = jiffies;
	f->vni = src_vni;
	f->nh = NULL;
	f->vdev = vxlan;
	RCU_INIT_POINTER(f->vdev, vxlan);
	INIT_LIST_HEAD(&f->nh_list);
	INIT_LIST_HEAD(&f->remotes);
	memcpy(f->eth_addr, mac, ETH_ALEN);
@@ -963,7 +963,7 @@ static void __vxlan_fdb_free(struct vxlan_fdb *f)
	nh = rcu_dereference_raw(f->nh);
	if (nh) {
		rcu_assign_pointer(f->nh, NULL);
		list_del_rcu(&f->nh_list);
		rcu_assign_pointer(f->vdev, NULL);
		nexthop_put(nh);
	}

@@ -1000,7 +1000,7 @@ static void vxlan_fdb_destroy(struct vxlan_dev *vxlan, struct vxlan_fdb *f,
	}

	hlist_del_rcu(&f->hlist);
	f->vdev = NULL;
	list_del_rcu(&f->nh_list);
	call_rcu(&f->rcu, vxlan_fdb_free);
}

@@ -4615,17 +4615,35 @@ static struct notifier_block vxlan_switchdev_notifier_block __read_mostly = {
	.notifier_call = vxlan_switchdev_event,
};

static void vxlan_fdb_nh_flush(struct nexthop *nh)
{
	struct vxlan_fdb *fdb;
	struct vxlan_dev *vxlan;
	u32 hash_index;

	rcu_read_lock();
	list_for_each_entry_rcu(fdb, &nh->fdb_list, nh_list) {
		vxlan = rcu_dereference(fdb->vdev);
		WARN_ON(!vxlan);
		hash_index = fdb_head_index(vxlan, fdb->eth_addr,
					    vxlan->default_dst.remote_vni);
		spin_lock_bh(&vxlan->hash_lock[hash_index]);
		if (!hlist_unhashed(&fdb->hlist))
			vxlan_fdb_destroy(vxlan, fdb, false, false);
		spin_unlock_bh(&vxlan->hash_lock[hash_index]);
	}
	rcu_read_unlock();
}

static int vxlan_nexthop_event(struct notifier_block *nb,
			       unsigned long event, void *ptr)
{
	struct nexthop *nh = ptr;
	struct vxlan_fdb *fdb, *tmp;

	if (!nh || event != NEXTHOP_EVENT_DEL)
		return NOTIFY_DONE;

	list_for_each_entry_safe(fdb, tmp, &nh->fdb_list, nh_list)
		vxlan_fdb_destroy(fdb->vdev, fdb, false, false);
	vxlan_fdb_nh_flush(nh);

	return NOTIFY_DONE;
}