--- zzzz-none-000/linux-4.4.271/net/bridge/br_fdb.c 2021-06-03 06:22:09.000000000 +0000 +++ dakota-7530ac-750/linux-4.4.271/net/bridge/br_fdb.c 2023-01-11 09:25:44.000000000 +0000 @@ -27,6 +27,8 @@ #include #include "br_private.h" +#include + static struct kmem_cache *br_fdb_cache __read_mostly; static struct net_bridge_fdb_entry *fdb_find(struct hlist_head *head, const unsigned char *addr, @@ -38,6 +40,20 @@ static u32 fdb_salt __read_mostly; +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", @@ -145,6 +161,22 @@ switchdev_port_obj_del(f->dst->dev, &fdb.obj); } +static void fdb_delete_recipient(struct net_bridge *br, struct net_bridge_fdb_entry *f) +{ +#ifdef CONFIG_AVM_BRIDGE_MULTICAST_TO_UNICAST + struct net_bridge_group_recipient *pos; + struct hlist_node *temp; + /* When deleting the fdb update each net_bridge_group_recipient which + * has it as recipient. Do so in an RCU-safe manner because the recipients + * might be walked concurrently in br_forward_as_unicast(). the pg_list updaters + * operate so under the br->multicast_lock so sync with that*/ + spin_lock_bh(&br->multicast_lock); + hlist_for_each_entry_safe(pos, temp, &f->pg_list, fdb_list) + br_multicast_delete_recipient(pos); + spin_unlock_bh(&br->multicast_lock); +#endif +} + static void fdb_delete(struct net_bridge *br, struct net_bridge_fdb_entry *f) { if (f->is_static) @@ -155,6 +187,7 @@ hlist_del_rcu(&f->hlist); fdb_notify(br, f, RTM_DELNEIGH); + fdb_delete_recipient(br, f); call_rcu(&f->rcu, fdb_rcu_free); } @@ -289,12 +322,27 @@ 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(unsigned long _data) { struct net_bridge *br = (struct net_bridge *)_data; unsigned long delay = hold_time(br); unsigned long next_timer = jiffies + br->ageing_time; int i; + struct br_fdb_event fdb_event; spin_lock(&br->hash_lock); for (i = 0; i < BR_HASH_SIZE; i++) { @@ -308,10 +356,16 @@ if (f->added_by_external_learn) continue; this_timer = f->updated + delay; - if (time_before_eq(this_timer, jiffies)) + if (time_before_eq(this_timer, jiffies)) { + memset(&fdb_event, 0, sizeof(fdb_event)); + ether_addr_copy(fdb_event.addr, f->addr.addr); fdb_delete(br, f); - else if (time_before(this_timer, next_timer)) + atomic_notifier_call_chain( + &br_fdb_update_notifier_list, 0, + (void *)&fdb_event); + } else if (time_before(this_timer, next_timer)) { next_timer = this_timer; + } } } spin_unlock(&br->hash_lock); @@ -389,6 +443,7 @@ return NULL; } +EXPORT_SYMBOL_GPL(__br_fdb_get); #if IS_ENABLED(CONFIG_ATM_LANE) /* Interface used by ATM LANE hook to test @@ -513,6 +568,9 @@ fdb->added_by_user = 0; fdb->added_by_external_learn = 0; fdb->updated = fdb->used = jiffies; +#ifdef CONFIG_AVM_BRIDGE_MULTICAST_TO_UNICAST + INIT_HLIST_HEAD(&fdb->pg_list); +#endif hlist_add_head_rcu(&fdb->hlist, head); } return fdb; @@ -561,12 +619,21 @@ 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 hlist_head *head = &br->hash[br_mac_hash(addr, vid)]; 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) @@ -588,8 +655,16 @@ } else { /* fastpath: update of existing entry */ if (unlikely(source != fdb->dst)) { + 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; + + atomic_notifier_call_chain( + &br_fdb_update_notifier_list, + 0, (void *)&fdb_event); } fdb->updated = jiffies; if (unlikely(added_by_user)) @@ -614,8 +689,46 @@ } } +/* 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); + +/* 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->hash[br_mac_hash(addr, vid)], 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; @@ -687,6 +800,26 @@ struct sk_buff *skb; int err = -ENOBUFS; + 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->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->addr.addr); + skb = nlmsg_new(fdb_nlmsg_size(), GFP_ATOMIC); if (skb == NULL) goto errout; @@ -698,6 +831,7 @@ kfree_skb(skb); goto errout; } + __br_notify(RTNLGRP_NEIGH, type, fdb); rtnl_notify(skb, net, 0, RTNLGRP_NEIGH, NULL, GFP_ATOMIC); return; errout: @@ -977,6 +1111,95 @@ 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; + struct hlist_head *head = 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; + head = &br->hash[br_mac_hash(addr, vid)]; + + spin_lock_bh(&p->br->hash_lock); + fdb = fdb_find(head, addr, vid); + if (!fdb) { + err = fdb_add_entry(br, p, addr, state, + nlh_flags, vid); + } else { + fdb->updated = jiffies; + } + spin_unlock_bh(&p->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; + struct net_bridge_vlan_group *vg; + struct net_bridge_vlan *v; + struct net_bridge_port *p = br_port_get_rcu(dev); + + if (!p) { + 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) { + pr_info("bridge: with unconfigured vlan %d on %s\n" + , vid, dev->name); + return -EINVAL; + } + + return __br_fdb_delete(p, addr, vid); + } + err = __br_fdb_delete(p, addr, 0); + + if (!vg || !vg->num_vlans) + return err; + + list_for_each_entry(v, &vg->vlan_list, vlist) { + if (!br_vlan_should_use(v)) + continue; + err &= __br_fdb_delete(p, addr, v->vid); + } + 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,