From 22dcc3c17b50e9fcdc1a28270892bfcf89eb5037 Mon Sep 17 00:00:00 2001
From: Andrew Thompson <thompsa@FreeBSD.org>
Date: Wed, 13 Jun 2007 18:58:04 +0000
Subject: [PATCH] Add the vlan tag to the bridge route table. This allows a
 vlan trunk to be bridged, previously legitimate traffic was not passed as the
 bridge could not tell that it was on a different Ethernet segment.

All non-tagged traffic is treated as vlan1 as per IEEE 802.1Q-2003
---
 sbin/ifconfig/ifbridge.c |  5 ++-
 sys/net/if_bridge.c      | 79 +++++++++++++++++++++++++++-------------
 sys/net/if_bridgevar.h   |  1 +
 3 files changed, 57 insertions(+), 28 deletions(-)

diff --git a/sbin/ifconfig/ifbridge.c b/sbin/ifconfig/ifbridge.c
index 4fa617557828..72fd491770a8 100644
--- a/sbin/ifconfig/ifbridge.c
+++ b/sbin/ifconfig/ifbridge.c
@@ -236,8 +236,8 @@ bridge_addresses(int s, const char *prefix)
 		ifba = ifbac.ifbac_req + i;
 		memcpy(ea.octet, ifba->ifba_dst,
 		    sizeof(ea.octet));
-		printf("%s%s %s %lu ", prefix, ether_ntoa(&ea),
-		    ifba->ifba_ifsname, ifba->ifba_expire);
+		printf("%s%s Vlan%d %s %lu ", prefix, ether_ntoa(&ea),
+		    ifba->ifba_vlan, ifba->ifba_ifsname, ifba->ifba_expire);
 		printb("flags", ifba->ifba_flags, IFBAFBITS);
 		printf("\n");
 	}
@@ -474,6 +474,7 @@ setbridge_static(const char *val, const char *mac, int s,
 
 	memcpy(req.ifba_dst, ea->octet, sizeof(req.ifba_dst));
 	req.ifba_flags = IFBAF_STATIC;
+	req.ifba_vlan = 1; /* XXX allow user to specify */
 
 	if (do_cmd(s, BRDGSADDR, &req, sizeof(req), 1) < 0)
 		err(1, "BRDGSADDR %s",  val);
diff --git a/sys/net/if_bridge.c b/sys/net/if_bridge.c
index de92540e5c61..c5bc0b51fbf8 100644
--- a/sys/net/if_bridge.c
+++ b/sys/net/if_bridge.c
@@ -131,6 +131,7 @@ __FBSDID("$FreeBSD$");
 #include <net/bridgestp.h>
 #include <net/if_bridgevar.h>
 #include <net/if_llc.h>
+#include <net/if_vlan_var.h>
 
 #include <net/route.h>
 #include <netinet/ip_fw.h>
@@ -192,6 +193,7 @@ struct bridge_rtnode {
 	unsigned long		brt_expire;	/* expiration time */
 	uint8_t			brt_flags;	/* address flags */
 	uint8_t			brt_addr[ETHER_ADDR_LEN];
+	uint16_t		brt_vlan;	/* vlan id */
 };
 
 /*
@@ -250,19 +252,21 @@ static void	bridge_broadcast(struct bridge_softc *, struct ifnet *,
 static void	bridge_span(struct bridge_softc *, struct mbuf *);
 
 static int	bridge_rtupdate(struct bridge_softc *, const uint8_t *,
-		    struct bridge_iflist *, int, uint8_t);
-static struct ifnet *bridge_rtlookup(struct bridge_softc *, const uint8_t *);
+		    uint16_t, struct bridge_iflist *, int, uint8_t);
+static struct ifnet *bridge_rtlookup(struct bridge_softc *, const uint8_t *,
+		    uint16_t);
 static void	bridge_rttrim(struct bridge_softc *);
 static void	bridge_rtage(struct bridge_softc *);
 static void	bridge_rtflush(struct bridge_softc *, int);
-static int	bridge_rtdaddr(struct bridge_softc *, const uint8_t *);
+static int	bridge_rtdaddr(struct bridge_softc *, const uint8_t *,
+		    uint16_t);
 
 static int	bridge_rtable_init(struct bridge_softc *);
 static void	bridge_rtable_fini(struct bridge_softc *);
 
 static int	bridge_rtnode_addr_cmp(const uint8_t *, const uint8_t *);
 static struct bridge_rtnode *bridge_rtnode_lookup(struct bridge_softc *,
-		    const uint8_t *);
+		    const uint8_t *, uint16_t);
 static int	bridge_rtnode_insert(struct bridge_softc *,
 		    struct bridge_rtnode *);
 static void	bridge_rtnode_destroy(struct bridge_softc *,
@@ -318,6 +322,10 @@ static int	bridge_ip6_checkbasic(struct mbuf **mp);
 static int	bridge_fragment(struct ifnet *, struct mbuf *,
 		    struct ether_header *, int, struct llc *);
 
+/* The default bridge vlan is 1 (IEEE 802.1Q-2003 Table 9-2) */
+#define	VLANTAGOF(_m)	\
+    (_m->m_flags & M_VLANTAG) ? EVL_VLANOFTAG(_m->m_pkthdr.ether_vtag) : 1
+
 static struct bstp_cb_ops bridge_ops = {
 	.bcb_state = bridge_state_change,
 	.bcb_rtage = bridge_rtable_expire
@@ -1168,6 +1176,7 @@ bridge_ioctl_rts(struct bridge_softc *sc, void *arg)
 		strlcpy(bareq.ifba_ifsname, brt->brt_ifp->if_xname,
 		    sizeof(bareq.ifba_ifsname));
 		memcpy(bareq.ifba_dst, brt->brt_addr, sizeof(brt->brt_addr));
+		bareq.ifba_vlan = brt->brt_vlan;
 		if ((brt->brt_flags & IFBAF_TYPEMASK) == IFBAF_DYNAMIC &&
 				time_uptime < brt->brt_expire)
 			bareq.ifba_expire = brt->brt_expire - time_uptime;
@@ -1197,7 +1206,7 @@ bridge_ioctl_saddr(struct bridge_softc *sc, void *arg)
 	if (bif == NULL)
 		return (ENOENT);
 
-	error = bridge_rtupdate(sc, req->ifba_dst, bif, 1,
+	error = bridge_rtupdate(sc, req->ifba_dst, req->ifba_vlan, bif, 1,
 	    req->ifba_flags);
 
 	return (error);
@@ -1226,7 +1235,7 @@ bridge_ioctl_daddr(struct bridge_softc *sc, void *arg)
 {
 	struct ifbareq *req = arg;
 
-	return (bridge_rtdaddr(sc, req->ifba_dst));
+	return (bridge_rtdaddr(sc, req->ifba_dst, req->ifba_vlan));
 }
 
 static int
@@ -1688,6 +1697,7 @@ bridge_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *sa,
 	struct ether_header *eh;
 	struct ifnet *dst_if;
 	struct bridge_softc *sc;
+	uint16_t vlan;
 
 	if (m->m_len < ETHER_HDR_LEN) {
 		m = m_pullup(m, ETHER_HDR_LEN);
@@ -1697,6 +1707,7 @@ bridge_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *sa,
 
 	eh = mtod(m, struct ether_header *);
 	sc = ifp->if_bridge;
+	vlan = VLANTAGOF(m);
 
 	BRIDGE_LOCK(sc);
 
@@ -1717,7 +1728,7 @@ bridge_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *sa,
 	if (ETHER_IS_MULTICAST(eh->ether_dhost))
 		dst_if = NULL;
 	else
-		dst_if = bridge_rtlookup(sc, eh->ether_dhost);
+		dst_if = bridge_rtlookup(sc, eh->ether_dhost, vlan);
 	if (dst_if == NULL) {
 		struct bridge_iflist *bif;
 		struct mbuf *mc;
@@ -1813,7 +1824,7 @@ bridge_start(struct ifnet *ifp)
 
 		BRIDGE_LOCK(sc);
 		if ((m->m_flags & (M_BCAST|M_MCAST)) == 0) {
-			dst_if = bridge_rtlookup(sc, eh->ether_dhost);
+			dst_if = bridge_rtlookup(sc, eh->ether_dhost, 1);
 		}
 
 		if (dst_if == NULL)
@@ -1839,12 +1850,14 @@ bridge_forward(struct bridge_softc *sc, struct mbuf *m)
 	struct bridge_iflist *bif;
 	struct ifnet *src_if, *dst_if, *ifp;
 	struct ether_header *eh;
+	uint16_t vlan;
 
 	src_if = m->m_pkthdr.rcvif;
 	ifp = sc->sc_ifp;
 
 	sc->sc_ifp->if_ipackets++;
 	sc->sc_ifp->if_ibytes += m->m_pkthdr.len;
+	vlan = VLANTAGOF(m);
 
 	/*
 	 * Look up the bridge_iflist.
@@ -1879,7 +1892,7 @@ bridge_forward(struct bridge_softc *sc, struct mbuf *m)
 	     eh->ether_shost[3] == 0 &&
 	     eh->ether_shost[4] == 0 &&
 	     eh->ether_shost[5] == 0) == 0) {
-		(void) bridge_rtupdate(sc, eh->ether_shost,
+		(void) bridge_rtupdate(sc, eh->ether_shost, vlan,
 		    bif, 0, IFBAF_DYNAMIC);
 	}
 
@@ -1900,7 +1913,7 @@ bridge_forward(struct bridge_softc *sc, struct mbuf *m)
 	 * "this" side of the bridge, drop it.
 	 */
 	if ((m->m_flags & (M_BCAST|M_MCAST)) == 0) {
-		dst_if = bridge_rtlookup(sc, eh->ether_dhost);
+		dst_if = bridge_rtlookup(sc, eh->ether_dhost, vlan);
 		if (src_if == dst_if) {
 			BRIDGE_UNLOCK(sc);
 			m_freem(m);
@@ -1997,11 +2010,13 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
 	struct ifnet *bifp;
 	struct ether_header *eh;
 	struct mbuf *mc, *mc2;
+	uint16_t vlan;
 
 	if ((sc->sc_ifp->if_drv_flags & IFF_DRV_RUNNING) == 0)
 		return (m);
 
 	bifp = sc->sc_ifp;
+	vlan = VLANTAGOF(m);
 
 	/*
 	 * Implement support for bridge monitoring. If this flag has been
@@ -2037,7 +2052,7 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
 		/* Note where to send the reply to */
 		if (bif->bif_flags & IFBIF_LEARNING)
 			(void) bridge_rtupdate(sc,
-			    eh->ether_shost, bif, 0, IFBAF_DYNAMIC);
+			    eh->ether_shost, vlan, bif, 0, IFBAF_DYNAMIC);
 
 		/* Mark the packet as arriving on the bridge interface */
 		m->m_pkthdr.rcvif = bifp;
@@ -2130,8 +2145,8 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
 	    OR_CARP_CHECK_WE_ARE_DST((iface))				\
 	    ) {								\
 		if (bif->bif_flags & IFBIF_LEARNING)			\
-			(void) bridge_rtupdate(sc,			\
-			    eh->ether_shost, bif, 0, IFBAF_DYNAMIC);	\
+			(void) bridge_rtupdate(sc, eh->ether_shost,	\
+			    vlan, bif, 0, IFBAF_DYNAMIC);		\
 		m->m_pkthdr.rcvif = iface;				\
 		BRIDGE_UNLOCK(sc);					\
 		return (m);						\
@@ -2307,7 +2322,7 @@ bridge_span(struct bridge_softc *sc, struct mbuf *m)
  *	Add a bridge routing entry.
  */
 static int
-bridge_rtupdate(struct bridge_softc *sc, const uint8_t *dst,
+bridge_rtupdate(struct bridge_softc *sc, const uint8_t *dst, uint16_t vlan,
     struct bridge_iflist *bif, int setflags, uint8_t flags)
 {
 	struct bridge_rtnode *brt;
@@ -2316,11 +2331,15 @@ bridge_rtupdate(struct bridge_softc *sc, const uint8_t *dst,
 
 	BRIDGE_LOCK_ASSERT(sc);
 
+	/* 802.1p frames map to vlan 1 */
+	if (vlan == 0)
+		vlan = 1;
+
 	/*
 	 * A route for this destination might already exist.  If so,
 	 * update it, otherwise create a new one.
 	 */
-	if ((brt = bridge_rtnode_lookup(sc, dst)) == NULL) {
+	if ((brt = bridge_rtnode_lookup(sc, dst, vlan)) == NULL) {
 		if (sc->sc_brtcnt >= sc->sc_brtmax) {
 			sc->sc_brtexceeded++;
 			return (ENOSPC);
@@ -2342,6 +2361,7 @@ bridge_rtupdate(struct bridge_softc *sc, const uint8_t *dst,
 
 		brt->brt_ifp = dst_if;
 		memcpy(brt->brt_addr, dst, ETHER_ADDR_LEN);
+		brt->brt_vlan = vlan;
 
 		if ((error = bridge_rtnode_insert(sc, brt)) != 0) {
 			uma_zfree(bridge_rtnode_zone, brt);
@@ -2365,13 +2385,13 @@ bridge_rtupdate(struct bridge_softc *sc, const uint8_t *dst,
  *	Lookup the destination interface for an address.
  */
 static struct ifnet *
-bridge_rtlookup(struct bridge_softc *sc, const uint8_t *addr)
+bridge_rtlookup(struct bridge_softc *sc, const uint8_t *addr, uint16_t vlan)
 {
 	struct bridge_rtnode *brt;
 
 	BRIDGE_LOCK_ASSERT(sc);
 
-	if ((brt = bridge_rtnode_lookup(sc, addr)) == NULL)
+	if ((brt = bridge_rtnode_lookup(sc, addr, vlan)) == NULL)
 		return (NULL);
 
 	return (brt->brt_ifp);
@@ -2472,17 +2492,23 @@ bridge_rtflush(struct bridge_softc *sc, int full)
  *	Remove an address from the table.
  */
 static int
-bridge_rtdaddr(struct bridge_softc *sc, const uint8_t *addr)
+bridge_rtdaddr(struct bridge_softc *sc, const uint8_t *addr, uint16_t vlan)
 {
 	struct bridge_rtnode *brt;
+	int found = 0;
 
 	BRIDGE_LOCK_ASSERT(sc);
 
-	if ((brt = bridge_rtnode_lookup(sc, addr)) == NULL)
-		return (ENOENT);
+	/*
+	 * If vlan is zero then we want to delete for all vlans so the lookup
+	 * may return more than one.
+	 */
+	while ((brt = bridge_rtnode_lookup(sc, addr, vlan)) != NULL) {
+		bridge_rtnode_destroy(sc, brt);
+		found = 1;
+	}
 
-	bridge_rtnode_destroy(sc, brt);
-	return (0);
+	return (found ? 0 : ENOENT);
 }
 
 /*
@@ -2592,10 +2618,11 @@ bridge_rtnode_addr_cmp(const uint8_t *a, const uint8_t *b)
 /*
  * bridge_rtnode_lookup:
  *
- *	Look up a bridge route node for the specified destination.
+ *	Look up a bridge route node for the specified destination. Compare the
+ *	vlan id or if zero then just return the first match.
  */
 static struct bridge_rtnode *
-bridge_rtnode_lookup(struct bridge_softc *sc, const uint8_t *addr)
+bridge_rtnode_lookup(struct bridge_softc *sc, const uint8_t *addr, uint16_t vlan)
 {
 	struct bridge_rtnode *brt;
 	uint32_t hash;
@@ -2606,7 +2633,7 @@ bridge_rtnode_lookup(struct bridge_softc *sc, const uint8_t *addr)
 	hash = bridge_rthash(sc, addr);
 	LIST_FOREACH(brt, &sc->sc_rthash[hash], brt_hash) {
 		dir = bridge_rtnode_addr_cmp(addr, brt->brt_addr);
-		if (dir == 0)
+		if (dir == 0 && (brt->brt_vlan == vlan || vlan == 0))
 			return (brt);
 		if (dir > 0)
 			return (NULL);
@@ -2640,7 +2667,7 @@ bridge_rtnode_insert(struct bridge_softc *sc, struct bridge_rtnode *brt)
 
 	do {
 		dir = bridge_rtnode_addr_cmp(brt->brt_addr, lbrt->brt_addr);
-		if (dir == 0)
+		if (dir == 0 && brt->brt_vlan == lbrt->brt_vlan)
 			return (EEXIST);
 		if (dir > 0) {
 			LIST_INSERT_BEFORE(lbrt, brt, brt_hash);
diff --git a/sys/net/if_bridgevar.h b/sys/net/if_bridgevar.h
index ae33688a00da..b11c34db6ffd 100644
--- a/sys/net/if_bridgevar.h
+++ b/sys/net/if_bridgevar.h
@@ -174,6 +174,7 @@ struct ifbareq {
 	unsigned long	ifba_expire;		/* address expire time */
 	uint8_t		ifba_flags;		/* address flags */
 	uint8_t		ifba_dst[ETHER_ADDR_LEN];/* destination address */
+	uint16_t	ifba_vlan;		/* vlan id */
 };
 
 #define	IFBAF_TYPEMASK	0x03	/* address type mask */