diff --git a/sbin/ifconfig/ifconfig.8 b/sbin/ifconfig/ifconfig.8 --- a/sbin/ifconfig/ifconfig.8 +++ b/sbin/ifconfig/ifconfig.8 @@ -28,7 +28,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd April 24, 2025 +.Dd May 23, 2025 .Dt IFCONFIG 8 .Os .Sh NAME @@ -2698,8 +2698,10 @@ .It Cm ifpvid Ar interface Ar vlan-id Set the Port VLAN ID (PVID, sometimes called native VLAN ID) for this interface. -Incoming frames which do not have an 802.1Q VLAN tag will inherit the -PVID from the interface on which they were received. +The effect of this option is described in the +.Dq VLAN SUPPORT +section of +.Xr bridge 4 . .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 @@ -36,7 +36,7 @@ .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE .\" POSSIBILITY OF SUCH DAMAGE. .\" -.Dd May 17, 2025 +.Dd May 23, 2025 .Dt IF_BRIDGE 4 .Os .Sh NAME @@ -275,24 +275,25 @@ driver has limited support for virtual LANs (VLANs). An incoming packet with an 802.1Q tag will be assigned to the appropriate VLAN. +The bridge always implements independent VLAN learning, i.e. MAC +addresses are learnt on a per-VLAN basis, and the same MAC address may +be learnt on multiple ports on different VLANs. +.Pp An interface's Port VLAN ID (PVID, sometimes called native VLAN ID) may be configured using the .Xr ifconfig 8 .Cm ifpvid -option; incoming packets with no 802.1Q tag (or where the VLAN ID in the -tag is zero) will be assigned to the interface's PVID. +option. +This affects handling of both incoming and outgoing frames. +For incoming frames, if the frame has no 802.1Q tag (or the VLAN ID in +the tag is zero), a tag will be inserted based on the port's PVID. +For outgoing frames, if the frame has an 802.1Q tag which matches the +port's PVID, the tag will be stripped from the frame prior to sending. .Pp An interface without a PVID configured will receive frames for all VLANs. -An interface with a PVID configured will only receive frames for its -configured PVID. -In either case, if the incoming frame has an 802.1Q tag, the tag will be -preserved in the output frame; if not, no tag will be added to the -output frame. -.Pp -There is no support for adding or removing 802.1Q tags on outgoing -frames, and for interfaces without a PVID configured, there is no way to -restrict which VLANs the interface can send or receive frames for. +An interface with a PVID configured may only send and receive frames for +its configured PVID. .Sh PACKET FILTERING Packet filtering can be used with any firewall package that hooks in via the .Xr pfil 9 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 @@ -332,16 +332,16 @@ static int bridge_output(struct ifnet *, struct mbuf *, struct sockaddr *, struct rtentry *); static int bridge_enqueue(struct bridge_softc *, struct ifnet *, - struct mbuf *); + struct mbuf *, struct bridge_iflist *); static void bridge_rtdelete(struct bridge_softc *, struct ifnet *ifp, int); static void bridge_forward(struct bridge_softc *, struct bridge_iflist *, - struct mbuf *m, ether_vlanid_t vlan); + struct mbuf *m); static bool bridge_member_ifaddrs(void); static void bridge_timer(void *); static void bridge_broadcast(struct bridge_softc *, struct ifnet *, - struct mbuf *, int, ether_vlanid_t); + struct mbuf *, int); static void bridge_span(struct bridge_softc *, struct mbuf *); static int bridge_rtupdate(struct bridge_softc *, const uint8_t *, @@ -2172,12 +2172,25 @@ * */ static int -bridge_enqueue(struct bridge_softc *sc, struct ifnet *dst_ifp, struct mbuf *m) +bridge_enqueue(struct bridge_softc *sc, struct ifnet *dst_ifp, struct mbuf *m, + struct bridge_iflist *bif) { int len, err = 0; short mflags; struct mbuf *m0; + /* + * Find the bridge member port this packet is being sent on, if the + * caller didn't already provide it. + */ + if (bif == NULL) + bif = bridge_lookup_member_if(sc, dst_ifp); + if (bif == NULL) { + /* Perhaps the interface was removed from the bridge */ + m_freem(m); + return (EINVAL); + } + /* We may be sending a fragment so traverse the mbuf */ for (; m; m = m0) { m0 = m->m_nextpkt; @@ -2185,6 +2198,15 @@ len = m->m_pkthdr.len; mflags = m->m_flags; + /* + * If the native VLAN ID of the outgoing interfaces matches the + * VLAN ID of this frame, remove the VLAN header. + */ + if (bif->bif_pvid != 0 && VLANTAGOF(m) == bif->bif_pvid) { + m->m_flags &= ~M_VLANTAG; + m->m_pkthdr.ether_vtag = 0; + } + /* * If underlying interface can not do VLAN tag insertion itself * then attach a packet tag that holds it. @@ -2256,7 +2278,7 @@ return; } - bridge_enqueue(sc, ifp, m); + bridge_enqueue(sc, ifp, m, NULL); } /* @@ -2351,7 +2373,7 @@ } } - bridge_enqueue(sc, dst_if, mc); + bridge_enqueue(sc, dst_if, mc, bif); } if (used == 0) m_freem(m); @@ -2369,7 +2391,7 @@ return (0); } - bridge_enqueue(sc, dst_if, m); + bridge_enqueue(sc, dst_if, m, NULL); return (0); } @@ -2396,9 +2418,9 @@ if (((m->m_flags & (M_BCAST|M_MCAST)) == 0) && (dst_if = bridge_rtlookup(sc, eh->ether_dhost, DOT1Q_VID_NULL)) != NULL) { - error = bridge_enqueue(sc, dst_if, m); + error = bridge_enqueue(sc, dst_if, m, NULL); } else - bridge_broadcast(sc, ifp, m, 0, DOT1Q_VID_NULL); + bridge_broadcast(sc, ifp, m, 0); return (error); } @@ -2452,18 +2474,20 @@ */ static void bridge_forward(struct bridge_softc *sc, struct bridge_iflist *sbif, - struct mbuf *m, ether_vlanid_t vlan) + struct mbuf *m) { struct bridge_iflist *dbif; struct ifnet *src_if, *dst_if, *ifp; struct ether_header *eh; uint8_t *dst; int error; + ether_vlanid_t vlan; NET_EPOCH_ASSERT(); src_if = m->m_pkthdr.rcvif; ifp = sc->sc_ifp; + vlan = VLANTAGOF(m); if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); if_inc_counter(ifp, IFCOUNTER_IBYTES, m->m_pkthdr.len); @@ -2555,7 +2579,7 @@ } if (dst_if == NULL) { - bridge_broadcast(sc, src_if, m, 1, vlan); + bridge_broadcast(sc, src_if, m, 1); return; } @@ -2592,7 +2616,7 @@ return; } - bridge_enqueue(sc, dst_if, m); + bridge_enqueue(sc, dst_if, m, dbif); return; drop: @@ -2663,15 +2687,30 @@ } /* - * If the frame has no vlan id, take the vlan from the interface. - * Otherwise, make sure it matches the port's pvid, if any. + * If the port has a pvid assigned, do VLAN access checks. */ - if (vlan == DOT1Q_VID_NULL) - vlan = bif->bif_pvid; - else if ((bif->bif_pvid != DOT1Q_VID_NULL) && (vlan != bif->bif_pvid)) { - if_inc_counter(sc->sc_ifp, IFCOUNTER_IERRORS, 1); - m_freem(m); - return (NULL); + if (bif->bif_pvid != DOT1Q_VID_NULL) { + if (vlan != DOT1Q_VID_NULL && vlan != bif->bif_pvid) { + /* + * If the frame has a tag, and the tag's vlan id doesn't + * match the pvid, drop the frame. + */ + if_inc_counter(bifp, IFCOUNTER_IERRORS, 1); + m_freem(m); + return (NULL); + } else if (vlan == DOT1Q_VID_NULL) { + /* + * If the frame is untagged, set its vlan to the port's + * pvid. + */ + vlan = bif->bif_pvid; + m->m_pkthdr.ether_vtag = bif->bif_pvid; + m->m_flags |= M_VLANTAG; + } + /* + * Otherwise, vlan == bif_pvid, i.e. this is a tagged frame + * where the tag matches the pvid, so accept it as-is. + */ } bridge_span(sc, m); @@ -2700,7 +2739,7 @@ } /* Perform the bridge forwarding function with the copy. */ - bridge_forward(sc, bif, mc, vlan); + bridge_forward(sc, bif, mc); #ifdef DEV_NETMAP /* @@ -2842,7 +2881,7 @@ #undef GRAB_OUR_PACKETS /* Perform the bridge forwarding function. */ - bridge_forward(sc, bif, m, vlan); + bridge_forward(sc, bif, m); return (NULL); } @@ -2880,16 +2919,18 @@ */ static void bridge_broadcast(struct bridge_softc *sc, struct ifnet *src_if, - struct mbuf *m, int runfilt, ether_vlanid_t vlan) + struct mbuf *m, int runfilt) { struct bridge_iflist *dbif, *sbif; struct mbuf *mc; struct ifnet *dst_if; int used = 0, i; + ether_vlanid_t vlan; NET_EPOCH_ASSERT(); sbif = bridge_lookup_member_if(sc, src_if); + vlan = VLANTAGOF(m); /* Filter on the bridge interface before broadcasting */ if (runfilt && PFIL_HOOKED_OUT_46) { @@ -2956,7 +2997,7 @@ continue; } - bridge_enqueue(sc, dst_if, mc); + bridge_enqueue(sc, dst_if, mc, dbif); } if (used == 0) m_freem(m); @@ -2992,7 +3033,7 @@ continue; } - bridge_enqueue(sc, dst_if, mc); + bridge_enqueue(sc, dst_if, mc, bif); } } 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 @@ -856,14 +856,14 @@ vnet_cleanup } -atf_test_case "vlan_pvid_tagged" "cleanup" -vlan_pvid_tagged_head() +atf_test_case "vlan_pvid_1q" "cleanup" +vlan_pvid_1q_head() { - atf_set descr 'bridge pvid with tagged frames for pvid' + atf_set descr '802.1q tag addition and removal' atf_set require.user root } -vlan_pvid_tagged_body() +vlan_pvid_1q_body() { vnet_init vnet_init_bridge @@ -874,68 +874,32 @@ vnet_mkjail one ${epone}b vnet_mkjail two ${eptwo}b - # Create two tagged interfaces on the appropriate VLANs - jexec one ifconfig ${epone}b up - jexec one ifconfig ${epone}b.20 create 192.0.2.1/24 up + # Set up one jail with an access port, and the other with a trunk port. + # This forces the bridge to add and remove .1q tags to bridge the + # traffic. + + jexec one ifconfig ${epone}b 192.0.2.1/24 up jexec two ifconfig ${eptwo}b up jexec two ifconfig ${eptwo}b.20 create 192.0.2.2/24 up bridge=$(vnet_mkbridge) - ifconfig ${bridge} up - ifconfig ${epone}a up - ifconfig ${eptwo}a up + # ifpvid forces frames on vlan 20 to be untagged. ifconfig ${bridge} addm ${epone}a ifpvid ${epone}a 20 - ifconfig ${bridge} addm ${eptwo}a ifpvid ${eptwo}a 20 - - atf_check -o ignore jexec one ping -c 3 -t 1 192.0.2.2 - atf_check -o ignore jexec two ping -c 3 -t 1 192.0.2.1 -} - -vlan_pvid_tagged_cleanup() -{ - vnet_cleanup -} - -atf_test_case "vlan_pvid_tagged_noaccess" "cleanup" -vlan_pvid_tagged_noaccess_head() -{ - atf_set descr 'bridge pvid with non-pvid tagged frames' - atf_set require.user root -} - -vlan_pvid_tagged_noaccess_body() -{ - vnet_init - vnet_init_bridge - - epone=$(vnet_mkepair) - eptwo=$(vnet_mkepair) - - vnet_mkjail one ${epone}b - vnet_mkjail two ${eptwo}b - - # Create two tagged interfaces on the appropriate VLANs - jexec one ifconfig ${epone}b up - jexec one ifconfig ${epone}b.20 create 192.0.2.1/24 up - jexec two ifconfig ${eptwo}b up - jexec two ifconfig ${eptwo}b.20 create 192.0.2.2/24 up - - bridge=$(vnet_mkbridge) + # No PVID here, so we send/receive tagged frames. + ifconfig ${bridge} addm ${eptwo}a ifconfig ${bridge} up ifconfig ${epone}a up ifconfig ${eptwo}a up - ifconfig ${bridge} addm ${epone}a ifpvid ${epone}a 20 - ifconfig ${bridge} addm ${eptwo}a ifpvid ${eptwo}a 30 - atf_check -o ignore -s exit:2 jexec one ping -c 3 -t 1 192.0.2.2 - atf_check -o ignore -s exit:2 jexec two ping -c 3 -t 1 192.0.2.1 + 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 } -vlan_pvid_tagged_noaccess_cleanup() +vlan_pvid_1q_cleanup() { - vnet_cleanup + vnet_cleanup } atf_init_test_cases() @@ -956,7 +920,6 @@ atf_add_test_case "member_ifaddrs_enabled" atf_add_test_case "member_ifaddrs_disabled" atf_add_test_case "vlan_pvid" + atf_add_test_case "vlan_pvid_1q" atf_add_test_case "vlan_pvid_filtered" - atf_add_test_case "vlan_pvid_tagged" - atf_add_test_case "vlan_pvid_tagged_noaccess" }