]> Pileus Git - ~andy/linux/blobdiff - net/bridge/br_fdb.c
Merge branch 'drm-nouveau-next' of git://anongit.freedesktop.org/git/nouveau/linux...
[~andy/linux] / net / bridge / br_fdb.c
index c5f5a4a933f4302d34fd35abff160287af496919..9203d5a1943fbd4ba272ae38e742d8692093f7f1 100644 (file)
@@ -27,6 +27,9 @@
 #include "br_private.h"
 
 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,
+                                            __u16 vid);
 static int fdb_insert(struct net_bridge *br, struct net_bridge_port *source,
                      const unsigned char *addr, u16 vid);
 static void fdb_notify(struct net_bridge *br,
@@ -89,11 +92,57 @@ static void fdb_delete(struct net_bridge *br, struct net_bridge_fdb_entry *f)
        call_rcu(&f->rcu, fdb_rcu_free);
 }
 
+/* Delete a local entry if no other port had the same address. */
+static void fdb_delete_local(struct net_bridge *br,
+                            const struct net_bridge_port *p,
+                            struct net_bridge_fdb_entry *f)
+{
+       const unsigned char *addr = f->addr.addr;
+       u16 vid = f->vlan_id;
+       struct net_bridge_port *op;
+
+       /* Maybe another port has same hw addr? */
+       list_for_each_entry(op, &br->port_list, list) {
+               if (op != p && ether_addr_equal(op->dev->dev_addr, addr) &&
+                   (!vid || nbp_vlan_find(op, vid))) {
+                       f->dst = op;
+                       f->added_by_user = 0;
+                       return;
+               }
+       }
+
+       /* Maybe bridge device has same hw addr? */
+       if (p && ether_addr_equal(br->dev->dev_addr, addr) &&
+           (!vid || br_vlan_find(br, vid))) {
+               f->dst = NULL;
+               f->added_by_user = 0;
+               return;
+       }
+
+       fdb_delete(br, f);
+}
+
+void br_fdb_find_delete_local(struct net_bridge *br,
+                             const struct net_bridge_port *p,
+                             const unsigned char *addr, u16 vid)
+{
+       struct hlist_head *head = &br->hash[br_mac_hash(addr, vid)];
+       struct net_bridge_fdb_entry *f;
+
+       spin_lock_bh(&br->hash_lock);
+       f = fdb_find(head, addr, vid);
+       if (f && f->is_local && !f->added_by_user && f->dst == p)
+               fdb_delete_local(br, p, f);
+       spin_unlock_bh(&br->hash_lock);
+}
+
 void br_fdb_changeaddr(struct net_bridge_port *p, const unsigned char *newaddr)
 {
        struct net_bridge *br = p->br;
-       bool no_vlan = (nbp_get_vlan_info(p) == NULL) ? true : false;
+       struct net_port_vlans *pv = nbp_get_vlan_info(p);
+       bool no_vlan = !pv;
        int i;
+       u16 vid;
 
        spin_lock_bh(&br->hash_lock);
 
@@ -104,38 +153,34 @@ void br_fdb_changeaddr(struct net_bridge_port *p, const unsigned char *newaddr)
                        struct net_bridge_fdb_entry *f;
 
                        f = hlist_entry(h, struct net_bridge_fdb_entry, hlist);
-                       if (f->dst == p && f->is_local) {
-                               /* maybe another port has same hw addr? */
-                               struct net_bridge_port *op;
-                               u16 vid = f->vlan_id;
-                               list_for_each_entry(op, &br->port_list, list) {
-                                       if (op != p &&
-                                           ether_addr_equal(op->dev->dev_addr,
-                                                            f->addr.addr) &&
-                                           nbp_vlan_find(op, vid)) {
-                                               f->dst = op;
-                                               goto insert;
-                                       }
-                               }
-
+                       if (f->dst == p && f->is_local && !f->added_by_user) {
                                /* delete old one */
-                               fdb_delete(br, f);
-insert:
-                               /* insert new address,  may fail if invalid
-                                * address or dup.
-                                */
-                               fdb_insert(br, p, newaddr, vid);
+                               fdb_delete_local(br, p, f);
 
                                /* if this port has no vlan information
                                 * configured, we can safely be done at
                                 * this point.
                                 */
                                if (no_vlan)
-                                       goto done;
+                                       goto insert;
                        }
                }
        }
 
+insert:
+       /* insert new address,  may fail if invalid address or dup. */
+       fdb_insert(br, p, newaddr, 0);
+
+       if (no_vlan)
+               goto done;
+
+       /* Now add entries for every VLAN configured on the port.
+        * This function runs under RTNL so the bitmap will not change
+        * from under us.
+        */
+       for_each_set_bit(vid, pv->vlan_bitmap, VLAN_N_VID)
+               fdb_insert(br, p, newaddr, vid);
+
 done:
        spin_unlock_bh(&br->hash_lock);
 }
@@ -146,10 +191,12 @@ void br_fdb_change_mac_address(struct net_bridge *br, const u8 *newaddr)
        struct net_port_vlans *pv;
        u16 vid = 0;
 
+       spin_lock_bh(&br->hash_lock);
+
        /* If old entry was unassociated with any port, then delete it. */
        f = __br_fdb_get(br, br->dev->dev_addr, 0);
        if (f && f->is_local && !f->dst)
-               fdb_delete(br, f);
+               fdb_delete_local(br, NULL, f);
 
        fdb_insert(br, NULL, newaddr, 0);
 
@@ -159,14 +206,16 @@ void br_fdb_change_mac_address(struct net_bridge *br, const u8 *newaddr)
         */
        pv = br_get_vlan_info(br);
        if (!pv)
-               return;
+               goto out;
 
        for_each_set_bit_from(vid, pv->vlan_bitmap, VLAN_N_VID) {
                f = __br_fdb_get(br, br->dev->dev_addr, vid);
                if (f && f->is_local && !f->dst)
-                       fdb_delete(br, f);
+                       fdb_delete_local(br, NULL, f);
                fdb_insert(br, NULL, newaddr, vid);
        }
+out:
+       spin_unlock_bh(&br->hash_lock);
 }
 
 void br_fdb_cleanup(unsigned long _data)
@@ -235,25 +284,11 @@ void br_fdb_delete_by_port(struct net_bridge *br,
 
                        if (f->is_static && !do_all)
                                continue;
-                       /*
-                        * if multiple ports all have the same device address
-                        * then when one port is deleted, assign
-                        * the local entry to other port
-                        */
-                       if (f->is_local) {
-                               struct net_bridge_port *op;
-                               list_for_each_entry(op, &br->port_list, list) {
-                                       if (op != p &&
-                                           ether_addr_equal(op->dev->dev_addr,
-                                                            f->addr.addr)) {
-                                               f->dst = op;
-                                               goto skip_delete;
-                                       }
-                               }
-                       }
 
-                       fdb_delete(br, f);
-               skip_delete: ;
+                       if (f->is_local)
+                               fdb_delete_local(br, p, f);
+                       else
+                               fdb_delete(br, f);
                }
        }
        spin_unlock_bh(&br->hash_lock);
@@ -397,6 +432,7 @@ static struct net_bridge_fdb_entry *fdb_create(struct hlist_head *head,
                fdb->vlan_id = vid;
                fdb->is_local = 0;
                fdb->is_static = 0;
+               fdb->added_by_user = 0;
                fdb->updated = fdb->used = jiffies;
                hlist_add_head_rcu(&fdb->hlist, head);
        }
@@ -447,7 +483,7 @@ int br_fdb_insert(struct net_bridge *br, struct net_bridge_port *source,
 }
 
 void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,
-                  const unsigned char *addr, u16 vid)
+                  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;
@@ -473,13 +509,18 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,
                        /* fastpath: update of existing entry */
                        fdb->dst = source;
                        fdb->updated = jiffies;
+                       if (unlikely(added_by_user))
+                               fdb->added_by_user = 1;
                }
        } else {
                spin_lock(&br->hash_lock);
                if (likely(!fdb_find(head, addr, vid))) {
                        fdb = fdb_create(head, source, addr, vid);
-                       if (fdb)
+                       if (fdb) {
+                               if (unlikely(added_by_user))
+                                       fdb->added_by_user = 1;
                                fdb_notify(br, fdb, RTM_NEWNEIGH);
+                       }
                }
                /* else  we lose race and someone else inserts
                 * it first, don't bother updating
@@ -647,6 +688,7 @@ static int fdb_add_entry(struct net_bridge_port *source, const __u8 *addr,
 
                modified = true;
        }
+       fdb->added_by_user = 1;
 
        fdb->used = jiffies;
        if (modified) {
@@ -664,7 +706,7 @@ static int __br_fdb_add(struct ndmsg *ndm, struct net_bridge_port *p,
 
        if (ndm->ndm_flags & NTF_USE) {
                rcu_read_lock();
-               br_fdb_update(p->br, p, addr, vid);
+               br_fdb_update(p->br, p, addr, vid, true);
                rcu_read_unlock();
        } else {
                spin_lock_bh(&p->br->hash_lock);
@@ -749,8 +791,7 @@ out:
        return err;
 }
 
-int fdb_delete_by_addr(struct net_bridge *br, const u8 *addr,
-                      u16 vlan)
+static int fdb_delete_by_addr(struct net_bridge *br, const u8 *addr, u16 vlan)
 {
        struct hlist_head *head = &br->hash[br_mac_hash(addr, vlan)];
        struct net_bridge_fdb_entry *fdb;