summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--net/bridge/Kconfig14
-rw-r--r--net/bridge/Makefile2
-rw-r--r--net/bridge/br_if.c1
-rw-r--r--net/bridge/br_private.h59
-rw-r--r--net/bridge/br_sysfs_br.c21
-rw-r--r--net/bridge/br_vlan.c199
6 files changed, 296 insertions, 0 deletions
diff --git a/net/bridge/Kconfig b/net/bridge/Kconfig
index 6dee7bf648a9..aa0d3b2f1bb7 100644
--- a/net/bridge/Kconfig
+++ b/net/bridge/Kconfig
@@ -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.
diff --git a/net/bridge/Makefile b/net/bridge/Makefile
index e859098f5ee9..e85498b2f166 100644
--- a/net/bridge/Makefile
+++ b/net/bridge/Makefile
@@ -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/
diff --git a/net/bridge/br_if.c b/net/bridge/br_if.c
index 2148d474a04f..af9d65ab4001 100644
--- a/net/bridge/br_if.c
+++ b/net/bridge/br_if.c
@@ -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);
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index 06e85d9c05aa..1f3b309beea8 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -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);
diff --git a/net/bridge/br_sysfs_br.c b/net/bridge/br_sysfs_br.c
index 5913a3a0047b..8baa9c08e1a4 100644
--- a/net/bridge/br_sysfs_br.c
+++ b/net/bridge/br_sysfs_br.c
@@ -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,
@@ -732,6 +750,9 @@ static struct attribute *bridge_attrs[] = {
&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
index 000000000000..209464ef5242
--- /dev/null
+++ b/net/bridge/br_vlan.c
@@ -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);
+}