Patchwork [10/11] bridge: Implement untagged vlan handling

login
register
mail settings
Submitter Vlad Yasevich
Date Dec. 12, 2012, 8:01 p.m.
Message ID <1355342477-4971-11-git-send-email-vyasevic@redhat.com>
Download mbox | patch
Permalink /patch/205646/
State Deferred
Delegated to: David Miller
Headers show

Comments

Vlad Yasevich - Dec. 12, 2012, 8:01 p.m.
When an untagged frame arrives on a port that has untagged vlan set,
the frame is assigned to the untagged VLAN.  It will then procede
through the forwarding/egress process with the VLAN id set to the
untagged VLAN.

At egress, a frame with a VLAN id matching untagged vid will have its
vlan header stripped off.

Signed-off-by: Vlad Yasevich <vyasevic@redhat.com>
---
 net/bridge/br_device.c    |   25 +++++++++--
 net/bridge/br_forward.c   |  111 ++++++++++++++++++++++++++++++++++++++++++++-
 net/bridge/br_input.c     |   46 +++++++++++++++----
 net/bridge/br_multicast.c |   37 +++++++++------
 net/bridge/br_private.h   |   20 +++++---
 5 files changed, 202 insertions(+), 37 deletions(-)

Patch

diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
index 1f9d0f9..e96d58a 100644
--- a/net/bridge/br_device.c
+++ b/net/bridge/br_device.c
@@ -31,7 +31,8 @@  netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
 	struct net_bridge_mdb_entry *mdst;
 	struct br_cpu_netstats *brstats = this_cpu_ptr(br->stats);
 	struct net_bridge_vlan *vlan;
-	u16 vid;
+	struct br_input_skb_cb *brcb;
+	u16 vid = 0;
 
 	rcu_read_lock();
 #ifdef CONFIG_BRIDGE_NETFILTER
@@ -47,15 +48,31 @@  netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
 	brstats->tx_bytes += skb->len;
 	u64_stats_update_end(&brstats->syncp);
 
-	BR_INPUT_SKB_CB(skb)->brdev = dev;
+	brcb = BR_INPUT_SKB_CB(skb);
+	memset(brcb, 0, sizeof(struct br_input_skb_cb));
+	brcb->brdev = dev;
+
+	if (br_get_vlan(skb, &vid)) {
+		u16 untagged_vid;
+
+		/* Untagged frame.  See if there is an untagged VLAN
+		 * configured.
+		 */
+		if ((vlan = rcu_dereference(br->untagged)) != NULL &&
+	    	    (untagged_vid = vlan->vid) != BR_INVALID_VID) {
+			__vlan_hwaccel_put_tag(skb, untagged_vid);
+			brcb->untagged = 1;
+			goto skip_lookup;
+		}
+	}
 
 	/* Any vlan transmitted by the bridge itself is permitted.
 	 * Try to cache the vlan in the CB to speed up forwarding.
 	 */
-	vid = br_get_vlan(skb);
 	vlan = br_vlan_find(br, vid);
+skip_lookup:
 	if (vlan)
-		BR_INPUT_SKB_CB(skb)->vlan = vlan;
+		brcb->vlan = vlan;
 
 	skb_reset_mac_header(skb);
 	skb_pull(skb, ETH_HLEN);
diff --git a/net/bridge/br_forward.c b/net/bridge/br_forward.c
index 4ae5f55..2170e6b 100644
--- a/net/bridge/br_forward.c
+++ b/net/bridge/br_forward.c
@@ -31,7 +31,7 @@  static inline bool br_allowed_egress(const struct net_bridge_port *p,
 {
 	struct net_port_vlan *pve;
 	struct net_bridge_vlan *vlan = NULL;
-	u16 vid;
+	u16 vid = 0;
 
 	if (list_empty(&p->vlan_list))
 		return true;
@@ -49,7 +49,7 @@  static inline bool br_allowed_egress(const struct net_bridge_port *p,
 	/* We don't have cached vlan information, so we need to do
 	 * it the hard way.
 	 */
-	vid = br_get_vlan(skb);
+	br_get_vlan(skb, &vid);
 	pve = nbp_vlan_find(p, vid);
 	if (pve)
 		return true;
@@ -57,6 +57,105 @@  static inline bool br_allowed_egress(const struct net_bridge_port *p,
 	return false;
 }
 
+/* Almost an exact copy of vlan_untag.  Needed here since it's not exported
+ * and not available if vlan support isn't built in.
+ */
+static struct sk_buff *br_vlan_untag(struct sk_buff *skb)
+{
+	struct vlan_hdr *vhdr;
+
+	if (skb->protocol != htons(ETH_P_8021Q)) {
+		skb->vlan_tci = 0;
+		return skb;
+	}
+
+	skb = skb_share_check(skb, GFP_ATOMIC);
+	if (unlikely(!skb))
+		goto err_free;
+
+	if (unlikely(!pskb_may_pull(skb, VLAN_HLEN)))
+		goto err_free;
+
+	vhdr = (struct vlan_hdr *)skb->data;
+	skb_pull_rcsum(skb, VLAN_HLEN);
+	vlan_set_encap_proto(skb, vhdr);
+
+	if (skb_cow(skb, skb_headroom(skb)) < 0)
+		goto err;
+
+	memmove(skb->data - ETH_HLEN, skb->data - VLAN_ETH_HLEN, 2 * ETH_ALEN);
+	skb->mac_header += VLAN_HLEN;
+	skb->vlan_tci = 0;
+	
+	skb_reset_network_header(skb);
+	skb_reset_transport_header(skb);
+	skb_reset_mac_len(skb);
+
+	return skb;
+
+err:
+	kfree_skb(skb);
+	return NULL;
+}
+			
+struct sk_buff *br_handle_vlan(const struct net_bridge *br,
+			      const struct net_bridge_port *p,
+			      struct sk_buff *skb)
+{
+	struct net_bridge_vlan *untag_vlan;
+	struct net_bridge_vlan *skb_vlan = BR_INPUT_SKB_CB(skb)->vlan;
+
+	if (p)
+		untag_vlan = rcu_dereference(p->untagged);
+	else if (br)
+		untag_vlan = rcu_dereference(br->untagged);
+	else {
+		kfree_skb(skb);
+		goto out;
+	}
+
+	if (BR_INPUT_SKB_CB(skb)->untagged) {
+		/* Frame arrived on an untagged vlan. If it is leaving
+		 * on untagged interface, remove the tag we added.
+		 */
+		if (skb_vlan && untag_vlan == skb_vlan) {
+			skb->vlan_tci = 0;
+			goto out;
+		}
+
+		/* Frame leaving on a tagged vlan.  If output device is the
+		 * bridge, we need to add the VLAN header.  If we sending to
+		 * port, we let dev_hard_start_xmit() add the header.
+		 */
+		if (br && !p) {
+			/* vlan_put_tag expects skb->data to point to mac
+			 * header.
+			 */
+			skb_push(skb, ETH_HLEN);
+			skb = __vlan_put_tag(skb, skb->vlan_tci);
+			if (!skb)
+				goto out;
+			/* put skb->data back to where it was */
+			skb_pull(skb, ETH_HLEN);
+			skb->vlan_tci = 0;
+		}
+	} else {
+		/* Frame arrived on a tagged vlan.  If the outgoing port is
+		 * not untagged, leave the frame alone. If it's leaving on
+		 * a valid untagged interface, need to strip the VLAN hader
+		 */
+		if (!untag_vlan ||
+		    (untag_vlan && untag_vlan->vid == BR_INVALID_VID))
+			goto out;
+
+		skb->vlan_tci = 0;
+		skb = br_vlan_untag(skb);
+	}
+
+out:
+	return skb;
+}
+
 /* Don't forward packets to originating port or forwarding diasabled */
 static inline int should_deliver(const struct net_bridge_port *p,
 				 const struct sk_buff *skb)
@@ -97,6 +196,10 @@  static void __br_deliver(const struct net_bridge_port *to, struct sk_buff *skb)
 {
 	skb->dev = to->dev;
 
+	skb = br_handle_vlan(NULL, to, skb);
+	if (!skb)
+		return;
+
 	if (unlikely(netpoll_tx_running(to->br->dev))) {
 		if (packet_length(skb) > skb->dev->mtu && !skb_is_gso(skb))
 			kfree_skb(skb);
@@ -120,6 +223,10 @@  static void __br_forward(const struct net_bridge_port *to, struct sk_buff *skb)
 		return;
 	}
 
+	skb = br_handle_vlan(NULL, to, skb);
+	if (!skb)
+		return;
+
 	indev = skb->dev;
 	skb->dev = to->dev;
 	skb_forward_csum(skb);
diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
index e51eb24..048266f 100644
--- a/net/bridge/br_input.c
+++ b/net/bridge/br_input.c
@@ -35,6 +35,10 @@  static int br_pass_frame_up(struct sk_buff *skb)
 	brstats->rx_bytes += skb->len;
 	u64_stats_update_end(&brstats->syncp);
 
+	skb = br_handle_vlan(br, NULL, skb);
+	if (!skb)
+		return NET_RX_DROP;
+
 	indev = skb->dev;
 	skb->dev = brdev;
 
@@ -43,11 +47,11 @@  static int br_pass_frame_up(struct sk_buff *skb)
 }
 
 static bool br_allowed_ingress(struct net_bridge_port *p, struct sk_buff *skb,
-			       u16 vid)
+			       u16 *vid)
 {
 	struct net_port_vlan *pve;
-
-	BR_INPUT_SKB_CB(skb)->vlan = NULL;
+	struct net_bridge_vlan *vlan;
+	struct br_input_skb_cb *brcb = BR_INPUT_SKB_CB(skb);
 
 	/* If there are no vlan in the permitted list, all packets are
 	 * permitted.
@@ -55,7 +59,27 @@  static bool br_allowed_ingress(struct net_bridge_port *p, struct sk_buff *skb,
 	if (list_empty(&p->vlan_list))
 		return true;
 
-	pve = nbp_vlan_find(p, vid);
+	if (br_get_vlan(skb, vid)) {
+		u16 untagged_vid;
+		/* Frame did not have a tag. See if untagged vlan is set
+		 * on this port.
+		 */
+		if ((vlan = rcu_dereference(p->untagged)) == NULL || 
+	    	    (untagged_vid = vlan->vid) == BR_INVALID_VID)
+			return false;
+
+		/* Untagged vlan is set on this port.  Any untagged ingress
+		 * frame is considered to belong to the untagged vlan, so
+		 * mark it as such.
+		 */
+		__vlan_hwaccel_put_tag(skb, untagged_vid);
+		brcb->vlan = vlan;
+		brcb->untagged = 1;
+		return true;
+	}
+
+	/* Frame has a valid vlan tag.  Find the VLAN it belongs to. */
+	pve = nbp_vlan_find(p, *vid);
 	if (pve) {
 		BR_INPUT_SKB_CB(skb)->vlan = pve->vlan;
 		return true;
@@ -73,13 +97,15 @@  int br_handle_frame_finish(struct sk_buff *skb)
 	struct net_bridge_fdb_entry *dst;
 	struct net_bridge_mdb_entry *mdst;
 	struct sk_buff *skb2;
-	u16 vid;
+	struct br_input_skb_cb *brcb = BR_INPUT_SKB_CB(skb);
+	u16 vid = 0;
 
 	if (!p || p->state == BR_STATE_DISABLED)
 		goto drop;
 
-	vid = br_get_vlan(skb);
-	if (!br_allowed_ingress(p, skb, vid))
+	memset(brcb, 0, sizeof(struct br_input_skb_cb));
+
+	if (!br_allowed_ingress(p, skb, &vid))
 		goto drop;
 
 	/* insert into forwarding database after filtering to avoid spoofing */
@@ -93,7 +119,7 @@  int br_handle_frame_finish(struct sk_buff *skb)
 	if (p->state == BR_STATE_LEARNING)
 		goto drop;
 
-	BR_INPUT_SKB_CB(skb)->brdev = br->dev;
+	brcb->brdev = br->dev;
 
 	/* The packet skb2 goes to the local host (NULL to skip). */
 	skb2 = NULL;
@@ -148,8 +174,10 @@  drop:
 static int br_handle_local_finish(struct sk_buff *skb)
 {
 	struct net_bridge_port *p = br_port_get_rcu(skb->dev);
+	u16 vid = 0;
 
-	br_fdb_update(p->br, p, eth_hdr(skb)->h_source, br_get_vlan(skb));
+	br_get_vlan(skb, &vid);
+	br_fdb_update(p->br, p, eth_hdr(skb)->h_source, vid);
 	return 0;	 /* process further */
 }
 
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index 072aa2d..a7fc2c7 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -905,6 +905,7 @@  static int br_ip4_multicast_igmp3_report(struct net_bridge *br,
 	int type;
 	int err = 0;
 	__be32 group;
+	u16 vid = 0;
 
 	if (!pskb_may_pull(skb, sizeof(*ih)))
 		return -EINVAL;
@@ -940,8 +941,8 @@  static int br_ip4_multicast_igmp3_report(struct net_bridge *br,
 			continue;
 		}
 
-		err = br_ip4_multicast_add_group(br, port, group,
-						 br_get_vlan(skb));
+		br_get_vlan(skb, &vid);
+		err = br_ip4_multicast_add_group(br, port, group, vid);
 		if (err)
 			break;
 	}
@@ -960,6 +961,7 @@  static int br_ip6_multicast_mld2_report(struct net_bridge *br,
 	int len;
 	int num;
 	int err = 0;
+	u16 vid = 0;
 
 	if (!pskb_may_pull(skb, sizeof(*icmp6h)))
 		return -EINVAL;
@@ -1001,8 +1003,9 @@  static int br_ip6_multicast_mld2_report(struct net_bridge *br,
 			continue;
 		}
 
+		br_get_vlan(skb, &vid);
 		err = br_ip6_multicast_add_group(br, port, &grec->grec_mca,
-						 br_get_vlan(skb));
+						 vid);
 		if (!err)
 			break;
 	}
@@ -1086,6 +1089,7 @@  static int br_ip4_multicast_query(struct net_bridge *br,
 	unsigned long now = jiffies;
 	__be32 group;
 	int err = 0;
+	u16 vid = 0;
 
 	spin_lock(&br->multicast_lock);
 	if (!netif_running(br->dev) ||
@@ -1120,8 +1124,8 @@  static int br_ip4_multicast_query(struct net_bridge *br,
 	if (!group)
 		goto out;
 
-	mp = br_mdb_ip4_get(mlock_dereference(br->mdb, br), group,
-			    br_get_vlan(skb));
+	br_get_vlan(skb, &vid);
+	mp = br_mdb_ip4_get(mlock_dereference(br->mdb, br), group, vid);
 	if (!mp)
 		goto out;
 
@@ -1162,6 +1166,7 @@  static int br_ip6_multicast_query(struct net_bridge *br,
 	unsigned long now = jiffies;
 	const struct in6_addr *group = NULL;
 	int err = 0;
+	u16 vid = 0;
 
 	spin_lock(&br->multicast_lock);
 	if (!netif_running(br->dev) ||
@@ -1193,8 +1198,8 @@  static int br_ip6_multicast_query(struct net_bridge *br,
 	if (!group)
 		goto out;
 
-	mp = br_mdb_ip6_get(mlock_dereference(br->mdb, br), group,
-			    br_get_vlan(skb));
+	br_get_vlan(skb, &vid);
+	mp = br_mdb_ip6_get(mlock_dereference(br->mdb, br), group, vid);
 	if (!mp)
 		goto out;
 
@@ -1343,6 +1348,7 @@  static int br_multicast_ipv4_rcv(struct net_bridge *br,
 	unsigned int len;
 	unsigned int offset;
 	int err;
+	u16 vid = 0;
 
 	/* We treat OOM as packet loss for now. */
 	if (!pskb_may_pull(skb, sizeof(*iph)))
@@ -1410,8 +1416,8 @@  static int br_multicast_ipv4_rcv(struct net_bridge *br,
 	case IGMP_HOST_MEMBERSHIP_REPORT:
 	case IGMPV2_HOST_MEMBERSHIP_REPORT:
 		BR_INPUT_SKB_CB(skb)->mrouters_only = 1;
-		err = br_ip4_multicast_add_group(br, port, ih->group,
-						 br_get_vlan(skb2));
+		vid = br_get_vlan(skb2, &vid);
+		err = br_ip4_multicast_add_group(br, port, ih->group, vid);
 		break;
 	case IGMPV3_HOST_MEMBERSHIP_REPORT:
 		err = br_ip4_multicast_igmp3_report(br, port, skb2);
@@ -1420,8 +1426,8 @@  static int br_multicast_ipv4_rcv(struct net_bridge *br,
 		err = br_ip4_multicast_query(br, port, skb2);
 		break;
 	case IGMP_HOST_LEAVE_MESSAGE:
-		br_ip4_multicast_leave_group(br, port, ih->group,
-					     br_get_vlan(skb2));
+		vid = br_get_vlan(skb2, &vid);
+		br_ip4_multicast_leave_group(br, port, ih->group, vid);
 		break;
 	}
 
@@ -1446,6 +1452,7 @@  static int br_multicast_ipv6_rcv(struct net_bridge *br,
 	unsigned int len;
 	int offset;
 	int err;
+	u16 vid = 0;
 
 	if (!pskb_may_pull(skb, sizeof(*ip6h)))
 		return -EINVAL;
@@ -1541,8 +1548,8 @@  static int br_multicast_ipv6_rcv(struct net_bridge *br,
 		}
 		mld = (struct mld_msg *)skb_transport_header(skb2);
 		BR_INPUT_SKB_CB(skb)->mrouters_only = 1;
-		err = br_ip6_multicast_add_group(br, port, &mld->mld_mca,
-						 br_get_vlan(skb2));
+		br_get_vlan(skb2, &vid);
+		err = br_ip6_multicast_add_group(br, port, &mld->mld_mca, vid);
 		break;
 	    }
 	case ICMPV6_MLD2_REPORT:
@@ -1559,8 +1566,8 @@  static int br_multicast_ipv6_rcv(struct net_bridge *br,
 			goto out;
 		}
 		mld = (struct mld_msg *)skb_transport_header(skb2);
-		br_ip6_multicast_leave_group(br, port, &mld->mld_mca,
-					     br_get_vlan(skb2));
+		br_get_vlan(skb2, &vid);
+		br_ip6_multicast_leave_group(br, port, &mld->mld_mca, vid);
 	    }
 	}
 
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index f82d5f6..62fdf7e 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -198,18 +198,20 @@  static inline struct net_bridge_port *br_port_get_rtnl(struct net_device *dev)
 		rtnl_dereference(dev->rx_handler_data) : NULL;
 }
 
-static inline u16 br_get_vlan(const struct sk_buff *skb)
+static inline int br_get_vlan(const struct sk_buff *skb, u16 *tag)
 {
-	u16 tag;
+	int rc = 0;
 
-	if (vlan_tx_tag_present(skb))
-		return vlan_tx_tag_get(skb) & VLAN_VID_MASK;
+	if (vlan_tx_tag_present(skb)) {
+		*tag = vlan_tx_tag_get(skb) & VLAN_VID_MASK;
+		return rc;
+	}
 
 	/* Untagged and VLAN 0 traffic is handled the same way */
-	if (vlan_get_tag(skb, &tag))
-		return 0;
+	rc = vlan_get_tag(skb, tag);
+	*tag = *tag & VLAN_VID_MASK;
 
-	return tag & VLAN_VID_MASK;
+	return rc;
 }
 
 struct br_cpu_netstats {
@@ -305,6 +307,7 @@  struct net_bridge
 struct br_input_skb_cb {
 	struct net_device *brdev;
 	struct net_bridge_vlan *vlan;
+	int untagged;
 #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
 	int igmp;
 	int mrouters_only;
@@ -431,6 +434,9 @@  extern int br_forward_finish(struct sk_buff *skb);
 extern void br_flood_deliver(struct net_bridge *br, struct sk_buff *skb);
 extern void br_flood_forward(struct net_bridge *br, struct sk_buff *skb,
 			     struct sk_buff *skb2);
+extern struct sk_buff *br_handle_vlan(const struct net_bridge *br,
+				      const struct net_bridge_port *p,
+				      struct sk_buff *skb);
 
 /* br_if.c */
 extern void br_port_carrier_check(struct net_bridge_port *p);