bridge: Make 802.1ad (Q-in-Q) configurable

Allowing tag stacking by default can permit VLAN-hopping attacks in
certain configurations.  To mitigate this, disallow sending Q-in-Q
frames by default unless the new "qinq" option is enabled on the
interface.  The bridge flag "defqinq" can be used to restore the
previous behaviour of allowing Q-in-Q on all interfaces.

The bridge.4 changes from the differential are omitted here and
will be landed via D51185.

Reviewed by:		kevans, pauamma_gundo.com (manpages)
Differential Revision:	https://reviews.freebsd.org/D51227
This commit is contained in:
Lexi Winter
2025-08-05 18:43:31 +01:00
parent d1bcdbd247
commit d43ac3b76e
5 changed files with 88 additions and 18 deletions
+18
View File
@@ -883,6 +883,18 @@ unsetbridge_defuntagged(if_ctx *ctx, const char *val __unused, int dummy __unuse
err(1, "BRDGSDEFPVID");
}
static void
setbridge_qinq(if_ctx *ctx, const char *val, int dummy __unused)
{
do_bridgeflag(ctx, val, IFBIF_QINQ, 1);
}
static void
unsetbridge_qinq(if_ctx *ctx, const char *val, int dummy __unused)
{
do_bridgeflag(ctx, val, IFBIF_QINQ, 0);
}
static struct cmd bridge_cmds[] = {
DEF_CMD_ARG("addm", setbridge_add),
DEF_CMD_ARG("deletem", setbridge_delete),
@@ -933,6 +945,12 @@ static struct cmd bridge_cmds[] = {
unsetbridge_flags),
DEF_CMD_ARG("defuntagged", setbridge_defuntagged),
DEF_CMD("-defuntagged", 0, unsetbridge_defuntagged),
DEF_CMD("defqinq", (int32_t)IFBRF_DEFQINQ,
setbridge_flags),
DEF_CMD("-defqinq", (int32_t)IFBRF_DEFQINQ,
unsetbridge_flags),
DEF_CMD_ARG("qinq", setbridge_qinq),
DEF_CMD_ARG("-qinq", unsetbridge_qinq),
};
static struct afswtch af_bridge = {
+19 -1
View File
@@ -28,7 +28,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd July 30, 2025
.Dd August 5, 2025
.Dt IFCONFIG 8
.Os
.Sh NAME
@@ -2747,6 +2747,24 @@ Remove the provided list of VLAN IDs from the interface's VLAN access
list.
The list should be formatted as described for
.Cm tagged .
.It Cm qinq Ar interface
Allow this interface to send 802.1ad
.Dq Q-in-Q
frames.
.It Cm -qinq Ar interface
Do not allow this interface to send 802.1ad
.Dq Q-in-Q
frames.
This is the default behavior.
.It Cm defqinq
Enable the
.Cm qinq
option by default on newly added members.
.It Cm -defqinq
Do not enable the
.Cm qinq
option by default on newly added members.
This is the default behavior.
.El
.Ss Link Aggregation and Link Failover Parameters
The following parameters are specific to lagg interfaces:
+24
View File
@@ -1496,6 +1496,8 @@ bridge_ioctl_add(struct bridge_softc *sc, void *arg)
bif->bif_savedcaps = ifs->if_capenable;
if (sc->sc_flags & IFBRF_VLANFILTER)
bif->bif_pvid = sc->sc_defpvid;
if (sc->sc_flags & IFBRF_DEFQINQ)
bif->bif_flags |= IFBIF_QINQ;
/*
* Assign the interface's MAC address to the bridge if it's the first
@@ -2828,6 +2830,16 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
NET_EPOCH_ASSERT();
/* We need the Ethernet header later, so make sure we have it now. */
if (m->m_len < ETHER_HDR_LEN) {
m = m_pullup(m, ETHER_HDR_LEN);
if (m == NULL) {
if_inc_counter(sc->sc_ifp, IFCOUNTER_IERRORS, 1);
m_freem(m);
return (NULL);
}
}
eh = mtod(m, struct ether_header *);
vlan = VLANTAGOF(m);
@@ -3250,6 +3262,18 @@ bridge_vfilter_in(const struct bridge_iflist *sbif, struct mbuf *m)
if ((sbif->bif_sc->sc_flags & IFBRF_VLANFILTER) == 0)
return (true);
/* If Q-in-Q is disabled, check for stacked tags. */
if ((sbif->bif_flags & IFBIF_QINQ) == 0) {
struct ether_header *eh;
uint16_t proto;
eh = mtod(m, struct ether_header *);
proto = ntohs(eh->ether_type);
if (proto == ETHERTYPE_VLAN || proto == ETHERTYPE_QINQ)
return (false);
}
if (vlan == DOT1Q_VID_NULL) {
/*
* The frame doesn't have a tag. If the interface does not
+4 -4
View File
@@ -136,8 +136,9 @@
typedef uint32_t ifbr_flags_t;
#define IFBRF_VLANFILTER (1U<<0) /* VLAN filtering enabled */
#define IFBRF_DEFQINQ (1U<<1) /* 802.1ad Q-in-Q allowed by default */
#define IFBRFBITS "\020\01VLANFILTER"
#define IFBRFBITS "\020\01VLANFILTER\02DEFQINQ"
/*
* Generic bridge control request.
@@ -172,12 +173,11 @@ struct ifbreq {
#define IFBIF_BSTP_ADMEDGE 0x0200 /* member stp admin edge enabled */
#define IFBIF_BSTP_ADMCOST 0x0400 /* member stp admin path cost */
#define IFBIF_PRIVATE 0x0800 /* if is a private segment */
/* was IFBIF_VLANFILTER 0x1000 */
#define IFBIF_QINQ 0x2000 /* if allows 802.1ad Q-in-Q */
#define IFBIF_QINQ 0x1000 /* if allows 802.1ad Q-in-Q */
#define IFBIFBITS "\020\001LEARNING\002DISCOVER\003STP\004SPAN" \
"\005STICKY\014PRIVATE\006EDGE\007AUTOEDGE\010PTP" \
"\011AUTOPTP"
"\011AUTOPTP\015QINQ"
#define IFBIFMASK ~(IFBIF_BSTP_EDGE|IFBIF_BSTP_AUTOEDGE|IFBIF_BSTP_PTP| \
IFBIF_BSTP_AUTOPTP|IFBIF_BSTP_ADMEDGE| \
IFBIF_BSTP_ADMCOST) /* not saved */
+23 -13
View File
@@ -1268,21 +1268,25 @@ vlan_qinq_body()
# Create a QinQ trunk between the two jails. The outer (provider) tag
# is 5, and the inner tag is 10.
jexec one ifconfig ${epone}b up
jexec one ifconfig ${epone}b.5 create vlanproto 802.1ad up
jexec one ifconfig ${epone}b.5.10 create inet 192.0.2.1/24 up
atf_check -s exit:0 jexec one ifconfig ${epone}b up
atf_check -s exit:0 jexec one \
ifconfig ${epone}b.5 create vlanproto 802.1ad up
atf_check -s exit:0 jexec one \
ifconfig ${epone}b.5.10 create inet 192.0.2.1/24 up
jexec two ifconfig ${eptwo}b up
jexec two ifconfig ${eptwo}b.5 create vlanproto 802.1ad up
jexec two ifconfig ${eptwo}b.5.10 create inet 192.0.2.2/24 up
atf_check -s exit:0 jexec two ifconfig ${eptwo}b up
atf_check -s exit:0 jexec two ifconfig \
${eptwo}b.5 create vlanproto 802.1ad up
atf_check -s exit:0 jexec two ifconfig \
${eptwo}b.5.10 create inet 192.0.2.2/24 up
bridge=$(vnet_mkbridge)
ifconfig ${bridge} up
ifconfig ${epone}a up
ifconfig ${eptwo}a up
ifconfig ${bridge} addm ${epone}a vlanfilter ${epone}a
ifconfig ${bridge} addm ${eptwo}a vlanfilter ${eptwo}a
atf_check -s exit:0 ifconfig ${bridge} vlanfilter defqinq up
atf_check -s exit:0 ifconfig ${epone}a up
atf_check -s exit:0 ifconfig ${eptwo}a up
atf_check -s exit:0 ifconfig ${bridge} addm ${epone}a
atf_check -s exit:0 ifconfig ${bridge} addm ${eptwo}a
# Right now there are no VLANs on the access list, so everything
# should be blocked.
@@ -1290,10 +1294,16 @@ vlan_qinq_body()
atf_check -s exit:2 -o ignore jexec two ping -c 3 -t 1 192.0.2.1
# Add the provider tag to the access list; now traffic should be passed.
ifconfig ${bridge} +tagged ${epone}a 5
ifconfig ${bridge} +tagged ${eptwo}a 5
atf_check -s exit:0 ifconfig ${bridge} +tagged ${epone}a 5
atf_check -s exit:0 ifconfig ${bridge} +tagged ${eptwo}a 5
atf_check -s exit:0 -o ignore jexec one ping -c 3 -t 1 192.0.2.2
atf_check -s exit:0 -o ignore jexec two ping -c 3 -t 1 192.0.2.1
# Remove the qinq flag from one of the interfaces; traffic should
# be blocked again.
atf_check -s exit:0 ifconfig ${bridge} -qinq ${epone}a
atf_check -s exit:2 -o ignore jexec one ping -c 3 -t 1 192.0.2.2
atf_check -s exit:2 -o ignore jexec two ping -c 3 -t 1 192.0.2.1
}
vlan_qinq_cleanup()