From: David Gwynne Subject: make veb(4) VLAN aware To: tech@openbsd.org Date: Wed, 29 Oct 2025 15:54:42 +1000 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 index 7 llprio 3 encap: vnetid 1 txprio packet rxprio outer groups: veb vmx0 flags=3 port 1 ifpriority 0 ifcost 0 -untagged tagged: 780,781,871 vport0 flags=3 port 8 ifpriority 0 ifcost 0 untagged 871 tap0 flags=3 port 5 ifpriority 0 ifcost 0 untagged 780 tagged: none tap1 flags=3 port 6 ifpriority 0 ifcost 0 untagged 781 tagged: none tap2 flags=3 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 #include #include +#include #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 #include +#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 #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 */