Download raw body.
make veb(4) VLAN aware
On Wed, Oct 29, 2025 at 03:54:42PM +1000, David Gwynne wrote:
> veb(4) is currently vlan unaware, meaning that it assumes that there's a
> single "namespace" for the mac addresses used by packets handled by the
> bridge. by default it blocks vlan (and svlan) packets, but if you allow
> it carry vlan packets it ignores the vlan tag when doing the mac address
> lookups.
>
> adding vlan awareness means that every mac address the bridge learns
> is now associated with a vlan identifier (vid). ie, the same mac
> in two different vlans will get separate entries in the forwarding
> database.
>
> this means ports in a veb now get vlan configuration. if you're used to
> operating vlans on switches with "industry standard" style config, this
> should feel pretty familiar to you.
>
> when an interface is added to a veb as a port, it is configured with a
> default vid. this vid is assigned to "untagged" packets (packets on
> the wire without a VLAN tag) received by this port inside the veb, and
> then stripped again when transmitted on another port with the same vid.
>
> veb can also handle vlan tagged packets directly now by adding "tagged"
> vid configuration to ports. this means you don't need to configure
> vlan(4) interfaces and add them as ports on a veb to handle tagged
> traffic.
>
> if you only want to handle tagged traffic on a port, you can disable
> "untagged" packet handling too.
>
> the result of all this is that you can implement vlan aware switching
> without a ton of boilerplate now. previously you were supposed to create
> a veb(4) per VLAN you wanted to wire up. if you wanted to handle tagged
> packets you had to create vlan(4) interfaces and add them to their
> respective veb(4)s. if you're using the same VIDs consistently on every
> port, this basically all collapses down to a single veb(4) with the
> approriate set of "tagged" vids on each port.
>
> vlan(4) still gets the first run at packets in ethernet input though.
> if you do want to remap vlan tags on the wire to a vlan within a
> veb(4), you can still create a vlan(4) interface to catch that vid
> and add it as an untagged port to that VLAN in the bridge.
>
> the actual implementation of this in the kernel was surprisingly simple.
> the etherbridge code was extended to include a vid next to the ethernet
> address it does lookups against, and Just Works(tm). the other users of
> the etherbridge code hardcode 0 as the vid they use. the veb(4)
> forwarding path was extended to figure out the right vid for the packet
> and carry it around.
>
> the majority of the changes are in the ioctl and ifconfig changes needed
> to support it. ive tried to keep the changes as unintrusive as possible,
> and as backwards compatible as possible.
>
> the main changes to ifconfig are the addition of "untagged" and
> "+tagged" commands and variations of them to manage which vids a
> port can use and how, and extending the mac addresses in "static",
> "deladdr" and the display of the address cache with @vid.
>
> if you have a simple veb(4) setup now, the config you have now should
> still work with the new code. simple means you haven't enabled link0.
> if you have enabled link0, this will be a breaking change that requires
> you to explicitly add tagged vids to the ports you want to carry vlans
> on.
>
> as a quick and dirty example here's a config i've been using, part of
> which replaces a vlan(4) interface on vmx0 with vnetid 871 with a
> tagged vid on the physical port and a vport interface:
>
> $ doas cat /etc/hostname.veb0
> add vmx0
> -untagged vmx0
> +tagged vmx0 871
> +tagged vmx0 780
> +tagged vmx0 781
> add vport0
> untagged vport0 871
> add tap0
> untagged vport0 780
> add tap1
> untagged vport0 781
> up
> $ ifconfig veb0
> veb0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST>
> index 7 llprio 3
> encap: vnetid 1 txprio packet rxprio outer
> groups: veb
> vmx0 flags=3<LEARNING,DISCOVER>
> port 1 ifpriority 0 ifcost 0 -untagged
> tagged: 780,781,871
> vport0 flags=3<LEARNING,DISCOVER>
> port 8 ifpriority 0 ifcost 0 untagged 871
> tap0 flags=3<LEARNING,DISCOVER>
> port 5 ifpriority 0 ifcost 0 untagged 780
> tagged: none
> tap1 flags=3<LEARNING,DISCOVER>
> port 6 ifpriority 0 ifcost 0 untagged 781
> tagged: none
> tap2 flags=3<LEARNING,DISCOVER>
> port 9 ifpriority 0 ifcost 0 untagged 780
> tagged: none
> Addresses (max cache: 100, timeout: 240):
> 00:00:5e:00:01:50@780 vmx0 created 167591 used 0 flags=0<>
> 8c:04:ba:cf:60:c0@780 vmx0 created 167590 used 1 flags=0<>
> 00:00:5e:00:01:51@781 vmx0 created 167591 used 0 flags=0<>
> 8c:04:ba:cf:60:c0@781 vmx0 created 167591 used 1 flags=0<>
> 00:00:5e:00:01:11@871 vmx0 created 167591 used 1 flags=0<>
> 00:00:5e:00:01:47@871 vmx0 created 167591 used 1 flags=0<>
> 00:50:56:a1:75:f0@871 vport0 created 167592 used 0 flags=0<>
> fe:e1:ba:d0:74:ef@871 vmx0 created 86776 used 9 flags=0<>
>
> you can add a static entry (and delete them) with "static" and
> "deladdr", you just have to add @vid to the address. eg
>
> # ifconfig veb0 deladdr 00:50:56:a1:75:f0@871 vport0
> # ifconfig veb0 static 00:50:56:a1:75:f0@871 vport0
>
> it is possible to add static entries pointing at ports that do not
> have the relevant vid configured. in that situation the tagged/untagged
> config takes precedence and packets using that address entry will
> be dropped.
>
> i dont think it makes sense to allow tagged packets over vport
> interfaces so the ability to configure that is blocked. vport
> interfaces plug the ip stack into the bridge, which requires untagged
> traffic. if you want to plug the stack into different VLANs on the
> same bridge, rather than create vlan interfaces on top of a vport,
> just create vport interfaces associated with the right vids.
>
> the ifconfig changes are pretty simplistic, there's some improvements
> around handling sets of vids that should be made, but the diff is
> big enough.
>
> finally, the main reason i wanted this was to improve the semantics
> of handling vlans on bridges. flipping the link0 flag is too easy
> and makes the network too open, and setting up a ton of veb(4)s and
> vlan(4)s per VLAN is too much boilerplate. i also need to properly
> get my head around PVLANs as per RFC 5517, which i find is easiest
> if i can actually implement it. being able to hack it into veb(4)
> on top of this diff is my current plan for that.
i've made largely cosmetic tweaks based on chats with claudio and lexi
winter.
claudio wasn't a fan of the "+tagged" and "-tagged" ifconfig commands,
so i've changed it so there's a "tagged" and "-tagged" commands now.
"tagged" modifies the set of vids. if you prefix the vid with +
then it will add the tag to the set, a - prefix removes the vid
from the set, and a vid on it's own without a prefix will replace
the set with just that tag. an obvious improvement in the future
will be to handle multiple vids and ranges of vids (eg, "+10-20",
"1,10-20", "-100-200", etc).
"-tagged" clears the vid map so no tagged packets will be handled
by the port.
the other tweak is to allow "ifconfig veb0 -vnetid". currently veb0
defaults to having vlan 1 as the default used for the untagged vid
on ports added to the bridge, which provides compatability for
existing (simple) veb configurations. ifconfig veb0 -vnetid means
a port will get added with the equivalent of "ifconfig veb0 -untagged
if0", ie, they basically won't handle any packets for any vlans
until explicitly configured otherwise. this prevents packets leaking
into vlans within the veb until the port is configured appropriately.
my hostname.veb0 looks like this now:
# cat /etc/hostname.veb0
-vnetid
add vmx0
tagged vmx0 +871
tagged vmx0 +780
tagged vmx0 +781
add vport0
untagged vport0 871
up
apart from chats about ifconfig, the only feedback i've received is
positive. i'm going to start committing it soon.
Index: sbin/ifconfig/brconfig.c
===================================================================
RCS file: /cvs/src/sbin/ifconfig/brconfig.c,v
diff -u -p -r1.34 brconfig.c
--- sbin/ifconfig/brconfig.c 21 Oct 2025 05:14:22 -0000 1.34
+++ sbin/ifconfig/brconfig.c 31 Oct 2025 02:07:33 -0000
@@ -46,9 +46,12 @@
#include <getopt.h>
#include <limits.h>
#include <arpa/inet.h>
+#include <time.h>
#include "ifconfig.h"
+#define VID_SEP '@'
+
void bridge_ifsetflag(const char *, u_int32_t);
void bridge_ifclrflag(const char *, u_int32_t);
@@ -57,6 +60,7 @@ void bridge_cfg(const char *);
void bridge_badrule(int, char **, int);
void bridge_showrule(struct ifbrlreq *);
int bridge_arprule(struct ifbrlreq *, int *, char ***);
+void bridge_vidmap(const char *);
#define IFBAFBITS "\020\1STATIC"
#define IFBIFBITS \
@@ -355,6 +359,16 @@ bridge_list(char *delim)
printf("port %u ifpriority %u ifcost %u",
reqp->ifbr_portno, reqp->ifbr_priority,
reqp->ifbr_path_cost);
+ switch (reqp->ifbr_pvid) {
+ case IFBR_PVID_NULL:
+ break;
+ case IFBR_PVID_NONE:
+ printf(" -untagged");
+ break;
+ default:
+ printf(" untagged %u", reqp->ifbr_pvid);
+ break;
+ }
if (reqp->ifbr_protected) {
int v;
@@ -370,6 +384,7 @@ bridge_list(char *delim)
stpstates[reqp->ifbr_state],
stproles[reqp->ifbr_role]);
printf("\n");
+ bridge_vidmap(buf);
bridge_rules(buf, 1);
}
free(bifc.ifbic_buf);
@@ -517,6 +532,130 @@ bridge_unprotect(const char *ifsname, in
}
void
+bridge_pvid(const char *ifsname, const char *val)
+{
+ struct ifbreq breq;
+
+ strlcpy(breq.ifbr_name, ifname, sizeof(breq.ifbr_name));
+ strlcpy(breq.ifbr_ifsname, ifsname, sizeof(breq.ifbr_ifsname));
+
+ if (strcmp(val, "default") == 0)
+ breq.ifbr_pvid = IFBR_PVID_NULL;
+ else if (strcmp(val, "none") == 0)
+ breq.ifbr_pvid = IFBR_PVID_NONE;
+ else {
+ const char *errstr;
+
+ breq.ifbr_pvid = strtonum(val,
+ IFBR_PVID_MIN, IFBR_PVID_MAX, &errstr);
+ if (errstr != NULL) {
+ errx(1, "%s untagged %s: %s is %s",
+ ifname, ifsname, val, errstr);
+ }
+ }
+
+ if (ioctl(sock, SIOCBRDGSPVID, &breq) == -1)
+ err(1, "%s untagged %s %s", ifname, ifsname, val);
+}
+
+void
+bridge_unpvid(const char *ifsname, int d)
+{
+ struct ifbreq breq;
+ const char *errstr;
+
+ strlcpy(breq.ifbr_name, ifname, sizeof(breq.ifbr_name));
+ strlcpy(breq.ifbr_ifsname, ifsname, sizeof(breq.ifbr_ifsname));
+ breq.ifbr_pvid = IFBR_PVID_NONE;
+
+ if (ioctl(sock, SIOCBRDGSPVID, &breq) == -1)
+ err(1, "%s -untagged %s", ifname, ifsname);
+}
+
+static void
+bridge_set_vidmap_bit(struct ifbrvidmap *ifbrvm, unsigned int vid)
+{
+ unsigned int voff = vid / 8;
+ unsigned int vbit = vid % 8;
+
+ ifbrvm->ifbrvm_map[voff] |= 1U << vbit;
+}
+
+static void
+bridge_set_vidmap_range(struct ifbrvidmap *ifbrvm, uint16_t from, uint16_t to)
+{
+ unsigned int vid;
+
+ for (vid = from; vid <= to; vid++)
+ bridge_set_vidmap_bit(ifbrvm, vid);
+}
+
+void
+bridge_set_vidmap(const char *ifsname, const char *arg)
+{
+ struct ifbrvidmap ifbrvm;
+
+ memset(&ifbrvm, 0, sizeof(ifbrvm));
+
+ strlcpy(ifbrvm.ifbrvm_name, ifname, sizeof(ifbrvm.ifbrvm_name));
+ strlcpy(ifbrvm.ifbrvm_ifsname, ifsname, sizeof(ifbrvm.ifbrvm_ifsname));
+
+ if (strcmp(arg, "all") == 0) {
+ ifbrvm.ifbrvm_op = IFBRVM_OP_SET;
+ bridge_set_vidmap_range(&ifbrvm, EVL_VLID_MIN, EVL_VLID_MAX);
+ } else if (strcmp(arg, "none") == 0) {
+ ifbrvm.ifbrvm_op = IFBRVM_OP_SET;
+ /* map is already all 0 */
+ } else {
+ const char *val;
+ uint16_t vid;
+ const char *errstr;
+
+ switch (arg[0]) {
+ case '+':
+ ifbrvm.ifbrvm_op = IFBRVM_OP_OR;
+ val = arg + 1;
+ break;
+ case '-':
+ ifbrvm.ifbrvm_op = IFBRVM_OP_ANDNOT;
+ val = arg + 1;
+ break;
+ default:
+ ifbrvm.ifbrvm_op = IFBRVM_OP_SET;
+ val = arg;
+ break;
+ }
+
+ vid = strtonum(val, EVL_VLID_MIN, EVL_VLID_MAX, &errstr);
+ if (errstr != NULL) {
+ errx(1, "%s tagged %s: %s is %s", ifname, ifsname,
+ val, errstr);
+ }
+
+ bridge_set_vidmap_bit(&ifbrvm, vid);
+ }
+
+ if (ioctl(sock, SIOCBRDGSVMAP, &ifbrvm) == -1)
+ err(1, "%s tagged %s %s", ifname, ifsname, arg);
+}
+
+void
+bridge_unset_vidmap(const char *ifsname, int d)
+{
+ struct ifbrvidmap ifbrvm;
+
+ memset(&ifbrvm, 0, sizeof(ifbrvm));
+
+ strlcpy(ifbrvm.ifbrvm_name, ifname, sizeof(ifbrvm.ifbrvm_name));
+ strlcpy(ifbrvm.ifbrvm_ifsname, ifsname, sizeof(ifbrvm.ifbrvm_ifsname));
+ ifbrvm.ifbrvm_op = IFBRVM_OP_SET;
+
+ if (ioctl(sock, SIOCBRDGSVMAP, &ifbrvm) == -1)
+ err(1, "%s -tagged %s", ifname, ifsname);
+
+}
+
+void
bridge_proto(const char *arg, int d)
{
struct ifbrparam bp;
@@ -584,23 +723,6 @@ bridge_maxaddr(const char *arg, int d)
}
void
-bridge_deladdr(const char *addr, int d)
-{
- struct ifbareq ifba;
- struct ether_addr *ea;
-
- strlcpy(ifba.ifba_name, ifname, sizeof(ifba.ifba_name));
- ea = ether_aton(addr);
- if (ea == NULL)
- err(1, "Invalid address: %s", addr);
-
- bcopy(ea, &ifba.ifba_dst, sizeof(struct ether_addr));
-
- if (ioctl(sock, SIOCBRDGDADDR, &ifba) == -1)
- err(1, "%s: %s", ifname, addr);
-}
-
-void
bridge_ifprio(const char *ifsname, const char *val)
{
struct ifbreq breq;
@@ -648,12 +770,72 @@ bridge_noifcost(const char *ifsname, int
err(1, "%s", ifname);
}
+static int
+bridge_addr_vid_parse(struct ifbvareq *ifbva,
+ const char *addr, const char *vid)
+{
+ struct ether_addr *ea;
+ const char *errstr;
+
+ memset(ifbva, 0, sizeof(*ifbva));
+ strlcpy(ifbva->ifbva_name, ifname, sizeof(ifbva->ifbva_name));
+
+ ea = ether_aton(addr);
+ if (ea == NULL)
+ errx(1, "Invalid address: %s", addr);
+
+ ifbva->ifbva_dst = *ea;
+ ifbva->ifbva_vid = strtonum(vid, EVL_VLID_MIN, EVL_VLID_MAX, &errstr);
+ if (errstr != NULL)
+ errx(1, "Invalid vid %s: %s", vid, errstr);
+
+ return (0);
+}
+
+static int
+bridge_addr_vid(struct ifbvareq *ifbva, const char *addr, size_t sep)
+{
+ char *buf;
+ int rv;
+
+ buf = strdup(addr);
+ if (buf == NULL)
+ err(1, NULL);
+
+ buf[sep] = '\0';
+ rv = bridge_addr_vid_parse(ifbva, buf, buf + sep + 1);
+ free(buf);
+ return (rv);
+}
+
+void
+bridge_addvaddr(const char *ifsname, const char *addr, size_t sep)
+{
+ struct ifbvareq ifbva;
+
+ if (bridge_addr_vid(&ifbva, addr, sep) == -1)
+ errx(1, "unable to parse address%cvid", VID_SEP);
+
+ strlcpy(ifbva.ifbva_ifsname, ifsname, sizeof(ifbva.ifbva_ifsname));
+ ifbva.ifbva_flags = IFBAF_STATIC;
+
+ if (ioctl(sock, SIOCBRDGSVADDR, &ifbva) == -1)
+ err(1, "%s static %s %s", ifname, ifsname, addr);
+}
+
void
bridge_addaddr(const char *ifsname, const char *addr)
{
+ char *chr;
struct ifbareq ifba;
struct ether_addr *ea;
+ chr = strchr(addr, VID_SEP);
+ if (chr != NULL) {
+ bridge_addvaddr(ifsname, addr, chr - addr);
+ return;
+ }
+
strlcpy(ifba.ifba_name, ifname, sizeof(ifba.ifba_name));
strlcpy(ifba.ifba_ifsname, ifsname, sizeof(ifba.ifba_ifsname));
@@ -668,6 +850,42 @@ bridge_addaddr(const char *ifsname, cons
err(1, "%s: %s", ifname, addr);
}
+static void
+bridge_delvaddr(const char *addr, size_t sep)
+{
+ struct ifbvareq ifbva;
+
+ if (bridge_addr_vid(&ifbva, addr, sep) == -1)
+ errx(1, "unable to parse address%cvid", VID_SEP);
+
+ if (ioctl(sock, SIOCBRDGDVADDR, &ifbva) == -1)
+ err(1, "%s deladdr %s", ifname, addr);
+}
+
+void
+bridge_deladdr(const char *addr, int d)
+{
+ char *chr;
+ struct ifbareq ifba;
+ struct ether_addr *ea;
+
+ chr = strchr(addr, VID_SEP);
+ if (chr != NULL) {
+ bridge_delvaddr(addr, chr - addr);
+ return;
+ }
+
+ strlcpy(ifba.ifba_name, ifname, sizeof(ifba.ifba_name));
+ ea = ether_aton(addr);
+ if (ea == NULL)
+ err(1, "Invalid address: %s", addr);
+
+ bcopy(ea, &ifba.ifba_dst, sizeof(struct ether_addr));
+
+ if (ioctl(sock, SIOCBRDGDADDR, &ifba) == -1)
+ err(1, "%s: %s", ifname, addr);
+}
+
void
bridge_addendpoint(const char *endpoint, const char *addr)
{
@@ -727,6 +945,79 @@ bridge_delendpoint(const char *addr, int
err(1, "%s -endpoint %s", ifname, addr);
}
+static int
+bridge_vaddrs_try(const char *delim)
+{
+ char dstaddr[NI_MAXHOST];
+ char dstport[NI_MAXSERV];
+ const int niflag = NI_NUMERICHOST|NI_DGRAM;
+ struct ifbaconf ifbac;
+ struct ifbvareq *ifbva;
+ char *inbuf = NULL, buf[sizeof(ifbva->ifbva_ifsname) + 1], *inb;
+ struct sockaddr *sa;
+ int i, len = 8192;
+ struct timespec now;
+
+ while (1) {
+ ifbac.ifbac_len = len;
+ inb = realloc(inbuf, len);
+ if (inb == NULL)
+ err(1, "malloc");
+ ifbac.ifbac_buf = inbuf = inb;
+ strlcpy(ifbac.ifbac_name, ifname, sizeof(ifbac.ifbac_name));
+ if (ioctl(sock, SIOCBRDGVRTS, &ifbac) == -1) {
+ switch (errno) {
+ case ENETDOWN:
+ return (0);
+ case ENOTTY:
+ return (-1);
+ default:
+ err(1, "%s", ifname);
+ }
+ }
+ if (ifbac.ifbac_len + sizeof(*ifbva) < len)
+ break;
+ len *= 2;
+ }
+
+ clock_gettime(CLOCK_MONOTONIC, &now);
+
+ for (i = 0; i < ifbac.ifbac_len / sizeof(*ifbva); i++) {
+ ifbva = ifbac.ifbac_vreq + i;
+ strlcpy(buf, ifbva->ifbva_ifsname, sizeof(buf));
+ printf("%s%s", delim, ether_ntoa(&ifbva->ifbva_dst));
+ if (ifbva->ifbva_vid != EVL_VLID_NULL)
+ printf("%c%u", VID_SEP, ifbva->ifbva_vid);
+ if (buf[0] != '\0')
+ printf(" %s", buf);
+
+ /*
+ * printf(" created %lld", now.tv_sec - ifbva->ifbva_created);
+ * printf(" age");
+ */
+ printf(" %lld", now.tv_sec - ifbva->ifbva_used);
+ printb(" flags", ifbva->ifbva_flags, IFBAFBITS);
+
+ sa = (struct sockaddr *)&ifbva->ifbva_dstsa;
+ if (sa->sa_family != AF_UNSPEC &&
+ getnameinfo(sa, sa->sa_len,
+ dstaddr, sizeof(dstaddr),
+ dstport, sizeof(dstport), niflag) == 0)
+ printf(" tunnel %s:%s", dstaddr, dstport);
+ printf("\n");
+ }
+ free(inbuf);
+
+ return (0);
+}
+
+void
+bridge_vaddrs(const char *cmd, int d)
+{
+ if (bridge_vaddrs_try("") == -1)
+ err(1, "%s %s", ifname, cmd);
+}
+
void
bridge_addrs(const char *delim, int d)
{
@@ -847,7 +1138,50 @@ bridge_status(void)
printf("\tAddresses (max cache: %u, timeout: %u):\n",
bp1.ifbrp_csize, bp2.ifbrp_ctime);
+ /* try the new version of the addrs ioctl first */
+ if (bridge_vaddrs_try("\t\t") == 0)
+ return;
+
bridge_addrs("\t\t", 0);
+}
+
+void
+bridge_vidmap(const char *ifsname)
+{
+ struct ifbrvidmap ifbrvm;
+ size_t i;
+ char sep = ' ';
+
+ strlcpy(ifbrvm.ifbrvm_name, ifname, sizeof(ifbrvm.ifbrvm_name));
+ strlcpy(ifbrvm.ifbrvm_ifsname, ifsname, sizeof(ifbrvm.ifbrvm_ifsname));
+
+ if (ioctl(sock, SIOCBRDGGVMAP, &ifbrvm) == -1) {
+ if (errno != ENOTTY)
+ warn("%s port %s get tagged", ifname, ifsname);
+ return;
+ }
+
+ printf("\t\t" "tagged:");
+
+ for (i = 0; i < sizeof(ifbrvm.ifbrvm_map); i++) {
+ unsigned int voff = i * 8;
+ unsigned int vbit;
+ uint8_t tag = ifbrvm.ifbrvm_map[i];
+
+ if (tag == 0)
+ continue;
+
+ for (vbit = 0; vbit < 8; vbit++) {
+ if (tag & (1U << vbit)) {
+ printf("%c%u", sep, voff + vbit);
+ sep = ',';
+ }
+ }
+ }
+
+ if (sep == ' ')
+ printf(" none");
+ printf("\n");
}
void
Index: sbin/ifconfig/ifconfig.8
===================================================================
RCS file: /cvs/src/sbin/ifconfig/ifconfig.8,v
diff -u -p -r1.406 ifconfig.8
--- sbin/ifconfig/ifconfig.8 21 Oct 2025 05:27:04 -0000 1.406
+++ sbin/ifconfig/ifconfig.8 31 Oct 2025 02:07:33 -0000
@@ -2078,7 +2078,7 @@ device will try to establish a data conn
.Op Cm add Ar child-iface
.Op Cm addspan Ar child-iface
.Op Cm del Ar child-iface
-.Op Cm deladdr Ar address
+.Op Cm deladdr Ar address Ns Oo @ Ns Ar vid Oc
.Op Cm delspan Ar child-iface
.Op Oo Fl Oc Ns Cm discover Ar child-iface
.Op Cm flushrule Ar interface
@@ -2091,8 +2091,16 @@ device will try to establish a data conn
.Op Cm rule Ar filtering-rule
.Op Cm rulefile Ar filename
.Op Cm rules Ar interface
-.Op Cm static Ar interface Ar address
+.Op Cm static Ar child-iface Ar address Ns Oo @ Ns Ar vid Oc
.Op Cm timeout Ar time
+.Op Cm tagged Ar child-iface Ar vid
+.Op Cm -tagged Ar child-iface
+.Op Cm untagged Ar child-iface Ar vid
+.Op Cm -untagged Ar child-iface
+.Op Cm rxprio Ar prio
+.Op Cm txprio Ar prio
+.Op Cm vnetid Ar vid
+.Op Cm -vnetid
.Op Cm up
.Ek
.nr nS 0
@@ -2112,10 +2120,15 @@ as a span port on the bridge.
.It Cm del Ar child-iface
Remove the member
.Ar child-iface .
-.It Cm deladdr Ar address
+.It Cm deladdr Ar address Ns Oo @ Ns Ar vid Oc
Delete
.Ar address
+on VLAN
+.Ar vid
from the cache.
+if
+.Ar vid
+is not specified it uses the default VLAN identifier on the bridge.
.It Cm delspan Ar child-iface
Delete
.Ar child-iface
@@ -2268,11 +2281,19 @@ Load a set of rules from the file
.It Cm rules Ar interface
Display the active filtering rules in use on
.Ar interface .
-.It Cm static Ar interface Ar address
-Add a static entry into the address cache pointing to
-.Ar interface .
-Static entries are never aged out of the cache or replaced, even if the address
-is seen on a different interface.
+.It Cm static Ar child-iface Ar address Ns Oo @ Ns Ar vid Oc
+Add a static entry for
+.Ar address
+on VLAN
+.Ar vid
+into the address cache pointing to
+.Ar child-iface .
+If
+.Ar vid
+is not specified it defaults to the VLAN used by untagged packets on
+.Ar child-iface .
+Static entries are never aged out of the cache or replaced, even
+if the address is seen on a different interface.
.It Cm timeout Ar time
Set the timeout, in seconds, for addresses in the cache to
.Ar time .
@@ -2280,6 +2301,57 @@ The default is 240 seconds.
If
.Ar time
is set to zero, then entries will not be expired.
+.It Cm tagged Ar child-iface Ar vid
+Modify the set of VLAN identifiers that can be sent and received
+as VLAN tagged traffic on
+.Ar child-iface
+to the specified
+.Ar vid .
+If
+.Ar vid
+is prefixed with + or -, the identifier is added to or removed from
+the set respectively, otherwise the set is replaced.
+.\" XXX tagged all|none
+By default the set of tagged VLAN is empty.
+.It Cm -tagged Ar child-iface
+Clear all the VLAN identifiers from the set that can be sent or
+received as VLAN tagged traffic on
+.Ar child-iface .
+This effectively disables VLAN tagged packet handling by the bridge
+on the specified port.
+.It Cm untagged Ar child-iface Ar vid
+Set untagged traffic on
+.Ar child-iface
+to operate in the VLAN specified by
+.Ar vid .
+By default ports are configured with the default VLAN identifier
+configured on the bridge.
+.It Cm -untagged Ar child-iface
+Don't allow untagged traffic on
+.Ar child-iface .
+.It Cm rxprio Ar prio
+Configure the handling of the VLAN priority field in received packets.
+This is compatible with the configuration of
+.Cm rxprio
+on
+.Xr vlan 4
+interfaces.
+.It Cm txprio Ar prio
+Configure the handling of the VLAN priority field in transmitted packets.
+This is compatible with the configuration of
+.Cm txprio
+on
+.Xr vlan 4
+interfaces.
+.It Cm vnetid Ar vid
+Set the default VLAN identifier for untagged packets for ports added
+to the bridge.
+The default is VLAN 1.
+.It Cm -vnetid
+Remove the default VLAN identifier for untagged packets for ports added
+to the bridge.
+Ports added to the bridge will not be able to send or receive packets
+until they are explicitly configured.
.It Cm up
Start forwarding packets.
.El
Index: sbin/ifconfig/ifconfig.c
===================================================================
RCS file: /cvs/src/sbin/ifconfig/ifconfig.c,v
diff -u -p -r1.477 ifconfig.c
--- sbin/ifconfig/ifconfig.c 21 Oct 2025 05:14:22 -0000 1.477
+++ sbin/ifconfig/ifconfig.c 31 Oct 2025 02:07:33 -0000
@@ -572,6 +572,10 @@ const struct cmd {
{ "-autoedge", NEXTARG, 0, unsetautoedge },
{ "protected", NEXTARG2, 0, NULL, bridge_protect },
{ "-protected", NEXTARG, 0, bridge_unprotect },
+ { "untagged", NEXTARG2, 0, NULL, bridge_pvid },
+ { "-untagged", NEXTARG, 0, bridge_unpvid },
+ { "tagged", NEXTARG2, 0, NULL, bridge_set_vidmap },
+ { "-tagged", NEXTARG2, 0, bridge_unset_vidmap },
{ "ptp", NEXTARG, 0, setptp },
{ "-ptp", NEXTARG, 0, unsetptp },
{ "autoptp", NEXTARG, 0, setautoptp },
@@ -584,6 +588,7 @@ const struct cmd {
{ "deladdr", NEXTARG, 0, bridge_deladdr },
{ "maxaddr", NEXTARG, 0, bridge_maxaddr },
{ "addr", 0, 0, bridge_addrs },
+ { "vaddr", 0, 0, bridge_vaddrs },
{ "hellotime", NEXTARG, 0, bridge_hellotime },
{ "fwddelay", NEXTARG, 0, bridge_fwddelay },
{ "maxage", NEXTARG, 0, bridge_maxage },
Index: sbin/ifconfig/ifconfig.h
===================================================================
RCS file: /cvs/src/sbin/ifconfig/ifconfig.h,v
diff -u -p -r1.7 ifconfig.h
--- sbin/ifconfig/ifconfig.h 21 Oct 2025 05:14:22 -0000 1.7
+++ sbin/ifconfig/ifconfig.h 31 Oct 2025 02:07:33 -0000
@@ -55,11 +55,16 @@ void bridge_delendpoint(const char *, in
void bridge_deladdr(const char *, int);
void bridge_maxaddr(const char *, int);
void bridge_addrs(const char *, int);
+void bridge_vaddrs(const char *, int);
void bridge_hellotime(const char *, int);
void bridge_fwddelay(const char *, int);
void bridge_maxage(const char *, int);
void bridge_protect(const char *, const char *);
void bridge_unprotect(const char *, int);
+void bridge_pvid(const char *, const char *);
+void bridge_unpvid(const char *, int);
+void bridge_set_vidmap(const char *, const char *);
+void bridge_unset_vidmap(const char *, int);
void bridge_proto(const char *, int);
void bridge_ifprio(const char *, const char *);
void bridge_ifcost(const char *, const char *);
Index: sys/net/if.c
===================================================================
RCS file: /cvs/src/sys/net/if.c,v
diff -u -p -r1.741 if.c
--- sys/net/if.c 9 Sep 2025 09:16:18 -0000 1.741
+++ sys/net/if.c 31 Oct 2025 02:07:33 -0000
@@ -2457,11 +2457,14 @@ forceup:
case SIOCBRDGADDS:
case SIOCBRDGDELS:
case SIOCBRDGSADDR:
+ case SIOCBRDGSVADDR:
case SIOCBRDGSTO:
case SIOCBRDGDADDR:
+ case SIOCBRDGDVADDR:
case SIOCBRDGFLUSH:
case SIOCBRDGADDL:
case SIOCBRDGSIFPROT:
+ case SIOCBRDGSPVID:
case SIOCBRDGARL:
case SIOCBRDGFRL:
case SIOCBRDGSPRI:
@@ -2472,6 +2475,7 @@ forceup:
case SIOCBRDGSIFCOST:
case SIOCBRDGSTXHC:
case SIOCBRDGSPROTO:
+ case SIOCBRDGSVMAP:
#endif
if ((error = suser(p)) != 0)
break;
Index: sys/net/if_bpe.c
===================================================================
RCS file: /cvs/src/sys/net/if_bpe.c,v
diff -u -p -r1.25 if_bpe.c
--- sys/net/if_bpe.c 7 Jul 2025 02:28:50 -0000 1.25
+++ sys/net/if_bpe.c 31 Oct 2025 02:07:33 -0000
@@ -265,7 +265,7 @@ bpe_start(struct ifnet *ifp)
smr_read_enter();
endpoint = etherbridge_resolve_ea(&sc->sc_eb,
- (struct ether_addr *)ceh->ether_dhost);
+ 0, (struct ether_addr *)ceh->ether_dhost);
if (endpoint == NULL) {
/* "flood" to unknown hosts */
endpoint = &sc->sc_group;
@@ -710,13 +710,13 @@ bpe_add_addr(struct bpe_softc *sc, const
/* check endpoint for multicast or broadcast? */
return (etherbridge_add_addr(&sc->sc_eb, (void *)endpoint,
- &ifba->ifba_dst, type));
+ 0, &ifba->ifba_dst, type));
}
static int
bpe_del_addr(struct bpe_softc *sc, const struct ifbareq *ifba)
{
- return (etherbridge_del_addr(&sc->sc_eb, &ifba->ifba_dst));
+ return (etherbridge_del_addr(&sc->sc_eb, 0, &ifba->ifba_dst));
}
static inline struct bpe_softc *
@@ -770,7 +770,7 @@ bpe_input(struct ifnet *ifp0, struct mbu
ceh = (struct ether_header *)(itagp + 1);
etherbridge_map_ea(&sc->sc_eb, ceh->ether_shost,
- (struct ether_addr *)beh->ether_shost);
+ 0, (struct ether_addr *)beh->ether_shost);
m_adj(m, sizeof(*beh) + sizeof(*itagp));
Index: sys/net/if_bridge.h
===================================================================
RCS file: /cvs/src/sys/net/if_bridge.h,v
diff -u -p -r1.74 if_bridge.h
--- sys/net/if_bridge.h 21 Oct 2025 05:09:32 -0000 1.74
+++ sys/net/if_bridge.h 31 Oct 2025 02:07:33 -0000
@@ -39,6 +39,11 @@
#include <sys/timeout.h>
#include <net/pfvar.h>
+#define IFBR_PVID_NULL EVL_VLID_NULL
+#define IFBR_PVID_MIN EVL_VLID_MIN
+#define IFBR_PVID_MAX EVL_VLID_MAX
+#define IFBR_PVID_NONE 0xffff
+
/*
* Bridge control request: add/delete member interfaces.
*/
@@ -51,6 +56,7 @@ struct ifbreq {
u_int8_t ifbr_state; /* member stp state */
u_int8_t ifbr_priority; /* member stp priority */
+ u_int16_t ifbr_pvid; /* member port vlan id */
u_int32_t ifbr_path_cost; /* member stp path cost */
u_int32_t ifbr_stpflags; /* member stp flags */
u_int8_t ifbr_proto; /* member stp protocol */
@@ -134,15 +140,19 @@ struct ifbareq {
#define IFBAF_DYNAMIC 0x00 /* dynamically learned */
#define IFBAF_STATIC 0x01 /* static address */
+struct ifbvareq;
+
struct ifbaconf {
char ifbac_name[IFNAMSIZ]; /* bridge ifs name */
u_int32_t ifbac_len; /* buffer size */
union {
caddr_t ifbacu_buf; /* buffer */
struct ifbareq *ifbacu_req; /* request pointer */
+ struct ifbvareq *ifbacu_vreq; /* request pointer */
} ifbac_ifbacu;
#define ifbac_buf ifbac_ifbacu.ifbacu_buf
#define ifbac_req ifbac_ifbacu.ifbacu_req
+#define ifbac_vreq ifbac_ifbacu.ifbacu_vreq
};
struct ifbrparam {
@@ -236,6 +246,28 @@ struct ifbrlconf {
} ifbrl_ifbrlu;
#define ifbrl_buf ifbrl_ifbrlu.ifbrlu_buf
#define ifbrl_req ifbrl_ifbrlu.ifbrlu_req
+};
+
+struct ifbvareq {
+ char ifbva_name[IFNAMSIZ]; /* bridge name */
+ char ifbva_ifsname[IFNAMSIZ]; /* destination ifs */
+ time_t ifbva_created; /* monotime */
+ time_t ifbva_used; /* monotime */
+ unsigned int ifbva_flags; /* address flags */
+ uint16_t ifbva_vid; /* vlan */
+ struct ether_addr ifbva_dst; /* destination addr */
+ struct sockaddr_storage ifbva_dstsa; /* tunnel endpoint */
+};
+
+struct ifbrvidmap {
+ char ifbrvm_name[IFNAMSIZ];
+ char ifbrvm_ifsname[IFNAMSIZ];
+ unsigned int ifbrvm_op;
+#define IFBRVM_OP_SET 0x0 /* kernel = ifbrvm_map */
+#define IFBRVM_OP_OR 0x1 /* kernel |= ifbrvm_map */
+#define IFBRVM_OP_ANDNOT 0x2 /* kernel &= ~ifbrvm_map */
+ unsigned int ifbrvm_gen;
+ uint8_t ifbrvm_map[512];
};
#ifdef _KERNEL
Index: sys/net/if_etherbridge.c
===================================================================
RCS file: /cvs/src/sys/net/if_etherbridge.c,v
diff -u -p -r1.8 if_etherbridge.c
--- sys/net/if_etherbridge.c 7 Jul 2025 02:28:50 -0000 1.8
+++ sys/net/if_etherbridge.c 31 Oct 2025 02:07:33 -0000
@@ -239,20 +239,22 @@ ebe_free(void *arg)
}
void *
-etherbridge_resolve_ea(struct etherbridge *eb,
+etherbridge_resolve_ea(struct etherbridge *eb, uint16_t vid,
const struct ether_addr *ea)
{
- return (etherbridge_resolve(eb, ether_addr_to_e64(ea)));
+ return (etherbridge_resolve(eb, vid, ether_addr_to_e64(ea)));
}
void *
-etherbridge_resolve(struct etherbridge *eb, uint64_t eba)
+etherbridge_resolve(struct etherbridge *eb, uint16_t vid, uint64_t eba)
{
- struct eb_list *ebl = etherbridge_list(eb, eba);
+ struct eb_list *ebl;
struct eb_entry *ebe;
SMR_ASSERT_CRITICAL();
+ eba |= (uint64_t)vid << 48;
+ ebl = etherbridge_list(eb, eba);
ebe = ebl_find(ebl, eba);
if (ebe != NULL) {
if (ebe->ebe_type == EBE_DYNAMIC) {
@@ -268,14 +270,14 @@ etherbridge_resolve(struct etherbridge *
}
void
-etherbridge_map_ea(struct etherbridge *eb, void *port,
+etherbridge_map_ea(struct etherbridge *eb, void *port, uint16_t vid,
const struct ether_addr *ea)
{
- etherbridge_map(eb, port, ether_addr_to_e64(ea));
+ etherbridge_map(eb, port, vid, ether_addr_to_e64(ea));
}
void
-etherbridge_map(struct etherbridge *eb, void *port, uint64_t eba)
+etherbridge_map(struct etherbridge *eb, void *port, uint16_t vid, uint64_t eba)
{
struct eb_list *ebl;
struct eb_entry *oebe, *nebe;
@@ -288,6 +290,8 @@ etherbridge_map(struct etherbridge *eb,
return;
now = getuptime();
+
+ eba |= (uint64_t)vid << 48;
ebl = etherbridge_list(eb, eba);
smr_read_enter();
@@ -332,6 +336,7 @@ etherbridge_map(struct etherbridge *eb,
nebe->ebe_addr = eba;
nebe->ebe_port = nport;
nebe->ebe_type = EBE_DYNAMIC;
+ nebe->ebe_created = now;
nebe->ebe_age = now;
mtx_enter(&eb->eb_lock);
@@ -385,14 +390,15 @@ etherbridge_map(struct etherbridge *eb,
int
etherbridge_add_addr(struct etherbridge *eb, void *port,
- const struct ether_addr *ea, unsigned int type)
+ uint16_t vid, const struct ether_addr *ea, unsigned int type)
{
- uint64_t eba = ether_addr_to_e64(ea);
+ uint64_t eba = ether_addr_to_e64(ea) | (uint64_t)vid << 48;
struct eb_list *ebl;
struct eb_entry *nebe;
unsigned int num;
void *nport;
int error = 0;
+ time_t now;
if (ETH64_IS_MULTICAST(eba) || ETH64_IS_ANYADDR(eba))
return (EADDRNOTAVAIL);
@@ -407,13 +413,16 @@ etherbridge_add_addr(struct etherbridge
return (ENOMEM);
}
+ now = getuptime();
+
smr_init(&nebe->ebe_smr_entry);
nebe->ebe_etherbridge = eb;
nebe->ebe_addr = eba;
nebe->ebe_port = nport;
nebe->ebe_type = type;
- nebe->ebe_age = getuptime();
+ nebe->ebe_created = now;
+ nebe->ebe_age = now;
ebl = etherbridge_list(eb, eba);
@@ -441,9 +450,10 @@ etherbridge_add_addr(struct etherbridge
return (error);
}
int
-etherbridge_del_addr(struct etherbridge *eb, const struct ether_addr *ea)
+etherbridge_del_addr(struct etherbridge *eb, uint16_t vid,
+ const struct ether_addr *ea)
{
- uint64_t eba = ether_addr_to_e64(ea);
+ uint64_t eba = ether_addr_to_e64(ea) | (uint64_t)vid << 48;
struct eb_list *ebl;
struct eb_entry *oebe;
const struct eb_entry key = {
@@ -654,6 +664,67 @@ etherbridge_rtfind(struct etherbridge *e
}
nlen = baconf->ifbac_len;
baconf->ifbac_len = eb->eb_num * sizeof(bareq);
+ mtx_leave(&eb->eb_lock);
+
+ error = copyout(buf, baconf->ifbac_buf, len);
+ free(buf, M_TEMP, nlen);
+
+ return (error);
+}
+
+int
+etherbridge_vareq(struct etherbridge *eb, struct ifbaconf *baconf)
+{
+ struct eb_entry *ebe;
+ struct ifbvareq bvareq;
+ caddr_t buf;
+ size_t len, nlen;
+ int error;
+
+ if (baconf->ifbac_len == 0) {
+ /* single read is atomic */
+ baconf->ifbac_len = eb->eb_num * sizeof(bvareq);
+ return (0);
+ }
+
+ buf = malloc(baconf->ifbac_len, M_TEMP, M_WAITOK|M_CANFAIL);
+ if (buf == NULL)
+ return (ENOMEM);
+ len = 0;
+
+ mtx_enter(&eb->eb_lock);
+ RBT_FOREACH(ebe, eb_tree, &eb->eb_tree) {
+ nlen = len + sizeof(bvareq);
+ if (nlen > baconf->ifbac_len)
+ break;
+
+ strlcpy(bvareq.ifbva_name, eb->eb_name,
+ sizeof(bvareq.ifbva_name));
+ eb_port_ifname(eb,
+ bvareq.ifbva_ifsname, sizeof(bvareq.ifbva_ifsname),
+ ebe->ebe_port);
+ bvareq.ifbva_created = ebe->ebe_created;
+ bvareq.ifbva_used = ebe->ebe_age;
+ bvareq.ifbva_vid = ebe->ebe_addr >> 48;
+ ether_e64_to_addr(&bvareq.ifbva_dst, ebe->ebe_addr);
+
+ memset(&bvareq.ifbva_dstsa, 0, sizeof(bvareq.ifbva_dstsa));
+ eb_port_sa(eb, &bvareq.ifbva_dstsa, ebe->ebe_port);
+
+ switch (ebe->ebe_type) {
+ case EBE_DYNAMIC:
+ bvareq.ifbva_flags = IFBAF_DYNAMIC;
+ break;
+ case EBE_STATIC:
+ bvareq.ifbva_flags = IFBAF_STATIC;
+ break;
+ }
+
+ memcpy(buf + len, &bvareq, sizeof(bvareq));
+ len = nlen;
+ }
+ nlen = baconf->ifbac_len;
+ baconf->ifbac_len = eb->eb_num * sizeof(bvareq);
mtx_leave(&eb->eb_lock);
error = copyout(buf, baconf->ifbac_buf, len);
Index: sys/net/if_etherbridge.h
===================================================================
RCS file: /cvs/src/sys/net/if_etherbridge.h,v
diff -u -p -r1.5 if_etherbridge.h
--- sys/net/if_etherbridge.h 4 Nov 2024 00:13:15 -0000 1.5
+++ sys/net/if_etherbridge.h 31 Oct 2025 02:07:33 -0000
@@ -48,6 +48,7 @@ struct eb_entry {
#define EBE_DYNAMIC 0x0
#define EBE_STATIC 0x1
#define EBE_DEAD 0xdead
+ time_t ebe_created;
time_t ebe_age;
struct etherbridge *ebe_etherbridge;
@@ -80,12 +81,12 @@ int etherbridge_up(struct etherbridge *
int etherbridge_down(struct etherbridge *);
void etherbridge_destroy(struct etherbridge *);
-void etherbridge_map(struct etherbridge *, void *, uint64_t);
+void etherbridge_map(struct etherbridge *, void *, uint16_t, uint64_t);
void etherbridge_map_ea(struct etherbridge *, void *,
- const struct ether_addr *);
-void *etherbridge_resolve(struct etherbridge *, uint64_t);
+ uint16_t, const struct ether_addr *);
+void *etherbridge_resolve(struct etherbridge *, uint16_t, uint64_t);
void *etherbridge_resolve_ea(struct etherbridge *,
- const struct ether_addr *);
+ uint16_t, const struct ether_addr *);
void etherbridge_detach_port(struct etherbridge *, void *);
/* ioctl support */
@@ -94,9 +95,11 @@ int etherbridge_get_max(struct etherbri
int etherbridge_set_tmo(struct etherbridge *, struct ifbrparam *);
int etherbridge_get_tmo(struct etherbridge *, struct ifbrparam *);
int etherbridge_rtfind(struct etherbridge *, struct ifbaconf *);
+int etherbridge_vareq(struct etherbridge *, struct ifbaconf *);
int etherbridge_add_addr(struct etherbridge *, void *,
- const struct ether_addr *, unsigned int);
-int etherbridge_del_addr(struct etherbridge *, const struct ether_addr *);
+ uint16_t, const struct ether_addr *, unsigned int);
+int etherbridge_del_addr(struct etherbridge *,
+ uint16_t, const struct ether_addr *);
void etherbridge_flush(struct etherbridge *, uint32_t);
#endif /* _NET_ETHERBRIDGE_H_ */
Index: sys/net/if_gre.c
===================================================================
RCS file: /cvs/src/sys/net/if_gre.c,v
diff -u -p -r1.190 if_gre.c
--- sys/net/if_gre.c 7 Jul 2025 02:28:50 -0000 1.190
+++ sys/net/if_gre.c 31 Oct 2025 02:07:33 -0000
@@ -1460,7 +1460,7 @@ nvgre_input(const struct gre_tunnel *key
eh = mtod(m, struct ether_header *);
etherbridge_map_ea(&sc->sc_eb, (void *)&key->t_dst,
- (struct ether_addr *)eh->ether_shost);
+ 0, (struct ether_addr *)eh->ether_shost);
SET(m->m_pkthdr.csum_flags, M_FLOWID);
m->m_pkthdr.ph_flowid = bemtoh32(&key->t_key) & ~GRE_KEY_ENTROPY;
@@ -3711,13 +3711,13 @@ nvgre_add_addr(struct nvgre_softc *sc, c
}
return (etherbridge_add_addr(&sc->sc_eb, &endpoint,
- &ifba->ifba_dst, type));
+ 0, &ifba->ifba_dst, type));
}
static int
nvgre_del_addr(struct nvgre_softc *sc, const struct ifbareq *ifba)
{
- return (etherbridge_del_addr(&sc->sc_eb, &ifba->ifba_dst));
+ return (etherbridge_del_addr(&sc->sc_eb, 0, &ifba->ifba_dst));
}
static void
@@ -3753,7 +3753,7 @@ nvgre_start(struct ifnet *ifp)
smr_read_enter();
endpoint = etherbridge_resolve_ea(&sc->sc_eb,
- (struct ether_addr *)eh->ether_dhost);
+ 0, (struct ether_addr *)eh->ether_dhost);
if (endpoint == NULL) {
/* "flood" to unknown hosts */
endpoint = &tunnel->t_dst;
Index: sys/net/if_veb.c
===================================================================
RCS file: /cvs/src/sys/net/if_veb.c,v
diff -u -p -r1.45 if_veb.c
--- sys/net/if_veb.c 21 Oct 2025 05:13:20 -0000 1.45
+++ sys/net/if_veb.c 31 Oct 2025 02:07:33 -0000
@@ -63,6 +63,11 @@
#include <net/if_vlan_var.h>
#endif
+/* there are (basically) 4096 vids (vlan tags) */
+#define VEB_VID_COUNT 4096
+#define VEB_VID_BYTES (VEB_VID_COUNT / 8)
+#define VEB_VID_WORDS (VEB_VID_BYTES / sizeof(uint32_t))
+
/* SIOCBRDGIFFLGS, SIOCBRDGIFFLGS */
#define VEB_IFBIF_FLAGS \
(IFBIF_LOCKED|IFBIF_LEARNING|IFBIF_DISCOVER|IFBIF_BLOCKNONIP)
@@ -125,6 +130,8 @@ struct veb_port {
unsigned int p_link_state;
unsigned int p_bif_flags;
uint32_t p_protected;
+ uint16_t p_pvid;
+ uint32_t *p_vid_map;
struct veb_rules p_vrl;
unsigned int p_nvrl;
@@ -145,6 +152,9 @@ struct veb_softc {
unsigned int sc_dead;
struct etherbridge sc_eb;
+ int sc_dflt_pvid;
+ int sc_txprio;
+ int sc_rxprio;
struct rwlock sc_rule_lock;
struct veb_ports *sc_ports;
@@ -165,6 +175,8 @@ static int veb_enqueue(struct ifnet *, s
static int veb_output(struct ifnet *, struct mbuf *, struct sockaddr *,
struct rtentry *);
static void veb_start(struct ifqueue *);
+static struct mbuf *
+ veb_offload(struct ifnet *, struct ifnet *, struct mbuf *);
static int veb_up(struct veb_softc *);
static int veb_down(struct veb_softc *);
@@ -204,8 +216,15 @@ static int veb_port_set_flags(struct veb
static int veb_port_get_flags(struct veb_softc *, struct ifbreq *);
static int veb_port_set_protected(struct veb_softc *,
const struct ifbreq *);
+static int veb_port_set_pvid(struct veb_softc *,
+ const struct ifbreq *);
static int veb_add_addr(struct veb_softc *, const struct ifbareq *);
+static int veb_add_vid_addr(struct veb_softc *, const struct ifbvareq *);
static int veb_del_addr(struct veb_softc *, const struct ifbareq *);
+static int veb_del_vid_addr(struct veb_softc *, const struct ifbvareq *);
+
+static int veb_get_vid_map(struct veb_softc *, struct ifbrvidmap *);
+static int veb_set_vid_map(struct veb_softc *, const struct ifbrvidmap *);
static int veb_rule_add(struct veb_softc *, const struct ifbrlreq *);
static int veb_rule_list_flush(struct veb_softc *,
@@ -294,6 +313,10 @@ veb_clone_create(struct if_clone *ifc, i
return (error);
}
+ sc->sc_dflt_pvid = 1;
+ sc->sc_txprio = IF_HDRPRIO_PACKET;
+ sc->sc_rxprio = IF_HDRPRIO_OUTER;
+
ifp->if_softc = sc;
ifp->if_type = IFT_BRIDGE;
ifp->if_hdrlen = ETHER_HDR_LEN;
@@ -441,6 +464,9 @@ veb_span(struct veb_softc *sc, struct mb
continue;
}
+ if ((m = veb_offload(&sc->sc_if, ifp0, m)) == NULL)
+ continue;
+
if_enqueue(ifp0, m); /* XXX count error */
}
refcnt_rele_wake(&sm->m_refs);
@@ -451,9 +477,6 @@ veb_ip_filter(const struct mbuf *m)
{
const struct ether_header *eh;
- if (ISSET(m->m_flags, M_VLANTAG))
- return (1);
-
eh = mtod(m, struct ether_header *);
switch (ntohs(eh->ether_type)) {
case ETHERTYPE_IP:
@@ -469,23 +492,32 @@ veb_ip_filter(const struct mbuf *m)
}
static int
-veb_vlan_filter(const struct mbuf *m)
+veb_svlan_filter(const struct mbuf *m)
{
const struct ether_header *eh;
- if (ISSET(m->m_flags, M_VLANTAG))
- return (1);
-
eh = mtod(m, struct ether_header *);
- switch (ntohs(eh->ether_type)) {
- case ETHERTYPE_VLAN:
- case ETHERTYPE_QINQ:
- return (1);
- default:
- break;
+
+ return (eh->ether_type == htons(ETHERTYPE_QINQ));
+}
+
+static int
+veb_vid_map_filter(struct veb_port *p, uint16_t vid)
+{
+ uint32_t *map;
+ int drop = 1;
+
+ smr_read_enter();
+ map = SMR_PTR_GET(&p->p_vid_map);
+ if (map != NULL) {
+ unsigned int off = vid / 32;
+ unsigned int bit = vid % 32;
+
+ drop = !ISSET(map[off], 1U << bit);
}
+ smr_read_leave();
- return (0);
+ return (drop);
}
static int
@@ -494,9 +526,6 @@ veb_rule_arp_match(const struct veb_rule
struct ether_header *eh;
struct ether_arp ea;
- if (ISSET(m->m_flags, M_VLANTAG))
- return (0);
-
eh = mtod(m, struct ether_header *);
if (eh->ether_type != htons(ETHERTYPE_ARP))
@@ -544,11 +573,13 @@ veb_rule_arp_match(const struct veb_rule
static int
veb_rule_list_test(struct veb_rule *vr, int dir, struct mbuf *m,
- uint64_t src, uint64_t dst)
+ uint64_t src, uint64_t dst, uint16_t vid)
{
SMR_ASSERT_CRITICAL();
do {
+ /* XXX check vid */
+
if (ISSET(vr->vr_flags, VEB_R_F_ARP|VEB_R_F_RARP) &&
!veb_rule_arp_match(vr, m))
continue;
@@ -574,7 +605,7 @@ veb_rule_list_test(struct veb_rule *vr,
static inline int
veb_rule_filter(struct veb_port *p, int dir, struct mbuf *m,
- uint64_t src, uint64_t dst)
+ uint64_t src, uint64_t dst, uint16_t vid)
{
struct veb_rule *vr;
int filter = VEB_R_PASS;
@@ -582,7 +613,7 @@ veb_rule_filter(struct veb_port *p, int
smr_read_enter();
vr = SMR_TAILQ_FIRST(&p->p_vr_list[dir]);
if (vr != NULL)
- filter = veb_rule_list_test(vr, dir, m, src, dst);
+ filter = veb_rule_list_test(vr, dir, m, src, dst, vid);
smr_read_leave();
return (filter == VEB_R_BLOCK);
@@ -627,9 +658,6 @@ veb_pf(struct ifnet *ifp0, int dir, stru
if (ifp0->if_enqueue == vport_enqueue)
return (m);
- if (ISSET(m->m_flags, M_VLANTAG))
- return (m);
-
eh = mtod(m, struct ether_header *);
switch (ntohs(eh->ether_type)) {
case ETHERTYPE_IP:
@@ -798,9 +826,6 @@ veb_ipsec_in(struct ifnet *ifp0, struct
if (ifp0->if_enqueue == vport_enqueue)
return (m);
- if (ISSET(m->m_flags, M_VLANTAG))
- return (m);
-
eh = mtod(m, struct ether_header *);
switch (ntohs(eh->ether_type)) {
case ETHERTYPE_IP:
@@ -903,9 +928,6 @@ veb_ipsec_out(struct ifnet *ifp0, struct
if (ifp0->if_enqueue == vport_enqueue)
return (m);
- if (ISSET(m->m_flags, M_VLANTAG))
- return (m);
-
eh = mtod(m, struct ether_header *);
switch (ntohs(eh->ether_type)) {
case ETHERTYPE_IP:
@@ -1017,7 +1039,7 @@ veb_offload(struct ifnet *ifp, struct if
static void
veb_broadcast(struct veb_softc *sc, struct veb_port *rp, struct mbuf *m0,
- uint64_t src, uint64_t dst, struct netstack *ns)
+ uint64_t src, uint64_t dst, uint16_t vid, struct netstack *ns)
{
struct ifnet *ifp = &sc->sc_if;
struct veb_ports *pm;
@@ -1027,23 +1049,25 @@ veb_broadcast(struct veb_softc *sc, stru
struct mbuf *m;
unsigned int i;
+ if (rp->p_pvid == vid) { /* XXX which vlan is the right one? */
#if NPF > 0
- /*
- * we couldn't find a specific port to send this packet to,
- * but pf should still have a chance to apply policy to it.
- * let pf look at it, but use the veb interface as a proxy.
- */
- if (ISSET(ifp->if_flags, IFF_LINK1) &&
- (m0 = veb_pf(ifp, PF_FWD, m0, ns)) == NULL)
- return;
+ /*
+ * we couldn't find a specific port to send this packet to,
+ * but pf should still have a chance to apply policy to it.
+ * let pf look at it, but use the veb interface as a proxy.
+ */
+ if (ISSET(ifp->if_flags, IFF_LINK1) &&
+ (m0 = veb_pf(ifp, PF_FWD, m0, ns)) == NULL)
+ return;
#endif
#if 0 && defined(IPSEC)
- /* same goes for ipsec */
- if (ISSET(ifp->if_flags, IFF_LINK2) &&
- (m0 = veb_ipsec_out(ifp, m0)) == NULL)
- return;
+ /* same goes for ipsec */
+ if (ISSET(ifp->if_flags, IFF_LINK2) &&
+ (m0 = veb_ipsec_out(ifp, m0)) == NULL)
+ return;
#endif
+ }
counters_pkt(ifp->if_counters, ifc_opackets, ifc_obytes,
m0->m_pkthdr.len);
@@ -1069,6 +1093,11 @@ veb_broadcast(struct veb_softc *sc, stru
continue;
}
+ if (vid != tp->p_pvid) {
+ if (veb_vid_map_filter(tp, vid))
+ continue;
+ }
+
ifp0 = tp->p_ifp0;
if (!ISSET(ifp0->if_flags, IFF_RUNNING)) {
/* don't waste time */
@@ -1081,18 +1110,21 @@ veb_broadcast(struct veb_softc *sc, stru
continue;
}
- if (veb_rule_filter(tp, VEB_RULE_LIST_OUT, m0, src, dst))
+ if (veb_rule_filter(tp, VEB_RULE_LIST_OUT, m0, src, dst, vid))
continue;
- if ((m0 = veb_offload(ifp, ifp0, m0)) == NULL)
- goto rele;
-
m = m_dup_pkt(m0, max_linkhdr + ETHER_ALIGN, M_NOWAIT);
if (m == NULL) {
/* XXX count error? */
continue;
}
+ if (vid == tp->p_pvid)
+ CLR(m->m_flags, M_VLANTAG);
+
+ if ((m = veb_offload(ifp, ifp0, m)) == NULL)
+ goto rele;
+
(*tp->p_enqueue)(ifp0, m); /* XXX count error */
}
rele:
@@ -1104,7 +1136,8 @@ done:
static struct mbuf *
veb_transmit(struct veb_softc *sc, struct veb_port *rp, struct veb_port *tp,
- struct mbuf *m, uint64_t src, uint64_t dst, struct netstack *ns)
+ struct mbuf *m, uint64_t src, uint64_t dst, uint16_t vid,
+ struct netstack *ns)
{
struct ifnet *ifp = &sc->sc_if;
struct ifnet *ifp0;
@@ -1120,23 +1153,33 @@ veb_transmit(struct veb_softc *sc, struc
goto drop;
}
- if (veb_rule_filter(tp, VEB_RULE_LIST_OUT, m, src, dst))
+ /* pvid or tagged config can override address entries */
+ if (vid != tp->p_pvid) {
+ if (veb_vid_map_filter(tp, vid))
+ goto drop;
+ }
+
+ if (veb_rule_filter(tp, VEB_RULE_LIST_OUT, m, src, dst, vid))
goto drop;
ifp0 = tp->p_ifp0;
+ if (vid == tp->p_pvid) {
#if 0 && defined(IPSEC)
- if (ISSET(ifp->if_flags, IFF_LINK2) &&
- (m = veb_ipsec_out(ifp0, m0)) == NULL)
- return;
+ if (ISSET(ifp->if_flags, IFF_LINK2) &&
+ (m = veb_ipsec_out(ifp0, m0)) == NULL)
+ return;
#endif
#if NPF > 0
- if (ISSET(ifp->if_flags, IFF_LINK1) &&
- (m = veb_pf(ifp0, PF_FWD, m, ns)) == NULL)
- return (NULL);
+ if (ISSET(ifp->if_flags, IFF_LINK1) &&
+ (m = veb_pf(ifp0, PF_FWD, m, ns)) == NULL)
+ return (NULL);
#endif
+ CLR(m->m_flags, M_VLANTAG);
+ }
+
counters_pkt(ifp->if_counters, ifc_opackets, ifc_obytes,
m->m_pkthdr.len);
@@ -1158,6 +1201,7 @@ veb_vport_input(struct ifnet *ifp0, stru
return (m);
}
+
static struct mbuf *
veb_port_input(struct ifnet *ifp0, struct mbuf *m, uint64_t dst, void *brport,
struct netstack *ns)
@@ -1167,6 +1211,8 @@ veb_port_input(struct ifnet *ifp0, struc
struct ifnet *ifp = &sc->sc_if;
struct ether_header *eh;
uint64_t src;
+ uint16_t vid = p->p_pvid;
+ int prio;
#if NBPFILTER > 0
caddr_t if_bpf;
#endif
@@ -1174,20 +1220,6 @@ veb_port_input(struct ifnet *ifp0, struc
if (!ISSET(ifp->if_flags, IFF_RUNNING))
return (m);
- eh = mtod(m, struct ether_header *);
- src = ether_addr_to_e64((struct ether_addr *)eh->ether_shost);
-
- if (ISSET(p->p_bif_flags, IFBIF_LOCKED)) {
- struct veb_port *rp;
-
- smr_read_enter();
- rp = etherbridge_resolve(&sc->sc_eb, src);
- smr_read_leave();
-
- if (rp != p)
- goto drop;
- }
-
/* Is this a MAC Bridge component Reserved address? */
if (ETH64_IS_8021_RSVD(dst)) {
if (!ISSET(ifp->if_flags, IFF_LINK0)) {
@@ -1198,7 +1230,7 @@ veb_port_input(struct ifnet *ifp0, struc
goto drop;
}
- /* look at the last nibble of the 802.1 reserved address */
+ /* look at the last nibble of the 802.1 reserved address */
switch (dst & 0xf) {
case 0x0: /* Nearest Customer Bridge Group Address */
case 0xb: /* EDE-SS PEP (IEEE Std 802.1AEcg) */
@@ -1211,9 +1243,77 @@ veb_port_input(struct ifnet *ifp0, struc
}
}
+ eh = mtod(m, struct ether_header *);
+ if (!ISSET(m->m_flags, M_VLANTAG) &&
+ eh->ether_type == htons(ETHERTYPE_VLAN)) {
+ struct ether_vlan_header *evl;
+
+ evl = mtod(m, struct ether_vlan_header *);
+ m->m_pkthdr.ether_vtag = ntohs(evl->evl_tag);
+ SET(m->m_flags, M_VLANTAG);
+
+ memmove((caddr_t)evl + EVL_ENCAPLEN, evl,
+ offsetof(struct ether_vlan_header, evl_encap_proto));
+ m_adj(m, EVL_ENCAPLEN);
+
+ eh = mtod(m, struct ether_header *);
+ }
+
+ if (ISSET(m->m_flags, M_VLANTAG)) {
+ uint16_t tvid = EVL_VLANOFTAG(m->m_pkthdr.ether_vtag);
+
+ if (tvid == EVL_VLID_NULL) {
+ CLR(m->m_flags, M_VLANTAG);
+ } else if (veb_vid_map_filter(p, tvid)) {
+ /* count vlan tagged drop */
+ goto drop;
+ } else
+ vid = tvid;
+
+ prio = sc->sc_rxprio;
+ switch (prio) {
+ case IF_HDRPRIO_PACKET:
+ break;
+ case IF_HDRPRIO_OUTER:
+ prio = EVL_PRIOFTAG(m->m_pkthdr.ether_vtag);
+ /* IEEE 802.1p has prio 0 and 1 swapped */
+ if (prio <= 1)
+ prio = !prio;
+ /* FALLTHROUGH */
+ default:
+ m->m_pkthdr.pf.prio = prio;
+ break;
+ }
+ }
+
+ if (vid == IFBR_PVID_NONE)
+ goto drop;
+
+ src = ether_addr_to_e64((struct ether_addr *)eh->ether_shost);
+
+ if (ISSET(p->p_bif_flags, IFBIF_LOCKED)) {
+ struct veb_port *rp;
+
+ smr_read_enter();
+ rp = etherbridge_resolve(&sc->sc_eb, vid, src);
+ smr_read_leave();
+
+ if (rp != p)
+ goto drop;
+ }
+
counters_pkt(ifp->if_counters, ifc_ipackets, ifc_ibytes,
m->m_pkthdr.len);
+ /*
+ * set things up so we show BPF on veb which vlan this
+ * packet is on. i can't decide if the txprio or rxprio is
+ * better here, so i went with the third option of doing
+ * nothing. - dlg
+ */
+ SET(m->m_flags, M_VLANTAG);
+ m->m_pkthdr.ether_vtag = vid;
+
/* force packets into the one routing domain for pf */
m->m_pkthdr.ph_rtableid = ifp->if_rdomain;
@@ -1232,10 +1332,10 @@ veb_port_input(struct ifnet *ifp0, struc
goto drop;
if (!ISSET(ifp->if_flags, IFF_LINK0) &&
- veb_vlan_filter(m))
+ veb_svlan_filter(m))
goto drop;
- if (veb_rule_filter(p, VEB_RULE_LIST_IN, m, src, dst))
+ if (veb_rule_filter(p, VEB_RULE_LIST_IN, m, src, dst, vid))
goto drop;
#if NPF > 0
@@ -1253,7 +1353,14 @@ veb_port_input(struct ifnet *ifp0, struc
eh = mtod(m, struct ether_header *);
if (ISSET(p->p_bif_flags, IFBIF_LEARNING))
- etherbridge_map(&sc->sc_eb, p, src);
+ etherbridge_map(&sc->sc_eb, p, vid, src);
+
+ prio = sc->sc_txprio;
+ prio = (prio == IF_HDRPRIO_PACKET) ? m->m_pkthdr.pf.prio : prio;
+ /* IEEE 802.1p has prio 0 and 1 swapped */
+ if (prio <= 1)
+ prio = !prio;
+ m->m_pkthdr.ether_vtag |= (prio << EVL_PRIO_BITS);
CLR(m->m_flags, M_BCAST|M_MCAST);
@@ -1261,12 +1368,12 @@ veb_port_input(struct ifnet *ifp0, struc
struct veb_port *tp = NULL;
smr_read_enter();
- tp = etherbridge_resolve(&sc->sc_eb, dst);
+ tp = etherbridge_resolve(&sc->sc_eb, vid, dst);
if (tp != NULL)
veb_eb_port_take(NULL, tp);
smr_read_leave();
if (tp != NULL) {
- m = veb_transmit(sc, p, tp, m, src, dst, ns);
+ m = veb_transmit(sc, p, tp, m, src, dst, vid, ns);
veb_eb_port_rele(NULL, tp);
}
@@ -1278,7 +1385,7 @@ veb_port_input(struct ifnet *ifp0, struc
SET(m->m_flags, ETH64_IS_BROADCAST(dst) ? M_BCAST : M_MCAST);
}
- veb_broadcast(sc, p, m, src, dst, ns);
+ veb_broadcast(sc, p, m, src, dst, vid, ns);
return (NULL);
drop:
@@ -1317,6 +1424,7 @@ static int
veb_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
{
struct veb_softc *sc = ifp->if_softc;
+ struct ifreq *ifr = (struct ifreq *)data;
struct ifbrparam *bparam = (struct ifbrparam *)data;
int error = 0;
@@ -1334,6 +1442,46 @@ veb_ioctl(struct ifnet *ifp, u_long cmd,
}
break;
+ case SIOCSVNETID:
+ if (ifr->ifr_vnetid < IFBR_PVID_MIN ||
+ ifr->ifr_vnetid > IFBR_PVID_MAX) {
+ error = EINVAL;
+ break;
+ }
+
+ sc->sc_dflt_pvid = ifr->ifr_vnetid;
+ break;
+ case SIOCGVNETID:
+ if (sc->sc_dflt_pvid == IFBR_PVID_NONE)
+ error = EADDRNOTAVAIL;
+ else
+ ifr->ifr_vnetid = (int64_t)sc->sc_dflt_pvid;
+ break;
+ case SIOCDVNETID:
+ sc->sc_dflt_pvid = IFBR_PVID_NONE;
+ break;
+ case SIOCSTXHPRIO:
+ error = if_txhprio_l2_check(ifr->ifr_hdrprio);
+ if (error != 0)
+ break;
+
+ sc->sc_txprio = ifr->ifr_hdrprio;
+ break;
+ case SIOCGTXHPRIO:
+ ifr->ifr_hdrprio = sc->sc_txprio;
+ break;
+
+ case SIOCSRXHPRIO:
+ error = if_rxhprio_l2_check(ifr->ifr_hdrprio);
+ if (error != 0)
+ break;
+
+ sc->sc_rxprio = ifr->ifr_hdrprio;
+ break;
+ case SIOCGRXHPRIO:
+ ifr->ifr_hdrprio = sc->sc_rxprio;
+ break;
+
case SIOCBRDGADD:
error = suser(curproc);
if (error != 0)
@@ -1388,6 +1536,9 @@ veb_ioctl(struct ifnet *ifp, u_long cmd,
case SIOCBRDGRTS:
error = etherbridge_rtfind(&sc->sc_eb, (struct ifbaconf *)data);
break;
+ case SIOCBRDGVRTS:
+ error = etherbridge_vareq(&sc->sc_eb, (struct ifbaconf *)data);
+ break;
case SIOCBRDGIFS:
error = veb_port_list(sc, (struct ifbifconf *)data);
break;
@@ -1401,10 +1552,26 @@ veb_ioctl(struct ifnet *ifp, u_long cmd,
case SIOCBRDGDADDR:
error = veb_del_addr(sc, (struct ifbareq *)data);
break;
+ case SIOCBRDGSVADDR:
+ error = veb_add_vid_addr(sc, (struct ifbvareq *)data);
+ break;
+ case SIOCBRDGDVADDR:
+ error = veb_del_vid_addr(sc, (struct ifbvareq *)data);
+ break;
case SIOCBRDGSIFPROT:
error = veb_port_set_protected(sc, (struct ifbreq *)data);
break;
+ case SIOCBRDGSPVID:
+ error = veb_port_set_pvid(sc, (struct ifbreq *)data);
+ break;
+
+ case SIOCBRDGSVMAP:
+ error = veb_set_vid_map(sc, (const struct ifbrvidmap *)data);
+ break;
+ case SIOCBRDGGVMAP:
+ error = veb_get_vid_map(sc, (struct ifbrvidmap *)data);
+ break;
case SIOCBRDGSIFFLGS:
error = veb_port_set_flags(sc, (struct ifbreq *)data);
@@ -1565,6 +1732,7 @@ veb_add_port(struct veb_softc *sc, const
p->p_ifp0 = ifp0;
p->p_veb = sc;
+ p->p_pvid = sc->sc_dflt_pvid;
refcnt_init(&p->p_refs);
TAILQ_INIT(&p->p_vrl);
@@ -1733,6 +1901,265 @@ veb_port_set_protected(struct veb_softc
}
static int
+veb_port_set_pvid(struct veb_softc *sc, const struct ifbreq *ifbr)
+{
+ struct veb_port *p;
+ uint16_t pvid;
+ int error = 0;
+
+ switch (ifbr->ifbr_pvid) {
+ case EVL_VLID_NULL:
+ pvid = sc->sc_dflt_pvid;
+ break;
+ default:
+ if (ifbr->ifbr_pvid < EVL_VLID_MIN ||
+ ifbr->ifbr_pvid > EVL_VLID_MAX)
+ return (EINVAL);
+
+ /* FALLTHROUGH */
+ case IFBR_PVID_NONE:
+ pvid = ifbr->ifbr_pvid;
+ break;
+ }
+
+ p = veb_port_get(sc, ifbr->ifbr_ifsname);
+ if (p == NULL)
+ return (ESRCH);
+
+ if (pvid == IFBR_PVID_NONE &&
+ p->p_ifp0->if_enqueue == vport_enqueue) {
+ error = EOPNOTSUPP;
+ goto put;
+ }
+ p->p_pvid = pvid;
+
+put:
+ veb_port_put(sc, p);
+
+ return (error);
+}
+
+static int
+veb_get_vid_map(struct veb_softc *sc, struct ifbrvidmap *ifbrvm)
+{
+ struct veb_port *p;
+ uint32_t *map;
+ int error = 0;
+
+ p = veb_port_get(sc, ifbrvm->ifbrvm_ifsname);
+ if (p == NULL)
+ return (ESRCH);
+
+ if (p->p_ifp0->if_enqueue == vport_enqueue) {
+ error = ENOTTY;
+ goto put;
+ }
+
+ smr_read_enter();
+ map = p->p_vid_map;
+ if (map == NULL)
+ memset(ifbrvm->ifbrvm_map, 0, sizeof(ifbrvm->ifbrvm_map));
+ else {
+ size_t w;
+
+ for (w = 0; w < VEB_VID_WORDS; w++) {
+ uint32_t e = map[w];
+ size_t t = w * sizeof(e);
+ size_t b;
+
+ for (b = 0; b < sizeof(e); b++)
+ ifbrvm->ifbrvm_map[t + b] = e >> (b * 8);
+ }
+ }
+ smr_read_leave();
+
+put:
+ veb_port_put(sc, p);
+ return (error);
+}
+
+static int
+veb_chk_vid_map(const struct ifbrvidmap *ifbrvm)
+{
+ size_t off;
+ size_t bit;
+
+ /*
+ * vlan 0 and 4095 are not valid vlan tags
+ */
+
+ off = 0 / 8;
+ bit = 0 % 8;
+ if (ISSET(ifbrvm->ifbrvm_map[off], 1U << bit))
+ return (EINVAL);
+
+ off = 4095 / 8;
+ bit = 4095 % 8;
+ if (ISSET(ifbrvm->ifbrvm_map[off], 1U << bit))
+ return (EINVAL);
+
+ return (0);
+}
+
+static uint32_t *
+veb_new_vid_map(const struct ifbrvidmap *ifbrvm)
+{
+ uint32_t *map;
+ size_t w;
+
+ map = mallocarray(VEB_VID_WORDS, sizeof(*map), M_IFADDR,
+ M_WAITOK|M_CANFAIL);
+ if (map == NULL)
+ return (NULL);
+
+ for (w = 0; w < VEB_VID_WORDS; w++) {
+ uint32_t e = 0;
+ size_t t = w * sizeof(e);
+ size_t b;
+
+ for (b = 0; b < sizeof(e); b++)
+ e |= (uint32_t)ifbrvm->ifbrvm_map[t + b] << (b * 8);
+
+ map[w] = e;
+ }
+
+ return (map);
+}
+
+static inline void
+veb_free_vid_map(uint32_t *map)
+{
+ free(map, M_IFADDR, VEB_VID_BYTES);
+}
+
+struct veb_vid_map_dtor {
+ struct smr_entry smr;
+ uint32_t *map;
+};
+
+static void
+veb_dtor_vid_map(void *arg)
+{
+ struct veb_vid_map_dtor *dtor = arg;
+ veb_free_vid_map(dtor->map);
+ free(dtor, M_TEMP, sizeof(*dtor));
+}
+
+static void
+veb_destroy_vid_map(uint32_t *map)
+{
+ struct veb_vid_map_dtor *dtor;
+
+ dtor = malloc(sizeof(*dtor), M_TEMP, M_NOWAIT);
+ if (dtor == NULL) {
+ /* oh well, the proc can sleep instead */
+ smr_barrier();
+ veb_free_vid_map(map);
+ return;
+ }
+
+ smr_init(&dtor->smr);
+ dtor->map = map;
+ smr_call(&dtor->smr, veb_dtor_vid_map, dtor);
+}
+
+static void
+veb_set_vid_map_set(uint32_t *nmap, const uint32_t *omap)
+{
+ /* nop - nmap replaces (sets) the vid map */
+}
+
+static void
+veb_set_vid_map_or(uint32_t *nmap, const uint32_t *omap)
+{
+ size_t w;
+
+ if (omap == NULL)
+ return;
+
+ for (w = 0; w < VEB_VID_WORDS; w++)
+ nmap[w] |= omap[w];
+}
+
+static void
+veb_set_vid_map_andnot(uint32_t *nmap, const uint32_t *omap)
+{
+ size_t w;
+
+ if (omap == NULL) {
+ /* empty set, clear everything */
+ for (w = 0; w < VEB_VID_WORDS; w++)
+ nmap[w] = 0;
+ return;
+ }
+
+ for (w = 0; w < VEB_VID_WORDS; w++) {
+ uint32_t e = nmap[w];
+ nmap[w] = omap[w] & ~e;
+ }
+}
+
+static int
+veb_set_vid_map(struct veb_softc *sc, const struct ifbrvidmap *ifbrvm)
+{
+ void (*apply)(uint32_t *, const uint32_t *);
+ struct veb_port *p;
+ uint32_t *nmap = NULL, *omap = NULL;
+ int error = 0;
+
+ switch (ifbrvm->ifbrvm_op) {
+ case IFBRVM_OP_SET:
+ apply = veb_set_vid_map_set;
+ break;
+ case IFBRVM_OP_OR:
+ apply = veb_set_vid_map_or;
+ break;
+ case IFBRVM_OP_ANDNOT:
+ apply = veb_set_vid_map_andnot;
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ error = veb_chk_vid_map(ifbrvm);
+ if (error != 0)
+ return (error);
+
+ p = veb_port_get(sc, ifbrvm->ifbrvm_ifsname);
+ if (p == NULL)
+ return (ESRCH);
+
+ if (p->p_ifp0->if_enqueue == vport_enqueue) {
+ error = ENOTTY;
+ goto put;
+ }
+
+ nmap = veb_new_vid_map(ifbrvm);
+ if (nmap == NULL) {
+ error = ENOMEM;
+ goto put;
+ }
+
+ error = rw_enter(&sc->sc_rule_lock, RW_WRITE|RW_INTR);
+ if (error != 0)
+ goto put;
+
+ omap = SMR_PTR_GET_LOCKED(&p->p_vid_map);
+ apply(nmap, omap);
+ SMR_PTR_SET_LOCKED(&p->p_vid_map, nmap);
+ rw_exit(&sc->sc_rule_lock);
+ nmap = NULL;
+
+put:
+ veb_port_put(sc, p);
+ if (omap != NULL)
+ veb_destroy_vid_map(omap);
+ if (nmap != NULL)
+ veb_free_vid_map(nmap);
+ return (error);
+}
+
+static int
veb_rule_add(struct veb_softc *sc, const struct ifbrlreq *ifbr)
{
const struct ifbrarpf *brla = &ifbr->ifbr_arpf;
@@ -2055,6 +2482,7 @@ veb_port_list(struct veb_softc *sc, stru
breq.ifbr_ifsflags = p->p_bif_flags;
breq.ifbr_portno = ifp0->if_index;
breq.ifbr_protected = p->p_protected;
+ breq.ifbr_pvid = p->p_pvid;
if ((error = copyout(&breq, bifc->ifbic_req + n,
sizeof(breq))) != 0)
goto done;
@@ -2138,6 +2566,7 @@ veb_add_addr(struct veb_softc *sc, const
struct veb_port *p;
int error = 0;
unsigned int type;
+ uint16_t pvid;
if (ISSET(ifba->ifba_flags, ~IFBAF_TYPEMASK))
return (EINVAL);
@@ -2159,8 +2588,68 @@ veb_add_addr(struct veb_softc *sc, const
if (p == NULL)
return (ESRCH);
- error = etherbridge_add_addr(&sc->sc_eb, p, &ifba->ifba_dst, type);
+ pvid = p->p_pvid;
+ if (pvid == IFBR_PVID_NONE) {
+ error = EADDRNOTAVAIL;
+ goto put;
+ }
+
+ error = etherbridge_add_addr(&sc->sc_eb, p,
+ pvid, &ifba->ifba_dst, type);
+
+put:
+ veb_port_put(sc, p);
+
+ return (error);
+}
+
+static int
+veb_add_vid_addr(struct veb_softc *sc, const struct ifbvareq *ifbva)
+{
+ struct veb_port *p;
+ int error = 0;
+ unsigned int type;
+ uint16_t vid;
+
+ if (ISSET(ifbva->ifbva_flags, ~IFBAF_TYPEMASK))
+ return (EINVAL);
+ switch (ifbva->ifbva_flags & IFBAF_TYPEMASK) {
+ case IFBAF_DYNAMIC:
+ type = EBE_DYNAMIC;
+ break;
+ case IFBAF_STATIC:
+ type = EBE_STATIC;
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ if (ifbva->ifbva_dstsa.ss_family != AF_UNSPEC)
+ return (EAFNOSUPPORT);
+
+ if (ifbva->ifbva_vid != EVL_VLID_NULL) {
+ if (ifbva->ifbva_vid < EVL_VLID_MIN ||
+ ifbva->ifbva_vid > EVL_VLID_MAX)
+ return (EINVAL);
+ }
+
+ p = veb_port_get(sc, ifbva->ifbva_ifsname);
+ if (p == NULL)
+ return (ESRCH);
+
+ vid = ifbva->ifbva_vid;
+ if (vid == EVL_VLID_NULL) {
+ vid = p->p_pvid;
+ if (vid == IFBR_PVID_NONE) {
+ error = EADDRNOTAVAIL;
+ goto put;
+ }
+ }
+
+ error = etherbridge_add_addr(&sc->sc_eb, p,
+ vid, &ifbva->ifbva_dst, type);
+put:
veb_port_put(sc, p);
return (error);
@@ -2169,7 +2658,24 @@ veb_add_addr(struct veb_softc *sc, const
static int
veb_del_addr(struct veb_softc *sc, const struct ifbareq *ifba)
{
- return (etherbridge_del_addr(&sc->sc_eb, &ifba->ifba_dst));
+ uint16_t vid = sc->sc_dflt_pvid;
+
+ if (vid == IFBR_PVID_NONE)
+ return (ESRCH);
+
+ return (etherbridge_del_addr(&sc->sc_eb,
+ vid, &ifba->ifba_dst));
+}
+
+static int
+veb_del_vid_addr(struct veb_softc *sc, const struct ifbvareq *ifbva)
+{
+ if (ifbva->ifbva_vid < EVL_VLID_MIN ||
+ ifbva->ifbva_vid > EVL_VLID_MAX)
+ return (EINVAL);
+
+ return (etherbridge_del_addr(&sc->sc_eb,
+ ifbva->ifbva_vid, &ifbva->ifbva_dst));
}
static int
@@ -2270,6 +2776,7 @@ veb_p_fini(struct veb_port *p)
veb_rule_list_free(TAILQ_FIRST(&p->p_vrl));
if_put(ifp0);
+ veb_free_vid_map(p->p_vid_map);
free(p, M_DEVBUF, sizeof(*p)); /* hope you didn't forget smr_barrier */
}
Index: sys/net/if_vxlan.c
===================================================================
RCS file: /cvs/src/sys/net/if_vxlan.c,v
diff -u -p -r1.104 if_vxlan.c
--- sys/net/if_vxlan.c 7 Jul 2025 02:28:50 -0000 1.104
+++ sys/net/if_vxlan.c 31 Oct 2025 02:07:33 -0000
@@ -336,7 +336,7 @@ vxlan_encap(struct vxlan_softc *sc, stru
smr_read_enter();
endpoint = etherbridge_resolve_ea(&sc->sc_eb,
- (struct ether_addr *)eh->ether_dhost);
+ 0, (struct ether_addr *)eh->ether_dhost);
if (endpoint != NULL) {
gateway = *endpoint;
endpoint = &gateway;
@@ -695,7 +695,7 @@ vxlan_input(void *arg, struct mbuf *m, s
if (sc->sc_mode == VXLAN_TMODE_LEARNING) {
eh = mtod(m, struct ether_header *);
etherbridge_map_ea(&sc->sc_eb, &addr,
- (struct ether_addr *)eh->ether_shost);
+ 0, (struct ether_addr *)eh->ether_shost);
}
rxhprio = sc->sc_rxhprio;
@@ -1721,13 +1721,13 @@ vxlan_add_addr(struct vxlan_softc *sc, c
}
return (etherbridge_add_addr(&sc->sc_eb, &endpoint,
- &ifba->ifba_dst, type));
+ 0, &ifba->ifba_dst, type));
}
static int
vxlan_del_addr(struct vxlan_softc *sc, const struct ifbareq *ifba)
{
- return (etherbridge_del_addr(&sc->sc_eb, &ifba->ifba_dst));
+ return (etherbridge_del_addr(&sc->sc_eb, 0, &ifba->ifba_dst));
}
void
Index: sys/sys/sockio.h
===================================================================
RCS file: /cvs/src/sys/sys/sockio.h,v
diff -u -p -r1.84 sockio.h
--- sys/sys/sockio.h 11 Nov 2021 10:03:10 -0000 1.84
+++ sys/sys/sockio.h 31 Oct 2025 02:07:33 -0000
@@ -85,12 +85,17 @@
#define SIOCBRDGDELS _IOW('i', 66, struct ifbreq) /* del span port */
#define SIOCBRDGRTS _IOWR('i', 67, struct ifbaconf) /* get addresses */
#define SIOCBRDGSADDR _IOWR('i', 68, struct ifbareq) /* set addr flags */
+#define SIOCBRDGSVADDR _IOW('i', 68, struct ifbvareq) /* add addr@vid */
#define SIOCBRDGSTO _IOW('i', 69, struct ifbrparam)/* cache timeout */
#define SIOCBRDGGTO _IOWR('i', 70, struct ifbrparam)/* cache timeout */
#define SIOCBRDGDADDR _IOW('i', 71, struct ifbareq) /* delete addr */
+#define SIOCBRDGDVADDR _IOW('i', 71, struct ifbvareq) /* delete addr@vid */
#define SIOCBRDGFLUSH _IOW('i', 72, struct ifbreq) /* flush addr cache */
#define SIOCBRDGADDL _IOW('i', 73, struct ifbreq) /* add local port */
#define SIOCBRDGSIFPROT _IOW('i', 74, struct ifbreq) /* set protected grp */
+#define SIOCBRDGSPVID _IOW('i', 76, struct ifbreq) /* set pvid */
+#define SIOCBRDGSVMAP _IOW('i', 76, struct ifbrvidmap) /* set vid map */
+#define SIOCBRDGGVMAP _IOWR('i', 76, struct ifbrvidmap) /* get vid map */
#define SIOCBRDGARL _IOW('i', 77, struct ifbrlreq) /* add bridge rule */
#define SIOCBRDGFRL _IOW('i', 78, struct ifbrlreq) /* flush brdg rules */
@@ -105,6 +110,7 @@
#define SIOCBRDGSMA _IOW('i', 83, struct ifbrparam)/* set max age */
#define SIOCBRDGSIFPRIO _IOW('i', 84, struct ifbreq) /* set if priority */
#define SIOCBRDGSIFCOST _IOW('i', 85, struct ifbreq) /* set if cost */
+#define SIOCBRDGVRTS _IOWR('i', 86, struct ifbaconf) /* get vaddresses */
#define SIOCBRDGGPARAM _IOWR('i', 88, struct ifbropreq)/* get brdg STP parms */
#define SIOCBRDGSTXHC _IOW('i', 89, struct ifbrparam)/* set tx hold count */
make veb(4) VLAN aware