[Bridge] RFC: [PATCH] bridge vlan integration

David Kimdon david.kimdon at devicescape.com
Mon Sep 11 10:49:28 PDT 2006


Hi,

The attached patches enables the bridge to filter and forward packets
according to their IEEE 802.1q headers.  The goals behind this change
include :

- Enable running STP on 802.1q tagged networks.  STP packets
  must be untagged.  It isn't obvious how else to enable STP
  with the current bridge and vlan code.
- Add native support for an untagged vlan.  Currently an untagged
  vlan can be implimented using ebtables or similar.
- On devices bridging a large number of interfaces across various
  vlans this significantly simplifies configuration and, depending on
  configuration, can improve performance.

Comments appreciated,

David

From: Simon Barber <simon at devicescape.com>

Signed-off-by: Simon Barber <simon at devicescape.com>
Signed-off-by: David Kimdon <david.kimdon at devicescape.com>

Index: wireless-dev/include/linux/if_bridge.h
===================================================================
--- wireless-dev.orig/include/linux/if_bridge.h
+++ wireless-dev/include/linux/if_bridge.h
@@ -44,6 +44,10 @@
 #define BRCTL_SET_PORT_PRIORITY 16
 #define BRCTL_SET_PATH_COST 17
 #define BRCTL_GET_FDB_ENTRIES 18
+#define BRCTL_SET_PORT_UNTAGGED_VLAN 19
+#define BRCTL_ADD_PORT_VLAN 20
+#define BRCTL_DEL_PORT_VLAN 21
+#define BRCTL_GET_PORT_VLAN_INFO 22
 
 #define BR_STATE_DISABLED 0
 #define BR_STATE_LISTENING 1
@@ -91,6 +95,12 @@ struct __port_info
 	__u32 hold_timer_value;
 };
 
+struct __vlan_info
+{
+	__u32 untagged;
+	__u8 filter[4096/8];
+};
+
 struct __fdb_entry
 {
 	__u8 mac_addr[6];
Index: wireless-dev/include/linux/skbuff.h
===================================================================
--- wireless-dev.orig/include/linux/skbuff.h
+++ wireless-dev/include/linux/skbuff.h
@@ -296,6 +296,9 @@ struct sk_buff {
 #endif
 	__u32			nfmark;
 #endif /* CONFIG_NETFILTER */
+#ifdef CONFIG_BRIDGE_VLAN
+	unsigned int 		vlan;
+#endif
 #ifdef CONFIG_NET_SCHED
 	__u16			tc_index;	/* traffic control index */
 #ifdef CONFIG_NET_CLS_ACT
Index: wireless-dev/net/bridge/br_forward.c
===================================================================
--- wireless-dev.orig/net/bridge/br_forward.c
+++ wireless-dev/net/bridge/br_forward.c
@@ -24,7 +24,16 @@
 static inline int should_deliver(const struct net_bridge_port *p, 
 				 const struct sk_buff *skb)
 {
-	return (skb->dev != p->dev && p->state == BR_STATE_FORWARDING);
+	if (skb->dev == p->dev ||
+	    p->state != BR_STATE_FORWARDING)
+		return 0;
+
+#ifdef CONFIG_BRIDGE_VLAN
+	if (skb->vlan && br_vlan_filter(skb, &p->vlan))
+		return 0;
+#endif
+
+	return 1;
 }
 
 static inline unsigned packet_length(const struct sk_buff *skb)
@@ -47,6 +56,10 @@ int br_dev_queue_push_xmit(struct sk_buf
 		{
 			skb_push(skb, ETH_HLEN);
 
+	 		if (br_vlan_output_frame(&skb,
+ 						 skb->dev->br_port->vlan.untagged))
+ 				return 0;
+
 			dev_queue_xmit(skb);
 		}
 	}
Index: wireless-dev/net/bridge/br_if.c
===================================================================
--- wireless-dev.orig/net/bridge/br_if.c
+++ wireless-dev/net/bridge/br_if.c
@@ -227,6 +227,7 @@ static struct net_device *new_bridge_dev
 	INIT_LIST_HEAD(&br->age_list);
 
 	br_stp_timer_init(br);
+	br_vlan_init(&br->vlan);
 
 	return dev;
 }
@@ -278,6 +279,7 @@ static struct net_bridge_port *new_nbp(s
 	p->state = BR_STATE_DISABLED;
 	INIT_WORK(&p->carrier_check, port_carrier_check, dev);
 	br_stp_port_timer_init(p);
+	br_vlan_init(&p->vlan);
 
 	kobject_init(&p->kobj);
 	kobject_set_name(&p->kobj, SYSFS_BRIDGE_PORT_ATTR);
Index: wireless-dev/net/bridge/br_input.c
===================================================================
--- wireless-dev.orig/net/bridge/br_input.c
+++ wireless-dev/net/bridge/br_input.c
@@ -26,12 +26,20 @@ static void br_pass_frame_up(struct net_
 {
 	struct net_device *indev;
 
+	if (br_vlan_filter(skb, &br->vlan)) {
+		kfree_skb(skb);
+		return;
+	}
+
 	br->statistics.rx_packets++;
 	br->statistics.rx_bytes += skb->len;
 
 	indev = skb->dev;
 	skb->dev = br->dev;
 
+	if (br_vlan_output_frame(&skb, br->vlan.untagged))
+		return;
+
 	NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, indev, NULL,
 		netif_receive_skb);
 }
@@ -136,6 +144,10 @@ int br_handle_frame(struct net_bridge_po
 	}
 
 	if (p->state == BR_STATE_FORWARDING || p->state == BR_STATE_LEARNING) {
+		if (br_vlan_input_frame(skb, &p->vlan)) {
+			return 1;
+		}
+
 		if (br_should_route_hook) {
 			if (br_should_route_hook(pskb)) 
 				return 0;
Index: wireless-dev/net/bridge/br_ioctl.c
===================================================================
--- wireless-dev.orig/net/bridge/br_ioctl.c
+++ wireless-dev/net/bridge/br_ioctl.c
@@ -302,6 +302,18 @@ static int old_dev_ioctl(struct net_devi
 	case BRCTL_GET_FDB_ENTRIES:
 		return get_fdb_entries(br, (void __user *)args[1], 
 				       args[2], args[3]);
+
+#ifdef CONFIG_BRIDGE_VLAN
+	case BRCTL_SET_PORT_UNTAGGED_VLAN:
+		return br_vlan_set_untagged(br, args[1], args[2]);
+
+	case BRCTL_ADD_PORT_VLAN:
+	case BRCTL_DEL_PORT_VLAN:
+		return br_vlan_set_filter(br, args[0], args[1], args[2]);
+
+	case BRCTL_GET_PORT_VLAN_INFO:
+		return br_vlan_get_info(br, (void *)args[1], args[2]);
+#endif
 	}
 
 	return -EOPNOTSUPP;
Index: wireless-dev/net/bridge/br_private.h
===================================================================
--- wireless-dev.orig/net/bridge/br_private.h
+++ wireless-dev/net/bridge/br_private.h
@@ -59,6 +59,14 @@ struct net_bridge_fdb_entry
 	unsigned char			is_static;
 };
 
+#ifdef CONFIG_BRIDGE_VLAN
+struct net_bridge_port_vlan
+{
+	int				untagged;
+	u8				filter[4096/8];
+};
+#endif
+
 struct net_bridge_port
 {
 	struct net_bridge		*br;
@@ -84,6 +92,9 @@ struct net_bridge_port
 	struct kobject			kobj;
 	struct work_struct		carrier_check;
 	struct rcu_head			rcu;
+#ifdef CONFIG_BRIDGE_VLAN
+	struct net_bridge_port_vlan 	vlan;
+#endif
 };
 
 struct net_bridge
@@ -96,6 +107,9 @@ struct net_bridge
 	struct hlist_head		hash[BR_HASH_SIZE];
 	struct list_head		age_list;
 	unsigned long			feature_mask;
+#ifdef CONFIG_BRIDGE_VLAN
+	struct net_bridge_port_vlan 	vlan;
+#endif
 
 	/* STP */
 	bridge_id			designated_root;
@@ -258,4 +272,32 @@ extern void br_sysfs_delbr(struct net_de
 #define br_sysfs_delbr(dev)	do { } while(0)
 #endif /* CONFIG_SYSFS */
 
+#ifdef CONFIG_BRIDGE_VLAN
+#include <linux/if_vlan.h>
+
+static inline int br_vlan_filter(const struct sk_buff *skb,
+				 const struct net_bridge_port_vlan *vlan)
+{
+	return !(vlan->filter[skb->vlan / 8] & (1 << (skb->vlan & 7)));
+}
+
+/* br_vlan.c */
+extern int br_vlan_input_frame(struct sk_buff *skb,
+			       struct net_bridge_port_vlan *vlan);
+extern int br_vlan_output_frame(struct sk_buff **pskb, unsigned int untagged);
+extern void br_vlan_init(struct net_bridge_port_vlan *vlan);
+extern int br_vlan_set_untagged(struct net_bridge *br,
+				unsigned int port, unsigned int vid);
+extern int br_vlan_set_filter(struct net_bridge *br,
+			      unsigned int cmd,
+			      unsigned int port, unsigned int vid);
+extern int br_vlan_get_info(struct net_bridge *br,
+			    void *user_mem, unsigned long port);
+#else
+
+#define br_vlan_filter(skb, vlan)		(0)
+#define br_vlan_input_frame(skb, vlan)		(0)
+#define br_vlan_output_frame(pskb, untagged)	(0)
+#define br_vlan_init(vlan)			do { } while(0)
+#endif /* CONFIG_BRIDGE_VLAN */
 #endif
Index: wireless-dev/net/bridge/br_vlan.c
===================================================================
--- /dev/null
+++ wireless-dev/net/bridge/br_vlan.c
@@ -0,0 +1,203 @@
+/*
+ *	VLAN support
+ *	Linux ethernet bridge
+ *
+ *	Authors:
+ *	Simon Barber			<simon at devicescape.com>
+ *
+ *	This program is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU General Public License
+ *	as published by the Free Software Foundation; either version
+ *	2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/inetdevice.h>
+#include <linux/skbuff.h>
+#include <linux/if_bridge.h>
+#include <linux/netfilter_bridge.h>
+#include <asm/uaccess.h>
+#include "br_private.h"
+
+int br_vlan_input_frame(struct sk_buff *skb, struct net_bridge_port_vlan *vlan)
+{
+	if (skb->protocol != htons(ETH_P_8021Q)) {
+		skb->vlan = vlan->untagged;
+	} else {
+		struct vlan_ethhdr *vhdr = (struct vlan_ethhdr *)(skb->mac.raw);
+		unsigned short vlan_TCI = ntohs(vhdr->h_vlan_TCI);
+		unsigned short vid = (vlan_TCI & VLAN_VID_MASK);
+
+		skb->vlan = vid ? vid : vlan->untagged;
+	}
+
+	if (skb->vlan == 0)
+		goto err;
+
+	if (br_vlan_filter(skb, vlan))
+		goto err;
+
+	return 0;
+
+err:
+	kfree_skb(skb);
+	return 1;
+}
+
+int br_vlan_output_frame(struct sk_buff **pskb, unsigned int untagged)
+{
+	struct sk_buff *skb = *pskb;
+
+	if (skb->vlan == 0)	/* don't touch the frame */
+		return 0;
+
+	if (skb->vlan == untagged) {
+		/* frame should be untagged */
+		if (eth_hdr(skb)->h_proto == htons(ETH_P_8021Q)) {
+			/* remove VLAN tag */
+			if (skb_cloned(skb) || skb_shared(skb)) {
+				struct sk_buff *new_skb;
+
+				new_skb = skb_copy(skb, GFP_ATOMIC);
+				kfree_skb(skb);
+				if (!new_skb)
+					return 1;
+				*pskb = skb = new_skb;
+			}
+
+			skb->mac.raw += VLAN_HLEN;
+			memmove(skb->mac.raw, skb->data, ETH_ALEN * 2);
+			skb_pull(skb, VLAN_HLEN);
+		}
+	} else {
+		/* frame should be tagged */
+		if (eth_hdr(skb)->h_proto != htons(ETH_P_8021Q)) {
+			/* add VLAN tag */
+			struct vlan_ethhdr *vhdr;
+			if (skb_cloned(skb) || skb_shared(skb)) {
+				struct sk_buff *new_skb;
+
+				new_skb = skb_copy(skb, GFP_ATOMIC);
+				kfree_skb(skb);
+				if (!new_skb)
+					return 1;
+				*pskb = skb = new_skb;
+			}
+
+			if (skb_headroom(skb) < VLAN_HLEN) {
+				if (pskb_expand_head(skb, VLAN_HLEN, 0,
+						     GFP_ATOMIC)) {
+					kfree_skb(skb);
+					return 1;
+				}
+			}
+
+			skb_push(skb, VLAN_HLEN);
+
+			skb->mac.raw -= VLAN_HLEN;
+			memmove(skb->mac.raw, skb->mac.raw + VLAN_HLEN,
+				ETH_ALEN * 2);
+			vhdr = (struct vlan_ethhdr *)skb->mac.raw;
+			vhdr->h_vlan_proto = htons(ETH_P_8021Q);
+			vhdr->h_vlan_TCI = htons(skb->vlan);
+		} else {
+			/* ensure VID is correct */
+			struct vlan_ethhdr *vhdr =
+				(struct vlan_ethhdr *)skb->mac.raw;
+			vhdr->h_vlan_TCI = (vhdr->h_vlan_TCI & htons(~VLAN_VID_MASK)) |
+					    htons(skb->vlan);
+		}
+		// TODO: set priority in tag correctly
+	}
+
+	return 0;
+}
+
+void br_vlan_init(struct net_bridge_port_vlan *vlan)
+{
+	vlan->untagged = 1;
+	vlan->filter[0] = 1 << 1;
+}
+
+/* ioctl functions */
+int br_vlan_set_untagged(struct net_bridge *br,
+			 unsigned int port, unsigned int vid)
+{
+	struct net_bridge_port_vlan *vlan;
+
+	if (vid > 4094)
+		return -EINVAL;
+
+	if (port) {
+		struct net_bridge_port *p = br_get_port(br, port);
+
+		if (p == NULL)
+			return -EINVAL;
+		vlan = &p->vlan;
+	} else {
+		vlan = &br->vlan;
+	}
+
+	vlan->untagged = vid;
+
+	return 0;
+}
+
+int br_vlan_set_filter(struct net_bridge *br,
+		       unsigned int cmd, unsigned int port, unsigned int vid)
+{
+	struct net_bridge_port_vlan *vlan;
+	int add = (cmd == BRCTL_ADD_PORT_VLAN);
+
+	if (vid > 4094)
+		return -EINVAL;
+
+	if (port) {
+		struct net_bridge_port *p = br_get_port(br, port);
+
+		if (p == NULL)
+			return -EINVAL;
+		vlan = &p->vlan;
+	} else {
+		vlan = &br->vlan;
+	}
+
+	if (vid == 0) {
+		/* special case - add/del for all vlans */
+		memset(vlan->filter, add ? 255 : 0, 4096 / 8);
+		if (add) {
+			vlan->filter[4095 / 8] &= ~(1 << (4095 & 7));
+		}
+	} else if (add)
+		vlan->filter[vid / 8] |= 1 << (vid & 7);
+	else
+		vlan->filter[vid / 8] &= ~(1 << (vid & 7));
+
+	return 0;
+}
+
+int br_vlan_get_info(struct net_bridge *br, void *user_mem, unsigned long port)
+{
+	struct net_bridge_port_vlan *vlan;
+	struct __vlan_info v;
+
+	if (port) {
+		struct net_bridge_port *p = br_get_port(br, port);
+
+		if (p == NULL)
+			return -EINVAL;
+		vlan = &p->vlan;
+	} else {
+		vlan = &br->vlan;
+	}
+
+	memset(&v, 0, sizeof(v));
+	v.untagged = vlan->untagged;
+	memcpy(v.filter, vlan->filter, 4096 / 8);
+
+	if (copy_to_user((void __user *)user_mem, &v, sizeof(v)))
+		return -EFAULT;
+
+	return 0;
+}
Index: wireless-dev/net/bridge/Makefile
===================================================================
--- wireless-dev.orig/net/bridge/Makefile
+++ wireless-dev/net/bridge/Makefile
@@ -12,4 +12,6 @@ bridge-$(CONFIG_SYSFS) += br_sysfs_if.o 
 
 bridge-$(CONFIG_BRIDGE_NETFILTER) += br_netfilter.o
 
+bridge-$(CONFIG_BRIDGE_VLAN) += br_vlan.o
+
 obj-$(CONFIG_BRIDGE_NF_EBTABLES) += netfilter/
Index: wireless-dev/net/core/skbuff.c
===================================================================
--- wireless-dev.orig/net/core/skbuff.c
+++ wireless-dev/net/core/skbuff.c
@@ -486,6 +486,9 @@ struct sk_buff *skb_clone(struct sk_buff
 	nf_bridge_get(skb->nf_bridge);
 #endif
 #endif /*CONFIG_NETFILTER*/
+#ifdef CONFIG_BRIDGE_VLAN
+	C(vlan);
+#endif
 #ifdef CONFIG_NET_SCHED
 	C(tc_index);
 #ifdef CONFIG_NET_CLS_ACT
@@ -550,6 +553,9 @@ static void copy_skb_header(struct sk_bu
 	nf_bridge_get(old->nf_bridge);
 #endif
 #endif
+#ifdef CONFIG_BRIDGE_VLAN
+	new->vlan	= old->vlan;
+#endif
 #ifdef CONFIG_NET_SCHED
 #ifdef CONFIG_NET_CLS_ACT
 	new->tc_verd = old->tc_verd;
Index: wireless-dev/net/bridge/Kconfig
===================================================================
--- wireless-dev.orig/net/bridge/Kconfig
+++ wireless-dev/net/bridge/Kconfig
@@ -30,3 +30,13 @@ config BRIDGE
 	  will be called bridge.
 
 	  If unsure, say N.
+
+config BRIDGE_VLAN
+	bool "802.1Q bridge support"
+	depends on BRIDGE
+	---help---
+	  If you say Y here, then your bridge will be able to filter and
+	  forward packets according to their IEEE 802.1Q headers.
+
+	  If unsure, say N.
+
Index: wireless-dev/net/bridge/br_device.c
===================================================================
--- wireless-dev.orig/net/bridge/br_device.c
+++ wireless-dev/net/bridge/br_device.c
@@ -34,6 +34,9 @@ int br_dev_xmit(struct sk_buff *skb, str
 	const unsigned char *dest = skb->data;
 	struct net_bridge_fdb_entry *dst;
 
+	if (br_vlan_input_frame(skb, &br->vlan))
+		return 0;
+
 	br->statistics.tx_packets++;
 	br->statistics.tx_bytes += skb->len;
 



More information about the Bridge mailing list