Commit 3e19ae7c authored by Vladimir Oltean's avatar Vladimir Oltean Committed by David S. Miller
Browse files

net: bridge: use READ_ONCE() and WRITE_ONCE() compiler barriers for fdb->dst



Annotate the writer side of fdb->dst:

- fdb_create()
- br_fdb_update()
- fdb_add_entry()
- br_fdb_external_learn_add()

with WRITE_ONCE() and the reader side:

- br_fdb_test_addr()
- br_fdb_update()
- fdb_fill_info()
- fdb_add_entry()
- fdb_delete_by_addr_and_port()
- br_fdb_external_learn_add()
- br_switchdev_fdb_notify()

with compiler barriers such that the readers do not attempt to reload
fdb->dst multiple times, leading to potentially different destination
ports when the fdb entry is updated concurrently.

This is especially important in read-side sections where fdb->dst is
used more than once, but let's convert all accesses for the sake of
uniformity.

Suggested-by: default avatarNikolay Aleksandrov <nikolay@nvidia.com>
Signed-off-by: default avatarVladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 84fe7399
Loading
Loading
Loading
Loading
+17 −11
Original line number Diff line number Diff line
@@ -440,9 +440,14 @@ int br_fdb_test_addr(struct net_device *dev, unsigned char *addr)
	if (!port)
		ret = 0;
	else {
		const struct net_bridge_port *dst = NULL;

		fdb = br_fdb_find_rcu(port->br, addr, 0);
		ret = fdb && fdb->dst && fdb->dst->dev != dev &&
			fdb->dst->state == BR_STATE_FORWARDING;
		if (fdb)
			dst = READ_ONCE(fdb->dst);

		ret = dst && dst->dev != dev &&
		      dst->state == BR_STATE_FORWARDING;
	}
	rcu_read_unlock();

@@ -509,7 +514,7 @@ static struct net_bridge_fdb_entry *fdb_create(struct net_bridge *br,
	fdb = kmem_cache_alloc(br_fdb_cache, GFP_ATOMIC);
	if (fdb) {
		memcpy(fdb->key.addr.addr, addr, ETH_ALEN);
		fdb->dst = source;
		WRITE_ONCE(fdb->dst, source);
		fdb->key.vlan_id = vid;
		fdb->flags = flags;
		fdb->updated = fdb->used = jiffies;
@@ -600,10 +605,10 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,
			}

			/* fastpath: update of existing entry */
			if (unlikely(source != fdb->dst &&
			if (unlikely(source != READ_ONCE(fdb->dst) &&
				     !test_bit(BR_FDB_STICKY, &fdb->flags))) {
				br_switchdev_fdb_notify(fdb, RTM_DELNEIGH);
				fdb->dst = source;
				WRITE_ONCE(fdb->dst, source);
				fdb_modified = true;
				/* Take over HW learned entry */
				if (unlikely(test_bit(BR_FDB_ADDED_BY_EXT_LEARN,
@@ -650,6 +655,7 @@ static int fdb_fill_info(struct sk_buff *skb, const struct net_bridge *br,
			 const struct net_bridge_fdb_entry *fdb,
			 u32 portid, u32 seq, int type, unsigned int flags)
{
	const struct net_bridge_port *dst = READ_ONCE(fdb->dst);
	unsigned long now = jiffies;
	struct nda_cacheinfo ci;
	struct nlmsghdr *nlh;
@@ -665,7 +671,7 @@ static int fdb_fill_info(struct sk_buff *skb, const struct net_bridge *br,
	ndm->ndm_pad2    = 0;
	ndm->ndm_flags	 = 0;
	ndm->ndm_type	 = 0;
	ndm->ndm_ifindex = fdb->dst ? fdb->dst->dev->ifindex : br->dev->ifindex;
	ndm->ndm_ifindex = dst ? dst->dev->ifindex : br->dev->ifindex;
	ndm->ndm_state   = fdb_to_nud(br, fdb);

	if (test_bit(BR_FDB_OFFLOADED, &fdb->flags))
@@ -964,8 +970,8 @@ static int fdb_add_entry(struct net_bridge *br, struct net_bridge_port *source,
		if (flags & NLM_F_EXCL)
			return -EEXIST;

		if (fdb->dst != source) {
			fdb->dst = source;
		if (READ_ONCE(fdb->dst) != source) {
			WRITE_ONCE(fdb->dst, source);
			modified = true;
		}
	}
@@ -1132,7 +1138,7 @@ static int fdb_delete_by_addr_and_port(struct net_bridge *br,
	struct net_bridge_fdb_entry *fdb;

	fdb = br_fdb_find(br, addr, vlan);
	if (!fdb || fdb->dst != p)
	if (!fdb || READ_ONCE(fdb->dst) != p)
		return -ENOENT;

	fdb_delete(br, fdb, true);
@@ -1281,8 +1287,8 @@ int br_fdb_external_learn_add(struct net_bridge *br, struct net_bridge_port *p,
	} else {
		fdb->updated = jiffies;

		if (fdb->dst != p) {
			fdb->dst = p;
		if (READ_ONCE(fdb->dst) != p) {
			WRITE_ONCE(fdb->dst, p);
			modified = true;
		}

+4 −3
Original line number Diff line number Diff line
@@ -110,6 +110,7 @@ int br_switchdev_set_port_flag(struct net_bridge_port *p,
void
br_switchdev_fdb_notify(const struct net_bridge_fdb_entry *fdb, int type)
{
	const struct net_bridge_port *dst = READ_ONCE(fdb->dst);
	struct switchdev_notifier_fdb_info info = {
		.addr = fdb->key.addr.addr,
		.vid = fdb->key.vlan_id,
@@ -118,17 +119,17 @@ br_switchdev_fdb_notify(const struct net_bridge_fdb_entry *fdb, int type)
		.offloaded = test_bit(BR_FDB_OFFLOADED, &fdb->flags),
	};

	if (!fdb->dst)
	if (!dst)
		return;

	switch (type) {
	case RTM_DELNEIGH:
		call_switchdev_notifiers(SWITCHDEV_FDB_DEL_TO_DEVICE,
					 fdb->dst->dev, &info.info, NULL);
					 dst->dev, &info.info, NULL);
		break;
	case RTM_NEWNEIGH:
		call_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_DEVICE,
					 fdb->dst->dev, &info.info, NULL);
					 dst->dev, &info.info, NULL);
		break;
	}
}