The branch main has been updated by ivy: URL: https://cgit.FreeBSD.org/src/commit/?id=877a7a325b9824995e920d070a0bfb0c6a1cc7e2
commit 877a7a325b9824995e920d070a0bfb0c6a1cc7e2 Author: Lexi Winter <i...@freebsd.org> AuthorDate: 2025-07-05 04:54:25 +0000 Commit: Lexi Winter <i...@freebsd.org> CommitDate: 2025-07-05 07:04:31 +0000 bridge: transparently add and remove VLAN tags When vlan filtering is enabled, add or remove tags as required to allow ports with different configurations to communicate: - When receiving an untagged frame, insert a new tag based on the interface's configured untagged vlan. - When sending a tagged frame, and the frame's vlan id matches the outgoing interface's configured untagged vlan, strip the tag. Since we now set the vlan id in the mbuf, remove the vlan argument to bridge_forward() and bridge_broadcast() and take it from VLANTAGOF instead. Add tests for the new functionality. Reviewed by: kp, des Approved by: des (mentor) Differential Revision: https://reviews.freebsd.org/D50500 --- share/man/man4/bridge.4 | 7 ++-- sys/net/if_bridge.c | 76 ++++++++++++++++++++++++++--------------- tests/sys/net/if_bridge_test.sh | 46 +++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 30 deletions(-) diff --git a/share/man/man4/bridge.4 b/share/man/man4/bridge.4 index 73e7fd56af78..6fae37004efe 100644 --- a/share/man/man4/bridge.4 +++ b/share/man/man4/bridge.4 @@ -298,8 +298,11 @@ If an untagged VLAN ID is configured, incoming frames will be assigned to that VLAN, and the interface may receive outgoing untagged frames in that VLAN. .Pp -There is no support for adding or removing 802.1Q tags from frames -processed by the bridge. +The bridge will automatically insert or remove 802.1q tags as needed, +based on the interface configuration, when forwarding frames between +interfaces. +This tag processing is only done for interfaces with VLAN filtering +enabled. .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 index d8eff929e47b..5b54c119eabf 100644 --- a/sys/net/if_bridge.c +++ b/sys/net/if_bridge.c @@ -332,16 +332,16 @@ static void bridge_inject(struct ifnet *, struct mbuf *); 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 *, @@ -2175,12 +2175,25 @@ bridge_stop(struct ifnet *ifp, int disable) * */ 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; @@ -2188,6 +2201,18 @@ bridge_enqueue(struct bridge_softc *sc, struct ifnet *dst_ifp, struct mbuf *m) len = m->m_pkthdr.len; mflags = m->m_flags; + /* + * If VLAN filtering is enabled, and the native VLAN ID of the + * outgoing interface matches the VLAN ID of the frame, remove + * the VLAN header. + */ + if ((bif->bif_flags & IFBIF_VLANFILTER) && + bif->bif_untagged != DOT1Q_VID_NULL && + VLANTAGOF(m) == bif->bif_untagged) { + 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. @@ -2259,7 +2284,7 @@ bridge_dummynet(struct mbuf *m, struct ifnet *ifp) return; } - bridge_enqueue(sc, ifp, m); + bridge_enqueue(sc, ifp, m, NULL); } /* @@ -2354,7 +2379,7 @@ bridge_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *sa, } } - bridge_enqueue(sc, dst_if, mc); + bridge_enqueue(sc, dst_if, mc, bif); } if (used == 0) m_freem(m); @@ -2372,7 +2397,7 @@ sendunicast: return (0); } - bridge_enqueue(sc, dst_if, m); + bridge_enqueue(sc, dst_if, m, NULL); return (0); } @@ -2399,9 +2424,9 @@ bridge_transmit(struct ifnet *ifp, struct mbuf *m) 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); } @@ -2455,18 +2480,20 @@ bridge_qflush(struct ifnet *ifp __unused) */ 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); @@ -2558,7 +2585,7 @@ bridge_forward(struct bridge_softc *sc, struct bridge_iflist *sbif, } if (dst_if == NULL) { - bridge_broadcast(sc, src_if, m, 1, vlan); + bridge_broadcast(sc, src_if, m, 1); return; } @@ -2593,7 +2620,7 @@ bridge_forward(struct bridge_softc *sc, struct bridge_iflist *sbif, return; } - bridge_enqueue(sc, dst_if, m); + bridge_enqueue(sc, dst_if, m, dbif); return; drop: @@ -2682,6 +2709,8 @@ bridge_input(struct ifnet *ifp, struct mbuf *m) /* Otherwise, assign the untagged frame to the correct vlan. */ vlan = bif->bif_untagged; + m->m_pkthdr.ether_vtag = bif->bif_untagged; + m->m_flags |= M_VLANTAG; } bridge_span(sc, m); @@ -2710,7 +2739,7 @@ bridge_input(struct ifnet *ifp, struct mbuf *m) } /* Perform the bridge forwarding function with the copy. */ - bridge_forward(sc, bif, mc, vlan); + bridge_forward(sc, bif, mc); #ifdef DEV_NETMAP /* @@ -2849,7 +2878,7 @@ bridge_input(struct ifnet *ifp, struct mbuf *m) #undef GRAB_OUR_PACKETS /* Perform the bridge forwarding function. */ - bridge_forward(sc, bif, m, vlan); + bridge_forward(sc, bif, m); return (NULL); } @@ -2887,16 +2916,18 @@ bridge_inject(struct ifnet *ifp, struct mbuf *m) */ 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) { @@ -2962,7 +2993,7 @@ bridge_broadcast(struct bridge_softc *sc, struct ifnet *src_if, continue; } - bridge_enqueue(sc, dst_if, mc); + bridge_enqueue(sc, dst_if, mc, dbif); } if (used == 0) m_freem(m); @@ -2998,7 +3029,7 @@ bridge_span(struct bridge_softc *sc, struct mbuf *m) continue; } - bridge_enqueue(sc, dst_if, mc); + bridge_enqueue(sc, dst_if, mc, bif); } } @@ -3040,17 +3071,6 @@ bridge_vfilter_out(const struct bridge_iflist *dbif, const struct mbuf *m, if (vlan == DOT1Q_VID_NULL) return (false); - /* - * If the frame was received with a vlan tag then drop it, - * since we only support untagged ports. - * - * If the egress port doesn't have an untagged vlan configured, - * it doesn't want untagged frames, so drop it. - */ - if (VLANTAGOF(m) != DOT1Q_VID_NULL || - dbif->bif_untagged == DOT1Q_VID_NULL) - return (false); - /* * Make sure the frame's vlan matches the port's untagged vlan. */ diff --git a/tests/sys/net/if_bridge_test.sh b/tests/sys/net/if_bridge_test.sh index c6fa0a69ea7c..90cc91ac594f 100755 --- a/tests/sys/net/if_bridge_test.sh +++ b/tests/sys/net/if_bridge_test.sh @@ -952,6 +952,51 @@ vlan_pvid_tagged_cleanup() { vnet_cleanup } + +atf_test_case "vlan_pvid_1q" "cleanup" +vlan_pvid_1q_head() +{ + atf_set descr '802.1q tag addition and removal' + atf_set require.user root +} + +vlan_pvid_1q_body() +{ + vnet_init + vnet_init_bridge + + epone=$(vnet_mkepair) + eptwo=$(vnet_mkepair) + + vnet_mkjail one ${epone}b + vnet_mkjail two ${eptwo}b + + # 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} addm ${epone}a untagged ${epone}a 20 + ifconfig ${bridge} addm ${eptwo}a + + ifconfig ${bridge} up + ifconfig ${epone}a up + ifconfig ${eptwo}a up + + 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_1q_cleanup() +{ + vnet_cleanup +} + atf_init_test_cases() { atf_add_test_case "bridge_transmit_ipv4_unicast" @@ -971,6 +1016,7 @@ atf_init_test_cases() atf_add_test_case "member_ifaddrs_disabled" atf_add_test_case "member_ifaddrs_vlan" 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" }