Download raw body.
make veb(4) VLAN aware
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.
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 29 Oct 2025 04:15:50 -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_vid_map(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_vid_map(buf);
bridge_rules(buf, 1);
}
free(bifc.ifbic_buf);
@@ -517,6 +532,87 @@ bridge_unprotect(const char *ifsname, in
}
void
+bridge_pvid(const char *ifsname, const char *val)
+{
+ struct ifbreq breq;
+ const char *errstr;
+
+ 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 {
+ breq.ifbr_pvid = strtonum(val,
+ IFBR_PVID_MIN, IFBR_PVID_MAX, &errstr);
+ if (errstr != NULL) {
+ err(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_mod_vidmap(const char *ifsname, const char *val,
+ unsigned long req, const char *op)
+{
+ struct ifbrvidmap ifbrvm;
+ const char *errstr;
+ uint16_t vid;
+ unsigned int voff, vbit;
+
+ strlcpy(ifbrvm.ifbrvm_name, ifname, sizeof(ifbrvm.ifbrvm_name));
+ strlcpy(ifbrvm.ifbrvm_ifsname, ifsname, sizeof(ifbrvm.ifbrvm_ifsname));
+
+ vid = strtonum(val, EVL_VLID_MIN, EVL_VLID_MAX, &errstr);
+ if (errstr != NULL) {
+ err(1, "%s %s %s: %s is %s", ifname, op, ifsname, val,
+ errstr);
+ }
+
+ memset(ifbrvm.ifbrvm_tags, 0, sizeof(ifbrvm.ifbrvm_tags));
+ voff = vid / 8;
+ vbit = vid % 8;
+
+ ifbrvm.ifbrvm_tags[voff] = 1U << vbit;
+ if (ioctl(sock, req, &ifbrvm) == -1)
+ err(1, "%s %s %s %s", ifname, op, ifsname, val);
+}
+
+void
+bridge_add_vid(const char *ifsname, const char *val)
+{
+ /* add tags to the vid map on a port */
+ bridge_mod_vidmap(ifsname, val, SIOCBRDGAVMAP, "+tagged");
+}
+
+void
+bridge_clr_vid(const char *ifsname, const char *val)
+{
+ /* clear tags from the vid map on a port */
+ bridge_mod_vidmap(ifsname, val, SIOCBRDGCVMAP, "-tagged");
+}
+
+void
bridge_proto(const char *arg, int d)
{
struct ifbrparam bp;
@@ -584,23 +680,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 +727,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 +807,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 +902,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);
+ sa = (struct sockaddr *)&ifbva->ifbva_dstsa;
+ printf(" created %lld used %lld",
+ now.tv_sec - ifbva->ifbva_created,
+ now.tv_sec - ifbva->ifbva_used);
+ printb(" flags", ifbva->ifbva_flags, IFBAFBITS);
+ 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 *delim, int d)
+{
+ /* ifconfig will call us with the argv of the command */
+ if (strcmp(delim, "vaddr") == 0)
+ delim = "";
+
+ if (bridge_vaddrs_try(delim) == -1)
+ err(1, "%s", ifname);
+}
+
void
bridge_addrs(const char *delim, int d)
{
@@ -847,7 +1095,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_vid_map(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_tags); i++) {
+ unsigned int voff = i * 8;
+ unsigned int vbit;
+ uint8_t tag = ifbrvm.ifbrvm_tags[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 29 Oct 2025 04:15:50 -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,15 @@ 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 Ar vid
+.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 up
.Ek
.nr nS 0
@@ -2112,10 +2119,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,9 +2280,17 @@ 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 .
+.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
@@ -2280,6 +2300,47 @@ 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
+Add VLAN
+.Ar vid
+to the set of identifiers than can be sent and received as VLAN
+tagged traffic on
+.Ar child-iface .
+By default the set of tagged VLAN is empty.
+.It Cm -tagged Ar child-iface Ar vid
+Remove VLAN
+.Ar vid
+to the set of identifiers than can be sent and received as VLAN
+tagged traffic on
+.Ar child-iface .
+.It Cm untagged Ar child-iface Ar vid
+Set untagged traffic on
+.Ar child-iface
+to operate in VLAN
+.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 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 29 Oct 2025 04:15:50 -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_add_vid },
+ { "-tagged", NEXTARG2, 0, NULL, bridge_clr_vid },
{ "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 29 Oct 2025 04:15:50 -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_add_vid(const char *, const char *);
+void bridge_clr_vid(const char *, const char *);
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 29 Oct 2025 04:15:50 -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,9 @@ forceup:
case SIOCBRDGSIFCOST:
case SIOCBRDGSTXHC:
case SIOCBRDGSPROTO:
+ case SIOCBRDGSVMAP:
+ case SIOCBRDGAVMAP:
+ case SIOCBRDGCVMAP:
#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 29 Oct 2025 04:15:50 -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 29 Oct 2025 04:15:50 -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 {
@@ -150,6 +160,7 @@ struct ifbrparam {
union {
u_int32_t ifbrpu_csize; /* cache size */
int ifbrpu_ctime; /* cache time (sec) */
+ int ifbrpu_dflt_vid; /* default vlan */
u_int16_t ifbrpu_prio; /* bridge priority */
u_int8_t ifbrpu_hellotime; /* hello time (sec) */
u_int8_t ifbrpu_fwddelay; /* fwd delay (sec) */
@@ -236,6 +247,23 @@ 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];
+ uint8_t ifbrvm_tags[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 29 Oct 2025 04:15:50 -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 29 Oct 2025 04:15:50 -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 29 Oct 2025 04:15:50 -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 29 Oct 2025 04:15:50 -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_vid;
+ 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,17 @@ 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_add_vid_map(struct veb_softc *, const struct ifbrvidmap *);
+static int veb_clr_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 +315,10 @@ veb_clone_create(struct if_clone *ifc, i
return (error);
}
+ sc->sc_dflt_vid = 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 +466,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 +479,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 +494,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 +528,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 +575,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 +607,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 +615,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 +660,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 +828,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 +930,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 +1041,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 +1051,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 +1095,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 +1112,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 +1138,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 +1155,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 +1203,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 +1213,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 +1222,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 +1232,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 +1245,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 +1334,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 +1355,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 +1370,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 +1387,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 +1426,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 +1444,40 @@ veb_ioctl(struct ifnet *ifp, u_long cmd,
}
break;
+ case SIOCSVNETID:
+ if (ifr->ifr_vnetid < EVL_VLID_MIN ||
+ ifr->ifr_vnetid > EVL_VLID_MAX) {
+ error = EINVAL;
+ break;
+ }
+
+ sc->sc_dflt_vid = ifr->ifr_vnetid;
+ break;
+ case SIOCGVNETID:
+ ifr->ifr_vnetid = (int64_t)sc->sc_dflt_vid;
+ 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 +1532,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 +1548,32 @@ 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 SIOCBRDGAVMAP:
+ error = veb_add_vid_map(sc, (const struct ifbrvidmap *)data);
+ break;
+ case SIOCBRDGCVMAP:
+ error = veb_clr_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 +1734,7 @@ veb_add_port(struct veb_softc *sc, const
p->p_ifp0 = ifp0;
p->p_veb = sc;
+ p->p_pvid = sc->sc_dflt_vid;
refcnt_init(&p->p_refs);
TAILQ_INIT(&p->p_vrl);
@@ -1733,6 +1903,269 @@ 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 vid;
+ int error = 0;
+
+ switch (ifbr->ifbr_pvid) {
+ case EVL_VLID_NULL:
+ vid = sc->sc_dflt_vid;
+ break;
+ default:
+ if (ifbr->ifbr_pvid < EVL_VLID_MIN ||
+ ifbr->ifbr_pvid > EVL_VLID_MAX)
+ return (EINVAL);
+
+ /* FALLTHROUGH */
+ case IFBR_PVID_NONE:
+ vid = ifbr->ifbr_pvid;
+ break;
+ }
+
+ p = veb_port_get(sc, ifbr->ifbr_ifsname);
+ if (p == NULL)
+ return (ESRCH);
+
+ if (vid == IFBR_PVID_NONE &&
+ p->p_ifp0->if_enqueue == vport_enqueue) {
+ error = EOPNOTSUPP;
+ goto put;
+ }
+ p->p_pvid = vid;
+
+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_tags, 0, sizeof(ifbrvm->ifbrvm_tags));
+ 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_tags[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_tags[off], 1U << bit))
+ return (EINVAL);
+
+ off = 4095 / 8;
+ bit = 4095 % 8;
+ if (ISSET(ifbrvm->ifbrvm_tags[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_tags[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 int
+veb_mod_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;
+
+ 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 void
+veb_set_vid_map_apply(uint32_t *nmap, const uint32_t *omap)
+{
+ /* nop - nmap replaces (sets) the vid map */
+}
+
+static int
+veb_set_vid_map(struct veb_softc *sc, const struct ifbrvidmap *ifbrvm)
+{
+ return veb_mod_vid_map(sc, ifbrvm, veb_set_vid_map_apply);
+}
+
+static void
+veb_add_vid_map_apply(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 int
+veb_add_vid_map(struct veb_softc *sc, const struct ifbrvidmap *ifbrvm)
+{
+ return veb_mod_vid_map(sc, ifbrvm, veb_add_vid_map_apply);
+}
+
+static void
+veb_clr_vid_map_apply(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_clr_vid_map(struct veb_softc *sc, const struct ifbrvidmap *ifbrvm)
+{
+ return veb_mod_vid_map(sc, ifbrvm, veb_clr_vid_map_apply);
+}
+
+static int
veb_rule_add(struct veb_softc *sc, const struct ifbrlreq *ifbr)
{
const struct ifbrarpf *brla = &ifbr->ifbr_arpf;
@@ -2055,6 +2488,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;
@@ -2159,7 +2593,56 @@ 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);
+ error = etherbridge_add_addr(&sc->sc_eb, p,
+ p->p_pvid, &ifba->ifba_dst, type);
+
+ 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;
+
+uprintf("%s: vid %u\n", __func__, vid);
+
+ error = etherbridge_add_addr(&sc->sc_eb, p,
+ vid, &ifbva->ifbva_dst, type);
veb_port_put(sc, p);
@@ -2169,7 +2652,19 @@ 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));
+ return (etherbridge_del_addr(&sc->sc_eb,
+ sc->sc_dflt_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 +2765,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 29 Oct 2025 04:15:50 -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 29 Oct 2025 04:15:50 -0000
@@ -85,12 +85,20 @@
#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 SIOCBRDGAVMAP _IOW('i', 77, struct ifbrvidmap) /* add vid map bits */
+#define SIOCBRDGCVMAP _IOW('i', 78, struct ifbrvidmap) /* clr vid map bits */
+#define SIOCBRDGGVMAP _IOWR('i', 79, 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 +113,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