Skip to content

Commit

Permalink
bridge: Add vlan filtering infrastructure
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Vlad Yasevich authored and David S. Miller committed Feb 14, 2013
1 parent 2222299 commit 243a2e6
Show file tree
Hide file tree
Showing 6 changed files with 296 additions and 0 deletions.
14 changes: 14 additions & 0 deletions net/bridge/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 2 additions & 0 deletions net/bridge/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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/
1 change: 1 addition & 0 deletions net/bridge/br_if.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
59 changes: 59 additions & 0 deletions net/bridge/br_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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"

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down
21 changes: 21 additions & 0 deletions net/bridge/br_sysfs_br.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
};
Expand Down
199 changes: 199 additions & 0 deletions net/bridge/br_vlan.c
Original file line number Diff line number Diff line change
@@ -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);
}

0 comments on commit 243a2e6

Please sign in to comment.