bridge: Add vlan support for local fdb entries
authorVlad Yasevich <vyasevic@redhat.com>
Wed, 13 Feb 2013 12:00:19 +0000 (12:00 +0000)
committerDavid S. Miller <davem@davemloft.net>
Thu, 14 Feb 2013 00:42:16 +0000 (19:42 -0500)
When VLAN is added to the port, a local fdb entry for that port
(the entry with the mac address of the port) is added for that
VLAN.  This way we can correctly determine if the traffic
is for the bridge itself.  If the address of the port changes,
we try to change all the local fdb entries we have for that port.

Signed-off-by: Vlad Yasevich <vyasevic@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
net/bridge/br_fdb.c
net/bridge/br_if.c
net/bridge/br_private.h
net/bridge/br_vlan.c

index 4b75ad43aa85782a60f8a3bee190e60c0a55b1ed..8117900af4de988a222a12cb49ab1c6d2832f445 100644 (file)
@@ -28,7 +28,7 @@
 
 static struct kmem_cache *br_fdb_cache __read_mostly;
 static int fdb_insert(struct net_bridge *br, struct net_bridge_port *source,
-                     const unsigned char *addr);
+                     const unsigned char *addr, u16 vid);
 static void fdb_notify(struct net_bridge *br,
                       const struct net_bridge_fdb_entry *, int);
 
@@ -92,6 +92,7 @@ static void fdb_delete(struct net_bridge *br, struct net_bridge_fdb_entry *f)
 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;
        int i;
 
        spin_lock_bh(&br->hash_lock);
@@ -106,10 +107,12 @@ void br_fdb_changeaddr(struct net_bridge_port *p, const unsigned char *newaddr)
                        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)) {
+                                                            f->addr.addr) &&
+                                           nbp_vlan_find(op, vid)) {
                                                f->dst = op;
                                                goto insert;
                                        }
@@ -117,27 +120,55 @@ void br_fdb_changeaddr(struct net_bridge_port *p, const unsigned char *newaddr)
 
                                /* delete old one */
                                fdb_delete(br, f);
-                               goto insert;
+insert:
+                               /* insert new address,  may fail if invalid
+                                * address or dup.
+                                */
+                               fdb_insert(br, p, newaddr, vid);
+
+                               /* if this port has no vlan information
+                                * configured, we can safely be done at
+                                * this point.
+                                */
+                               if (no_vlan)
+                                       goto done;
                        }
                }
        }
- insert:
-       /* insert new address,  may fail if invalid address or dup. */
-       fdb_insert(br, p, newaddr);
 
+done:
        spin_unlock_bh(&br->hash_lock);
 }
 
 void br_fdb_change_mac_address(struct net_bridge *br, const u8 *newaddr)
 {
        struct net_bridge_fdb_entry *f;
+       struct net_port_vlans *pv;
+       u16 vid = 0;
 
        /* 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_insert(br, NULL, newaddr);
+       fdb_insert(br, NULL, newaddr, 0);
+
+       /* Now remove and add entries for every VLAN configured on the
+        * bridge.  This function runs under RTNL so the bitmap will not
+        * change from under us.
+        */
+       pv = br_get_vlan_info(br);
+       if (!pv)
+               return;
+
+       for (vid = find_next_bit(pv->vlan_bitmap, BR_VLAN_BITMAP_LEN, vid);
+            vid < BR_VLAN_BITMAP_LEN;
+            vid = find_next_bit(pv->vlan_bitmap, BR_VLAN_BITMAP_LEN, vid+1)) {
+               f = __br_fdb_get(br, br->dev->dev_addr, vid);
+               if (f && f->is_local && !f->dst)
+                       fdb_delete(br, f);
+               fdb_insert(br, NULL, newaddr, vid);
+       }
 }
 
 void br_fdb_cleanup(unsigned long _data)
@@ -379,15 +410,15 @@ static struct net_bridge_fdb_entry *fdb_create(struct hlist_head *head,
 }
 
 static int fdb_insert(struct net_bridge *br, struct net_bridge_port *source,
-                 const unsigned char *addr)
+                 const unsigned char *addr, u16 vid)
 {
-       struct hlist_head *head = &br->hash[br_mac_hash(addr, 0)];
+       struct hlist_head *head = &br->hash[br_mac_hash(addr, vid)];
        struct net_bridge_fdb_entry *fdb;
 
        if (!is_valid_ether_addr(addr))
                return -EINVAL;
 
-       fdb = fdb_find(head, addr, 0);
+       fdb = fdb_find(head, addr, vid);
        if (fdb) {
                /* it is okay to have multiple ports with same
                 * address, just use the first one.
@@ -400,7 +431,7 @@ static int fdb_insert(struct net_bridge *br, struct net_bridge_port *source,
                fdb_delete(br, fdb);
        }
 
-       fdb = fdb_create(head, source, addr, 0);
+       fdb = fdb_create(head, source, addr, vid);
        if (!fdb)
                return -ENOMEM;
 
@@ -411,12 +442,12 @@ static int fdb_insert(struct net_bridge *br, struct net_bridge_port *source,
 
 /* Add entry for local address of interface */
 int br_fdb_insert(struct net_bridge *br, struct net_bridge_port *source,
-                 const unsigned char *addr)
+                 const unsigned char *addr, u16 vid)
 {
        int ret;
 
        spin_lock_bh(&br->hash_lock);
-       ret = fdb_insert(br, source, addr);
+       ret = fdb_insert(br, source, addr, vid);
        spin_unlock_bh(&br->hash_lock);
        return ret;
 }
@@ -712,8 +743,8 @@ out:
        return err;
 }
 
-static int fdb_delete_by_addr(struct net_bridge *br, const u8 *addr,
-                             u16 vlan)
+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;
index 335c60cebfd12ef2150bddb522cd70c5d8aea077..ef1b91431c6bb1e0627119f877eca0d2339a899c 100644 (file)
@@ -397,7 +397,7 @@ int br_add_if(struct net_bridge *br, struct net_device *dev)
 
        dev_set_mtu(br->dev, br_min_mtu(br));
 
-       if (br_fdb_insert(br, p, dev->dev_addr))
+       if (br_fdb_insert(br, p, dev->dev_addr, 0))
                netdev_err(dev, "failed insert local address bridge forwarding table\n");
 
        kobject_uevent(&p->kobj, KOBJ_ADD);
index 799dbb37e5a2353ed35f21b68f102fdb60590261..32ecfa4ef47fe45c85318e0b2694108e1fa0d134 100644 (file)
@@ -382,11 +382,13 @@ extern int br_fdb_fillbuf(struct net_bridge *br, void *buf,
                          unsigned long count, unsigned long off);
 extern int br_fdb_insert(struct net_bridge *br,
                         struct net_bridge_port *source,
-                        const unsigned char *addr);
+                        const unsigned char *addr,
+                        u16 vid);
 extern void br_fdb_update(struct net_bridge *br,
                          struct net_bridge_port *source,
                          const unsigned char *addr,
                          u16 vid);
+extern int fdb_delete_by_addr(struct net_bridge *br, const u8 *addr, u16 vid);
 
 extern int br_fdb_delete(struct ndmsg *ndm, struct nlattr *tb[],
                         struct net_device *dev,
@@ -573,6 +575,7 @@ extern int br_vlan_filter_toggle(struct net_bridge *br, unsigned long val);
 extern int nbp_vlan_add(struct net_bridge_port *port, u16 vid, u16 flags);
 extern int nbp_vlan_delete(struct net_bridge_port *port, u16 vid);
 extern void nbp_vlan_flush(struct net_bridge_port *port);
+extern bool nbp_vlan_find(struct net_bridge_port *port, u16 vid);
 
 static inline struct net_port_vlans *br_get_vlan_info(
                                                const struct net_bridge *br)
@@ -676,6 +679,11 @@ static inline struct net_port_vlans *nbp_get_vlan_info(
        return NULL;
 }
 
+static inline bool nbp_vlan_find(struct net_bridge_port *port, u16 vid)
+{
+       return false;
+}
+
 static inline u16 br_vlan_get_tag(const struct sk_buff *skb, u16 *tag)
 {
        return 0;
index c79940cff3a1d5fca27982f2a818442316961db1..9ea358fbbf789870a2b85d0fee991b5b502887a6 100644 (file)
@@ -25,6 +25,9 @@ static void __vlan_delete_pvid(struct net_port_vlans *v, u16 vid)
 
 static int __vlan_add(struct net_port_vlans *v, u16 vid, u16 flags)
 {
+       struct net_bridge_port *p = NULL;
+       struct net_bridge *br;
+       struct net_device *dev;
        int err;
 
        if (test_bit(vid, v->vlan_bitmap)) {
@@ -33,19 +36,35 @@ static int __vlan_add(struct net_port_vlans *v, u16 vid, u16 flags)
                return 0;
        }
 
-       if (v->port_idx && vid) {
-               struct net_device *dev = v->parent.port->dev;
+       if (vid) {
+               if (v->port_idx) {
+                       p = v->parent.port;
+                       br = p->br;
+                       dev = p->dev;
+               } else {
+                       br = v->parent.br;
+                       dev = br->dev;
+               }
 
-               /* Add VLAN to the device filter if it is supported.
-                * Stricly speaking, this is not necessary now, since devices
-                * are made promiscuous by the bridge, but if that ever changes
-                * this code will allow tagged traffic to enter the bridge.
-                */
-               if (dev->features & NETIF_F_HW_VLAN_FILTER) {
+               if (p && (dev->features & NETIF_F_HW_VLAN_FILTER)) {
+                       /* Add VLAN to the device filter if it is supported.
+                        * Stricly speaking, this is not necessary now, since
+                        * devices are made promiscuous by the bridge, but if
+                        * that ever changes this code will allow tagged
+                        * traffic to enter the bridge.
+                        */
                        err = dev->netdev_ops->ndo_vlan_rx_add_vid(dev, vid);
                        if (err)
                                return err;
                }
+
+               err = br_fdb_insert(br, p, dev->dev_addr, vid);
+               if (err) {
+                       br_err(br, "failed insert local address into bridge "
+                              "forwarding table\n");
+                       goto out_filt;
+               }
+
        }
 
        set_bit(vid, v->vlan_bitmap);
@@ -54,6 +73,11 @@ static int __vlan_add(struct net_port_vlans *v, u16 vid, u16 flags)
                __vlan_add_pvid(v, vid);
 
        return 0;
+
+out_filt:
+       if (p && (dev->features & NETIF_F_HW_VLAN_FILTER))
+               dev->netdev_ops->ndo_vlan_rx_kill_vid(dev, vid);
+       return err;
 }
 
 static int __vlan_del(struct net_port_vlans *v, u16 vid)
@@ -253,6 +277,15 @@ int br_vlan_delete(struct net_bridge *br, u16 vid)
        if (!pv)
                return -EINVAL;
 
+       if (vid) {
+               /* If the VID !=0 remove fdb for this vid. VID 0 is special
+                * in that it's the default and is always there in the fdb.
+                */
+               spin_lock_bh(&br->hash_lock);
+               fdb_delete_by_addr(br, br->dev->dev_addr, vid);
+               spin_unlock_bh(&br->hash_lock);
+       }
+
        __vlan_del(pv, vid);
        return 0;
 }
@@ -329,6 +362,15 @@ int nbp_vlan_delete(struct net_bridge_port *port, u16 vid)
        if (!pv)
                return -EINVAL;
 
+       if (vid) {
+               /* If the VID !=0 remove fdb for this vid. VID 0 is special
+                * in that it's the default and is always there in the fdb.
+                */
+               spin_lock_bh(&port->br->hash_lock);
+               fdb_delete_by_addr(port->br, port->dev->dev_addr, vid);
+               spin_unlock_bh(&port->br->hash_lock);
+       }
+
        return __vlan_del(pv, vid);
 }
 
@@ -344,3 +386,22 @@ void nbp_vlan_flush(struct net_bridge_port *port)
 
        __vlan_flush(pv);
 }
+
+bool nbp_vlan_find(struct net_bridge_port *port, u16 vid)
+{
+       struct net_port_vlans *pv;
+       bool found = false;
+
+       rcu_read_lock();
+       pv = rcu_dereference(port->vlan_info);
+
+       if (!pv)
+               goto out;
+
+       if (test_bit(vid, pv->vlan_bitmap))
+               found = true;
+
+out:
+       rcu_read_unlock();
+       return found;
+}