Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 21 Oct 2020 21:28:20 +0000 (UTC)
From:      "Alexander V. Chernikov" <melifaro@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r366917 - in head: sbin/ifconfig sys/net tests/sys/net
Message-ID:  <202010212128.09LLSK8d080756@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: melifaro
Date: Wed Oct 21 21:28:20 2020
New Revision: 366917
URL: https://svnweb.freebsd.org/changeset/base/366917

Log:
  Add support for stacked VLANs (IEEE 802.1ad, AKA Q-in-Q).
  
  802.1ad interfaces are created with ifconfig using the "vlanproto" parameter.
  Eg., the following creates a 802.1Q VLAN (id #42) over a 802.1ad S-VLAN
  (id #5) over a physical Ethernet interface (em0).
  
  ifconfig vlan5 create vlandev em0 vlan 5 vlanproto 802.1ad up
  ifconfig vlan42 create vlandev vlan5 vlan 42 inet 10.5.42.1/24
  
  VLAN_MTU, VLAN_HWCSUM and VLAN_TSO capabilities should be properly
  supported. VLAN_HWTAGGING is only partially supported, as there is
  currently no IFCAP_VLAN_* denoting the possibility to set the VLAN
  EtherType to anything else than 0x8100 (802.1ad uses 0x88A8).
  
  Submitted by:	Olivier Piras
  Sponsored by:	RG Nets
  Differential Revision:	https://reviews.freebsd.org/D26436

Modified:
  head/sbin/ifconfig/ifclone.c
  head/sbin/ifconfig/ifconfig.8
  head/sbin/ifconfig/ifconfig.h
  head/sbin/ifconfig/ifieee80211.c
  head/sbin/ifconfig/ifvlan.c
  head/sbin/ifconfig/ifvxlan.c
  head/sys/net/ethernet.h
  head/sys/net/if_clone.c
  head/sys/net/if_ethersubr.c
  head/sys/net/if_vlan.c
  head/sys/net/if_vlan_var.h
  head/tests/sys/net/if_vlan.sh

Modified: head/sbin/ifconfig/ifclone.c
==============================================================================
--- head/sbin/ifconfig/ifclone.c	Wed Oct 21 20:42:29 2020	(r366916)
+++ head/sbin/ifconfig/ifclone.c	Wed Oct 21 21:28:20 2020	(r366917)
@@ -49,6 +49,11 @@ static const char rcsid[] =
 
 #include "ifconfig.h"
 
+typedef enum {
+	MT_PREFIX,
+	MT_FILTER,
+} clone_match_type;
+
 static void
 list_cloners(void)
 {
@@ -76,7 +81,11 @@ list_cloners(void)
 }
 
 struct clone_defcb {
-	char ifprefix[IFNAMSIZ];
+	union {
+		char ifprefix[IFNAMSIZ];
+		clone_match_func *ifmatch;
+	};
+	clone_match_type clone_mt;
 	clone_callback_func *clone_cb;
 	SLIST_ENTRY(clone_defcb) next;
 };
@@ -85,16 +94,29 @@ static SLIST_HEAD(, clone_defcb) clone_defcbh =
    SLIST_HEAD_INITIALIZER(clone_defcbh);
 
 void
-clone_setdefcallback(const char *ifprefix, clone_callback_func *p)
+clone_setdefcallback_prefix(const char *ifprefix, clone_callback_func *p)
 {
 	struct clone_defcb *dcp;
 
 	dcp = malloc(sizeof(*dcp));
 	strlcpy(dcp->ifprefix, ifprefix, IFNAMSIZ-1);
+	dcp->clone_mt = MT_PREFIX;
 	dcp->clone_cb = p;
 	SLIST_INSERT_HEAD(&clone_defcbh, dcp, next);
 }
 
+void
+clone_setdefcallback_filter(clone_match_func *filter, clone_callback_func *p)
+{
+	struct clone_defcb *dcp;
+
+	dcp = malloc(sizeof(*dcp));
+	dcp->ifmatch  = filter;
+	dcp->clone_mt = MT_FILTER;
+	dcp->clone_cb = p;
+	SLIST_INSERT_HEAD(&clone_defcbh, dcp, next);
+}
+
 /*
  * Do the actual clone operation.  Any parameters must have been
  * setup by now.  If a callback has been setup to do the work
@@ -114,8 +136,14 @@ ifclonecreate(int s, void *arg)
 	if (clone_cb == NULL) {
 		/* Try to find a default callback */
 		SLIST_FOREACH(dcp, &clone_defcbh, next) {
-			if (strncmp(dcp->ifprefix, ifr.ifr_name,
-			    strlen(dcp->ifprefix)) == 0) {
+			if ((dcp->clone_mt == MT_PREFIX) &&
+			    (strncmp(dcp->ifprefix, ifr.ifr_name,
+			             strlen(dcp->ifprefix)) == 0)) {
+				clone_cb = dcp->clone_cb;
+				break;
+			}
+			if ((dcp->clone_mt == MT_FILTER) &&
+			          dcp->ifmatch(ifr.ifr_name)) {
 				clone_cb = dcp->clone_cb;
 				break;
 			}

Modified: head/sbin/ifconfig/ifconfig.8
==============================================================================
--- head/sbin/ifconfig/ifconfig.8	Wed Oct 21 20:42:29 2020	(r366916)
+++ head/sbin/ifconfig/ifconfig.8	Wed Oct 21 21:28:20 2020	(r366917)
@@ -582,7 +582,7 @@ they support in their capabilities.
 is a synonym for enabling all available WOL mechanisms.
 To disable WOL use
 .Fl wol .
-.It Cm vlanmtu , vlanhwtag, vlanhwfilter, vlanhwcsum, vlanhwtso
+.It Cm vlanmtu , vlanhwtag , vlanhwfilter , vlanhwcsum , vlanhwtso
 If the driver offers user-configurable VLAN support, enable
 reception of extended frames, tag processing in hardware,
 frame filtering in hardware, checksum offloading, or TSO on VLAN,
@@ -592,7 +592,7 @@ Note that this must be configured on a physical interf
 not on a
 .Xr vlan 4
 interface itself.
-.It Fl vlanmtu , vlanhwtag , vlanhwfilter , vlanhwtso
+.It Fl vlanmtu , vlanhwtag, vlanhwfilter, vlanhwtso
 If the driver offers user-configurable VLAN support, disable
 reception of extended frames, tag processing in hardware,
 frame filtering in hardware, or TSO on VLAN,
@@ -2696,7 +2696,7 @@ interfaces:
 Set the VLAN tag value to
 .Ar vlan_tag .
 This value is a 12-bit VLAN Identifier (VID) which is used to create an 802.1Q
-VLAN header for packets sent from the
+or 802.1ad VLAN header for packets sent from the
 .Xr vlan 4
 interface.
 Note that
@@ -2704,6 +2704,15 @@ Note that
 and
 .Cm vlandev
 must both be set at the same time.
+.It Cm vlanproto Ar vlan_proto
+Set the VLAN encapsulation protocol to
+.Ar vlan_proto .
+Supported encapsulation protocols are currently
+.Dq 802.1Q
+and
+.Dq 802.1ad .
+The default encapsulation protocol is
+.Dq 802.1Q .
 .It Cm vlanpcp Ar priority_code_point
 Priority code point
 .Pq Dv PCP

Modified: head/sbin/ifconfig/ifconfig.h
==============================================================================
--- head/sbin/ifconfig/ifconfig.h	Wed Oct 21 20:42:29 2020	(r366916)
+++ head/sbin/ifconfig/ifconfig.h	Wed Oct 21 21:28:20 2020	(r366917)
@@ -145,8 +145,10 @@ void	printb(const char *s, unsigned value, const char 
 
 void	ifmaybeload(const char *name);
 
+typedef int  clone_match_func(const char *);
 typedef void clone_callback_func(int, struct ifreq *);
-void	clone_setdefcallback(const char *, clone_callback_func *);
+void	clone_setdefcallback_prefix(const char *, clone_callback_func *);
+void	clone_setdefcallback_filter(clone_match_func *, clone_callback_func *);
 
 void	sfp_status(int s, struct ifreq *ifr, int verbose);
 

Modified: head/sbin/ifconfig/ifieee80211.c
==============================================================================
--- head/sbin/ifconfig/ifieee80211.c	Wed Oct 21 20:42:29 2020	(r366916)
+++ head/sbin/ifconfig/ifieee80211.c	Wed Oct 21 21:28:20 2020	(r366917)
@@ -6069,5 +6069,5 @@ ieee80211_ctor(void)
 	for (i = 0; i < nitems(ieee80211_cmds);  i++)
 		cmd_register(&ieee80211_cmds[i]);
 	af_register(&af_ieee80211);
-	clone_setdefcallback("wlan", wlan_create);
+	clone_setdefcallback_prefix("wlan", wlan_create);
 }

Modified: head/sbin/ifconfig/ifvlan.c
==============================================================================
--- head/sbin/ifconfig/ifvlan.c	Wed Oct 21 20:42:29 2020	(r366916)
+++ head/sbin/ifconfig/ifvlan.c	Wed Oct 21 21:28:20 2020	(r366917)
@@ -66,8 +66,12 @@ static const char rcsid[] =
 
 #define	NOTAG	((u_short) -1)
 
+static const char proto_8021Q[]  = "802.1q";
+static const char proto_8021ad[] = "802.1ad";
+
 static 	struct vlanreq params = {
 	.vlr_tag	= NOTAG,
+	.vlr_proto	= ETHERTYPE_VLAN,
 };
 
 static int
@@ -87,6 +91,17 @@ vlan_status(int s)
 	if (getvlan(s, &ifr, &vreq) == -1)
 		return;
 	printf("\tvlan: %d", vreq.vlr_tag);
+	printf(" vlanproto: ");
+	switch (vreq.vlr_proto) {
+		case ETHERTYPE_VLAN:
+			printf(proto_8021Q);
+			break;
+		case ETHERTYPE_QINQ:
+			printf(proto_8021ad);
+			break;
+		default:
+			printf("0x%04x", vreq.vlr_proto);
+	}
 	if (ioctl(s, SIOCGVLANPCP, (caddr_t)&ifr) != -1)
 		printf(" vlanpcp: %u", ifr.ifr_vlan_pcp);
 	printf(" parent interface: %s", vreq.vlr_parent[0] == '\0' ?
@@ -94,9 +109,49 @@ vlan_status(int s)
 	printf("\n");
 }
 
+static int
+vlan_match_ethervid(const char *name)
+{
+	return (strchr(name, '.') != NULL);
+}
+
 static void
+vlan_parse_ethervid(const char *name)
+{
+	char ifname[IFNAMSIZ];
+	char *cp;
+	int vid;
+
+	strlcpy(ifname, name, IFNAMSIZ);
+	if ((cp = strrchr(ifname, '.')) == NULL)
+		return;
+	/*
+	 * Don't mix vlan/vlandev parameters with dot notation.
+	 */
+	if (params.vlr_tag != NOTAG || params.vlr_parent[0] != '\0')
+		errx(1, "ambiguous vlan specification");
+	/*
+	 * Derive params from interface name: "parent.vid".
+	 */
+	*cp++ = '\0';
+	if ((*cp < '1') || (*cp > '9'))
+		errx(1, "invalid vlan tag");
+
+	vid = *cp++ - '0';
+	while ((*cp >= '0') && (*cp <= '9'))
+		vid = (vid * 10) + (*cp++ - '0');
+	if ((*cp != '\0') || (vid & ~0xFFF))
+		errx(1, "invalid vlan tag");
+
+	strlcpy(params.vlr_parent, ifname, IFNAMSIZ);
+	params.vlr_tag = (vid & 0xFFF);
+}
+
+static void
 vlan_create(int s, struct ifreq *ifr)
 {
+	vlan_parse_ethervid(ifr->ifr_name);
+
 	if (params.vlr_tag != NOTAG || params.vlr_parent[0] != '\0') {
 		/*
 		 * One or both parameters were specified, make sure both.
@@ -159,6 +214,24 @@ DECL_CMD_FUNC(setvlandev, val, d)
 }
 
 static
+DECL_CMD_FUNC(setvlanproto, val, d)
+{
+	struct vlanreq vreq;
+
+	if (strncasecmp(proto_8021Q, val,
+	    strlen(proto_8021Q)) == 0) {
+		params.vlr_proto = ETHERTYPE_VLAN;
+	} else if (strncasecmp(proto_8021ad, val,
+	           strlen(proto_8021ad)) == 0) {
+		params.vlr_proto = ETHERTYPE_QINQ;
+	} else
+		errx(1, "invalid value for vlanproto");
+
+	if (getvlan(s, &ifr, &vreq) != -1)
+		vlan_set(s, &ifr);
+}
+
+static
 DECL_CMD_FUNC(setvlanpcp, val, d)
 {
 	u_long ul;
@@ -195,10 +268,12 @@ DECL_CMD_FUNC(unsetvlandev, val, d)
 static struct cmd vlan_cmds[] = {
 	DEF_CLONE_CMD_ARG("vlan",			setvlantag),
 	DEF_CLONE_CMD_ARG("vlandev",			setvlandev),
+	DEF_CLONE_CMD_ARG("vlanproto",			setvlanproto),
 	DEF_CMD_ARG("vlanpcp",				setvlanpcp),
 	/* NB: non-clone cmds */
 	DEF_CMD_ARG("vlan",				setvlantag),
 	DEF_CMD_ARG("vlandev",				setvlandev),
+	DEF_CMD_ARG("vlanproto",			setvlanproto),
 	/* XXX For compatibility.  Should become DEF_CMD() some day. */
 	DEF_CMD_OPTARG("-vlandev",			unsetvlandev),
 	DEF_CMD("vlanmtu",	IFCAP_VLAN_MTU,		setifcap),
@@ -227,5 +302,6 @@ vlan_ctor(void)
 		cmd_register(&vlan_cmds[i]);
 	af_register(&af_vlan);
 	callback_register(vlan_cb, NULL);
-	clone_setdefcallback("vlan", vlan_create);
+	clone_setdefcallback_prefix("vlan", vlan_create);
+	clone_setdefcallback_filter(vlan_match_ethervid, vlan_create);
 }

Modified: head/sbin/ifconfig/ifvxlan.c
==============================================================================
--- head/sbin/ifconfig/ifvxlan.c	Wed Oct 21 20:42:29 2020	(r366916)
+++ head/sbin/ifconfig/ifvxlan.c	Wed Oct 21 21:28:20 2020	(r366917)
@@ -642,5 +642,5 @@ vxlan_ctor(void)
 		cmd_register(&vxlan_cmds[i]);
 	af_register(&af_vxlan);
 	callback_register(vxlan_cb, NULL);
-	clone_setdefcallback("vxlan", vxlan_create);
+	clone_setdefcallback_prefix("vxlan", vxlan_create);
 }

Modified: head/sys/net/ethernet.h
==============================================================================
--- head/sys/net/ethernet.h	Wed Oct 21 20:42:29 2020	(r366916)
+++ head/sys/net/ethernet.h	Wed Oct 21 21:28:20 2020	(r366917)
@@ -428,6 +428,7 @@ struct mbuf;
 struct route;
 struct sockaddr;
 struct bpf_if;
+struct ether_8021q_tag;
 
 extern	uint32_t ether_crc32_le(const uint8_t *, size_t);
 extern	uint32_t ether_crc32_be(const uint8_t *, size_t);
@@ -441,10 +442,16 @@ extern	int  ether_output_frame(struct ifnet *, struct 
 extern	char *ether_sprintf(const u_int8_t *);
 void	ether_vlan_mtap(struct bpf_if *, struct mbuf *,
 	    void *, u_int);
-struct mbuf  *ether_vlanencap(struct mbuf *, uint16_t);
-bool	ether_8021q_frame(struct mbuf **mp, struct ifnet *ife, struct ifnet *p,
-	    uint16_t vid, uint8_t pcp);
+struct mbuf  *ether_vlanencap_proto(struct mbuf *, uint16_t, uint16_t);
+bool	ether_8021q_frame(struct mbuf **mp, struct ifnet *ife,
+		struct ifnet *p, struct ether_8021q_tag *);
 void	ether_gen_addr(struct ifnet *ifp, struct ether_addr *hwaddr);
+
+static __inline struct mbuf *ether_vlanencap(struct mbuf *m, uint16_t tag)
+{
+
+	return ether_vlanencap_proto(m, tag, ETHERTYPE_VLAN);
+}
 
 /* new ethernet interface attached event */
 typedef void (*ether_ifattach_event_handler_t)(void *, struct ifnet *);

Modified: head/sys/net/if_clone.c
==============================================================================
--- head/sys/net/if_clone.c	Wed Oct 21 20:42:29 2020	(r366916)
+++ head/sys/net/if_clone.c	Wed Oct 21 21:28:20 2020	(r366917)
@@ -571,7 +571,7 @@ if_clone_addgroup(struct ifnet *ifp, struct if_clone *
 
 /*
  * A utility function to extract unit numbers from interface names of
- * the form name###.
+ * the form name###[.###].
  *
  * Returns 0 on success and an error on failure.
  */
@@ -582,7 +582,9 @@ ifc_name2unit(const char *name, int *unit)
 	int		cutoff = INT_MAX / 10;
 	int		cutlim = INT_MAX % 10;
 
-	for (cp = name; *cp != '\0' && (*cp < '0' || *cp > '9'); cp++);
+	if ((cp = strrchr(name, '.')) == NULL)
+		cp = name;
+	for (; *cp != '\0' && (*cp < '0' || *cp > '9'); cp++);
 	if (*cp == '\0') {
 		*unit = -1;
 	} else if (cp[0] == '0' && cp[1] != '\0') {

Modified: head/sys/net/if_ethersubr.c
==============================================================================
--- head/sys/net/if_ethersubr.c	Wed Oct 21 20:42:29 2020	(r366916)
+++ head/sys/net/if_ethersubr.c	Wed Oct 21 21:28:20 2020	(r366917)
@@ -441,12 +441,19 @@ bad:			if (m != NULL)
 static bool
 ether_set_pcp(struct mbuf **mp, struct ifnet *ifp, uint8_t pcp)
 {
+	struct ether_8021q_tag qtag;
 	struct ether_header *eh;
 
 	eh = mtod(*mp, struct ether_header *);
 	if (ntohs(eh->ether_type) == ETHERTYPE_VLAN ||
-	    ether_8021q_frame(mp, ifp, ifp, 0, pcp))
+	    ntohs(eh->ether_type) == ETHERTYPE_QINQ)
 		return (true);
+
+	qtag.vid = 0;
+	qtag.pcp = pcp;
+	qtag.proto = ETHERTYPE_VLAN;
+	if (ether_8021q_frame(mp, ifp, ifp, &qtag))
+		return (true);
 	if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
 	return (false);
 }
@@ -616,9 +623,9 @@ ether_input_internal(struct ifnet *ifp, struct mbuf *m
 	 * If the hardware did not process an 802.1Q tag, do this now,
 	 * to allow 802.1P priority frames to be passed to the main input
 	 * path correctly.
-	 * TODO: Deal with Q-in-Q frames, but not arbitrary nesting levels.
 	 */
-	if ((m->m_flags & M_VLANTAG) == 0 && etype == ETHERTYPE_VLAN) {
+	if ((m->m_flags & M_VLANTAG) == 0 &&
+	    ((etype == ETHERTYPE_VLAN) || (etype == ETHERTYPE_QINQ))) {
 		struct ether_vlan_header *evl;
 
 		if (m->m_len < sizeof(*evl) &&
@@ -1303,7 +1310,7 @@ ether_vlan_mtap(struct bpf_if *bp, struct mbuf *m, voi
 }
 
 struct mbuf *
-ether_vlanencap(struct mbuf *m, uint16_t tag)
+ether_vlanencap_proto(struct mbuf *m, uint16_t tag, uint16_t proto)
 {
 	struct ether_vlan_header *evl;
 
@@ -1325,7 +1332,7 @@ ether_vlanencap(struct mbuf *m, uint16_t tag)
 	evl = mtod(m, struct ether_vlan_header *);
 	bcopy((char *)evl + ETHER_VLAN_ENCAP_LEN,
 	    (char *)evl, ETHER_HDR_LEN - ETHER_TYPE_LEN);
-	evl->evl_encap_proto = htons(ETHERTYPE_VLAN);
+	evl->evl_encap_proto = htons(proto);
 	evl->evl_tag = htons(tag);
 	return (m);
 }
@@ -1354,7 +1361,7 @@ SYSCTL_INT(_net_link_vlan, OID_AUTO, mtag_pcp, CTLFLAG
 
 bool
 ether_8021q_frame(struct mbuf **mp, struct ifnet *ife, struct ifnet *p,
-    uint16_t vid, uint8_t pcp)
+    struct ether_8021q_tag *qtag)
 {
 	struct m_tag *mtag;
 	int n;
@@ -1391,7 +1398,7 @@ ether_8021q_frame(struct mbuf **mp, struct ifnet *ife,
 	 * If PCP is set in mbuf, use it
 	 */
 	if ((*mp)->m_flags & M_VLANTAG) {
-		pcp = EVL_PRIOFTAG((*mp)->m_pkthdr.ether_vtag);
+		qtag->pcp = EVL_PRIOFTAG((*mp)->m_pkthdr.ether_vtag);
 	}
 
 	/*
@@ -1403,14 +1410,15 @@ ether_8021q_frame(struct mbuf **mp, struct ifnet *ife,
 	 */
 	if (vlan_mtag_pcp && (mtag = m_tag_locate(*mp, MTAG_8021Q,
 	    MTAG_8021Q_PCP_OUT, NULL)) != NULL)
-		tag = EVL_MAKETAG(vid, *(uint8_t *)(mtag + 1), 0);
+		tag = EVL_MAKETAG(qtag->vid, *(uint8_t *)(mtag + 1), 0);
 	else
-		tag = EVL_MAKETAG(vid, pcp, 0);
-	if (p->if_capenable & IFCAP_VLAN_HWTAGGING) {
+		tag = EVL_MAKETAG(qtag->vid, qtag->pcp, 0);
+	if ((p->if_capenable & IFCAP_VLAN_HWTAGGING) &&
+	    (qtag->proto == ETHERTYPE_VLAN)) {
 		(*mp)->m_pkthdr.ether_vtag = tag;
 		(*mp)->m_flags |= M_VLANTAG;
 	} else {
-		*mp = ether_vlanencap(*mp, tag);
+		*mp = ether_vlanencap_proto(*mp, tag, qtag->proto);
 		if (*mp == NULL) {
 			if_printf(ife, "unable to prepend 802.1Q header");
 			return (false);

Modified: head/sys/net/if_vlan.c
==============================================================================
--- head/sys/net/if_vlan.c	Wed Oct 21 20:42:29 2020	(r366916)
+++ head/sys/net/if_vlan.c	Wed Oct 21 21:28:20 2020	(r366917)
@@ -17,7 +17,7 @@
  * no representations about the suitability of this software for any
  * purpose.  It is provided "as is" without express or implied
  * warranty.
- * 
+ *
  * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''.  M.I.T. DISCLAIMS
  * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
@@ -185,17 +185,17 @@ struct ifvlan {
 	struct	ifvlantrunk *ifv_trunk;
 	struct	ifnet *ifv_ifp;
 #define	TRUNK(ifv)	((ifv)->ifv_trunk)
-#define	PARENT(ifv)	((ifv)->ifv_trunk->parent)
+#define	PARENT(ifv)	(TRUNK(ifv)->parent)
 	void	*ifv_cookie;
 	int	ifv_pflags;	/* special flags we have set on parent */
 	int	ifv_capenable;
 	int	ifv_encaplen;	/* encapsulation length */
 	int	ifv_mtufudge;	/* MTU fudged by this much */
 	int	ifv_mintu;	/* min transmission unit */
-	uint16_t ifv_proto;	/* encapsulation ethertype */
-	uint16_t ifv_tag;	/* tag to apply on packets leaving if */
-	uint16_t ifv_vid;	/* VLAN ID */
-	uint8_t	ifv_pcp;	/* Priority Code Point (PCP). */
+	struct  ether_8021q_tag ifv_qtag;
+#define ifv_proto	ifv_qtag.proto
+#define ifv_vid		ifv_qtag.vid
+#define ifv_pcp		ifv_qtag.pcp
 	struct task lladdr_task;
 	CK_SLIST_HEAD(, vlan_mc_entry) vlan_mc_listhead;
 #ifndef VLAN_ARRAY
@@ -222,9 +222,9 @@ static eventhandler_tag ifdetach_tag;
 static eventhandler_tag iflladdr_tag;
 
 /*
- * if_vlan uses two module-level synchronizations primitives to allow concurrent 
- * modification of vlan interfaces and (mostly) allow for vlans to be destroyed 
- * while they are being used for tx/rx. To accomplish this in a way that has 
+ * if_vlan uses two module-level synchronizations primitives to allow concurrent
+ * modification of vlan interfaces and (mostly) allow for vlans to be destroyed
+ * while they are being used for tx/rx. To accomplish this in a way that has
  * acceptable performance and cooperation with other parts of the network stack
  * there is a non-sleepable epoch(9) and an sx(9).
  *
@@ -244,7 +244,7 @@ static eventhandler_tag iflladdr_tag;
 static struct sx _VLAN_SX_ID;
 
 #define VLAN_LOCKING_INIT() \
-	sx_init(&_VLAN_SX_ID, "vlan_sx")
+	sx_init_flags(&_VLAN_SX_ID, "vlan_sx", SX_RECURSE)
 
 #define VLAN_LOCKING_DESTROY() \
 	sx_destroy(&_VLAN_SX_ID)
@@ -306,7 +306,8 @@ static	int vlan_output(struct ifnet *ifp, struct mbuf 
     const struct sockaddr *dst, struct route *ro);
 static	void vlan_unconfig(struct ifnet *ifp);
 static	void vlan_unconfig_locked(struct ifnet *ifp, int departing);
-static	int vlan_config(struct ifvlan *ifv, struct ifnet *p, uint16_t tag);
+static	int vlan_config(struct ifvlan *ifv, struct ifnet *p, uint16_t tag,
+	uint16_t proto);
 static	void vlan_link_state(struct ifnet *ifp);
 static	void vlan_capabilities(struct ifvlan *ifv);
 static	void vlan_trunk_capabilities(struct ifnet *ifp);
@@ -760,7 +761,7 @@ vlan_pcp(struct ifnet *ifp, uint16_t *pcpp)
 
 /*
  * Return a driver specific cookie for this interface.  Synchronization
- * with setcookie must be provided by the driver. 
+ * with setcookie must be provided by the driver.
  */
 static void *
 vlan_cookie(struct ifnet *ifp)
@@ -811,16 +812,6 @@ vlan_devat(struct ifnet *ifp, uint16_t vid)
 }
 
 /*
- * Recalculate the cached VLAN tag exposed via the MIB.
- */
-static void
-vlan_tag_recalculate(struct ifvlan *ifv)
-{
-
-       ifv->ifv_tag = EVL_MAKETAG(ifv->ifv_vid, ifv->ifv_pcp, 0);
-}
-
-/*
  * VLAN support can be loaded as a module.  The only place in the
  * system that's intimately aware of this is ether_input.  We hook
  * into this code through vlan_input_p which is defined there and
@@ -867,7 +858,7 @@ vlan_modevent(module_t mod, int type, void *data)
 #else
 			       "hash tables with chaining"
 #endif
-			
+
 			       "\n");
 		break;
 	case MOD_UNLOAD:
@@ -926,7 +917,7 @@ VNET_SYSUNINIT(vnet_vlan_uninit, SI_SUB_INIT_IF, SI_OR
 #endif
 
 /*
- * Check for <etherif>.<vlan> style interface names.
+ * Check for <etherif>.<vlan>[.<vlan> ...] style interface names.
  */
 static struct ifnet *
 vlan_clone_match_ethervid(const char *name, int *vidp)
@@ -937,7 +928,7 @@ vlan_clone_match_ethervid(const char *name, int *vidp)
 	int vid;
 
 	strlcpy(ifname, name, IFNAMSIZ);
-	if ((cp = strchr(ifname, '.')) == NULL)
+	if ((cp = strrchr(ifname, '.')) == NULL)
 		return (NULL);
 	*cp = '\0';
 	if ((ifp = ifunit_ref(ifname)) == NULL)
@@ -990,6 +981,7 @@ vlan_clone_create(struct if_clone *ifc, char *name, si
 	int unit;
 	int error;
 	int vid;
+	uint16_t proto;
 	struct ifvlan *ifv;
 	struct ifnet *ifp;
 	struct ifnet *p;
@@ -998,14 +990,15 @@ vlan_clone_create(struct if_clone *ifc, char *name, si
 	struct vlanreq vlr;
 	static const u_char eaddr[ETHER_ADDR_LEN];	/* 00:00:00:00:00:00 */
 
+	proto = ETHERTYPE_VLAN;
+
 	/*
-	 * There are 3 (ugh) ways to specify the cloned device:
+	 * There are two ways to specify the cloned device:
 	 * o pass a parameter block with the clone request.
-	 * o specify parameters in the text of the clone device name
 	 * o specify no parameters and get an unattached device that
 	 *   must be configured separately.
-	 * The first technique is preferred; the latter two are
-	 * supported for backwards compatibility.
+	 * The first technique is preferred; the latter is supported
+	 * for backwards compatibility.
 	 *
 	 * XXXRW: Note historic use of the word "tag" here.  New ioctls may be
 	 * called for.
@@ -1023,10 +1016,8 @@ vlan_clone_create(struct if_clone *ifc, char *name, si
 			return (error);
 		}
 		vid = vlr.vlr_tag;
+		proto = vlr.vlr_proto;
 		wildcard = (unit < 0);
-	} else if ((p = vlan_clone_match_ethervid(name, &vid)) != NULL) {
-		unit = -1;
-		wildcard = 0;
 	} else {
 		p = NULL;
 		error = ifc_name2unit(name, &unit);
@@ -1092,7 +1083,7 @@ vlan_clone_create(struct if_clone *ifc, char *name, si
 	sdl->sdl_type = IFT_L2VLAN;
 
 	if (p != NULL) {
-		error = vlan_config(ifv, p, vid);
+		error = vlan_config(ifv, p, vid, proto);
 		if_rele(p);
 		if (error != 0) {
 			/*
@@ -1117,8 +1108,10 @@ static int
 vlan_clone_destroy(struct if_clone *ifc, struct ifnet *ifp)
 {
 	struct ifvlan *ifv = ifp->if_softc;
-	int unit = ifp->if_dunit;
 
+	if (ifp->if_vlantrunk)
+		return (EBUSY);
+
 	ether_ifdetach(ifp);	/* first, remove it from system-wide lists */
 	vlan_unconfig(ifp);	/* now it can be unconfigured and freed */
 	/*
@@ -1130,7 +1123,7 @@ vlan_clone_destroy(struct if_clone *ifc, struct ifnet 
 	NET_EPOCH_WAIT();
 	if_free(ifp);
 	free(ifv, M_VLAN);
-	ifc_free_unit(ifc, unit);
+	ifc_free_unit(ifc, ifp->if_dunit);
 
 	return (0);
 }
@@ -1196,7 +1189,7 @@ vlan_transmit(struct ifnet *ifp, struct mbuf *m)
 		return (ENETDOWN);
 	}
 
-	if (!ether_8021q_frame(&m, ifp, p, ifv->ifv_vid, ifv->ifv_pcp)) {
+	if (!ether_8021q_frame(&m, ifp, p, &ifv->ifv_qtag)) {
 		if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
 		return (0);
 	}
@@ -1223,12 +1216,19 @@ vlan_output(struct ifnet *ifp, struct mbuf *m, const s
 
 	NET_EPOCH_ASSERT();
 
+	/*
+	 * Find the first non-VLAN parent interface.
+	 */
 	ifv = ifp->if_softc;
-	if (TRUNK(ifv) == NULL) {
-		m_freem(m);
-		return (ENETDOWN);
-	}
-	p = PARENT(ifv);
+	do {
+		if (TRUNK(ifv) == NULL) {
+			m_freem(m);
+			return (ENETDOWN);
+		}
+		p = PARENT(ifv);
+		ifv = p->if_softc;
+	} while (p->if_type == IFT_L2VLAN);
+
 	return p->if_output(ifp, m, dst, ro);
 }
 
@@ -1357,7 +1357,8 @@ vlan_lladdr_fn(void *arg, int pending __unused)
 }
 
 static int
-vlan_config(struct ifvlan *ifv, struct ifnet *p, uint16_t vid)
+vlan_config(struct ifvlan *ifv, struct ifnet *p, uint16_t vid,
+	uint16_t proto)
 {
 	struct epoch_tracker et;
 	struct ifvlantrunk *trunk;
@@ -1369,6 +1370,7 @@ vlan_config(struct ifvlan *ifv, struct ifnet *p, uint1
 	 * they handle the tagging and headers themselves.
 	 */
 	if (p->if_type != IFT_ETHER &&
+	    p->if_type != IFT_L2VLAN &&
 	    (p->if_capenable & IFCAP_VLAN_HWTAGGING) == 0)
 		return (EPROTONOSUPPORT);
 	if ((p->if_flags & VLAN_IFFLAGS) != VLAN_IFFLAGS)
@@ -1400,11 +1402,10 @@ vlan_config(struct ifvlan *ifv, struct ifnet *p, uint1
 
 	ifv->ifv_vid = vid;	/* must set this before vlan_inshash() */
 	ifv->ifv_pcp = 0;       /* Default: best effort delivery. */
-	vlan_tag_recalculate(ifv);
 	error = vlan_inshash(trunk, ifv);
 	if (error)
 		goto done;
-	ifv->ifv_proto = ETHERTYPE_VLAN;
+	ifv->ifv_proto = proto;
 	ifv->ifv_encaplen = ETHER_VLAN_ENCAP_LEN;
 	ifv->ifv_mintu = ETHERMIN;
 	ifv->ifv_pflags = 0;
@@ -1915,7 +1916,7 @@ vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data
 			break;
 		}
 		oldmtu = ifp->if_mtu;
-		error = vlan_config(ifv, p, vlr.vlr_tag);
+		error = vlan_config(ifv, p, vlr.vlr_tag, vlr.vlr_proto);
 		if_rele(p);
 
 		/*
@@ -1943,11 +1944,12 @@ vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data
 			strlcpy(vlr.vlr_parent, PARENT(ifv)->if_xname,
 			    sizeof(vlr.vlr_parent));
 			vlr.vlr_tag = ifv->ifv_vid;
+			vlr.vlr_proto = ifv->ifv_proto;
 		}
 		VLAN_SUNLOCK();
 		error = copyout(&vlr, ifr_data_get_ptr(ifr), sizeof(vlr));
 		break;
-		
+
 	case SIOCSIFFLAGS:
 		/*
 		 * We should propagate selected flags to the parent,
@@ -2001,7 +2003,6 @@ vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data
 		}
 		ifv->ifv_pcp = ifr->ifr_vlan_pcp;
 		ifp->if_pcp = ifv->ifv_pcp;
-		vlan_tag_recalculate(ifv);
 		/* broadcast event about PCP change */
 		EVENTHANDLER_INVOKE(ifnet_event, ifp, IFNET_EVENT_PCP);
 		break;

Modified: head/sys/net/if_vlan_var.h
==============================================================================
--- head/sys/net/if_vlan_var.h	Wed Oct 21 20:42:29 2020	(r366916)
+++ head/sys/net/if_vlan_var.h	Wed Oct 21 21:28:20 2020	(r366917)
@@ -69,6 +69,7 @@
 struct	vlanreq {
 	char	vlr_parent[IFNAMSIZ];
 	u_short	vlr_tag;
+	u_short	vlr_proto;
 };
 #define	SIOCSETVLAN	SIOCSIFGENERIC
 #define	SIOCGETVLAN	SIOCGIFGENERIC
@@ -122,6 +123,15 @@ struct	vlanreq {
 #define	MTAG_8021Q		1326104895
 #define	MTAG_8021Q_PCP_IN	0		/* Input priority. */
 #define	MTAG_8021Q_PCP_OUT	1		/* Output priority. */
+
+/*
+ * 802.1q full tag. Proto and vid are stored in host byte order.
+ */
+struct ether_8021q_tag {
+	uint16_t proto;
+	uint16_t vid;
+	uint8_t  pcp;
+};
 
 #define	VLAN_CAPABILITIES(_ifp) do {				\
 	if ((_ifp)->if_vlantrunk != NULL) 			\

Modified: head/tests/sys/net/if_vlan.sh
==============================================================================
--- head/tests/sys/net/if_vlan.sh	Wed Oct 21 20:42:29 2020	(r366916)
+++ head/tests/sys/net/if_vlan.sh	Wed Oct 21 21:28:20 2020	(r366917)
@@ -36,7 +36,185 @@ basic_cleanup()
 	vnet_cleanup
 }
 
+# Simple Q-in-Q (802.1Q over 802.1ad)
+
+atf_test_case "qinq_simple" "cleanup"
+qinq_simple_head()
+{
+	atf_set descr 'Simple Q-in-Q test (802.1Q over 802.1ad)'
+	atf_set require.user root
+}
+
+qinq_simple_body()
+{
+	vnet_init
+
+	epair_qinq=$(vnet_mkepair)
+
+	vnet_mkjail jqinq0 ${epair_qinq}a
+	vnet_mkjail jqinq1 ${epair_qinq}b
+
+	vlan5a=$(jexec jqinq0 ifconfig vlan create \
+		vlandev ${epair_qinq}a vlan 5 vlanproto 802.1ad)
+	vlan42a=$(jexec jqinq0 ifconfig vlan create \
+		vlandev ${vlan5a} vlan 42 vlanproto 802.1q)
+	jexec jqinq0 ifconfig ${epair_qinq}a up
+	jexec jqinq0 ifconfig ${vlan5a} up
+	jexec jqinq0 ifconfig ${vlan42a} 10.5.42.1/24 up
+
+	vlan5b=$(jexec jqinq1 ifconfig vlan create \
+		vlandev ${epair_qinq}b vlan 5 vlanproto 802.1ad)
+	vlan42b=$(jexec jqinq1 ifconfig vlan create \
+		vlandev ${vlan5b} vlan 42 vlanproto 802.1q)
+	jexec jqinq1 ifconfig ${epair_qinq}b up
+	jexec jqinq1 ifconfig ${vlan5b} up
+	jexec jqinq1 ifconfig ${vlan42b} 10.5.42.2/24 up
+
+	atf_check -s exit:0 -o ignore jexec jqinq1 ping -c 1 10.5.42.1
+}
+
+qinq_simple_cleanup()
+{
+	vnet_cleanup
+}
+
+# Deep Q-in-Q (802.1Q over 802.1ad over 802.1ad)
+
+atf_test_case "qinq_deep" "cleanup"
+qinq_deep_head()
+{
+	atf_set descr 'Deep Q-in-Q test (802.1Q over 802.1ad over 802.1ad)'
+	atf_set require.user root
+}
+
+qinq_deep_body()
+{
+	vnet_init
+
+	epair_qinq=$(vnet_mkepair)
+
+	vnet_mkjail jqinq2 ${epair_qinq}a
+	vnet_mkjail jqinq3 ${epair_qinq}b
+
+	vlan5a=$(jexec jqinq2 ifconfig vlan create \
+		vlandev ${epair_qinq}a vlan 5 vlanproto 802.1ad)
+	vlan6a=$(jexec jqinq2 ifconfig vlan create \
+		vlandev ${vlan5a} vlan 6 vlanproto 802.1ad)
+	vlan42a=$(jexec jqinq2 ifconfig vlan create \
+		vlandev ${vlan6a} vlan 42 vlanproto 802.1q)
+	jexec jqinq2 ifconfig ${epair_qinq}a up
+	jexec jqinq2 ifconfig ${vlan5a} up
+	jexec jqinq2 ifconfig ${vlan6a} up
+	jexec jqinq2 ifconfig ${vlan42a} 10.6.42.1/24 up
+
+	vlan5b=$(jexec jqinq3 ifconfig vlan create \
+		vlandev ${epair_qinq}b vlan 5 vlanproto 802.1ad)
+	vlan6b=$(jexec jqinq3 ifconfig vlan create \
+		vlandev ${vlan5b} vlan 6 vlanproto 802.1ad)
+	vlan42b=$(jexec jqinq3 ifconfig vlan create \
+		vlandev ${vlan6b} vlan 42 vlanproto 802.1q)
+	jexec jqinq3 ifconfig ${epair_qinq}b up
+	jexec jqinq3 ifconfig ${vlan5b} up
+	jexec jqinq3 ifconfig ${vlan6b} up
+	jexec jqinq3 ifconfig ${vlan42b} 10.6.42.2/24 up
+
+	atf_check -s exit:0 -o ignore jexec jqinq3 ping -c 1 10.6.42.1
+}
+
+qinq_deep_cleanup()
+{
+	vnet_cleanup
+}
+
+# Legacy Q-in-Q (802.1Q over 802.1Q)
+
+atf_test_case "qinq_legacy" "cleanup"
+qinq_legacy_head()
+{
+	atf_set descr 'Legacy Q-in-Q test (802.1Q over 802.1Q)'
+	atf_set require.user root
+}
+
+qinq_legacy_body()
+{
+	vnet_init
+
+	epair_qinq=$(vnet_mkepair)
+
+	vnet_mkjail jqinq4 ${epair_qinq}a
+	vnet_mkjail jqinq5 ${epair_qinq}b
+
+	vlan5a=$(jexec jqinq4 ifconfig vlan create \
+		vlandev ${epair_qinq}a vlan 5)
+	vlan42a=$(jexec jqinq4 ifconfig vlan create \
+		vlandev ${vlan5a} vlan 42)
+	jexec jqinq4 ifconfig ${epair_qinq}a up
+	jexec jqinq4 ifconfig ${vlan5a} up
+	jexec jqinq4 ifconfig ${vlan42a} 10.5.42.1/24 up
+
+	vlan5b=$(jexec jqinq5 ifconfig vlan create \
+		vlandev ${epair_qinq}b vlan 5)
+	vlan42b=$(jexec jqinq5 ifconfig vlan create \
+		vlandev ${vlan5b} vlan 42)
+	jexec jqinq5 ifconfig ${epair_qinq}b up
+	jexec jqinq5 ifconfig ${vlan5b} up
+	jexec jqinq5 ifconfig ${vlan42b} 10.5.42.2/24 up
+
+	atf_check -s exit:0 -o ignore jexec jqinq5 ping -c 1 10.5.42.1
+}
+
+qinq_legacy_cleanup()
+{
+	vnet_cleanup
+}
+
+# Simple Q-in-Q with dot notation
+
+atf_test_case "qinq_dot" "cleanup"
+qinq_dot_head()
+{
+	atf_set descr 'Simple Q-in-Q test with dot notation'
+	atf_set require.user root
+}
+
+qinq_dot_body()
+{
+	vnet_init
+
+	epair_qinq=$(vnet_mkepair)
+
+	vnet_mkjail jqinq6 ${epair_qinq}a
+	vnet_mkjail jqinq7 ${epair_qinq}b
+
+	jexec jqinq6 ifconfig vlan5 create \
+		vlandev ${epair_qinq}a vlan 5 vlanproto 802.1ad
+	jexec jqinq6 ifconfig vlan5.42 create \
+		vlanproto 802.1q
+	jexec jqinq6 ifconfig ${epair_qinq}a up
+	jexec jqinq6 ifconfig vlan5 up
+	jexec jqinq6 ifconfig vlan5.42 10.5.42.1/24 up
+
+	vlan5b=$(jexec jqinq7 ifconfig vlan create \
+		vlandev ${epair_qinq}b vlan 5 vlanproto 802.1ad)
+	vlan42b=$(jexec jqinq7 ifconfig vlan create \
+		vlandev ${vlan5b} vlan 42 vlanproto 802.1q)
+	jexec jqinq7 ifconfig ${epair_qinq}b up
+	jexec jqinq7 ifconfig ${vlan5b} up
+	jexec jqinq7 ifconfig ${vlan42b} 10.5.42.2/24 up
+
+	atf_check -s exit:0 -o ignore jexec jqinq7 ping -c 1 10.5.42.1
+}
+
+qinq_dot_cleanup()
+{
+	vnet_cleanup
+}
+
 atf_init_test_cases()
 {
 	atf_add_test_case "basic"
+	atf_add_test_case "qinq_simple"
+	atf_add_test_case "qinq_deep"
+	atf_add_test_case "qinq_legacy"
+	atf_add_test_case "qinq_dot"
 }



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202010212128.09LLSK8d080756>