git: 032d32c2c276 - main - bridge: add per-interface vlan access list
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Sat, 05 Jul 2025 07:13:18 UTC
The branch main has been updated by ivy: URL: https://cgit.FreeBSD.org/src/commit/?id=032d32c2c276983ce680ffaa6e7d6c12a9584fab commit 032d32c2c276983ce680ffaa6e7d6c12a9584fab Author: Lexi Winter <ivy@FreeBSD.org> AuthorDate: 2025-05-28 07:57:33 +0000 Commit: Lexi Winter <ivy@FreeBSD.org> CommitDate: 2025-07-05 07:04:31 +0000 bridge: add per-interface vlan access list The new ifconfig options 'tagged', '+tagged' and '-tagged' allow the vlan access list of a bridge interface to be configured: - Incoming tagged frames will be dropped if the vlan tag isn't in the interface's access list. - Outgoing frames will be dropped if the vlan tag isn't in the interface's access list (e.g., for BUM traffic). This has no effect if vlan filtering is not enabled on the interface. Since we now add a tag to untagged frames at ingress, remove the vlan argument from bridge_vfilter_out() and use VLANTAGOF instead. Reviewed by: des, kp, adrian Approved by: des (mentor) Differential Revision: https://reviews.freebsd.org/D50503 --- lib/libifconfig/libifconfig.h | 2 + lib/libifconfig/libifconfig_bridge.c | 54 +++++++---- sbin/ifconfig/ifbridge.c | 125 +++++++++++++++++++++++++ sbin/ifconfig/ifconfig.8 | 32 +++++++ share/man/man4/bridge.4 | 13 ++- sys/net/if_bridge.c | 173 ++++++++++++++++++++++++++++------- sys/net/if_bridgevar.h | 24 +++++ tests/sys/net/if_bridge_test.sh | 129 ++++++++++++++++++++++++++ 8 files changed, 503 insertions(+), 49 deletions(-) diff --git a/lib/libifconfig/libifconfig.h b/lib/libifconfig/libifconfig.h index 8d5ca01b0ce6..fc835485a51e 100644 --- a/lib/libifconfig/libifconfig.h +++ b/lib/libifconfig/libifconfig.h @@ -29,6 +29,7 @@ #include <sys/types.h> #include <net/if.h> +#include <net/if_bridgevar.h> /* for ifbvlan_set_t */ #include <netinet/in.h> #include <netinet/ip_carp.h> @@ -64,6 +65,7 @@ struct lagg_reqport; struct ifconfig_bridge_status { struct ifbropreq *params; /**< current operational parameters */ struct ifbreq *members; /**< list of bridge members */ + ifbvlan_set_t *member_vlans; /**< bridge member vlan sets */ size_t members_count; /**< how many member interfaces */ uint32_t cache_size; /**< size of address cache */ uint32_t cache_lifetime; /**< address cache entry lifetime */ diff --git a/lib/libifconfig/libifconfig_bridge.c b/lib/libifconfig/libifconfig_bridge.c index 2a9bbc35858b..b4a920f488c5 100644 --- a/lib/libifconfig/libifconfig_bridge.c +++ b/lib/libifconfig/libifconfig_bridge.c @@ -66,40 +66,37 @@ ifconfig_bridge_get_bridge_status(ifconfig_handle_t *h, { struct ifbifconf members; struct ifbrparam cache_param; - struct _ifconfig_bridge_status *bridge; - char *buf; + struct _ifconfig_bridge_status *bridge = NULL; + char *buf = NULL; + members.ifbic_buf = NULL; *bridgep = NULL; bridge = calloc(1, sizeof(struct _ifconfig_bridge_status)); if (bridge == NULL) { h->error.errtype = OTHER; h->error.errcode = ENOMEM; - return (-1); + goto err; } bridge->inner.params = &bridge->params; if (ifconfig_bridge_ioctlwrap(h, name, BRDGGCACHE, &cache_param, sizeof(cache_param), false) != 0) { - free(bridge); - return (-1); + goto err; } bridge->inner.cache_size = cache_param.ifbrp_csize; if (ifconfig_bridge_ioctlwrap(h, name, BRDGGTO, &cache_param, sizeof(cache_param), false) != 0) { - free(bridge); - return (-1); + goto err; } bridge->inner.cache_lifetime = cache_param.ifbrp_ctime; if (ifconfig_bridge_ioctlwrap(h, name, BRDGPARAM, &bridge->params, sizeof(bridge->params), false) != 0) { - free(bridge); - return (-1); + goto err; } - members.ifbic_buf = NULL; for (size_t len = 8192; (buf = realloc(members.ifbic_buf, len)) != NULL; len *= 2) { @@ -107,27 +104,52 @@ ifconfig_bridge_get_bridge_status(ifconfig_handle_t *h, members.ifbic_len = len; if (ifconfig_bridge_ioctlwrap(h, name, BRDGGIFS, &members, sizeof(members), false) != 0) { - free(buf); - free(bridge); - return (-1); + goto err; } if ((members.ifbic_len + sizeof(*members.ifbic_req)) < len) break; } if (buf == NULL) { - free(members.ifbic_buf); - free(bridge); h->error.errtype = OTHER; h->error.errcode = ENOMEM; - return (-1); + goto err; } bridge->inner.members = members.ifbic_req; bridge->inner.members_count = members.ifbic_len / sizeof(*members.ifbic_req); + bridge->inner.member_vlans = calloc(bridge->inner.members_count, + sizeof(ifbvlan_set_t)); + if (bridge->inner.member_vlans == NULL) { + h->error.errtype = OTHER; + h->error.errcode = ENOMEM; + goto err; + } + for (size_t i = 0; i < bridge->inner.members_count; ++i) { + struct ifbif_vlan_req vreq; + memset(&vreq, 0, sizeof(vreq)); + strlcpy(vreq.bv_ifname, bridge->inner.members[i].ifbr_ifsname, + sizeof(vreq.bv_ifname)); + + if (ifconfig_bridge_ioctlwrap(h, name, BRDGGIFVLANSET, &vreq, + sizeof(vreq), false) != 0) { + goto err; + } + + __BIT_COPY(BRVLAN_SETSIZE, &vreq.bv_set, + &bridge->inner.member_vlans[i]); + } + *bridgep = &bridge->inner; return (0); + +err: + free(members.ifbic_buf); + if (bridge) + free(bridge->inner.member_vlans); + free(bridge); + return (-1); } void diff --git a/sbin/ifconfig/ifbridge.c b/sbin/ifconfig/ifbridge.c index a60ddabcbdd4..ce5d2f4894fa 100644 --- a/sbin/ifconfig/ifbridge.c +++ b/sbin/ifconfig/ifbridge.c @@ -146,6 +146,36 @@ bridge_addresses(if_ctx *ctx, const char *prefix) free(inbuf); } +static void +print_vlans(ifbvlan_set_t *vlans) +{ + unsigned printed = 0; + + for (unsigned vlan = DOT1Q_VID_MIN; vlan <= DOT1Q_VID_MAX;) { + unsigned last; + + if (!BRVLAN_TEST(vlans, vlan)) { + ++vlan; + continue; + } + + last = vlan; + while (last < DOT1Q_VID_MAX && BRVLAN_TEST(vlans, last + 1)) + ++last; + + if (printed == 0) + printf(" tagged "); + else + printf(","); + + printf("%u", vlan); + if (last != vlan) + printf("-%u", last); + ++printed; + vlan = last + 1; + } +} + static void bridge_status(if_ctx *ctx) { @@ -213,6 +243,7 @@ bridge_status(if_ctx *ctx) } if (member->ifbr_untagged != 0) printf(" untagged %u", (unsigned)member->ifbr_untagged); + print_vlans(&bridge->member_vlans[i]); printf("\n"); } @@ -674,6 +705,97 @@ unsetbridge_vlanfilter(if_ctx *ctx, const char *val, int dummy __unused) do_bridgeflag(ctx, val, IFBIF_VLANFILTER, 0); } +static int +parse_vlans(ifbvlan_set_t *set, const char *str) +{ + char *s, *token; + + /* "none" means the empty vlan set */ + if (strcmp(str, "none") == 0) { + __BIT_ZERO(BRVLAN_SETSIZE, set); + return (0); + } + + /* "all" means all vlans, except for 0 and 4095 which are reserved */ + if (strcmp(str, "all") == 0) { + __BIT_FILL(BRVLAN_SETSIZE, set); + BRVLAN_CLR(set, DOT1Q_VID_NULL); + BRVLAN_CLR(set, DOT1Q_VID_RSVD_IMPL); + return (0); + } + + if ((s = strdup(str)) == NULL) + return (-1); + + while ((token = strsep(&s, ",")) != NULL) { + unsigned long first, last; + char *p, *lastp; + + if ((lastp = strchr(token, '-')) != NULL) + *lastp++ = '\0'; + + first = last = strtoul(token, &p, 10); + if (*p != '\0') + goto err; + if (first < DOT1Q_VID_MIN || first > DOT1Q_VID_MAX) + goto err; + + if (lastp) { + last = strtoul(lastp, &p, 10); + if (*p != '\0') + goto err; + if (last < DOT1Q_VID_MIN || last > DOT1Q_VID_MAX || + last < first) + goto err; + } + + for (unsigned vlan = first; vlan <= last; ++vlan) + BRVLAN_SET(set, vlan); + } + + free(s); + return (0); + +err: + free(s); + return (-1); +} + +static void +set_bridge_vlanset(if_ctx *ctx, const char *ifn, const char *vlans, int op) +{ + struct ifbif_vlan_req req; + + memset(&req, 0, sizeof(req)); + + if (parse_vlans(&req.bv_set, vlans) != 0) + errx(1, "invalid vlan set: %s", vlans); + + strlcpy(req.bv_ifname, ifn, sizeof(req.bv_ifname)); + req.bv_op = op; + + if (do_cmd(ctx, BRDGSIFVLANSET, &req, sizeof(req), 1) < 0) + err(1, "BRDGSIFVLANSET %s", vlans); +} + +static void +setbridge_tagged(if_ctx *ctx, const char *ifn, const char *vlans) +{ + set_bridge_vlanset(ctx, ifn, vlans, BRDG_VLAN_OP_SET); +} + +static void +addbridge_tagged(if_ctx *ctx, const char *ifn, const char *vlans) +{ + set_bridge_vlanset(ctx, ifn, vlans, BRDG_VLAN_OP_ADD); +} + +static void +delbridge_tagged(if_ctx *ctx, const char *ifn, const char *vlans) +{ + set_bridge_vlanset(ctx, ifn, vlans, BRDG_VLAN_OP_DEL); +} + static struct cmd bridge_cmds[] = { DEF_CMD_ARG("addm", setbridge_add), DEF_CMD_ARG("deletem", setbridge_delete), @@ -714,6 +836,9 @@ static struct cmd bridge_cmds[] = { DEF_CMD_ARG("-vlanfilter", unsetbridge_vlanfilter), DEF_CMD_ARG2("untagged", setbridge_untagged), DEF_CMD_ARG("-untagged", unsetbridge_untagged), + DEF_CMD_ARG2("tagged", setbridge_tagged), + DEF_CMD_ARG2("+tagged", addbridge_tagged), + DEF_CMD_ARG2("-tagged", delbridge_tagged), 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 index 490da7b2ce2c..3fb8b5f02b76 100644 --- a/sbin/ifconfig/ifconfig.8 +++ b/sbin/ifconfig/ifconfig.8 @@ -2714,6 +2714,38 @@ Setting will automatically enable VLAN filtering on the interface. .It Cm -untagged Ar interface Ar vlan-id Clear the untagged VLAN identifier for an interface. +.It Cm tagged Ar interface Ar vlan-list +Set the interface's VLAN access list to the provided list of VLANs. +The list should be a comma-separated list of one or more VLAN IDs +or ranges formatted as +.Ar first-last , +the value +.Dq none +meaning the empty set, +or the value +.Dq all +meaning all VLANs (1-4094). +.Pp +Setting +.Cm tagged +will automatically enable VLAN filtering on the interface. +.It Cm +tagged Ar interface Ar vlan-list +Add the provided list of VLAN IDs to the interface's VLAN access list. +The list should be formatted as described for +.Cm tagged . +.Pp +Setting +.Cm +tagged +will automatically enable VLAN filtering on the interface. +.It Cm -tagged Ar interface Ar vlan-list +Remove the provided list of VLAN IDs from the interface's VLAN access +list. +The list should be formatted as described for +.Cm tagged . +.Pp +Setting +.Cm -tagged +will automatically enable VLAN filtering on the interface. .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 index 6fae37004efe..06f7fed06477 100644 --- a/share/man/man4/bridge.4 +++ b/share/man/man4/bridge.4 @@ -289,7 +289,8 @@ VLAN filtering may be enabled on an interface using the .Cm vlanfilter option. When VLAN filtering is enabled, an interface may only send and receive -untagged frames. +frames based on its configured VLAN access list. +.Pp The interface's untagged VLAN ID may be configured using the .Xr ifconfig 8 .Cm untagged @@ -298,6 +299,16 @@ 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 +The tagged VLAN access list may be configured using the +.Cm tagged , +.Cm +tagged +and +.Cm -tagged +options to +.Xr ifconfig 8 . +An interface may send and receive tagged frames for any VLAN in its +access list. +.Pp The bridge will automatically insert or remove 802.1q tags as needed, based on the interface configuration, when forwarding frames between interfaces. diff --git a/sys/net/if_bridge.c b/sys/net/if_bridge.c index 5b54c119eabf..ba4e0425c945 100644 --- a/sys/net/if_bridge.c +++ b/sys/net/if_bridge.c @@ -255,6 +255,7 @@ struct bridge_iflist { uint32_t bif_addrexceeded;/* # of address violations */ struct epoch_context bif_epoch_ctx; ether_vlanid_t bif_untagged; /* untagged vlan id */ + ifbvlan_set_t bif_vlan_set; /* allowed tagged vlans */ }; /* @@ -353,8 +354,9 @@ static void bridge_rtage(struct bridge_softc *); 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_in(const struct bridge_iflist *, struct mbuf *); static bool bridge_vfilter_out(const struct bridge_iflist *, - const struct mbuf *, ether_vlanid_t); + const struct mbuf *); static void bridge_rtable_init(struct bridge_softc *); static void bridge_rtable_fini(struct bridge_softc *); @@ -403,6 +405,8 @@ 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_sifvlanset(struct bridge_softc *, void *); +static int bridge_ioctl_gifvlanset(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 *); @@ -623,6 +627,12 @@ static const struct bridge_control bridge_control_table[] = { { bridge_ioctl_sifuntagged, sizeof(struct ifbreq), BC_F_COPYIN|BC_F_SUSER }, + + { bridge_ioctl_sifvlanset, sizeof(struct ifbif_vlan_req), + BC_F_COPYIN|BC_F_SUSER }, + + { bridge_ioctl_gifvlanset, sizeof(struct ifbif_vlan_req), + BC_F_COPYIN|BC_F_COPYOUT }, }; static const int bridge_control_table_size = nitems(bridge_control_table); @@ -959,6 +969,7 @@ bridge_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) struct ifbaconf ifbaconf; struct ifbrparam ifbrparam; struct ifbropreq ifbropreq; + struct ifbif_vlan_req ifvlanreq; } args; struct ifdrv *ifd = (struct ifdrv *) data; const struct bridge_control *bc; @@ -1897,6 +1908,65 @@ bridge_ioctl_sifuntagged(struct bridge_softc *sc, void *arg) return (0); } +static int +bridge_ioctl_sifvlanset(struct bridge_softc *sc, void *arg) +{ + struct ifbif_vlan_req *req = arg; + struct bridge_iflist *bif; + + bif = bridge_lookup_member(sc, req->bv_ifname); + if (bif == NULL) + return (ENOENT); + + /* Reject invalid VIDs. */ + if (BRVLAN_TEST(&req->bv_set, DOT1Q_VID_NULL) || + BRVLAN_TEST(&req->bv_set, DOT1Q_VID_RSVD_IMPL)) + return (EINVAL); + + switch (req->bv_op) { + /* Replace the existing vlan set with the new set */ + case BRDG_VLAN_OP_SET: + BIT_COPY(BRVLAN_SETSIZE, &req->bv_set, &bif->bif_vlan_set); + break; + + /* Modify the existing vlan set to add the given vlans */ + case BRDG_VLAN_OP_ADD: + BIT_OR(BRVLAN_SETSIZE, &bif->bif_vlan_set, &req->bv_set); + break; + + /* Modify the existing vlan set to remove the given vlans */ + case BRDG_VLAN_OP_DEL: + BIT_ANDNOT(BRVLAN_SETSIZE, &bif->bif_vlan_set, &req->bv_set); + break; + + /* Invalid or unknown operation */ + default: + return (EINVAL); + } + + /* + * The only reason to modify the VLAN access list is to use VLAN + * filtering on this interface, so enable it automatically. + */ + bif->bif_flags |= IFBIF_VLANFILTER; + + return (0); +} + +static int +bridge_ioctl_gifvlanset(struct bridge_softc *sc, void *arg) +{ + struct ifbif_vlan_req *req = arg; + struct bridge_iflist *bif; + + bif = bridge_lookup_member(sc, req->bv_ifname); + if (bif == NULL) + return (ENOENT); + + BIT_COPY(BRVLAN_SETSIZE, &bif->bif_vlan_set, &req->bv_set); + return (0); +} + static int bridge_ioctl_addspan(struct bridge_softc *sc, void *arg) { @@ -2606,7 +2676,7 @@ bridge_forward(struct bridge_softc *sc, struct bridge_iflist *sbif, goto drop; /* Do VLAN filtering. */ - if (!bridge_vfilter_out(dbif, m, vlan)) + if (!bridge_vfilter_out(dbif, m)) goto drop; if ((dbif->bif_flags & IFBIF_STP) && @@ -2691,27 +2761,13 @@ bridge_input(struct ifnet *ifp, struct mbuf *m) } /* 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; - m->m_pkthdr.ether_vtag = bif->bif_untagged; - m->m_flags |= M_VLANTAG; + if (!bridge_vfilter_in(bif, m)) { + if_inc_counter(sc->sc_ifp, IFCOUNTER_IERRORS, 1); + m_freem(m); + return (NULL); } + /* bridge_vfilter_in() may add a tag */ + vlan = VLANTAGOF(m); bridge_span(sc, m); @@ -2922,12 +2978,10 @@ bridge_broadcast(struct bridge_softc *sc, struct ifnet *src_if, 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) { @@ -2947,7 +3001,7 @@ bridge_broadcast(struct bridge_softc *sc, struct ifnet *src_if, continue; /* Do VLAN filtering. */ - if (!bridge_vfilter_out(dbif, m, vlan)) + if (!bridge_vfilter_out(dbif, m)) continue; if ((dbif->bif_flags & IFBIF_STP) && @@ -3033,16 +3087,63 @@ bridge_span(struct bridge_softc *sc, struct mbuf *m) } } +/* + * Incoming VLAN filtering. Given a frame and the member interface it was + * received on, decide whether the port configuration allows it. + */ +static bool +bridge_vfilter_in(const struct bridge_iflist *sbif, struct mbuf *m) +{ + ether_vlanid_t vlan; + + vlan = VLANTAGOF(m); + /* Make sure the vlan id is reasonable. */ + if (vlan > DOT1Q_VID_MAX) + return (false); + + /* If VLAN filtering isn't enabled, pass everything. */ + if ((sbif->bif_flags & IFBIF_VLANFILTER) == 0) + return (true); + + if (vlan == DOT1Q_VID_NULL) { + /* + * The frame doesn't have a tag. If the interface does not + * have an untagged vlan configured, drop the frame. + */ + if (sbif->bif_untagged == DOT1Q_VID_NULL) + return (false); + + /* + * Otherwise, insert a new tag based on the interface's + * untagged vlan id. + */ + m->m_pkthdr.ether_vtag = sbif->bif_untagged; + m->m_flags |= M_VLANTAG; + } else { + /* + * The frame has a tag, so check it matches the interface's + * vlan access list. We explicitly do not accept tagged + * frames for the untagged vlan id here (unless it's also + * in the access list). + */ + if (!BRVLAN_TEST(&sbif->bif_vlan_set, vlan)) + return (false); + } + + /* Accept the frame. */ + return (true); +} + /* * 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) +bridge_vfilter_out(const struct bridge_iflist *dbif, const struct mbuf *m) { struct ether_header *eh; + ether_vlanid_t vlan; NET_EPOCH_ASSERT(); @@ -3050,6 +3151,8 @@ bridge_vfilter_out(const struct bridge_iflist *dbif, const struct mbuf *m, if ((dbif->bif_flags & IFBIF_VLANFILTER) == 0) return (true); + vlan = VLANTAGOF(m); + /* * Always allow untagged 802.1D STP frames, even if they would * otherwise be dropped. This is required for STP to work on @@ -3072,15 +3175,21 @@ bridge_vfilter_out(const struct bridge_iflist *dbif, const struct mbuf *m, return (false); /* - * Make sure the frame's vlan matches the port's untagged vlan. + * If the frame's vlan matches the interfaces's untagged vlan, + * allow it. */ - if (vlan != dbif->bif_untagged) - return (false); + if (vlan == dbif->bif_untagged) + return (true); /* - * Everything looks fine, so pass this frame. + * If the frame's vlan is on the interface's tagged access list, + * allow it. */ - return (true); + if (BRVLAN_TEST(&dbif->bif_vlan_set, vlan)) + return (true); + + /* The frame was not permitted, so drop it. */ + return (false); } /* diff --git a/sys/net/if_bridgevar.h b/sys/net/if_bridgevar.h index c72afcc1785d..97b63e3d4416 100644 --- a/sys/net/if_bridgevar.h +++ b/sys/net/if_bridgevar.h @@ -78,6 +78,8 @@ #define _NET_IF_BRIDGEVAR_H_ #include <sys/types.h> +#include <sys/_bitset.h> +#include <sys/bitset.h> #include <sys/callout.h> #include <sys/queue.h> #include <sys/condvar.h> @@ -123,6 +125,8 @@ #define BRDGSTXHC 29 /* set tx hold count (ifbrparam) */ #define BRDGSIFAMAX 30 /* set max interface addrs (ifbreq) */ #define BRDGSIFUNTAGGED 31 /* set if untagged vlan */ +#define BRDGSIFVLANSET 32 /* set if vlan set */ +#define BRDGGIFVLANSET 33 /* get if vlan set */ /* * Generic bridge control request. @@ -307,6 +311,26 @@ struct ifbpstpconf { eaddr[5] = pv >> 0; \ } while (0) +/* + * Bridge VLAN access request. + */ +#define BRVLAN_SETSIZE 4096 +typedef __BITSET_DEFINE(ifbvlan_set, BRVLAN_SETSIZE) ifbvlan_set_t; + +#define BRVLAN_SET(set, bit) __BIT_SET(BRVLAN_SETSIZE, (bit), set) +#define BRVLAN_CLR(set, bit) __BIT_CLR(BRVLAN_SETSIZE, (bit), set) +#define BRVLAN_TEST(set, bit) __BIT_ISSET(BRVLAN_SETSIZE, (bit), set) + +#define BRDG_VLAN_OP_SET 1 /* replace current vlan set */ +#define BRDG_VLAN_OP_ADD 2 /* add vlans to current set */ +#define BRDG_VLAN_OP_DEL 3 /* remove vlans from current set */ + +struct ifbif_vlan_req { + char bv_ifname[IFNAMSIZ]; + uint8_t bv_op; + ifbvlan_set_t bv_set; +}; + #ifdef _KERNEL #define BRIDGE_INPUT(_ifp, _m) do { \ diff --git a/tests/sys/net/if_bridge_test.sh b/tests/sys/net/if_bridge_test.sh index 90cc91ac594f..4815c1aef570 100755 --- a/tests/sys/net/if_bridge_test.sh +++ b/tests/sys/net/if_bridge_test.sh @@ -997,6 +997,133 @@ vlan_pvid_1q_cleanup() vnet_cleanup } +# +# Test vlan filtering. +# +atf_test_case "vlan_filtering" "cleanup" +vlan_filtering_head() +{ + atf_set descr 'tagged traffic with filtering' + atf_set require.user root +} + +vlan_filtering_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 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 vlanfilter ${epone}a + ifconfig ${bridge} addm ${eptwo}a vlanfilter ${eptwo}a + + # Right now there are no VLANs on the access list, so everything + # should be blocked. + 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 + + # Set the untagged vlan on both ports to 20 and make sure traffic is + # still blocked. We intentionally do not pass tagged traffic for the + # untagged vlan. + atf_check -s exit:0 ifconfig ${bridge} untagged ${epone}a 20 + atf_check -s exit:0 ifconfig ${bridge} untagged ${eptwo}a 20 + + 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 + + atf_check -s exit:0 ifconfig ${bridge} -untagged ${epone}a + atf_check -s exit:0 ifconfig ${bridge} -untagged ${eptwo}a + + # Add VLANs 10-30 to the access list; now access should be allowed. + ifconfig ${bridge} +tagged ${epone}a 10-30 + ifconfig ${bridge} +tagged ${eptwo}a 10-30 + 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 + + # Remove vlan 20 from the access list, now access should be blocked + # again. + ifconfig ${bridge} -tagged ${epone}a 20 + ifconfig ${bridge} -tagged ${eptwo}a 20 + 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_filtering_cleanup() +{ + vnet_cleanup +} + +# +# Test the ifconfig 'tagged' option. +# +atf_test_case "vlan_ifconfig_tagged" "cleanup" +vlan_ifconfig_tagged_head() +{ + atf_set descr 'test the ifconfig tagged option' + atf_set require.user root +} + +vlan_ifconfig_tagged_body() +{ + vnet_init + vnet_init_bridge + + ep=$(vnet_mkepair) + bridge=$(vnet_mkbridge) + + ifconfig ${bridge} addm ${ep}a vlanfilter ${ep}a up + ifconfig ${ep}a up + + # To start with, no vlans should be configured. + atf_check -s exit:0 -o not-match:"tagged" ifconfig ${bridge} + + # Add vlans 100-149. + atf_check -s exit:0 ifconfig ${bridge} tagged ${ep}a 100-149 + atf_check -s exit:0 -o match:"tagged 100-149" ifconfig ${bridge} + + # Replace the vlan list with 139-199. + atf_check -s exit:0 ifconfig ${bridge} tagged ${ep}a 139-199 + atf_check -s exit:0 -o match:"tagged 139-199" ifconfig ${bridge} + + # Add vlans 100-170. + atf_check -s exit:0 ifconfig ${bridge} +tagged ${ep}a 100-170 + atf_check -s exit:0 -o match:"tagged 100-199" ifconfig ${bridge} + + # Remove vlans 104, 105, and 150-159 + atf_check -s exit:0 ifconfig ${bridge} -tagged ${ep}a 104,105,150-159 + atf_check -s exit:0 -o match:"tagged 100-103,106-149,160-199" \ + ifconfig ${bridge} + + # Remove the entire vlan list. + atf_check -s exit:0 ifconfig ${bridge} tagged ${ep}a none + atf_check -s exit:0 -o not-match:"tagged" ifconfig ${bridge} + + # Test some invalid vlans sets. + for bad_vlan in -1 0 4096 4097 foo 0-10 4000-5000 foo-40 40-foo; do + atf_check -s exit:1 -e ignore \ + ifconfig ${bridge} tagged "$bad_vlan" + done +} + +vlan_ifconfig_tagged_cleanup() +{ + vnet_cleanup +} + atf_init_test_cases() { atf_add_test_case "bridge_transmit_ipv4_unicast" @@ -1019,4 +1146,6 @@ atf_init_test_cases() 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_filtering" + atf_add_test_case "vlan_ifconfig_tagged" }