--- zzzz-none-000/linux-5.4.213/net/bridge/br_fdb.c 2022-09-15 10:04:56.000000000 +0000 +++ alder-5690pro-762/linux-5.4.213/net/bridge/br_fdb.c 2024-08-14 09:02:13.000000000 +0000 @@ -24,6 +24,8 @@ #include #include "br_private.h" +#include + static const struct rhashtable_params br_fdb_rht_params = { .head_offset = offsetof(struct net_bridge_fdb_entry, rhnode), .key_offset = offsetof(struct net_bridge_fdb_entry, key), @@ -37,6 +39,20 @@ static void fdb_notify(struct net_bridge *br, const struct net_bridge_fdb_entry *, int, bool); +ATOMIC_NOTIFIER_HEAD(br_fdb_notifier_list); + +void br_fdb_register_notify(struct notifier_block *nb) +{ + atomic_notifier_chain_register(&br_fdb_notifier_list, nb); +} +EXPORT_SYMBOL_GPL(br_fdb_register_notify); + +void br_fdb_unregister_notify(struct notifier_block *nb) +{ + atomic_notifier_chain_unregister(&br_fdb_notifier_list, nb); +} +EXPORT_SYMBOL_GPL(br_fdb_unregister_notify); + int __init br_fdb_init(void) { br_fdb_cache = kmem_cache_create("bridge_fdb_cache", @@ -146,6 +162,7 @@ { return fdb_find_rcu(&br->fdb_hash_tbl, addr, vid); } +EXPORT_SYMBOL_GPL(br_fdb_find_rcu); /* When a static FDB entry is added, the mac address from the entry is * added to the bridge private HW address list and all required ports @@ -203,11 +220,18 @@ hlist_del_init_rcu(&f->fdb_node); rhashtable_remove_fast(&br->fdb_hash_tbl, &f->rhnode, br_fdb_rht_params); + if (test_and_clear_bit(BR_FDB_DYNAMIC_LEARNED, &f->flags)) + atomic_dec(&br->fdb_n_learned); fdb_notify(br, f, RTM_DELNEIGH, swdev_notify); call_rcu(&f->rcu, fdb_rcu_free); } -/* Delete a local entry if no other port had the same address. */ +/* Delete a local entry if no other port had the same address. + * + * This function should only be called on entries with BR_FDB_LOCAL set, + * so even with BR_FDB_ADDED_BY_USER cleared we never need to increase + * the accounting for dynamically learned entries again. + */ static void fdb_delete_local(struct net_bridge *br, const struct net_bridge_port *p, struct net_bridge_fdb_entry *f) @@ -329,6 +353,20 @@ spin_unlock_bh(&br->hash_lock); } +ATOMIC_NOTIFIER_HEAD(br_fdb_update_notifier_list); + +void br_fdb_update_register_notify(struct notifier_block *nb) +{ + atomic_notifier_chain_register(&br_fdb_update_notifier_list, nb); +} +EXPORT_SYMBOL_GPL(br_fdb_update_register_notify); + +void br_fdb_update_unregister_notify(struct notifier_block *nb) +{ + atomic_notifier_chain_unregister(&br_fdb_update_notifier_list, nb); +} +EXPORT_SYMBOL_GPL(br_fdb_update_unregister_notify); + void br_fdb_cleanup(struct work_struct *work) { struct net_bridge *br = container_of(work, struct net_bridge, @@ -337,6 +375,7 @@ unsigned long delay = hold_time(br); unsigned long work_delay = delay; unsigned long now = jiffies; + struct br_fdb_event fdb_event; /* this part is tricky, in order to avoid blocking learning and * consequently forwarding, we rely on rcu to delete objects with @@ -353,8 +392,13 @@ work_delay = min(work_delay, this_timer - now); } else { spin_lock_bh(&br->hash_lock); - if (!hlist_unhashed(&f->fdb_node)) - fdb_delete(br, f, true); + if (!hlist_unhashed(&f->fdb_node)) { + memset(&fdb_event, 0, sizeof(fdb_event)); + ether_addr_copy(fdb_event.addr, f->key.addr.addr); + fdb_delete(br, f, true); + atomic_notifier_call_chain(&br_fdb_update_notifier_list, 0, + (void *)&fdb_event); + } spin_unlock_bh(&br->hash_lock); } } @@ -485,10 +529,20 @@ const unsigned char *addr, __u16 vid, unsigned char is_local, - unsigned char is_static) + unsigned char is_static, + unsigned char added_by_user) { + u32 max_learned = READ_ONCE(br->fdb_max_learned); + bool learned = !is_local && !added_by_user; struct net_bridge_fdb_entry *fdb; + if (likely(learned)) { + int n_learned = atomic_read(&br->fdb_n_learned); + + if (unlikely(max_learned && n_learned >= max_learned)) + return NULL; + } + fdb = kmem_cache_alloc(br_fdb_cache, GFP_ATOMIC); if (fdb) { memcpy(fdb->key.addr.addr, addr, ETH_ALEN); @@ -496,11 +550,14 @@ fdb->key.vlan_id = vid; fdb->is_local = is_local; fdb->is_static = is_static; - fdb->added_by_user = 0; + fdb->added_by_user = added_by_user; fdb->added_by_external_learn = 0; fdb->offloaded = 0; fdb->is_sticky = 0; fdb->updated = fdb->used = jiffies; + fdb->flags = learned ? BIT(BR_FDB_DYNAMIC_LEARNED) : 0; + if (likely(learned)) + atomic_inc(&br->fdb_n_learned); if (rhashtable_lookup_insert_fast(&br->fdb_hash_tbl, &fdb->rhnode, br_fdb_rht_params)) { @@ -533,7 +590,7 @@ fdb_delete(br, fdb, true); } - fdb = fdb_create(br, source, addr, vid, 1, 1); + fdb = fdb_create(br, source, addr, vid, 1, 1, 0); if (!fdb) return -ENOMEM; @@ -554,11 +611,20 @@ return ret; } +/* Get the bridge device */ +struct net_device *br_fdb_bridge_dev_get_and_hold(struct net_bridge *br) +{ + dev_hold(br->dev); + return br->dev; +} +EXPORT_SYMBOL_GPL(br_fdb_bridge_dev_get_and_hold); + void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source, const unsigned char *addr, u16 vid, bool added_by_user) { struct net_bridge_fdb_entry *fdb; bool fdb_modified = false; + struct br_fdb_event fdb_event; /* some users want to always flood. */ if (hold_time(br) == 0) @@ -581,16 +647,29 @@ /* fastpath: update of existing entry */ if (unlikely(source != fdb->dst && !fdb->is_sticky)) { + ether_addr_copy(fdb_event.addr, addr); + fdb_event.br = br; + fdb_event.orig_dev = fdb->dst->dev; + fdb_event.dev = source->dev; fdb->dst = source; fdb_modified = true; + /* Take over HW learned entry */ if (unlikely(fdb->added_by_external_learn)) fdb->added_by_external_learn = 0; + + atomic_notifier_call_chain( + &br_fdb_update_notifier_list, + 0, (void *)&fdb_event); } if (now != fdb->updated) fdb->updated = now; - if (unlikely(added_by_user)) + if (unlikely(added_by_user)) { fdb->added_by_user = 1; + if (test_and_clear_bit(BR_FDB_DYNAMIC_LEARNED, + &fdb->flags)) + atomic_dec(&br->fdb_n_learned); + } if (unlikely(fdb_modified)) { trace_br_fdb_update(br, source, addr, vid, added_by_user); fdb_notify(br, fdb, RTM_NEWNEIGH, true); @@ -598,10 +677,8 @@ } } else { spin_lock(&br->hash_lock); - fdb = fdb_create(br, source, addr, vid, 0, 0); + fdb = fdb_create(br, source, addr, vid, 0, 0, added_by_user); if (fdb) { - if (unlikely(added_by_user)) - fdb->added_by_user = 1; trace_br_fdb_update(br, source, addr, vid, added_by_user); fdb_notify(br, fdb, RTM_NEWNEIGH, true); @@ -613,8 +690,64 @@ } } +/* Refresh FDB entries for bridge packets being forwarded by offload engines */ +void br_refresh_fdb_entry(struct net_device *dev, const char *addr) +{ + struct net_bridge_port *p = br_port_get_rcu(dev); + + if (!p || p->state == BR_STATE_DISABLED) + return; + + if (!is_valid_ether_addr(addr)) { + pr_info("bridge: Attempt to refresh with invalid ether address %pM\n", + addr); + return; + } + + rcu_read_lock(); + br_fdb_update(p->br, p, addr, 0, true); + rcu_read_unlock(); +} +EXPORT_SYMBOL_GPL(br_refresh_fdb_entry); + +/* Update timestamp of FDB entries for bridge packets being forwarded by offload engines */ +void br_fdb_entry_refresh(struct net_device *dev, const char *addr, __u16 vid) +{ + struct net_bridge_fdb_entry *fdb; + struct net_bridge_port *p = br_port_get_rcu(dev); + + if (!p || p->state == BR_STATE_DISABLED) + return; + + rcu_read_lock(); + fdb = fdb_find_rcu(&p->br->fdb_hash_tbl, addr, vid); + if (likely(fdb)) { + fdb->updated = jiffies; + } + rcu_read_unlock(); +} +EXPORT_SYMBOL_GPL(br_fdb_entry_refresh); + +/* Look up the MAC address in the device's bridge fdb table */ +struct net_bridge_fdb_entry *br_fdb_has_entry(struct net_device *dev, + const char *addr, __u16 vid) +{ + struct net_bridge_port *p = br_port_get_rcu(dev); + struct net_bridge_fdb_entry *fdb; + + if (!p || p->state == BR_STATE_DISABLED) + return NULL; + + rcu_read_lock(); + fdb = fdb_find_rcu(&p->br->fdb_hash_tbl, addr, vid); + rcu_read_unlock(); + + return fdb; +} +EXPORT_SYMBOL_GPL(br_fdb_has_entry); + static int fdb_to_nud(const struct net_bridge *br, - const struct net_bridge_fdb_entry *fdb) + const struct net_bridge_fdb_entry *fdb) { if (fdb->is_local) return NUD_PERMANENT; @@ -698,6 +831,26 @@ if (swdev_notify) br_switchdev_fdb_notify(fdb, type); + if (fdb->dst) { + int event; + struct br_fdb_event fdb_event; + + if (type == RTM_NEWNEIGH) + event = BR_FDB_EVENT_ADD; + else + event = BR_FDB_EVENT_DEL; + + fdb_event.dev = fdb->dst->dev; + ether_addr_copy(fdb_event.addr, fdb->key.addr.addr); + fdb_event.is_local = fdb->is_local; + atomic_notifier_call_chain(&br_fdb_notifier_list, + event, + (void *)&fdb_event); + } + + /* fdb changed port or was deleted, must cancel any existing bypass. */ + avm_pa_flush_sessions_for_mac(fdb->key.addr.addr); + skb = nlmsg_new(fdb_nlmsg_size(), GFP_ATOMIC); if (skb == NULL) goto errout; @@ -709,6 +862,8 @@ kfree_skb(skb); goto errout; } + + __br_notify(RTNLGRP_NEIGH, type, fdb); rtnl_notify(skb, net, 0, RTNLGRP_NEIGH, NULL, GFP_ATOMIC); return; errout: @@ -823,7 +978,7 @@ if (!(flags & NLM_F_CREATE)) return -ENOENT; - fdb = fdb_create(br, source, addr, vid, 0, 0); + fdb = fdb_create(br, source, addr, vid, 0, 1, 1); if (!fdb) return -ENOMEM; @@ -836,6 +991,10 @@ fdb->dst = source; modified = true; } + + fdb->added_by_user = 1; + if (test_and_clear_bit(BR_FDB_DYNAMIC_LEARNED, &fdb->flags)) + atomic_dec(&br->fdb_n_learned); } if (fdb_to_nud(br, fdb) != state) { @@ -867,8 +1026,6 @@ modified = true; } - fdb->added_by_user = 1; - fdb->used = jiffies; if (modified) { fdb->updated = jiffies; @@ -1004,6 +1161,106 @@ return err; } +/* This function creates a new FDB entry. + * The caller can specify the FDB entry type like static, + * local or external entry. + * This has to be called only for bridge-port netdevs. + */ +int br_fdb_add_or_refresh_by_netdev(struct net_device *dev, + const unsigned char *addr, u16 vid, + u16 state) +{ + struct net_bridge_fdb_entry *fdb = NULL; + struct net_bridge *br = NULL; + int err = 0; + u16 nlh_flags = NLM_F_CREATE; + struct net_bridge_port *p = NULL; + + if (!dev) { + pr_info("bridge: netdevice is NULL\n"); + return -EINVAL; + } + + rcu_read_lock(); + p = br_port_get_check_rcu(dev); + if (!p) { + rcu_read_unlock(); + pr_info("bridge: %s not a bridge port\n", + dev->name); + return -EINVAL; + } + + br = p->br; + + spin_lock_bh(&br->hash_lock); + fdb = br_fdb_find(br, addr, vid); + if (!fdb) { + err = fdb_add_entry(br, p, addr, state, + nlh_flags, vid, 0); + } else { + fdb->updated = jiffies; + } + spin_unlock_bh(&br->hash_lock); + rcu_read_unlock(); + + return err; +} +EXPORT_SYMBOL_GPL(br_fdb_add_or_refresh_by_netdev); + +/* This function has to be called only for bridge-port netdevs.*/ +/* For bridge netdev br_fdb_delete has to be called.*/ +int br_fdb_delete_by_netdev(struct net_device *dev, + const unsigned char *addr, u16 vid) +{ + int err = 0; + struct net_bridge_vlan_group *vg; + struct net_bridge_vlan *v; + struct net_bridge_port *p = NULL; + + rcu_read_lock(); + p = br_port_get_check_rcu(dev); + if (!p) { + rcu_read_unlock(); + pr_info("bridge: %s not a bridge port\n", + dev->name); + return -EINVAL; + } + vg = nbp_vlan_group(p); + + if (vid) { + v = br_vlan_find(vg, vid); + if (!v) { + rcu_read_unlock(); + pr_info("bridge: with unconfigured vlan %d on %s\n" + , vid, dev->name); + return -EINVAL; + } + + err = __br_fdb_delete(p->br, p, addr, vid); + rcu_read_unlock(); + return err; + } + err = __br_fdb_delete(p->br, p, addr, 0); + + if (!vg || !vg->num_vlans) { + rcu_read_unlock(); + return err; + } + + /* We have vlans configured on this port and user didn't + * specify a VLAN. So, delete entry for every vlan on this port. + */ + list_for_each_entry(v, &vg->vlan_list, vlist) { + if (!br_vlan_should_use(v)) + continue; + err &= __br_fdb_delete(p->br, p, addr, v->vid); + } + rcu_read_unlock(); + + return err; +} +EXPORT_SYMBOL_GPL(br_fdb_delete_by_netdev); + /* Remove neighbor entry with RTM_DELNEIGH */ int br_fdb_delete(struct ndmsg *ndm, struct nlattr *tb[], struct net_device *dev, @@ -1119,13 +1376,11 @@ fdb = br_fdb_find(br, addr, vid); if (!fdb) { - fdb = fdb_create(br, p, addr, vid, 0, 0); + fdb = fdb_create(br, p, addr, vid, 0, 0, swdev_notify); if (!fdb) { err = -ENOMEM; goto err_unlock; } - if (swdev_notify) - fdb->added_by_user = 1; fdb->added_by_external_learn = 1; fdb_notify(br, fdb, RTM_NEWNEIGH, swdev_notify); } else { @@ -1145,8 +1400,15 @@ modified = true; } - if (swdev_notify) + if (swdev_notify) { fdb->added_by_user = 1; + if (test_and_clear_bit(BR_FDB_DYNAMIC_LEARNED, &fdb->flags)) + atomic_dec(&br->fdb_n_learned); + } + + if ((swdev_notify || !p) && + test_and_clear_bit(BR_FDB_DYNAMIC_LEARNED, &fdb->flags)) + atomic_dec(&br->fdb_n_learned); if (modified) fdb_notify(br, fdb, RTM_NEWNEIGH, swdev_notify);