The branch main has been updated by ivy: URL: https://cgit.FreeBSD.org/src/commit/?id=95e7d47a4bcea741f44aac4cbdcdb41bbbff6d70
commit 95e7d47a4bcea741f44aac4cbdcdb41bbbff6d70 Author: Lexi Winter <i...@freebsd.org> AuthorDate: 2025-07-05 06:36:00 +0000 Commit: Lexi Winter <i...@freebsd.org> CommitDate: 2025-07-05 07:04:31 +0000 bridge: allow vlan(4) interfaces on a bridge A vlan interface on top of a bridge will act as a layer 3 port for bridge traffic on that vlan, sometimes called an "SVI". This allows the host to send/receive traffic on that vlan without having to create a separate epair(4) and vlan(4) to tag and untag the traffic. Reviewed by: zlei, kp, des Approved by: des (mentor) Differential Revision: https://reviews.freebsd.org/D50504 --- share/man/man4/bridge.4 | 6 ++++++ sys/net/if_bridge.c | 39 +++++++++++++++++++++++++++++++++++++-- sys/net/if_vlan.c | 1 + tests/sys/net/if_bridge_test.sh | 41 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 2 deletions(-) diff --git a/share/man/man4/bridge.4 b/share/man/man4/bridge.4 index 06f7fed06477..d3dea9eaa938 100644 --- a/share/man/man4/bridge.4 +++ b/share/man/man4/bridge.4 @@ -281,6 +281,11 @@ multiple interfaces on different VLANs. Incoming frames with an 802.1Q tag will be assigned to the appropriate VLAN. .Pp +Traffic sent to or from the host is not assigned to a VLAN by default. +To allow the host to communicate on a VLAN, configure a +.Xr vlan 4 +interface on the bridge and (if necessary) assign IP addresses there. +.Pp By default no access control is enabled, so any interface may participate in any VLAN. .Pp @@ -581,6 +586,7 @@ ifconfig bridge0 addm fxp0 addm gif0 up .Xr ipfw 4 , .Xr netmap 4 , .Xr pf 4 , +.Xr vlan 4 , .Xr ifconfig 8 .Sh HISTORY The diff --git a/sys/net/if_bridge.c b/sys/net/if_bridge.c index ba4e0425c945..5b3ee740d75e 100644 --- a/sys/net/if_bridge.c +++ b/sys/net/if_bridge.c @@ -847,6 +847,7 @@ bridge_clone_create(struct if_clone *ifc, char *name, size_t len, ifp->if_softc = sc; if_initname(ifp, bridge_name, ifd->unit); ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_capabilities = ifp->if_capenable = IFCAP_VLAN_HWTAGGING; ifp->if_ioctl = bridge_ioctl; #ifdef ALTQ ifp->if_start = bridge_altq_start; @@ -2484,16 +2485,17 @@ bridge_transmit(struct ifnet *ifp, struct mbuf *m) struct ether_header *eh; struct ifnet *dst_if; int error = 0; + ether_vlanid_t vlan; sc = ifp->if_softc; ETHER_BPF_MTAP(ifp, m); eh = mtod(m, struct ether_header *); + vlan = VLANTAGOF(m); if (((m->m_flags & (M_BCAST|M_MCAST)) == 0) && - (dst_if = bridge_rtlookup(sc, eh->ether_dhost, DOT1Q_VID_NULL)) != - NULL) { + (dst_if = bridge_rtlookup(sc, eh->ether_dhost, vlan)) != NULL) { error = bridge_enqueue(sc, dst_if, m, NULL); } else bridge_broadcast(sc, ifp, m, 0); @@ -2894,6 +2896,15 @@ bridge_input(struct ifnet *ifp, struct mbuf *m) } \ if ((iface) != bifp) \ ETHER_BPF_MTAP(iface, m); \ + /* Pass tagged packets to if_vlan, if it's loaded */ \ + if (VLANTAGOF(m) != 0) { \ + if (bifp->if_vlantrunk == NULL) { \ + m_freem(m); \ + return (NULL); \ + } \ + (*vlan_input_p)(bifp, m); \ + return (NULL); \ + } \ return (m); \ } \ \ @@ -2950,6 +2961,30 @@ bridge_inject(struct ifnet *ifp, struct mbuf *m) { struct bridge_softc *sc; + if (ifp->if_type == IFT_L2VLAN) { + /* + * vlan(4) gives us the vlan ifnet, so we need to get the + * bridge softc to get a pointer to ether_input to send the + * packet to. + */ + struct ifnet *bifp = NULL; + + if (vlan_trunkdev_p == NULL) { + m_freem(m); + return; + } + + bifp = vlan_trunkdev_p(ifp); + if (bifp == NULL) { + m_freem(m); + return; + } + + sc = if_getsoftc(bifp); + sc->sc_if_input(ifp, m); + return; + } + KASSERT((if_getcapenable(ifp) & IFCAP_NETMAP) != 0, ("%s: iface %s is not running in netmap mode", __func__, if_name(ifp))); diff --git a/sys/net/if_vlan.c b/sys/net/if_vlan.c index e9e1c82cb688..22fcb7bf7c64 100644 --- a/sys/net/if_vlan.c +++ b/sys/net/if_vlan.c @@ -1673,6 +1673,7 @@ vlan_config(struct ifvlan *ifv, struct ifnet *p, uint16_t vid, */ if (p->if_type != IFT_ETHER && p->if_type != IFT_L2VLAN && + p->if_type != IFT_BRIDGE && (p->if_capenable & IFCAP_VLAN_HWTAGGING) == 0) return (EPROTONOSUPPORT); if ((p->if_flags & VLAN_IFFLAGS) != VLAN_IFFLAGS) diff --git a/tests/sys/net/if_bridge_test.sh b/tests/sys/net/if_bridge_test.sh index 4815c1aef570..d057d2997486 100755 --- a/tests/sys/net/if_bridge_test.sh +++ b/tests/sys/net/if_bridge_test.sh @@ -1124,6 +1124,46 @@ vlan_ifconfig_tagged_cleanup() vnet_cleanup } +# +# Test a vlan(4) "SVI" interface on top of a bridge. +# +atf_test_case "vlan_svi" "cleanup" +vlan_svi_head() +{ + atf_set descr 'vlan bridge with an SVI' + atf_set require.user root +} + +vlan_svi_body() +{ + vnet_init + vnet_init_bridge + + epone=$(vnet_mkepair) + + vnet_mkjail one ${epone}b + + jexec one ifconfig ${epone}b up + jexec one ifconfig ${epone}b.20 create 192.0.2.1/24 up + + bridge=$(vnet_mkbridge) + + ifconfig ${bridge} up + ifconfig ${epone}a up + ifconfig ${bridge} addm ${epone}a tagged ${epone}a 20 + + svi=$(vnet_mkvlan) + ifconfig ${svi} vlan 20 vlandev ${bridge} + ifconfig ${svi} inet 192.0.2.2/24 up + + atf_check -s exit:0 -o ignore ping -c 3 -t 1 192.0.2.1 +} + +vlan_svi_cleanup() +{ + vnet_cleanup +} + atf_init_test_cases() { atf_add_test_case "bridge_transmit_ipv4_unicast" @@ -1148,4 +1188,5 @@ atf_init_test_cases() atf_add_test_case "vlan_pvid_tagged" atf_add_test_case "vlan_filtering" atf_add_test_case "vlan_ifconfig_tagged" + atf_add_test_case "vlan_svi" }