diff --git a/sbin/ifconfig/ifbridge.c b/sbin/ifconfig/ifbridge.c --- a/sbin/ifconfig/ifbridge.c +++ b/sbin/ifconfig/ifbridge.c @@ -211,6 +211,8 @@ else printf(" ", state); } + if (member->ifbr_pvid != 0) + printf(" pvid %u", (unsigned)member->ifbr_pvid); printf("\n"); } @@ -576,6 +578,24 @@ err(1, "BRDGSIFCOST %s", cost); } +static void +setbridge_ifpvid(if_ctx *ctx, const char *ifn, const char *vlanid) +{ + struct ifbreq req; + u_long val; + + memset(&req, 0, sizeof(req)); + + if (get_val(vlanid, &val) < 0) + errx(1, "invalid value: %s", vlanid); + + strlcpy(req.ifbr_ifsname, ifn, sizeof(req.ifbr_ifsname)); + req.ifbr_pvid = val; + + if (do_cmd(ctx, BRDGSIFPVID, &req, sizeof(req), 1) < 0) + err(1, "BRDGSIFPVID %s", vlanid); +} + static void setbridge_ifmaxaddr(if_ctx *ctx, const char *ifn, const char *arg) { @@ -659,6 +679,7 @@ DEF_CMD_ARG2("ifpriority", setbridge_ifpriority), DEF_CMD_ARG2("ifpathcost", setbridge_ifpathcost), DEF_CMD_ARG2("ifmaxaddr", setbridge_ifmaxaddr), + DEF_CMD_ARG2("ifpvid", setbridge_ifpvid), DEF_CMD_ARG("timeout", setbridge_timeout), DEF_CMD_ARG("private", setbridge_private), DEF_CMD_ARG("-private", unsetbridge_private), diff --git a/sbin/ifconfig/ifconfig.8 b/sbin/ifconfig/ifconfig.8 --- a/sbin/ifconfig/ifconfig.8 +++ b/sbin/ifconfig/ifconfig.8 @@ -2695,6 +2695,11 @@ source addresses are dropped until an existing host cache entry expires or is removed. Set to 0 to disable. +.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. .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 April 10, 2024 +.Dd April 2, 2025 .Dt IF_BRIDGE 4 .Os .Sh NAME @@ -250,6 +250,30 @@ .Va net.link.bridge.log_stp node using .Xr sysctl 8 . +.Sh VLAN SUPPORT +The +.Nm +driver has limited support for virtual LANs (VLANs). +An incoming packet with an 802.1Q tag will be assigned to the +appropriate VLAN. +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. +.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. .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 @@ -254,6 +254,7 @@ uint32_t bif_addrcnt; /* cur. # of addresses */ uint32_t bif_addrexceeded;/* # of address violations */ struct epoch_context bif_epoch_ctx; + ether_vlanid_t bif_pvid; /* native vlan id */ }; /* @@ -335,12 +336,12 @@ static void bridge_rtdelete(struct bridge_softc *, struct ifnet *ifp, int); static void bridge_forward(struct bridge_softc *, struct bridge_iflist *, - struct mbuf *m); + struct mbuf *m, ether_vlanid_t vlan); static void bridge_timer(void *); static void bridge_broadcast(struct bridge_softc *, struct ifnet *, - struct mbuf *, int); + struct mbuf *, int, ether_vlanid_t); static void bridge_span(struct bridge_softc *, struct mbuf *); static int bridge_rtupdate(struct bridge_softc *, const uint8_t *, @@ -399,6 +400,7 @@ static int bridge_ioctl_sifprio(struct bridge_softc *, void *); static int bridge_ioctl_sifcost(struct bridge_softc *, void *); static int bridge_ioctl_sifmaxaddr(struct bridge_softc *, void *); +static int bridge_ioctl_sifpvid(struct bridge_softc *, void *); static int bridge_ioctl_addspan(struct bridge_softc *, void *); static int bridge_ioctl_delspan(struct bridge_softc *, void *); static int bridge_ioctl_gbparam(struct bridge_softc *, void *); @@ -604,6 +606,8 @@ { bridge_ioctl_sifmaxaddr, sizeof(struct ifbreq), BC_F_COPYIN|BC_F_SUSER }, + { bridge_ioctl_sifpvid, sizeof(struct ifbreq), + BC_F_COPYIN|BC_F_SUSER }, }; static const int bridge_control_table_size = nitems(bridge_control_table); @@ -1460,6 +1464,7 @@ req->ifbr_addrcnt = bif->bif_addrcnt; req->ifbr_addrmax = bif->bif_addrmax; req->ifbr_addrexceeded = bif->bif_addrexceeded; + req->ifbr_pvid = bif->bif_pvid; /* Copy STP state options as flags */ if (bp->bp_operedge) @@ -1837,6 +1842,23 @@ return (0); } +static int +bridge_ioctl_sifpvid(struct bridge_softc *sc, void *arg) +{ + struct ifbreq *req = arg; + struct bridge_iflist *bif; + + bif = bridge_lookup_member(sc, req->ifbr_ifsname); + if (bif == NULL) + return (ENOENT); + + if (req->ifbr_pvid > DOT1Q_VID_MAX) + return (EINVAL); + + bif->bif_pvid = req->ifbr_pvid; + return (0); +} + static int bridge_ioctl_addspan(struct bridge_softc *sc, void *arg) { @@ -2341,7 +2363,7 @@ NULL) { error = bridge_enqueue(sc, dst_if, m); } else - bridge_broadcast(sc, ifp, m, 0); + bridge_broadcast(sc, ifp, m, 0, DOT1Q_VID_NULL); return (error); } @@ -2395,12 +2417,11 @@ */ static void bridge_forward(struct bridge_softc *sc, struct bridge_iflist *sbif, - struct mbuf *m) + struct mbuf *m, ether_vlanid_t vlan) { struct bridge_iflist *dbif; struct ifnet *src_if, *dst_if, *ifp; struct ether_header *eh; - uint16_t vlan; uint8_t *dst; int error; @@ -2411,7 +2432,6 @@ if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); if_inc_counter(ifp, IFCOUNTER_IBYTES, m->m_pkthdr.len); - vlan = VLANTAGOF(m); if ((sbif->bif_flags & IFBIF_STP) && sbif->bif_stp.bp_state == BSTP_IFSTATE_DISCARDING) @@ -2500,7 +2520,7 @@ } if (dst_if == NULL) { - bridge_broadcast(sc, src_if, m, 1); + bridge_broadcast(sc, src_if, m, 1, vlan); return; } @@ -2520,6 +2540,12 @@ if (sbif->bif_flags & dbif->bif_flags & IFBIF_PRIVATE) goto drop; + /* + * If the destination port is on a different vlan, drop the frame. + */ + if ((dbif->bif_pvid != DOT1Q_VID_NULL) && (vlan != dbif->bif_pvid)) + goto drop; + if ((dbif->bif_flags & IFBIF_STP) && dbif->bif_stp.bp_state == BSTP_IFSTATE_DISCARDING) goto drop; @@ -2601,6 +2627,18 @@ return (NULL); } + /* + * 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 (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); + } + bridge_span(sc, m); if (m->m_flags & (M_BCAST|M_MCAST)) { @@ -2627,7 +2665,7 @@ } /* Perform the bridge forwarding function with the copy. */ - bridge_forward(sc, bif, mc); + bridge_forward(sc, bif, mc, vlan); #ifdef DEV_NETMAP /* @@ -2761,7 +2799,7 @@ #undef GRAB_OUR_PACKETS /* Perform the bridge forwarding function. */ - bridge_forward(sc, bif, m); + bridge_forward(sc, bif, m, vlan); return (NULL); } @@ -2799,7 +2837,7 @@ */ static void bridge_broadcast(struct bridge_softc *sc, struct ifnet *src_if, - struct mbuf *m, int runfilt) + struct mbuf *m, int runfilt, ether_vlanid_t vlan) { struct bridge_iflist *dbif, *sbif; struct mbuf *mc; @@ -2827,6 +2865,11 @@ if (sbif && (sbif->bif_flags & dbif->bif_flags & IFBIF_PRIVATE)) continue; + /* VLAN filtering for interfaces with non-zero VLAN ID */ + if ((dbif->bif_pvid != DOT1Q_VID_NULL) && + (vlan != dbif->bif_pvid)) + continue; + if ((dbif->bif_flags & IFBIF_STP) && dbif->bif_stp.bp_state == BSTP_IFSTATE_DISCARDING) continue; 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 @@ -122,6 +122,7 @@ #define BRDGSPROTO 28 /* set protocol (ifbrparam) */ #define BRDGSTXHC 29 /* set tx hold count (ifbrparam) */ #define BRDGSIFAMAX 30 /* set max interface addrs (ifbreq) */ +#define BRDGSIFPVID 31 /* set if PVID */ /* * Generic bridge control request. @@ -139,7 +140,8 @@ uint32_t ifbr_addrcnt; /* member if addr number */ uint32_t ifbr_addrmax; /* member if addr max */ uint32_t ifbr_addrexceeded; /* member if addr violations */ - uint8_t pad[32]; + ether_vlanid_t ifbr_pvid; /* member if PVID */ + uint8_t pad[30]; }; /* BRDGGIFFLAGS, BRDGSIFFLAGS */ 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 @@ -703,6 +703,164 @@ vnet_cleanup } +atf_test_case "vlan_pvid" "cleanup" +vlan_pvid_head() +{ + atf_set descr 'bridge with two ports with pvid set' + atf_set require.user root +} + +vlan_pvid_body() +{ + vnet_init + vnet_init_bridge + + epone=$(vnet_mkepair) + eptwo=$(vnet_mkepair) + + vnet_mkjail one ${epone}b + vnet_mkjail two ${eptwo}b + + jexec one ifconfig ${epone}b 192.0.2.1/24 up + jexec two ifconfig ${eptwo}b 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 ifpvid ${epone}a 20 + ifconfig ${bridge} addm ${eptwo}a ifpvid ${eptwo}a 20 + + 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_cleanup() +{ + vnet_cleanup +} + +atf_test_case "vlan_pvid_filtered" "cleanup" +vlan_pvid_filtered_head() +{ + atf_set descr 'bridge with two ports with different pvids' + atf_set require.user root +} + +vlan_pvid_filtered_body() +{ + vnet_init + vnet_init_bridge + + epone=$(vnet_mkepair) + eptwo=$(vnet_mkepair) + + vnet_mkjail one ${epone}b + vnet_mkjail two ${eptwo}b + + jexec one ifconfig ${epone}b 192.0.2.1/24 up + jexec two ifconfig ${eptwo}b 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 ifpvid ${epone}a 20 + ifconfig ${bridge} addm ${eptwo}a ifpvid ${eptwo}a 30 + + 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_pvid_filtered_cleanup() +{ + vnet_cleanup +} + +atf_test_case "vlan_pvid_tagged" "cleanup" +vlan_pvid_tagged_head() +{ + atf_set descr 'bridge pvid with tagged frames for pvid' + atf_set require.user root +} + +vlan_pvid_tagged_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) + + 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 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.30 create 192.0.2.1/24 up + jexec two ifconfig ${eptwo}b up + jexec two ifconfig ${eptwo}b.30 create 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 #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_noaccess_cleanup() +{ + vnet_cleanup +} + atf_init_test_cases() { atf_add_test_case "bridge_transmit_ipv4_unicast" @@ -718,4 +876,8 @@ atf_add_test_case "mtu" atf_add_test_case "vlan" atf_add_test_case "many_bridge_members" + atf_add_test_case "vlan_pvid" + atf_add_test_case "vlan_pvid_filtered" + atf_add_test_case "vlan_pvid_tagged" + atf_add_test_case "vlan_pvid_tagged_noaccess" }