Store the resolved port-VLAN as the destination of dynamically learned FDB entries. Roaming updates the destination directly, while an existing raw port destination is conditionally upgraded without generating a spurious notification. Conditional replacements prevent both this upgrade and a VLAN deletion fallback from overwriting a concurrent roam.
Reviewed-by: Ido Schimmel <[email protected]> Signed-off-by: Nikolay Aleksandrov <[email protected]> --- net/bridge/br_fdb.c | 57 ++++++++++++++++++++++++++--------------- net/bridge/br_private.h | 18 +++++++++++++ 2 files changed, 55 insertions(+), 20 deletions(-) diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c index b87c6f8875da..5664f3d649fa 100644 --- a/net/bridge/br_fdb.c +++ b/net/bridge/br_fdb.c @@ -385,7 +385,7 @@ void br_fdb_find_delete_local(struct net_bridge *br, } static struct net_bridge_fdb_entry *fdb_create(struct net_bridge *br, - struct net_bridge_port *source, + struct net_bridge_dst dst, const unsigned char *addr, __u16 vid, unsigned long flags) @@ -409,7 +409,7 @@ static struct net_bridge_fdb_entry *fdb_create(struct net_bridge *br, return NULL; memcpy(fdb->key.addr.addr, addr, ETH_ALEN); - br_fdb_dst_write(fdb, br_port_to_dst(source)); + br_fdb_dst_write(fdb, dst); fdb->key.vlan_id = vid; fdb->flags = flags; fdb->updated = fdb->used = jiffies; @@ -448,7 +448,7 @@ static int fdb_add_local(struct net_bridge *br, struct net_bridge_port *source, fdb_delete(br, fdb, true); } - fdb = fdb_create(br, source, addr, vid, + fdb = fdb_create(br, br_port_to_dst(source), addr, vid, BIT(BR_FDB_LOCAL) | BIT(BR_FDB_STATIC)); if (!fdb) return -ENOMEM; @@ -907,8 +907,8 @@ void br_fdb_cleanup_by_dst(struct net_bridge *br, * back to the raw port destination */ if (vlan && br_dst_vlan(dst) == vlan) - br_fdb_dst_write(f, - br_port_to_dst(p)); + br_fdb_dst_replace(f, dst, + br_port_to_dst(p)); continue; } } @@ -997,9 +997,12 @@ static bool __fdb_mark_active(struct net_bridge_fdb_entry *fdb) static void __fdb_update(struct net_bridge *br, struct net_bridge_fdb_entry *fdb, struct net_bridge_port *source, + struct net_bridge_dst dst, const unsigned char *addr, u16 vid, unsigned long flags) { + struct net_bridge_port *old_port; + struct net_bridge_dst old_dst; bool fdb_modified = false; unsigned long now; @@ -1017,18 +1020,30 @@ static void __fdb_update(struct net_bridge *br, fdb_modified = __fdb_mark_active(fdb); } + old_dst = br_fdb_dst_read(fdb); + old_port = br_dst_port(old_dst); /* fastpath: update of existing entry */ - if (unlikely(source != br_fdb_dst_port(fdb) && - !test_bit(BR_FDB_STICKY, &fdb->flags))) { - br_switchdev_fdb_notify(br, fdb, RTM_DELNEIGH); - br_fdb_dst_write(fdb, br_port_to_dst(source)); - fdb_modified = true; - /* Take over HW learned entry */ - if (unlikely(test_bit(BR_FDB_ADDED_BY_EXT_LEARN, &fdb->flags))) - clear_bit(BR_FDB_ADDED_BY_EXT_LEARN, &fdb->flags); - /* Clear locked flag when roaming to an unlocked port */ - if (unlikely(test_bit(BR_FDB_LOCKED, &fdb->flags))) - clear_bit(BR_FDB_LOCKED, &fdb->flags); + if (unlikely(!br_dst_equal(dst, old_dst) && + (source == old_port || + !test_bit(BR_FDB_STICKY, &fdb->flags)))) { + if (source != old_port) { + br_switchdev_fdb_notify(br, fdb, RTM_DELNEIGH); + br_fdb_dst_write(fdb, dst); + fdb_modified = true; + /* take over HW learned entry */ + if (unlikely(test_bit(BR_FDB_ADDED_BY_EXT_LEARN, + &fdb->flags))) + clear_bit(BR_FDB_ADDED_BY_EXT_LEARN, + &fdb->flags); + /* clear locked flag when roaming to an unlocked port */ + if (unlikely(test_bit(BR_FDB_LOCKED, &fdb->flags))) + clear_bit(BR_FDB_LOCKED, &fdb->flags); + } else { + /* raw port-to-VLAN promotion shouldn't overwrite a + * concurrent roam to another port + */ + br_fdb_dst_replace(fdb, old_dst, dst); + } } if (unlikely(test_bit(BR_FDB_ADDED_BY_USER, &flags))) { @@ -1047,6 +1062,8 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source, struct net_bridge_vlan *vlan, const unsigned char *addr, unsigned long flags) { + struct net_bridge_dst dst = vlan ? br_vlan_to_dst(vlan) : + br_port_to_dst(source); struct net_bridge_fdb_entry *fdb; u16 vid = vlan ? vlan->vid : 0; @@ -1056,10 +1073,10 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source, fdb = fdb_find_rcu(&br->fdb_hash_tbl, addr, vid); if (likely(fdb)) { - __fdb_update(br, fdb, source, addr, vid, flags); + __fdb_update(br, fdb, source, dst, addr, vid, flags); } else { spin_lock(&br->hash_lock); - fdb = fdb_create(br, source, addr, vid, flags); + fdb = fdb_create(br, dst, addr, vid, flags); if (fdb) { trace_br_fdb_update(br, source, addr, vid, flags); fdb_notify(br, fdb, RTM_NEWNEIGH, true); @@ -1213,7 +1230,7 @@ static int fdb_add_entry(struct net_bridge *br, struct net_bridge_port *source, if (!(flags & NLM_F_CREATE)) return -ENOENT; - fdb = fdb_create(br, source, addr, vid, + fdb = fdb_create(br, br_port_to_dst(source), addr, vid, BIT(BR_FDB_ADDED_BY_USER)); if (!fdb) return -ENOMEM; @@ -1564,7 +1581,7 @@ int br_fdb_external_learn_add(struct net_bridge *br, struct net_bridge_port *p, if (locked) flags |= BIT(BR_FDB_LOCKED); - fdb = fdb_create(br, p, addr, vid, flags); + fdb = fdb_create(br, br_port_to_dst(p), addr, vid, flags); if (!fdb) { err = -ENOMEM; goto err_unlock; diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h index 951b6ac5f484..a1c5b1abc1ef 100644 --- a/net/bridge/br_private.h +++ b/net/bridge/br_private.h @@ -694,6 +694,13 @@ static inline void br_dst_write(struct net_bridge_dst *dst, ACCESS_PRIVATE(&src, value)); } +static inline bool br_dst_equal(struct net_bridge_dst dst1, + struct net_bridge_dst dst2) +{ + return ACCESS_PRIVATE(&dst1, value) == + ACCESS_PRIVATE(&dst2, value); +} + static inline struct net_bridge_dst br_port_to_dst(const struct net_bridge_port *p) { @@ -768,6 +775,17 @@ static inline void br_fdb_dst_write(struct net_bridge_fdb_entry *fdb, br_dst_write(&fdb->dst, dst); } +static inline bool +br_fdb_dst_replace(struct net_bridge_fdb_entry *fdb, + struct net_bridge_dst old, + struct net_bridge_dst new) +{ + unsigned long old_value = ACCESS_PRIVATE(&old, value); + + return cmpxchg(&ACCESS_PRIVATE(&fdb->dst, value), old_value, + ACCESS_PRIVATE(&new, value)) == old_value; +} + static inline struct net_bridge_port * br_fdb_dst_port(const struct net_bridge_fdb_entry *fdb) { -- 2.47.3
