Page MenuHomeFreeBSD

D49993.id.diff
No OneTemporary

D49993.id.diff

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(" <unknown state %d>", state);
}
+ if (member->ifbr_untagged != 0)
+ printf(" untagged %u", (unsigned)member->ifbr_untagged);
printf("\n");
}
@@ -576,6 +578,45 @@
err(1, "BRDGSIFCOST %s", cost);
}
+static void
+setbridge_untagged(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 VLAN identifier: %s", vlanid);
+
+ /*
+ * Reject vlan 0, since it's not a valid vlan identifier and has a
+ * special meaning in the kernel interface.
+ */
+ if (val == 0)
+ errx(1, "invalid VLAN identifier: %lu", val);
+
+ strlcpy(req.ifbr_ifsname, ifn, sizeof(req.ifbr_ifsname));
+ req.ifbr_untagged = val;
+
+ if (do_cmd(ctx, BRDGSIFUNTAGGED, &req, sizeof(req), 1) < 0)
+ err(1, "BRDGSIFUNTAGGED %s", vlanid);
+}
+
+static void
+unsetbridge_untagged(if_ctx *ctx, const char *ifn, int dummy __unused)
+{
+ struct ifbreq req;
+
+ memset(&req, 0, sizeof(req));
+
+ strlcpy(req.ifbr_ifsname, ifn, sizeof(req.ifbr_ifsname));
+ req.ifbr_untagged = 0;
+
+ if (do_cmd(ctx, BRDGSIFUNTAGGED, &req, sizeof(req), 1) < 0)
+ err(1, "BRDGSIFUNTAGGED");
+}
+
static void
setbridge_ifmaxaddr(if_ctx *ctx, const char *ifn, const char *arg)
{
@@ -612,17 +653,27 @@
static void
setbridge_private(if_ctx *ctx, const char *val, int dummy __unused)
{
-
do_bridgeflag(ctx, val, IFBIF_PRIVATE, 1);
}
static void
unsetbridge_private(if_ctx *ctx, const char *val, int dummy __unused)
{
-
do_bridgeflag(ctx, val, IFBIF_PRIVATE, 0);
}
+static void
+setbridge_vlanfilter(if_ctx *ctx, const char *val, int dummy __unused)
+{
+ do_bridgeflag(ctx, val, IFBIF_VLANFILTER, 1);
+}
+
+static void
+unsetbridge_vlanfilter(if_ctx *ctx, const char *val, int dummy __unused)
+{
+ do_bridgeflag(ctx, val, IFBIF_VLANFILTER, 0);
+}
+
static struct cmd bridge_cmds[] = {
DEF_CMD_ARG("addm", setbridge_add),
DEF_CMD_ARG("deletem", setbridge_delete),
@@ -659,6 +710,10 @@
DEF_CMD_ARG2("ifpriority", setbridge_ifpriority),
DEF_CMD_ARG2("ifpathcost", setbridge_ifpathcost),
DEF_CMD_ARG2("ifmaxaddr", setbridge_ifmaxaddr),
+ DEF_CMD_ARG("vlanfilter", setbridge_vlanfilter),
+ DEF_CMD_ARG("-vlanfilter", unsetbridge_vlanfilter),
+ DEF_CMD_ARG2("untagged", setbridge_untagged),
+ DEF_CMD_ARG("-untagged", unsetbridge_untagged),
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
@@ -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 July 5, 2025
.Dt IFCONFIG 8
.Os
.Sh NAME
@@ -2696,6 +2696,25 @@
removed.
Set to 0 to disable.
.El
+.Ss Bridge VLAN Filtering Parameters
+The behaviour of these options is described in the
+.Dq VLAN SUPPORT
+section of
+.Xr bridge 4 .
+.Bl -tag -width indent
+.It Cm vlanfilter Ar interface
+Enable VLAN filtering on an interface.
+.It Cm -vlanfilter Ar interface
+Disable VLAN filtering on an interface.
+.It Cm untagged Ar interface Ar vlan-id
+Set the untagged VLAN identifier for an interface.
+.Pp
+Setting
+.Cm untagged
+will automatically enable VLAN filtering on the interface.
+.It Cm -untagged Ar interface Ar vlan-id
+Clear the untagged VLAN identifier for an interface.
+.El
.Ss Link Aggregation and Link Failover Parameters
The following parameters are specific to lagg interfaces:
.Bl -tag -width indent
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 28, 2025
+.Dd July 5, 2025
.Dt IF_BRIDGE 4
.Os
.Sh NAME
@@ -271,6 +271,35 @@
.Va net.link.bridge.log_stp
node using
.Xr sysctl 8 .
+.Sh VLAN SUPPORT
+The
+.Nm
+driver has limited support for virtual LANs (VLANs).
+The bridge implements independent VLAN learning, i.e. MAC addresses are
+learned on a per-VLAN basis, and the same MAC address may be learned on
+multiple interfaces on different VLANs.
+Incoming frames with an 802.1Q tag will be assigned to the appropriate
+VLAN.
+.Pp
+By default no access control is enabled, so any interface may
+participate in any VLAN.
+.Pp
+VLAN filtering may be enabled on an interface using the
+.Xr ifconfig 8
+.Cm vlanfilter
+option.
+When VLAN filtering is enabled, an interface may only send and receive
+untagged frames.
+The interface's untagged VLAN ID may be configured using the
+.Xr ifconfig 8
+.Cm untagged
+option.
+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.
.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_untagged; /* untagged vlan id */
};
/*
@@ -335,13 +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 bool bridge_member_ifaddrs(void);
-
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 *,
@@ -353,6 +353,8 @@
static void bridge_rtflush(struct bridge_softc *, int);
static int bridge_rtdaddr(struct bridge_softc *, const uint8_t *,
ether_vlanid_t);
+static bool bridge_vfilter_out(const struct bridge_iflist *,
+ const struct mbuf *, ether_vlanid_t);
static void bridge_rtable_init(struct bridge_softc *);
static void bridge_rtable_fini(struct bridge_softc *);
@@ -400,6 +402,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_sifuntagged(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 *);
@@ -618,6 +621,8 @@
{ bridge_ioctl_sifmaxaddr, sizeof(struct ifbreq),
BC_F_COPYIN|BC_F_SUSER },
+ { bridge_ioctl_sifuntagged, sizeof(struct ifbreq),
+ BC_F_COPYIN|BC_F_SUSER },
};
static const int bridge_control_table_size = nitems(bridge_control_table);
@@ -1495,6 +1500,7 @@
req->ifbr_addrcnt = bif->bif_addrcnt;
req->ifbr_addrmax = bif->bif_addrmax;
req->ifbr_addrexceeded = bif->bif_addrexceeded;
+ req->ifbr_untagged = bif->bif_untagged;
/* Copy STP state options as flags */
if (bp->bp_operedge)
@@ -1872,6 +1878,25 @@
return (0);
}
+static int
+bridge_ioctl_sifuntagged(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_untagged > DOT1Q_VID_MAX)
+ return (EINVAL);
+
+ if (req->ifbr_untagged != DOT1Q_VID_NULL)
+ bif->bif_flags |= IFBIF_VLANFILTER;
+ bif->bif_untagged = req->ifbr_untagged;
+ return (0);
+}
+
static int
bridge_ioctl_addspan(struct bridge_softc *sc, void *arg)
{
@@ -2376,7 +2401,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);
}
@@ -2430,12 +2455,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;
@@ -2446,7 +2470,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)
@@ -2535,7 +2558,7 @@
}
if (dst_if == NULL) {
- bridge_broadcast(sc, src_if, m, 1);
+ bridge_broadcast(sc, src_if, m, 1, vlan);
return;
}
@@ -2555,6 +2578,10 @@
if (sbif->bif_flags & dbif->bif_flags & IFBIF_PRIVATE)
goto drop;
+ /* Do VLAN filtering. */
+ if (!bridge_vfilter_out(dbif, m, vlan))
+ goto drop;
+
if ((dbif->bif_flags & IFBIF_STP) &&
dbif->bif_stp.bp_state == BSTP_IFSTATE_DISCARDING)
goto drop;
@@ -2636,6 +2663,27 @@
return (NULL);
}
+ /* Do VLAN filtering. */
+ if (bif->bif_flags & IFBIF_VLANFILTER) {
+ /*
+ * If the frame was received with a tag, drop it, since we only
+ * support untagged ports which shouldn't be receiving tagged
+ * frames.
+ *
+ * If the frame was received without a tag, and the port doesn't
+ * have an untagged vlan configured, drop it.
+ */
+ if (vlan != DOT1Q_VID_NULL ||
+ bif->bif_untagged == DOT1Q_VID_NULL) {
+ if_inc_counter(sc->sc_ifp, IFCOUNTER_IERRORS, 1);
+ m_freem(m);
+ return (NULL);
+ }
+
+ /* Otherwise, assign the untagged frame to the correct vlan. */
+ vlan = bif->bif_untagged;
+ }
+
bridge_span(sc, m);
if (m->m_flags & (M_BCAST|M_MCAST)) {
@@ -2662,7 +2710,7 @@
}
/* Perform the bridge forwarding function with the copy. */
- bridge_forward(sc, bif, mc);
+ bridge_forward(sc, bif, mc, vlan);
#ifdef DEV_NETMAP
/*
@@ -2801,7 +2849,7 @@
#undef GRAB_OUR_PACKETS
/* Perform the bridge forwarding function. */
- bridge_forward(sc, bif, m);
+ bridge_forward(sc, bif, m, vlan);
return (NULL);
}
@@ -2839,7 +2887,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;
@@ -2867,6 +2915,10 @@
if (sbif && (sbif->bif_flags & dbif->bif_flags & IFBIF_PRIVATE))
continue;
+ /* Do VLAN filtering. */
+ if (!bridge_vfilter_out(dbif, m, vlan))
+ continue;
+
if ((dbif->bif_flags & IFBIF_STP) &&
dbif->bif_stp.bp_state == BSTP_IFSTATE_DISCARDING)
continue;
@@ -2950,6 +3002,67 @@
}
}
+/*
+ * Outgoing VLAN filtering. Given a frame, its vlan, and the member interface
+ * we intend to send it to, decide whether the port configuration allows it to
+ * be sent.
+ */
+static bool
+bridge_vfilter_out(const struct bridge_iflist *dbif, const struct mbuf *m,
+ ether_vlanid_t vlan)
+{
+ struct ether_header *eh;
+
+ NET_EPOCH_ASSERT();
+
+ /* If VLAN filtering isn't enabled, pass everything. */
+ if ((dbif->bif_flags & IFBIF_VLANFILTER) == 0)
+ return (true);
+
+ /*
+ * Always allow untagged 802.1D STP frames, even if they would
+ * otherwise be dropped. This is required for STP to work on
+ * a filtering bridge.
+ *
+ * Tagged STP (Cisco PVST+) is a non-standard extension, so
+ * handle those frames via the normal filtering path.
+ */
+ eh = mtod(m, struct ether_header *);
+ if (vlan == DOT1Q_VID_NULL &&
+ memcmp(eh->ether_dhost, bstp_etheraddr, ETHER_ADDR_LEN) == 0)
+ return (true);
+
+ /*
+ * If the frame wasn't assigned to a vlan at ingress, drop it.
+ * We can't forward these frames to filtering ports because we
+ * don't know what VLAN they're supposed to be in.
+ */
+ 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.
+ */
+ if (vlan != dbif->bif_untagged)
+ return (false);
+
+ /*
+ * Everything looks fine, so pass this frame.
+ */
+ return (true);
+}
+
/*
* bridge_rtupdate:
*
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 BRDGSIFUNTAGGED 31 /* set if untagged vlan */
/*
* Generic bridge control request.
@@ -139,6 +140,7 @@
uint32_t ifbr_addrcnt; /* member if addr number */
uint32_t ifbr_addrmax; /* member if addr max */
uint32_t ifbr_addrexceeded; /* member if addr violations */
+ ether_vlanid_t ifbr_untagged; /* member if untagged vlan */
uint8_t pad[32];
};
@@ -155,10 +157,11 @@
#define IFBIF_BSTP_ADMEDGE 0x0200 /* member stp admin edge enabled */
#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 IFBIFBITS "\020\001LEARNING\002DISCOVER\003STP\004SPAN" \
"\005STICKY\014PRIVATE\006EDGE\007AUTOEDGE\010PTP" \
- "\011AUTOPTP"
+ "\011AUTOPTP\015VLANFILTER"
#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/sys/sys/param.h b/sys/sys/param.h
--- a/sys/sys/param.h
+++ b/sys/sys/param.h
@@ -74,7 +74,7 @@
* cannot include sys/param.h and should only be updated here.
*/
#undef __FreeBSD_version
-#define __FreeBSD_version 1500050
+#define __FreeBSD_version 1500051
/*
* __FreeBSD_kernel__ indicates that this system uses the kernel of FreeBSD,
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
@@ -829,6 +829,129 @@
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 untagged ${epone}a 20
+ ifconfig ${bridge} addm ${eptwo}a untagged ${eptwo}a 20
+
+ # With VLAN filtering enabled, traffic should be passed.
+ 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
+
+ # Removed the untagged VLAN on one port; traffic should not be passed.
+ ifconfig ${bridge} -untagged ${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_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 untagged ${epone}a 20
+ ifconfig ${bridge} addm ${eptwo}a untagged ${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 untagged ${epone}a 20
+ ifconfig ${bridge} addm ${eptwo}a untagged ${eptwo}a 20
+
+ # Tagged frames should not be passed.
+ 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_tagged_cleanup()
+{
+ vnet_cleanup
+}
atf_init_test_cases()
{
atf_add_test_case "bridge_transmit_ipv4_unicast"
@@ -847,4 +970,7 @@
atf_add_test_case "member_ifaddrs_enabled"
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_filtered"
+ atf_add_test_case "vlan_pvid_tagged"
}

File Metadata

Mime Type
text/plain
Expires
Tue, Feb 3, 1:41 AM (14 h, 58 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
28419264
Default Alt Text
D49993.id.diff (17 KB)

Event Timeline