bridge: Add vlan filtering infrastructure
authorVlad Yasevich <vyasevic@redhat.com>
Wed, 13 Feb 2013 12:00:09 +0000 (12:00 +0000)
committerDavid S. Miller <davem@davemloft.net>
Thu, 14 Feb 2013 00:41:46 +0000 (19:41 -0500)
Adds an optional infrustructure component to bridge that would allow
native vlan filtering in the bridge.  Each bridge port (as well
as the bridge device) now get a VLAN bitmap.  Each bit in the bitmap
is associated with a vlan id.  This way if the bit corresponding to
the vid is set in the bitmap that the packet with vid is allowed to
enter and exit the port.

Write access the bitmap is protected by RTNL and read access
protected by RCU.

Vlan functionality is disabled by default.

Signed-off-by: Vlad Yasevich <vyasevic@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
net/bridge/Kconfig
net/bridge/Makefile
net/bridge/br_if.c
net/bridge/br_private.h
net/bridge/br_sysfs_br.c
net/bridge/br_vlan.c [new file with mode: 0644]

index 6dee7bf648a9af5a8da4bfd2b46082b71c197aa4..aa0d3b2f1bb7f1aa2ea1dfc5b0b230c9b962f258 100644 (file)
@@ -46,3 +46,17 @@ config BRIDGE_IGMP_SNOOPING
          Say N to exclude this support and reduce the binary size.
 
          If unsure, say Y.
+
+config BRIDGE_VLAN_FILTERING
+       bool "VLAN filtering"
+       depends on BRIDGE
+       depends on VLAN_8021Q
+       default n
+       ---help---
+         If you say Y here, then the Ethernet bridge will be able selectively
+         receive and forward traffic based on VLAN information in the packet
+         any VLAN information configured on the bridge port or bridge device.
+
+         Say N to exclude this support and reduce the binary size.
+
+         If unsure, say Y.
index e859098f5ee9b11468e4de7159b9d2cdeaa8e84b..e85498b2f1669f7dae3b7f5f5e1c0970b1be65c6 100644 (file)
@@ -14,4 +14,6 @@ bridge-$(CONFIG_BRIDGE_NETFILTER) += br_netfilter.o
 
 bridge-$(CONFIG_BRIDGE_IGMP_SNOOPING) += br_multicast.o br_mdb.o
 
+bridge-$(CONFIG_BRIDGE_VLAN_FILTERING) += br_vlan.o
+
 obj-$(CONFIG_BRIDGE_NF_EBTABLES) += netfilter/
index 2148d474a04fa1a2b585c07714320668b04aa1a0..af9d65ab4001af4f93b08b94e0d17b1c32b5a01d 100644 (file)
@@ -139,6 +139,7 @@ static void del_nbp(struct net_bridge_port *p)
 
        br_ifinfo_notify(RTM_DELLINK, p);
 
+       nbp_vlan_flush(p);
        br_fdb_delete_by_port(br, p, 1);
 
        list_del_rcu(&p->list);
index 06e85d9c05aaa8da827c75009a77c80a6a2a8ec2..1f3b309beea83284e1d56aa4194db82173ee8bad 100644 (file)
@@ -18,6 +18,7 @@
 #include <linux/netpoll.h>
 #include <linux/u64_stats_sync.h>
 #include <net/route.h>
+#include <linux/if_vlan.h>
 
 #define BR_HASH_BITS 8
 #define BR_HASH_SIZE (1 << BR_HASH_BITS)
@@ -26,6 +27,7 @@
 
 #define BR_PORT_BITS   10
 #define BR_MAX_PORTS   (1<<BR_PORT_BITS)
+#define BR_VLAN_BITMAP_LEN     BITS_TO_LONGS(VLAN_N_VID)
 
 #define BR_VERSION     "2.3"
 
@@ -63,6 +65,16 @@ struct br_ip
        __be16          proto;
 };
 
+struct net_port_vlans {
+       u16                             port_idx;
+       union {
+               struct net_bridge_port          *port;
+               struct net_bridge               *br;
+       }                               parent;
+       struct rcu_head                 rcu;
+       unsigned long                   vlan_bitmap[BR_VLAN_BITMAP_LEN];
+};
+
 struct net_bridge_fdb_entry
 {
        struct hlist_node               hlist;
@@ -156,6 +168,9 @@ struct net_bridge_port
 #ifdef CONFIG_NET_POLL_CONTROLLER
        struct netpoll                  *np;
 #endif
+#ifdef CONFIG_BRIDGE_VLAN_FILTERING
+       struct net_port_vlans __rcu     *vlan_info;
+#endif
 };
 
 #define br_port_exists(dev) (dev->priv_flags & IFF_BRIDGE_PORT)
@@ -257,6 +272,10 @@ struct net_bridge
        struct timer_list               topology_change_timer;
        struct timer_list               gc_timer;
        struct kobject                  *ifobj;
+#ifdef CONFIG_BRIDGE_VLAN_FILTERING
+       u8                              vlan_enabled;
+       struct net_port_vlans __rcu     *vlan_info;
+#endif
 };
 
 struct br_input_skb_cb {
@@ -531,6 +550,46 @@ static inline void br_mdb_uninit(void)
 }
 #endif
 
+/* br_vlan.c */
+#ifdef CONFIG_BRIDGE_VLAN_FILTERING
+extern int br_vlan_add(struct net_bridge *br, u16 vid);
+extern int br_vlan_delete(struct net_bridge *br, u16 vid);
+extern void br_vlan_flush(struct net_bridge *br);
+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);
+extern int nbp_vlan_delete(struct net_bridge_port *port, u16 vid);
+extern void nbp_vlan_flush(struct net_bridge_port *port);
+#else
+static inline int br_vlan_add(struct net_bridge *br, u16 vid)
+{
+       return -EOPNOTSUPP;
+}
+
+static inline int br_vlan_delete(struct net_bridge *br, u16 vid)
+{
+       return -EOPNOTSUPP;
+}
+
+static inline void br_vlan_flush(struct net_bridge *br)
+{
+}
+
+static inline int nbp_vlan_add(struct net_bridge_port *port, u16 vid)
+{
+       return -EOPNOTSUPP;
+}
+
+static inline int nbp_vlan_delete(struct net_bridge_port *port, u16 vid)
+{
+       return -EOPNOTSUPP;
+}
+
+static inline void nbp_vlan_flush(struct net_bridge_port *port)
+{
+}
+
+#endif
+
 /* br_netfilter.c */
 #ifdef CONFIG_BRIDGE_NETFILTER
 extern int br_netfilter_init(void);
index 5913a3a0047b35575fa45ebfd7d3a74cad7b75d9..8baa9c08e1a4b1442d4d99e4d448f25b03685e47 100644 (file)
@@ -691,6 +691,24 @@ static ssize_t store_nf_call_arptables(
 static DEVICE_ATTR(nf_call_arptables, S_IRUGO | S_IWUSR,
                   show_nf_call_arptables, store_nf_call_arptables);
 #endif
+#ifdef CONFIG_BRIDGE_VLAN_FILTERING
+static ssize_t show_vlan_filtering(struct device *d,
+                                  struct device_attribute *attr,
+                                  char *buf)
+{
+       struct net_bridge *br = to_bridge(d);
+       return sprintf(buf, "%d\n", br->vlan_enabled);
+}
+
+static ssize_t store_vlan_filtering(struct device *d,
+                                   struct device_attribute *attr,
+                                   const char *buf, size_t len)
+{
+       return store_bridge_parm(d, buf, len, br_vlan_filter_toggle);
+}
+static DEVICE_ATTR(vlan_filtering, S_IRUGO | S_IWUSR,
+                  show_vlan_filtering, store_vlan_filtering);
+#endif
 
 static struct attribute *bridge_attrs[] = {
        &dev_attr_forward_delay.attr,
@@ -731,6 +749,9 @@ static struct attribute *bridge_attrs[] = {
        &dev_attr_nf_call_iptables.attr,
        &dev_attr_nf_call_ip6tables.attr,
        &dev_attr_nf_call_arptables.attr,
+#endif
+#ifdef CONFIG_BRIDGE_VLAN_FILTERING
+       &dev_attr_vlan_filtering.attr,
 #endif
        NULL
 };
diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c
new file mode 100644 (file)
index 0000000..209464e
--- /dev/null
@@ -0,0 +1,199 @@
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
+#include <linux/slab.h>
+
+#include "br_private.h"
+
+static int __vlan_add(struct net_port_vlans *v, u16 vid)
+{
+       int err;
+
+       if (test_bit(vid, v->vlan_bitmap))
+               return -EEXIST;
+
+       if (v->port_idx && vid) {
+               struct net_device *dev = v->parent.port->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) {
+                       err = dev->netdev_ops->ndo_vlan_rx_add_vid(dev, vid);
+                       if (err)
+                               return err;
+               }
+       }
+
+       set_bit(vid, v->vlan_bitmap);
+       return 0;
+}
+
+static int __vlan_del(struct net_port_vlans *v, u16 vid)
+{
+       if (!test_bit(vid, v->vlan_bitmap))
+               return -EINVAL;
+
+       if (v->port_idx && vid) {
+               struct net_device *dev = v->parent.port->dev;
+
+               if (dev->features & NETIF_F_HW_VLAN_FILTER)
+                       dev->netdev_ops->ndo_vlan_rx_kill_vid(dev, vid);
+       }
+
+       clear_bit(vid, v->vlan_bitmap);
+       if (bitmap_empty(v->vlan_bitmap, BR_VLAN_BITMAP_LEN)) {
+               if (v->port_idx)
+                       rcu_assign_pointer(v->parent.port->vlan_info, NULL);
+               else
+                       rcu_assign_pointer(v->parent.br->vlan_info, NULL);
+               kfree_rcu(v, rcu);
+       }
+       return 0;
+}
+
+static void __vlan_flush(struct net_port_vlans *v)
+{
+       bitmap_zero(v->vlan_bitmap, BR_VLAN_BITMAP_LEN);
+       if (v->port_idx)
+               rcu_assign_pointer(v->parent.port->vlan_info, NULL);
+       else
+               rcu_assign_pointer(v->parent.br->vlan_info, NULL);
+       kfree_rcu(v, rcu);
+}
+
+/* Must be protected by RTNL */
+int br_vlan_add(struct net_bridge *br, u16 vid)
+{
+       struct net_port_vlans *pv = NULL;
+       int err;
+
+       ASSERT_RTNL();
+
+       pv = rtnl_dereference(br->vlan_info);
+       if (pv)
+               return __vlan_add(pv, vid);
+
+       /* Create port vlan infomration
+        */
+       pv = kzalloc(sizeof(*pv), GFP_KERNEL);
+       if (!pv)
+               return -ENOMEM;
+
+       pv->parent.br = br;
+       err = __vlan_add(pv, vid);
+       if (err)
+               goto out;
+
+       rcu_assign_pointer(br->vlan_info, pv);
+       return 0;
+out:
+       kfree(pv);
+       return err;
+}
+
+/* Must be protected by RTNL */
+int br_vlan_delete(struct net_bridge *br, u16 vid)
+{
+       struct net_port_vlans *pv;
+
+       ASSERT_RTNL();
+
+       pv = rtnl_dereference(br->vlan_info);
+       if (!pv)
+               return -EINVAL;
+
+       __vlan_del(pv, vid);
+       return 0;
+}
+
+void br_vlan_flush(struct net_bridge *br)
+{
+       struct net_port_vlans *pv;
+
+       ASSERT_RTNL();
+
+       pv = rtnl_dereference(br->vlan_info);
+       if (!pv)
+               return;
+
+       __vlan_flush(pv);
+}
+
+int br_vlan_filter_toggle(struct net_bridge *br, unsigned long val)
+{
+       if (!rtnl_trylock())
+               return restart_syscall();
+
+       if (br->vlan_enabled == val)
+               goto unlock;
+
+       br->vlan_enabled = val;
+
+unlock:
+       rtnl_unlock();
+       return 0;
+}
+
+/* Must be protected by RTNL */
+int nbp_vlan_add(struct net_bridge_port *port, u16 vid)
+{
+       struct net_port_vlans *pv = NULL;
+       int err;
+
+       ASSERT_RTNL();
+
+       pv = rtnl_dereference(port->vlan_info);
+       if (pv)
+               return __vlan_add(pv, vid);
+
+       /* Create port vlan infomration
+        */
+       pv = kzalloc(sizeof(*pv), GFP_KERNEL);
+       if (!pv) {
+               err = -ENOMEM;
+               goto clean_up;
+       }
+
+       pv->port_idx = port->port_no;
+       pv->parent.port = port;
+       err = __vlan_add(pv, vid);
+       if (err)
+               goto clean_up;
+
+       rcu_assign_pointer(port->vlan_info, pv);
+       return 0;
+
+clean_up:
+       kfree(pv);
+       return err;
+}
+
+/* Must be protected by RTNL */
+int nbp_vlan_delete(struct net_bridge_port *port, u16 vid)
+{
+       struct net_port_vlans *pv;
+
+       ASSERT_RTNL();
+
+       pv = rtnl_dereference(port->vlan_info);
+       if (!pv)
+               return -EINVAL;
+
+       return __vlan_del(pv, vid);
+}
+
+void nbp_vlan_flush(struct net_bridge_port *port)
+{
+       struct net_port_vlans *pv;
+
+       ASSERT_RTNL();
+
+       pv = rtnl_dereference(port->vlan_info);
+       if (!pv)
+               return;
+
+       __vlan_flush(pv);
+}