diff --git a/sbin/ifconfig/ifbridge.c b/sbin/ifconfig/ifbridge.c --- a/sbin/ifconfig/ifbridge.c +++ b/sbin/ifconfig/ifbridge.c @@ -857,6 +857,18 @@ 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), @@ -909,6 +921,12 @@ 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 = { diff --git a/sbin/ifconfig/ifconfig.8 b/sbin/ifconfig/ifconfig.8 --- a/sbin/ifconfig/ifconfig.8 +++ b/sbin/ifconfig/ifconfig.8 @@ -2714,6 +2714,7 @@ Do not enable the .Cm vlanfilter option by default on newly added members. +This is the default behavior. .It Cm untagged Ar interface Ar vlan-id Set the untagged VLAN identifier for an interface. .Pp @@ -2730,6 +2731,7 @@ Do not enable the .Cm untagged option by default on newly added members. +This is the default behavior. .It Cm tagged Ar interface Ar vlan-list Set the interface's VLAN access list to the provided list of VLANs. The list should be a comma-separated list of one or more VLAN IDs @@ -2762,6 +2764,24 @@ Setting .Cm -tagged will automatically enable VLAN filtering on the interface. +.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: diff --git a/share/man/man4/bridge.4 b/share/man/man4/bridge.4 --- a/share/man/man4/bridge.4 +++ b/share/man/man4/bridge.4 @@ -521,6 +521,48 @@ and .Xr inet6 4 addresses as normal. +.Ss IEEE 802.1ad (Q-in-Q) configuration +IEEE 802.1ad, also called Q-in-Q or +.Dq tag stacking , +allows a single Ethernet frame to contain multiple tags. +This allows one Ethernet network to transport traffic between endpoints +using its own VLAN tags without interfering with any pre-existing tags, +and is often used in service provider networks to provide +.Dq virtual wire +Ethernet services. +.Pp +By default, +.Nm +does not permit member interfaces to send Q-in-Q frames, because in +certain configuration this allows +.Dq VLAN-hopping +attacks on the bridge. +For example, consider a bridge with port ix0 configured as a tagged +port in VLAN 10, and port ix1 configured as untagged in VLAN 10 and +tagged in VLAN 20. +If ix0 is allowed to send Q-in-Q frames, then it can send a frame with +two tags: one for VLAN 10, followed by one for VLAN 20. +When the bridge forwards the frame to ix1, it will strip the VLAN tag +for VLAN 10, then forward the frame to ix1 with the tag for VLAN 20 +intact, effectively allowing ix1 to send traffic on VLAN 20 even +though the bridge configuration should not permit that. +.Pp +To permit an interface to send Q-in-Q frames, enable the +.Xr ifconfig 8 +.Cm qinq +flag on the interface. +This is only required on the interface which will send Q-in-Q frames, +not interface receiving the frames. +To enable Q-in-Q for all interfaces by default, set the +.Cm defqinq +flag on the bridge. +.Pp +To avoid VLAN-hopping attacks when enabling Q-in-Q, it is recommended +that all bridge ports be configured with either the +.Dq tagged +option or the +.Dq untagged +option, not both. .Ss Migrating from legacy to VLAN filtering bridge configuration For most configurations, migrating an existing legacy configuration to a VLAN filtering configuration is straightforward. diff --git a/sys/net/if_bridge.c b/sys/net/if_bridge.c --- a/sys/net/if_bridge.c +++ b/sys/net/if_bridge.c @@ -1474,6 +1474,8 @@ bif->bif_flags |= IFBIF_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 @@ -2809,6 +2811,16 @@ 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); @@ -3231,6 +3243,18 @@ if ((sbif->bif_flags & IFBIF_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 diff --git a/sys/net/if_bridgevar.h b/sys/net/if_bridgevar.h --- a/sys/net/if_bridgevar.h +++ b/sys/net/if_bridgevar.h @@ -136,8 +136,9 @@ typedef uint32_t ifbr_flags_t; #define IFBRF_DEFVLANFILTER (1U<<0) /* enable vlanfilter by default */ +#define IFBRF_DEFQINQ (1U<<1) /* 802.1ad Q-in-Q allowed */ -#define IFBRFBITS "\020\01DEFVLANFILTER" +#define IFBRFBITS "\020\01DEFVLANFILTER\02DEFQINQ" /* * Generic bridge control request. @@ -173,10 +174,11 @@ #define IFBIF_BSTP_ADMCOST 0x0400 /* member stp admin path cost */ #define IFBIF_PRIVATE 0x0800 /* if is a private segment */ #define IFBIF_VLANFILTER 0x1000 /* if does vlan filtering */ +#define IFBIF_QINQ 0x2000 /* if allows 802.1ad Q-in-Q */ #define IFBIFBITS "\020\001LEARNING\002DISCOVER\003STP\004SPAN" \ "\005STICKY\014PRIVATE\006EDGE\007AUTOEDGE\010PTP" \ - "\011AUTOPTP\015VLANFILTER" + "\011AUTOPTP\015VLANFILTER\016QINQ" #define IFBIFMASK ~(IFBIF_BSTP_EDGE|IFBIF_BSTP_AUTOEDGE|IFBIF_BSTP_PTP| \ IFBIF_BSTP_AUTOPTP|IFBIF_BSTP_ADMEDGE| \ IFBIF_BSTP_ADMCOST) /* not saved */ diff --git a/tests/sys/net/if_bridge_test.sh b/tests/sys/net/if_bridge_test.sh --- a/tests/sys/net/if_bridge_test.sh +++ b/tests/sys/net/if_bridge_test.sh @@ -1188,21 +1188,25 @@ # 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} defvlanfilter 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. @@ -1210,10 +1214,16 @@ 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()