git: f94c370de6e7 - main - bridge: Allow VLAN protocol to be configured

From: Lexi Winter <ivy_at_FreeBSD.org>
Date: Tue, 05 Aug 2025 19:26:02 UTC
The branch main has been updated by ivy:

URL: https://cgit.FreeBSD.org/src/commit/?id=f94c370de6e7d32cd3b635893870438b744214e7

commit f94c370de6e7d32cd3b635893870438b744214e7
Author:     Lexi Winter <ivy@FreeBSD.org>
AuthorDate: 2025-08-05 17:51:11 +0000
Commit:     Lexi Winter <ivy@FreeBSD.org>
CommitDate: 2025-08-05 18:35:30 +0000

    bridge: Allow VLAN protocol to be configured
    
    Add a new per-interface option "ifvlanproto", which can be either
    "802.1q" (the default) or "802.1ad".  This controls what type of
    tag we attach to outgoing packets on the interface.
    
    Reviewed by:            pauamma_gundo.com (manpages)
    Differential Revision:  https://reviews.freebsd.org/D51231
---
 sbin/ifconfig/ifbridge.c | 38 ++++++++++++++++++++++++++++++++++++++
 sbin/ifconfig/ifconfig.8 | 11 +++++++++++
 sys/net/if_bridge.c      | 36 ++++++++++++++++++++++++++++++++----
 sys/net/if_bridgevar.h   |  3 +++
 4 files changed, 84 insertions(+), 4 deletions(-)

diff --git a/sbin/ifconfig/ifbridge.c b/sbin/ifconfig/ifbridge.c
index 3566acdcf54c..d02d92d10b59 100644
--- a/sbin/ifconfig/ifbridge.c
+++ b/sbin/ifconfig/ifbridge.c
@@ -190,6 +190,21 @@ print_vlans(ifbvlan_set_t *vlans)
 	}
 }
 
+static char const *
+vlan_proto_name(uint16_t proto)
+{
+	switch (proto) {
+	case 0:
+		return "none";
+	case ETHERTYPE_VLAN:
+		return "802.1q";
+	case ETHERTYPE_QINQ:
+		return "802.1ad";
+	default:
+		return "unknown";
+	}
+}
+
 static void
 bridge_status(if_ctx *ctx)
 {
@@ -261,6 +276,9 @@ bridge_status(if_ctx *ctx)
 			else
 				printf(" <unknown state %d>", state);
 		}
+		if (member->ifbr_vlanproto != 0)
+			printf(" vlan protocol %s",
+			    vlan_proto_name(member->ifbr_vlanproto));
 		if (member->ifbr_pvid != 0)
 			printf(" untagged %u", (unsigned)member->ifbr_pvid);
 		print_vlans(&bridge->member_vlans[i]);
@@ -895,6 +913,25 @@ unsetbridge_qinq(if_ctx *ctx, const char *val, int dummy __unused)
 	do_bridgeflag(ctx, val, IFBIF_QINQ, 0);
 }
 
+static void
+setbridge_ifvlanproto(if_ctx *ctx, const char *ifname, const char *proto)
+{
+	struct ifbreq req;
+
+	memset(&req, 0, sizeof(req));
+	strlcpy(req.ifbr_ifsname, ifname, sizeof(req.ifbr_ifsname));
+
+	if (strcmp(proto, "802.1q") == 0)
+		req.ifbr_vlanproto = ETHERTYPE_VLAN;
+	else if (strcmp(proto, "802.1ad") == 0)
+		req.ifbr_vlanproto = ETHERTYPE_QINQ;
+	else
+		errx(1, "unrecognised VLAN protocol: %s", proto);
+
+	if (do_cmd(ctx, BRDGSIFVLANPROTO, &req, sizeof(req), 1) < 0)
+		err(1, "BRDGSIFVLANPROTO");
+}
+
 static struct cmd bridge_cmds[] = {
 	DEF_CMD_ARG("addm",		setbridge_add),
 	DEF_CMD_ARG("deletem",		setbridge_delete),
@@ -936,6 +973,7 @@ static struct cmd bridge_cmds[] = {
 	DEF_CMD_ARG2("tagged",		setbridge_tagged),
 	DEF_CMD_ARG2("+tagged",		addbridge_tagged),
 	DEF_CMD_ARG2("-tagged",		delbridge_tagged),
+	DEF_CMD_ARG2("ifvlanproto",	setbridge_ifvlanproto),
 	DEF_CMD_ARG("timeout",		setbridge_timeout),
 	DEF_CMD_ARG("private",		setbridge_private),
 	DEF_CMD_ARG("-private",		unsetbridge_private),
diff --git a/sbin/ifconfig/ifconfig.8 b/sbin/ifconfig/ifconfig.8
index 69a81f72421f..d7b2570ab20d 100644
--- a/sbin/ifconfig/ifconfig.8
+++ b/sbin/ifconfig/ifconfig.8
@@ -2765,6 +2765,17 @@ Do not enable the
 .Cm qinq
 option by default on newly added members.
 This is the default behavior.
+.It Cm ifvlanproto Ar interface Ar proto
+Set the VLAN encapsulation protocol on
+.Ar interface
+to
+.Ar proto ,
+which must be either
+.Dq 802.1q
+or
+.Dq 802.1ad .
+The default is
+.Dq 802.1q .
 .El
 .Ss Link Aggregation and Link Failover Parameters
 The following parameters are specific to lagg interfaces:
diff --git a/sys/net/if_bridge.c b/sys/net/if_bridge.c
index 26ea4400e67d..945318c5af1a 100644
--- a/sys/net/if_bridge.c
+++ b/sys/net/if_bridge.c
@@ -259,6 +259,7 @@ struct bridge_iflist {
 	struct epoch_context	bif_epoch_ctx;
 	ether_vlanid_t		bif_pvid;	/* port vlan id */
 	ifbvlan_set_t		bif_vlan_set;	/* if allowed tagged vlans */
+	uint16_t		bif_vlanproto;	/* vlan protocol */
 };
 
 /*
@@ -423,6 +424,7 @@ static int	bridge_ioctl_gflags(struct bridge_softc *, void *);
 static int	bridge_ioctl_sflags(struct bridge_softc *, void *);
 static int	bridge_ioctl_gdefpvid(struct bridge_softc *, void *);
 static int	bridge_ioctl_sdefpvid(struct bridge_softc *, void *);
+static int	bridge_ioctl_svlanproto(struct bridge_softc *, void *);
 static int	bridge_pfil(struct mbuf **, struct ifnet *, struct ifnet *,
 		    int);
 #ifdef INET
@@ -654,6 +656,9 @@ static const struct bridge_control bridge_control_table[] = {
 
 	{ bridge_ioctl_sdefpvid,	sizeof(struct ifbrparam),
 	  BC_F_COPYIN|BC_F_SUSER },
+
+	{ bridge_ioctl_svlanproto,	sizeof(struct ifbreq),
+	  BC_F_COPYIN|BC_F_SUSER },
 };
 static const int bridge_control_table_size = nitems(bridge_control_table);
 
@@ -1494,6 +1499,7 @@ bridge_ioctl_add(struct bridge_softc *sc, void *arg)
 	bif->bif_ifp = ifs;
 	bif->bif_flags = IFBIF_LEARNING | IFBIF_DISCOVER;
 	bif->bif_savedcaps = ifs->if_capenable;
+	bif->bif_vlanproto = ETHERTYPE_VLAN;
 	if (sc->sc_flags & IFBRF_VLANFILTER)
 		bif->bif_pvid = sc->sc_defpvid;
 	if (sc->sc_flags & IFBRF_DEFQINQ)
@@ -1579,6 +1585,7 @@ bridge_ioctl_gifflags(struct bridge_softc *sc, void *arg)
 	req->ifbr_addrmax = bif->bif_addrmax;
 	req->ifbr_addrexceeded = bif->bif_addrexceeded;
 	req->ifbr_pvid = bif->bif_pvid;
+	req->ifbr_vlanproto = bif->bif_vlanproto;
 
 	/* Copy STP state options as flags */
 	if (bp->bp_operedge)
@@ -2254,6 +2261,24 @@ bridge_ioctl_sdefpvid(struct bridge_softc *sc, void *arg)
 	return (0);
 }
 
+static int
+bridge_ioctl_svlanproto(struct bridge_softc *sc, void *arg)
+{
+	struct ifbreq *req = arg;
+	struct bridge_iflist *bif;
+
+	bif = bridge_lookup_member(sc, req->ifbr_ifsname);
+	if (bif == NULL)
+		return (EXTERROR(ENOENT, "Interface is not a bridge member"));
+
+	if (req->ifbr_vlanproto != ETHERTYPE_VLAN &&
+	    req->ifbr_vlanproto != ETHERTYPE_QINQ)
+		return (EXTERROR(EINVAL, "Invalid VLAN protocol"));
+
+	bif->bif_vlanproto = req->ifbr_vlanproto;
+
+	return (0);
+}
 /*
  * bridge_ifdetach:
  *
@@ -2397,12 +2422,15 @@ bridge_enqueue(struct bridge_softc *sc, struct ifnet *dst_ifp, struct mbuf *m,
 		}
 
 		/*
-		 * If underlying interface can not do VLAN tag insertion itself
-		 * then attach a packet tag that holds it.
+		 * There are two cases where we have to insert our own tag:
+		 * if the member interface doesn't support hardware tagging,
+		 * or if the tag proto is not 802.1q.
 		 */
 		if ((m->m_flags & M_VLANTAG) &&
-		    (dst_ifp->if_capenable & IFCAP_VLAN_HWTAGGING) == 0) {
-			m = ether_vlanencap(m, m->m_pkthdr.ether_vtag);
+		    ((dst_ifp->if_capenable & IFCAP_VLAN_HWTAGGING) == 0 ||
+		      bif->bif_vlanproto != ETHERTYPE_VLAN)) {
+			m = ether_vlanencap_proto(m, m->m_pkthdr.ether_vtag,
+			    bif->bif_vlanproto);
 			if (m == NULL) {
 				if_printf(dst_ifp,
 				    "unable to prepend VLAN header\n");
diff --git a/sys/net/if_bridgevar.h b/sys/net/if_bridgevar.h
index 15194fecff7c..b0f579f688ac 100644
--- a/sys/net/if_bridgevar.h
+++ b/sys/net/if_bridgevar.h
@@ -131,6 +131,7 @@
 #define	BRDGSFLAGS		35	/* set bridge flags (ifbrparam) */
 #define	BRDGGDEFPVID		36	/* get default pvid (ifbrparam) */
 #define	BRDGSDEFPVID		37	/* set default pvid (ifbrparam) */
+#define	BRDGSIFVLANPROTO	38	/* set if vlan protocol (ifbreq) */
 
 /* BRDGSFLAGS, Bridge flags (non-interface-specific) */
 typedef uint32_t ifbr_flags_t;
@@ -157,6 +158,7 @@ struct ifbreq {
 	uint32_t	ifbr_addrmax;		/* member if addr max */
 	uint32_t	ifbr_addrexceeded;	/* member if addr violations */
 	ether_vlanid_t	ifbr_pvid;		/* member if PVID */
+	uint16_t	ifbr_vlanproto;		/* member if VLAN protocol */
 	uint8_t		pad[32];
 };
 
@@ -252,6 +254,7 @@ struct ifbrparam {
 							 * addresses */
 #define	ifbrp_flags	ifbrp_ifbrpu.ifbrpu_int32	/* bridge flags */
 #define	ifbrp_defpvid	ifbrp_ifbrpu.ifbrpu_int16	/* default pvid */
+#define	ifbrp_vlanproto	ifbrp_ifbrpu.ifbrpu_int8	/* vlan protocol */
 
 /*
  * Bridge current operational parameters structure.