Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F177544768
D60834.1794677260.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
143 KB
Referenced Files
None
Subscribers
None
D60834.1794677260.diff
View Options
diff --git a/lib/libc/gen/exterr_cat_filenames.h b/lib/libc/gen/exterr_cat_filenames.h
--- a/lib/libc/gen/exterr_cat_filenames.h
+++ b/lib/libc/gen/exterr_cat_filenames.h
@@ -21,5 +21,6 @@
[EXTERR_CAT_INOTIFY] = "kern/vfs_inotify.c",
[EXTERR_CAT_VFSSYSCALL] = "kern/vfs_syscalls.c",
[EXTERR_CAT_BRIDGE] = "net/if_bridge.c",
+ [EXTERR_CAT_VEB] = "net/if_veb.c",
[EXTERR_CAT_SWAP] = "vm/swap_pager.c",
[EXTERR_CAT_MMAP] = "vm/vm_mmap.c",
diff --git a/lib/libifconfig/Makefile b/lib/libifconfig/Makefile
--- a/lib/libifconfig/Makefile
+++ b/lib/libifconfig/Makefile
@@ -18,7 +18,8 @@
libifconfig_lagg.c \
libifconfig_media.c \
libifconfig_nl.c \
- libifconfig_sfp.c
+ libifconfig_sfp.c \
+ libifconfig_veb.c
GEN= libifconfig_sfp_tables.h \
libifconfig_sfp_tables.c \
diff --git a/lib/libifconfig/libifconfig.h b/lib/libifconfig/libifconfig.h
--- a/lib/libifconfig/libifconfig.h
+++ b/lib/libifconfig/libifconfig.h
@@ -415,6 +415,64 @@
int ifconfig_carp_set_info(ifconfig_handle_t *h, const char *name,
const struct ifconfig_carp *carpr);
+struct ifconfig_veb_port {
+ char ifname[IFNAMSIZ];
+ uint32_t maxaddr;
+ bool learning;
+ bool discover;
+ bool sticky;
+ bool private;
+ bool vport;
+};
+
+struct ifconfig_veb_rtnode {
+ char port[IFNAMSIZ];
+ struct ether_addr mac;
+ ether_vlanid_t vlan;
+ uint8_t type;
+ uint32_t expire;
+};
+
+struct ifconfig_veb_status {
+ struct ifconfig_veb_port *ports;
+ uint32_t numports;
+ uint32_t timeout;
+ uint32_t maxaddr;
+ uint32_t exceeded;
+};
+
+enum {
+ IFCONFIG_VEB_PORT_LEARNING,
+ IFCONFIG_VEB_PORT_DISCOVER,
+ IFCONFIG_VEB_PORT_STICKY,
+ IFCONFIG_VEB_PORT_PRIVATE,
+};
+
+int ifconfig_veb_get_veb_status(ifconfig_handle_t *h,
+ const char *ifname, struct ifconfig_veb_status **veb_status);
+void ifconfig_veb_free_veb_status(struct ifconfig_veb_status *veb_status);
+int ifconfig_veb_showaddr(ifconfig_handle_t *h, const char *ifname,
+ struct ifconfig_veb_rtnode **vebrtp, uint32_t *rtn_count);
+void ifconfig_veb_free_veb_showaddr(struct ifconfig_veb_rtnode *vebrt);
+int ifconfig_veb_set_timeout(ifconfig_handle_t *h,
+ const char *ifname, int value);
+int ifconfig_veb_set_maxaddr(ifconfig_handle_t *h,
+ const char *ifname, int maxaddr);
+int ifconfig_veb_add_port(ifconfig_handle_t *h,
+ const char *ifname, const char *portname);
+int ifconfig_veb_del_port(ifconfig_handle_t *h,
+ const char *ifname, const char *portname);
+int ifconfig_veb_add_static(ifconfig_handle_t *h, const char *ifname,
+ const char *portname, struct ether_addr *ea, ether_vlanid_t vlan);
+int ifconfig_veb_del_addr(ifconfig_handle_t *h, const char *ifname,
+ struct ether_addr *ea, ether_vlanid_t vlan);
+int ifconfig_veb_set_port_maxaddr(ifconfig_handle_t *h, const char *ifname,
+ const char *portname, uint32_t maxaddr);
+int ifconfig_veb_set_port_flag(ifconfig_handle_t *h, const char *ifname,
+ const char *portname, int attr, bool on);
+int ifconfig_veb_flush(ifconfig_handle_t *h,
+ const char *ifname, bool all);
+
/** Retrieve additional information about an inet address
* @param h An open ifconfig state object
* @param name The interface name
diff --git a/lib/libifconfig/libifconfig_veb.c b/lib/libifconfig/libifconfig_veb.c
new file mode 100644
--- /dev/null
+++ b/lib/libifconfig/libifconfig_veb.c
@@ -0,0 +1,659 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Aaron Espinoza <acesp25@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_veb_nl.h>
+
+#include <netlink/netlink.h>
+#include <netlink/netlink_generic.h>
+#include <netlink/netlink_snl.h>
+#include <netlink/netlink_snl_generic.h>
+#include <netlink/netlink_snl_route.h>
+#include <netlink/route/interface.h>
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <err.h>
+
+#include "libifconfig.h"
+#include "libifconfig_internal.h"
+
+struct nl_parsed_veb {
+ struct snl_parray ifla_ports;
+ uint32_t ifla_timeout;
+ uint32_t ifla_maxaddr;
+ uint32_t ifla_exceeded;
+};
+
+struct nl_parsed_veb_port {
+ char *ifla_ifname;
+ uint32_t ifla_maxaddr;
+ bool ifla_learning;
+ bool ifla_discover;
+ bool ifla_sticky;
+ bool ifla_private;
+ bool ifla_vport;
+};
+
+struct nla_veb_info {
+ const char *kind;
+ struct nl_parsed_veb data;
+};
+
+struct nla_veb_link {
+ uint32_t ifi_index;
+ struct nla_veb_info linkinfo;
+};
+
+struct veb_rt_entry {
+ char *port;
+ ether_vlanid_t vlan;
+ struct nlattr *mac;
+ uint8_t type;
+ uint32_t expire;
+};
+
+#define _OUT(_field) offsetof(struct nl_parsed_veb_port, _field)
+static const struct snl_attr_parser nla_veb_port[] = {
+ { .type = IFLA_VEB_PORT_IFNAME, .off = _OUT(ifla_ifname),
+ .cb = snl_attr_get_string },
+ { .type = IFLA_VEB_PORT_MAXADDR, .off = _OUT(ifla_maxaddr),
+ .cb = snl_attr_get_uint32 },
+ { .type = IFLA_VEB_PORT_LEARNING, .off = _OUT(ifla_learning),
+ .cb = snl_attr_get_bool },
+ { .type = IFLA_VEB_PORT_DISCOVER, .off = _OUT(ifla_discover),
+ .cb = snl_attr_get_bool },
+ { .type = IFLA_VEB_PORT_STICKY, .off = _OUT(ifla_sticky),
+ .cb = snl_attr_get_bool },
+ { .type = IFLA_VEB_PORT_PRIVATE, .off = _OUT(ifla_private),
+ .cb = snl_attr_get_bool },
+ { .type = IFLA_VEB_PORT_VPORT, .off = _OUT(ifla_vport),
+ .cb = snl_attr_get_bool },
+};
+#undef _OUT
+SNL_DECLARE_ATTR_PARSER_EXT(veb_port_parser,
+ sizeof(struct nl_parsed_veb_port), nla_veb_port, NULL);
+
+#define _OUT(_field) offsetof(struct nl_parsed_veb, _field)
+static const struct snl_attr_parser nla_veb[] = {
+ { .type = IFLA_VEB_PORT, .off = _OUT(ifla_ports),
+ .arg = &veb_port_parser, .cb = snl_attr_get_multi },
+ { .type = IFLA_VEB_RTABLE_TIMEOUT, .off = _OUT(ifla_timeout),
+ .cb = snl_attr_get_uint32 },
+ { .type = IFLA_VEB_RTABLE_MAXADDR, .off = _OUT(ifla_maxaddr),
+ .cb = snl_attr_get_uint32 },
+ { .type = IFLA_VEB_RTABLE_EXCEEDED, .off = _OUT(ifla_exceeded),
+ .cb = snl_attr_get_uint32 },
+};
+#undef _OUT
+SNL_DECLARE_ATTR_PARSER(veb_parser, nla_veb);
+
+#define _OUT(_field) offsetof(struct nla_veb_info, _field)
+static const struct snl_attr_parser ap_veb_linkinfo[] = {
+ { .type = IFLA_INFO_KIND, .off = _OUT(kind),
+ .cb = snl_attr_get_string },
+ { .type = IFLA_INFO_DATA, .off = _OUT(data),
+ .arg = &veb_parser, .cb = snl_attr_get_nested },
+};
+#undef _OUT
+SNL_DECLARE_ATTR_PARSER(veb_linkinfo_parser, ap_veb_linkinfo);
+
+#define _OUT(_field) offsetof(struct veb_rt_entry, _field)
+static const struct snl_attr_parser nla_veb_rtable[] = {
+ { .type = VEB_NL_RTABLE_MAC, .off = _OUT(mac),
+ .cb = snl_attr_get_nla },
+ { .type = VEB_NL_RTABLE_VLAN, .off = _OUT(vlan),
+ .cb = snl_attr_get_uint16 },
+ { .type = VEB_NL_RTABLE_PORT, .off = _OUT(port),
+ .cb = snl_attr_get_string },
+ { .type = VEB_NL_RTABLE_TYPE, .off = _OUT(type),
+ .cb = snl_attr_get_uint8 },
+ { .type = VEB_NL_RTABLE_EXPIRE, .off = _OUT(expire),
+ .cb = snl_attr_get_uint32 },
+};
+#undef _OUT
+SNL_DECLARE_GENL_PARSER(veb_rtable_parser, nla_veb_rtable);
+
+#define _IN(_field) offsetof(struct ifinfomsg, _field)
+#define _OUT(_field) offsetof(struct nla_veb_link, _field)
+static const struct snl_attr_parser ap_veb_link[] = {
+ { .type = IFLA_LINKINFO, .off = _OUT(linkinfo),
+ .arg = &veb_linkinfo_parser, .cb = snl_attr_get_nested },
+};
+static const struct snl_field_parser fp_veb_link[] = {
+ { .off_in = _IN(ifi_index), .off_out = _OUT(ifi_index),
+ .cb = snl_field_get_uint32 },
+};
+#undef _IN
+#undef _OUT
+SNL_DECLARE_PARSER(veb_link_parser, struct ifinfomsg, fp_veb_link, ap_veb_link);
+
+static inline void
+veb_nl_init(struct snl_state *ss, struct snl_writer *nw,
+ const char *name, uint32_t flags)
+{
+ struct nlmsghdr *hdr;
+
+ snl_init_writer(ss, nw);
+ hdr = snl_create_msg_request(nw, NL_RTM_NEWLINK);
+ hdr->nlmsg_flags |= flags;
+ snl_reserve_msg_object(nw, struct ifinfomsg);
+ snl_add_msg_attr_string(nw, IFLA_IFNAME, name);
+}
+
+static inline int
+veb_nl_fini(ifconfig_handle_t *h, struct snl_state *ss, struct snl_writer *nw)
+{
+ struct snl_errmsg_data e = {};
+ struct nlmsghdr *hdr;
+
+ if ((hdr = snl_finalize_msg(nw)) == NULL) {
+ ifconfig_error(h, NETLINK, ENOMEM);
+ return (-1);
+ }
+ if (!snl_send_message(ss, hdr)) {
+ ifconfig_error(h, NETLINK, EIO);
+ return (-1);
+ }
+ if (!snl_read_reply_code(ss, hdr->nlmsg_seq, &e)) {
+ ifconfig_error(h, NETLINK, e.error);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static inline int
+veb_genl_init(ifconfig_handle_t *h, struct snl_state *ss)
+{
+ int family_id;
+
+ if (!snl_init(ss, NETLINK_GENERIC)) {
+ ifconfig_error(h, NETLINK, EIO);
+ return (-1);
+ }
+
+ family_id = snl_get_genl_family(ss, VEB_NL_FAMILY_NAME);
+ if (family_id == 0) {
+ snl_free(ss);
+ ifconfig_error(h, NETLINK, ENOENT);
+ return (-1);
+ }
+
+ return (family_id);
+}
+
+int
+ifconfig_veb_get_veb_status(ifconfig_handle_t *h,
+ const char *name, struct ifconfig_veb_status **veb_status)
+{
+ struct snl_state ss = {};
+ struct snl_writer nw;
+ struct nlmsghdr *hdr;
+ struct nla_veb_link veb_link = {};
+ struct ifconfig_veb_status *veb = NULL;
+ int port_count;
+
+ *veb_status = NULL;
+
+ if (!snl_init(&ss, NETLINK_ROUTE)) {
+ ifconfig_error(h, NETLINK, ENOTSUP);
+ return (-1);
+ }
+
+ snl_init_writer(&ss, &nw);
+ hdr = snl_create_msg_request(&nw, NL_RTM_GETLINK);
+ snl_reserve_msg_object(&nw, struct ifinfomsg);
+ snl_add_msg_attr_string(&nw, IFLA_IFNAME, name);
+
+ if ((hdr = snl_finalize_msg(&nw)) == NULL) {
+ ifconfig_error(h, NETLINK, ENOMEM);
+ goto err;
+ }
+
+ if (!snl_send_message(&ss, hdr)) {
+ ifconfig_error(h, NETLINK, EIO);
+ goto err;
+ }
+
+ hdr = snl_read_reply(&ss, hdr->nlmsg_seq);
+ if (hdr == NULL || hdr->nlmsg_type != NL_RTM_NEWLINK) {
+ ifconfig_error(h, NETLINK, EIO);
+ goto err;
+ }
+ if (!snl_parse_nlmsg(&ss, hdr, &veb_link_parser, &veb_link)) {
+ ifconfig_error(h, NETLINK, EINVAL);
+ goto err;
+ }
+
+ if (veb_link.linkinfo.kind == NULL ||
+ strcmp(veb_link.linkinfo.kind, "veb") != 0) {
+ ifconfig_error(h, NETLINK, EINVAL);
+ goto err;
+ }
+
+ port_count = veb_link.linkinfo.data.ifla_ports.count;
+ if ((veb = calloc(1, sizeof(*veb))) == NULL ||
+ (port_count > 0 &&
+ (veb->ports = calloc(port_count, sizeof(*veb->ports))) == NULL)) {
+ ifconfig_error(h, OTHER, ENOMEM);
+ goto err;
+ }
+
+ veb->maxaddr = veb_link.linkinfo.data.ifla_maxaddr;
+ veb->timeout = veb_link.linkinfo.data.ifla_timeout;
+ veb->exceeded = veb_link.linkinfo.data.ifla_exceeded;
+ veb->numports = port_count;
+
+ for (int i = 0; i < port_count; i++) {
+ const struct nl_parsed_veb_port *veb_port =
+ veb_link.linkinfo.data.ifla_ports.items[i];
+
+ if (veb_port->ifla_ifname == NULL)
+ continue;
+
+ strlcpy(veb->ports[i].ifname, veb_port->ifla_ifname,
+ sizeof(veb->ports[i].ifname));
+ veb->ports[i].maxaddr = veb_port->ifla_maxaddr;
+ veb->ports[i].discover = veb_port->ifla_discover;
+ veb->ports[i].learning = veb_port->ifla_learning;
+ veb->ports[i].sticky = veb_port->ifla_sticky;
+ veb->ports[i].private = veb_port->ifla_private;
+ veb->ports[i].vport = veb_port->ifla_vport;
+ }
+
+ *veb_status = veb;
+ snl_free(&ss);
+
+ return (0);
+err:
+ snl_free(&ss);
+ if (veb != NULL) {
+ free(veb->ports);
+ free(veb);
+ }
+
+ return (-1);
+}
+
+void
+ifconfig_veb_free_veb_status(struct ifconfig_veb_status *veb_status)
+{
+ if (veb_status != NULL) {
+ free(veb_status->ports);
+ free(veb_status);
+ }
+}
+
+static int
+veb_set_cmd(ifconfig_handle_t *h,
+ const char *name, int attr, uint32_t value)
+{
+ struct snl_state ss = {};
+ struct snl_writer nw = {};
+ int error, off, off2;
+
+ if (!snl_init(&ss, NETLINK_ROUTE)) {
+ ifconfig_error(h, NETLINK, ENOTSUP);
+ return (-1);
+ }
+
+ veb_nl_init(&ss, &nw, name, 0);
+ off = snl_add_msg_attr_nested(&nw, IFLA_LINKINFO);
+ snl_add_msg_attr_string(&nw, IFLA_INFO_KIND, "veb");
+
+ off2 = snl_add_msg_attr_nested(&nw, IFLA_INFO_DATA);
+ snl_add_msg_attr_u32(&nw, attr, value);
+ snl_end_attr_nested(&nw, off2);
+
+ snl_end_attr_nested(&nw, off);
+
+ error = veb_nl_fini(h,&ss, &nw);
+
+ snl_free(&ss);
+
+ return (error);
+}
+
+
+int
+ifconfig_veb_set_timeout(ifconfig_handle_t *h,
+ const char *ifname, int timeout)
+{
+ return (veb_set_cmd(h, ifname, IFLA_VEB_RTABLE_TIMEOUT, timeout));
+}
+
+int
+ifconfig_veb_set_maxaddr(ifconfig_handle_t *h,
+ const char *ifname, int maxaddr)
+{
+ return (veb_set_cmd(h, ifname, IFLA_VEB_RTABLE_MAXADDR, maxaddr));
+}
+
+static int
+veb_rtable_cmd(ifconfig_handle_t *h,
+ const char *name, const char *port, struct ether_addr *ea,
+ ether_vlanid_t vlan, int cmd)
+{
+ struct snl_state ss = {};
+ struct snl_writer nw;
+ int error, family_id;
+
+ if ((family_id = veb_genl_init(h, &ss)) < 0)
+ return (-1);
+
+ snl_init_writer(&ss, &nw);
+ snl_create_genl_msg_request(&nw, family_id, cmd);
+ snl_add_msg_attr_string(&nw, VEB_NL_RTABLE_IFNAME, name);
+ if (cmd == VEB_NL_CMD_ADDSTATIC && port != NULL)
+ snl_add_msg_attr_string(&nw, VEB_NL_RTABLE_PORT, port);
+ snl_add_msg_attr(&nw, VEB_NL_RTABLE_MAC, ETHER_ADDR_LEN, ea->octet);
+ snl_add_msg_attr_u16(&nw, VEB_NL_RTABLE_VLAN, vlan);
+
+ error = veb_nl_fini(h, &ss, &nw);
+ snl_free(&ss);
+
+ return (error);
+}
+
+int
+ifconfig_veb_add_static(ifconfig_handle_t *h, const char *ifname,
+ const char *portname, struct ether_addr *ea, ether_vlanid_t vlan)
+{
+ return (veb_rtable_cmd(h, ifname, portname, ea, vlan, VEB_NL_CMD_ADDSTATIC));
+}
+int
+ifconfig_veb_del_addr(ifconfig_handle_t *h, const char *ifname,
+ struct ether_addr *ea, ether_vlanid_t vlan)
+{
+ return (veb_rtable_cmd(h, ifname, NULL, ea, vlan, VEB_NL_CMD_DELADDR));
+}
+
+static int
+veb_port_cmd(ifconfig_handle_t *h, const char *ifname, int attr,
+ const char *portname)
+{
+ struct snl_state ss = {};
+ struct snl_writer nw = {};
+ int error, off, off2;
+
+ if (!snl_init(&ss, NETLINK_ROUTE)) {
+ ifconfig_error(h, NETLINK, ENOTSUP);
+ return (-1);
+ }
+
+ veb_nl_init(&ss, &nw, ifname, 0);
+ off = snl_add_msg_attr_nested(&nw, IFLA_LINKINFO);
+ snl_add_msg_attr_string(&nw, IFLA_INFO_KIND, "veb");
+
+ off2 = snl_add_msg_attr_nested(&nw, IFLA_INFO_DATA);
+ snl_add_msg_attr_string(&nw, attr, portname);
+
+ snl_end_attr_nested(&nw, off2);
+ snl_end_attr_nested(&nw, off);
+
+ error = veb_nl_fini(h, &ss, &nw);
+
+ snl_free(&ss);
+
+ return (error);
+}
+
+int
+ifconfig_veb_add_port(ifconfig_handle_t *h,
+ const char *ifname, const char *portname)
+{
+ return (veb_port_cmd(h, ifname, IFLA_VEB_ADDPORT, portname));
+}
+
+int
+ifconfig_veb_del_port(ifconfig_handle_t *h,
+ const char *ifname, const char *portname)
+{
+ return (veb_port_cmd(h, ifname, IFLA_VEB_DELPORT, portname));
+}
+
+int
+ifconfig_veb_set_port_maxaddr(ifconfig_handle_t *h, const char *ifname,
+ const char *portname, uint32_t maxaddr)
+{
+ struct snl_state ss = {};
+ struct snl_writer nw = {};
+ int error, off, off2, off3;
+
+ if (!snl_init(&ss, NETLINK_ROUTE)) {
+ ifconfig_error(h, NETLINK, ENOTSUP);
+ return (-1);
+ }
+
+ veb_nl_init(&ss, &nw, ifname, 0);
+ off = snl_add_msg_attr_nested(&nw, IFLA_LINKINFO);
+ snl_add_msg_attr_string(&nw, IFLA_INFO_KIND, "veb");
+
+ off2 = snl_add_msg_attr_nested(&nw, IFLA_INFO_DATA);
+ off3 = snl_add_msg_attr_nested(&nw, IFLA_VEB_SETPORT);
+ snl_add_msg_attr_string(&nw, IFLA_VEB_PORT_IFNAME, portname);
+ snl_add_msg_attr_u32(&nw, IFLA_VEB_PORT_MAXADDR, maxaddr);
+
+ snl_end_attr_nested(&nw, off3);
+ snl_end_attr_nested(&nw, off2);
+ snl_end_attr_nested(&nw, off);
+
+ error = veb_nl_fini(h, &ss, &nw);
+
+ snl_free(&ss);
+
+ return (error);
+}
+
+static int
+veb_set_port_flag(ifconfig_handle_t *h, const char *ifname,
+ const char *portname, int attr, bool on)
+{
+ struct snl_state ss = {};
+ struct snl_writer nw = {};
+ int error, off, off2, off3;
+
+ if (!snl_init(&ss, NETLINK_ROUTE)) {
+ ifconfig_error(h, NETLINK, ENOTSUP);
+ return (-1);
+ }
+
+ veb_nl_init(&ss, &nw, ifname, 0);
+ off = snl_add_msg_attr_nested(&nw, IFLA_LINKINFO);
+ snl_add_msg_attr_string(&nw, IFLA_INFO_KIND, "veb");
+
+ off2 = snl_add_msg_attr_nested(&nw, IFLA_INFO_DATA);
+ off3 = snl_add_msg_attr_nested(&nw, IFLA_VEB_SETPORT);
+ snl_add_msg_attr_string(&nw, IFLA_VEB_PORT_IFNAME, portname);
+ snl_add_msg_attr_bool(&nw, attr, on);
+
+ snl_end_attr_nested(&nw, off3);
+ snl_end_attr_nested(&nw, off2);
+ snl_end_attr_nested(&nw, off);
+
+ error = veb_nl_fini(h, &ss, &nw);
+
+ snl_free(&ss);
+
+ return (error);
+}
+
+int
+ifconfig_veb_set_port_flag(ifconfig_handle_t *h, const char *ifname,
+ const char *portname, int flag, bool on)
+{
+ switch(flag) {
+ case IFCONFIG_VEB_PORT_DISCOVER:
+ flag = IFLA_VEB_PORT_DISCOVER;
+ break;
+ case IFCONFIG_VEB_PORT_LEARNING:
+ flag = IFLA_VEB_PORT_LEARNING;
+ break;
+ case IFCONFIG_VEB_PORT_STICKY:
+ flag = IFLA_VEB_PORT_STICKY;
+ break;
+ case IFCONFIG_VEB_PORT_PRIVATE:
+ flag = IFLA_VEB_PORT_PRIVATE;
+ break;
+ default:
+ ifconfig_error(h, OTHER, EINVAL);
+ return (-1);
+ }
+
+ return (veb_set_port_flag(h, ifname, portname, flag, on));
+}
+
+int
+ifconfig_veb_flush(ifconfig_handle_t *h,
+ const char *name, bool all)
+{
+ struct snl_state ss = {};
+ struct snl_writer nw = {};
+ int error, off, off2;
+
+ if (!snl_init(&ss, NETLINK_ROUTE)) {
+ ifconfig_error(h, NETLINK, ENOTSUP);
+ return (-1);
+ }
+
+ veb_nl_init(&ss, &nw, name, 0);
+ off = snl_add_msg_attr_nested(&nw, IFLA_LINKINFO);
+ snl_add_msg_attr_string(&nw, IFLA_INFO_KIND, "veb");
+
+ off2 = snl_add_msg_attr_nested(&nw, IFLA_INFO_DATA);
+
+ snl_add_msg_attr_bool(&nw, IFLA_VEB_RTABLE_FLUSH, all);
+
+ snl_end_attr_nested(&nw, off2);
+ snl_end_attr_nested(&nw, off);
+
+ error = veb_nl_fini(h, &ss, &nw);
+
+ snl_free(&ss);
+
+ return (error);
+}
+
+int
+ifconfig_veb_showaddr(ifconfig_handle_t *h, const char *name,
+ struct ifconfig_veb_rtnode **vebrtp, uint32_t *rtn_count)
+{
+ struct snl_state ss = {};
+ struct snl_errmsg_data e = {};
+ struct snl_writer nw;
+ struct nlmsghdr *hdr;
+ struct ifconfig_veb_rtnode *vebrt = NULL, *tmp;
+ uint32_t n = 0, cap = 0;
+ uint32_t seq_id;
+ int family_id;
+
+ family_id = veb_genl_init(h, &ss);
+ if (family_id == -1) {
+ goto err;
+ }
+
+ snl_init_writer(&ss, &nw);
+ hdr = snl_create_genl_msg_request(&nw, family_id, VEB_NL_CMD_GETRTABLE);
+ hdr->nlmsg_flags |= NLM_F_DUMP;
+ snl_add_msg_attr_string(&nw, VEB_NL_RTABLE_IFNAME, name);
+
+ hdr = snl_finalize_msg(&nw);
+ if (hdr == NULL) {
+ ifconfig_error(h, NETLINK, ENOMEM);
+ goto err;
+ }
+
+ seq_id = hdr->nlmsg_seq;
+ if (!snl_send_message(&ss, hdr)) {
+ ifconfig_error(h, NETLINK, EIO);
+ goto err;
+ }
+
+ while ((hdr = snl_read_reply_multi(&ss, seq_id, &e)) != NULL) {
+ struct veb_rt_entry rt = {};
+ struct ifconfig_veb_rtnode *rn;
+
+ if (!snl_parse_nlmsg(&ss, hdr, &veb_rtable_parser, &rt))
+ continue;
+ if (rt.port == NULL || rt.mac == NULL ||
+ NLA_DATA_LEN(rt.mac) != ETHER_ADDR_LEN)
+ continue;
+
+ if (n == cap) {
+ size_t ncap = cap ? cap * 2 : 64;
+
+ tmp = reallocarray(vebrt, ncap, sizeof(*vebrt));
+ if (tmp == NULL) {
+ ifconfig_error(h, OTHER, ENOMEM);
+ goto err;
+ }
+ vebrt = tmp;
+ cap = ncap;
+ }
+
+ rn = &vebrt[n++];
+ memset(rn, 0, sizeof(*rn));
+ strlcpy(rn->port, rt.port, sizeof(rn->port));
+ memcpy(&rn->mac, NLA_DATA(rt.mac), ETHER_ADDR_LEN);
+ rn->vlan = rt.vlan;
+ rn->type = rt.type;
+ rn->expire = rt.expire;
+ }
+
+ if (e.error != 0) {
+ ifconfig_error(h, NETLINK, e.error);
+ goto err;
+ }
+
+ *vebrtp = vebrt;
+ *rtn_count = n;
+ snl_free(&ss);
+
+ return (0);
+err:
+ free(vebrt);
+ snl_free(&ss);
+
+ return (-1);
+}
+
+void
+ifconfig_veb_free_veb_showaddr(struct ifconfig_veb_rtnode *vebrt)
+{
+ if (vebrt != NULL) {
+ free(vebrt);
+ }
+}
diff --git a/sbin/ifconfig/Makefile b/sbin/ifconfig/Makefile
--- a/sbin/ifconfig/Makefile
+++ b/sbin/ifconfig/Makefile
@@ -72,6 +72,7 @@
SRCS+= ifconfig_netlink.c
SRCS+= ifvfstatus.c # VF status information
SRCS+= ifgeneve.c # GENEVE support
+SRCS+= ifveb.c # VEB support
.else
CFLAGS+=-DWITHOUT_NETLINK
.endif
diff --git a/sbin/ifconfig/ifveb.c b/sbin/ifconfig/ifveb.c
new file mode 100644
--- /dev/null
+++ b/sbin/ifconfig/ifveb.c
@@ -0,0 +1,347 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Aaron Espinoza <acesp25@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+
+#include <net/ethernet.h>
+#include <net/if.h>
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <err.h>
+
+#include "ifconfig.h"
+#include "libifconfig.h"
+
+static const char *const veb_rtable_types[] = {
+ [0] = "dynamic",
+ [1] = "static",
+ [2] = "sticky"
+};
+
+static void
+veb_status(if_ctx *ctx)
+{
+ struct ifconfig_veb_status *veb = NULL;
+
+ if (ifconfig_veb_get_veb_status(lifh, ctx->ifname, &veb) != 0)
+ return;
+
+ printf("\tmaxaddr: %u", veb->maxaddr);
+ printf("\ttimeout: %u", veb->timeout);
+ printf("\texceeded: %u\n", veb->exceeded);
+
+ for (uint32_t i = 0; i < veb->numports; i++) {
+ struct ifconfig_veb_port *port = &veb->ports[i];
+
+ const struct {
+ bool on;
+ const char *name;
+ } f[] = {
+ { port->learning, "LEARNING" },
+ { port->discover, "DISCOVER" },
+ { port->sticky, "STICKY" },
+ { port->private, "PRIVATE" },
+ { port->vport, "VPORT" },
+ };
+ const char *sep = "";
+
+ printf("\tport: %s flags=<", port->ifname);
+ for (size_t ii = 0; ii < nitems(f); ii++) {
+ if (f[ii].on) {
+ printf("%s%s", sep, f[ii].name);
+ sep = ",";
+ }
+ }
+ printf("> maxaddr: %u\n", port->maxaddr);
+ }
+
+ ifconfig_veb_free_veb_status(veb);
+}
+
+static void
+setveb_addport(if_ctx *ctx, const char *portname, int dummy __unused)
+{
+ if (ifconfig_veb_add_port(lifh, ctx->ifname, portname) != 0)
+ errc(1, ifconfig_err_errno(lifh), "vebportadd %s", portname);
+}
+
+static void
+setveb_delport(if_ctx *ctx, const char *portname, int dummy __unused)
+{
+ if (ifconfig_veb_del_port(lifh, ctx->ifname, portname) != 0)
+ errc(1, ifconfig_err_errno(lifh), "vebportdel %s", portname);
+}
+
+static void
+setveb_setportmaxaddr(if_ctx *ctx, const char *portname, const char *arg)
+{
+ const char *errstr;
+ uint32_t maxaddr;
+
+ maxaddr = strtonum(arg, 0, UINT32_MAX, &errstr);
+ if (errstr != NULL)
+ errx(1, "portmaxaddr %s: %s", arg, errstr);
+
+ if (ifconfig_veb_set_port_maxaddr(lifh, ctx->ifname,
+ portname, maxaddr) != 0)
+ errc(1, ifconfig_err_errno(lifh),
+ "vebsetportmaxaddr %s", portname);
+}
+
+static void
+setveb_learn(if_ctx *ctx, const char *portname, int dummy __unused)
+{
+ if (ifconfig_veb_set_port_flag(lifh, ctx->ifname, portname,
+ IFCONFIG_VEB_PORT_LEARNING, true) != 0)
+ errc(1, ifconfig_err_errno(lifh), "veblearn %s", portname);
+}
+
+static void
+setveb_unlearn(if_ctx *ctx, const char *portname, int dummy __unused)
+{
+ if (ifconfig_veb_set_port_flag(lifh, ctx->ifname, portname,
+ IFCONFIG_VEB_PORT_LEARNING, false) != 0)
+ errc(1, ifconfig_err_errno(lifh), "-veblearn %s", portname);
+}
+
+static void
+setveb_discover(if_ctx *ctx, const char *portname, int dummy __unused)
+{
+ if (ifconfig_veb_set_port_flag(lifh, ctx->ifname, portname,
+ IFCONFIG_VEB_PORT_DISCOVER, true) != 0)
+ errc(1, ifconfig_err_errno(lifh), "vebdiscover %s", portname);
+}
+
+static void
+setveb_undiscover(if_ctx *ctx, const char *portname, int dummy __unused)
+{
+ if (ifconfig_veb_set_port_flag(lifh, ctx->ifname, portname,
+ IFCONFIG_VEB_PORT_DISCOVER, false) != 0)
+ errc(1, ifconfig_err_errno(lifh), "-vebdiscover %s", portname);
+}
+
+static void
+setveb_sticky(if_ctx *ctx, const char *portname, int dummy __unused)
+{
+ if (ifconfig_veb_set_port_flag(lifh, ctx->ifname, portname,
+ IFCONFIG_VEB_PORT_STICKY, true) != 0)
+ errc(1, ifconfig_err_errno(lifh), "vebsticky %s", portname);
+}
+
+static void
+setveb_unsticky(if_ctx *ctx, const char *portname, int dummy __unused)
+{
+ if (ifconfig_veb_set_port_flag(lifh, ctx->ifname, portname,
+ IFCONFIG_VEB_PORT_STICKY, false) != 0)
+ errc(1, ifconfig_err_errno(lifh), "-vebsticky %s", portname);
+}
+
+static void
+setveb_private(if_ctx *ctx, const char *portname, int dummy __unused)
+{
+ if (ifconfig_veb_set_port_flag(lifh, ctx->ifname, portname,
+ IFCONFIG_VEB_PORT_PRIVATE, true) != 0)
+ errc(1, ifconfig_err_errno(lifh), "vebprivate %s", portname);
+}
+
+static void
+setveb_unprivate(if_ctx *ctx, const char *portname, int dummy __unused)
+{
+ if (ifconfig_veb_set_port_flag(lifh, ctx->ifname, portname,
+ IFCONFIG_VEB_PORT_PRIVATE, false) != 0)
+ errc(1, ifconfig_err_errno(lifh), "-vebprivate %s", portname);
+}
+
+static void
+setveb_settimeout(if_ctx *ctx, const char *arg, int dummy __unused)
+{
+ const char *errstr;
+ uint32_t timeout;
+
+ timeout = strtonum(arg, 0, UINT32_MAX, &errstr);
+ if (errstr != NULL)
+ errx(1, "timeout %s: %s", arg, errstr);
+
+ if (ifconfig_veb_set_timeout(lifh, ctx->ifname, timeout) != 0)
+ errc(1, ifconfig_err_errno(lifh), "vebtimeout %u", timeout);
+}
+
+static void
+setveb_setmaxaddr(if_ctx *ctx, const char *arg, int dummy __unused)
+{
+ const char *errstr;
+ uint32_t maxaddr;
+
+ maxaddr = strtonum(arg, 0, UINT32_MAX, &errstr);
+ if (errstr != NULL)
+ errx(1, "maxaddr %s: %s", arg, errstr);
+
+ if (ifconfig_veb_set_maxaddr(lifh, ctx->ifname, maxaddr) != 0)
+ errc(1, ifconfig_err_errno(lifh), "vebmaxaddr %u", maxaddr);
+}
+
+static int
+setveb_static(if_ctx *ctx, int argc, const char *const *argv)
+{
+ struct ether_addr *ea;
+ const char *port, *errstr;
+ ether_vlanid_t vlan = DOT1Q_VID_NULL;
+ int arg = 0;
+
+ if (argc < 2)
+ errx(1, "usage: vebstatic <interface> <address> [vlan <id>]");
+
+ port = argv[arg++];
+
+ ea = ether_aton(argv[arg]);
+ if (ea == NULL)
+ errx(1, "invalid address: %s", argv[arg]);
+ arg++;
+
+ if (argc > 2 && strcmp(argv[arg], "vlan") == 0) {
+ if (argc < 4)
+ errx(1, "usage: vebstatic <interface> <address> "
+ "[vlan <id>]");
+
+ vlan = strtonum(argv[arg + 1],
+ DOT1Q_VID_MIN, DOT1Q_VID_MAX, &errstr);
+
+ if (errstr != NULL)
+ errx(1, "vlan %s: %s", argv[arg + 1], errstr);
+
+ arg += 2;
+ }
+
+ if (ifconfig_veb_add_static(lifh, ctx->ifname, port, ea, vlan) != 0)
+ errc(1, ifconfig_err_errno(lifh), "vebstatic %s %s %u",
+ port, ether_ntoa(ea), vlan);
+
+ return (arg);
+}
+
+static int
+setveb_deladdr(if_ctx *ctx, int argc, const char *const *argv)
+{
+ struct ether_addr *ea;
+ const char *errstr;
+ ether_vlanid_t vlan = DOT1Q_VID_NULL;
+ int arg = 0;
+
+ if (argc < 1)
+ errx(1, "usage: vebdeladdr <address> [vlan <id>]");
+
+ ea = ether_aton(argv[arg]);
+ if (ea == NULL)
+ errx(1, "invalid address: %s", argv[arg]);
+ arg++;
+
+ if (argc > 1 && strcmp(argv[arg], "vlan") == 0) {
+ if (argc < 3)
+ errx(1, "usage: vebdeladdr <address> [vlan <id>]");
+
+ vlan = strtonum(argv[arg + 1],
+ DOT1Q_VID_MIN, DOT1Q_VID_MAX, &errstr);
+
+ if (errstr != NULL)
+ errx(1, "vlan %s: %s", argv[arg + 1], errstr);
+
+ arg += 2;
+ }
+
+ if (ifconfig_veb_del_addr(lifh, ctx->ifname, ea, vlan) != 0)
+ errc(1, ifconfig_err_errno(lifh), "vebdeladdr %s %u",
+ ether_ntoa(ea), vlan);
+
+ return (arg);
+}
+
+static void
+setveb_flush(if_ctx *ctx, const char *val __unused, int d)
+{
+ if (ifconfig_veb_flush(lifh, ctx->ifname, d) != 0)
+ errc(1, ifconfig_err_errno(lifh), "vebflush %d", d);
+}
+
+static void
+setveb_showaddr(if_ctx *ctx, const char *val __unused, int dummy __unused)
+{
+ struct ifconfig_veb_rtnode *rtnodes;
+ uint32_t count;
+
+ if (ifconfig_veb_showaddr(lifh, ctx->ifname, &rtnodes, &count) != 0)
+ errc(1, ifconfig_err_errno(lifh), "vebaddr");
+
+ for (uint32_t i = 0; i < count; i++) {
+ printf("%s Vlan%u %s %u %s\n", ether_ntoa(&rtnodes[i].mac),
+ rtnodes[i].vlan, rtnodes[i].port, rtnodes[i].expire,
+ rtnodes[i].type < nitems(veb_rtable_types)
+ ? veb_rtable_types[rtnodes[i].type] : "?");
+ }
+
+ ifconfig_veb_free_veb_showaddr(rtnodes);
+}
+
+static struct cmd veb_cmds[] = {
+ DEF_CMD_ARG("vebportadd", setveb_addport),
+ DEF_CMD_ARG("vebportdel", setveb_delport),
+ DEF_CMD_ARG2("vebportmaxaddr", setveb_setportmaxaddr),
+ DEF_CMD_ARG("veblearn", setveb_learn),
+ DEF_CMD_ARG("-veblearn", setveb_unlearn),
+ DEF_CMD_ARG("vebdiscover", setveb_discover),
+ DEF_CMD_ARG("-vebdiscover", setveb_undiscover),
+ DEF_CMD_ARG("vebsticky", setveb_sticky),
+ DEF_CMD_ARG("-vebsticky", setveb_unsticky),
+ DEF_CMD_ARG("vebprivate", setveb_private),
+ DEF_CMD_ARG("-vebprivate", setveb_unprivate),
+ DEF_CMD_ARG("vebtimeout", setveb_settimeout),
+ DEF_CMD_ARG("vebmaxaddr", setveb_setmaxaddr),
+ DEF_CMD_VARG("vebstatic", setveb_static),
+ DEF_CMD_VARG("vebdeladdr", setveb_deladdr),
+ DEF_CMD("vebflush", 0, setveb_flush),
+ DEF_CMD("vebflushall", 1, setveb_flush),
+ DEF_CMD("vebaddr", 1, setveb_showaddr),
+};
+
+static struct afswtch af_veb = {
+ .af_name = "af_veb",
+ .af_af = AF_UNSPEC,
+ .af_other_status = veb_status,
+};
+
+static __constructor void
+veb_constructor(void)
+{
+ for (size_t i = 0; i < nitems(veb_cmds); i++)
+ cmd_register(&veb_cmds[i]);
+
+ af_register(&af_veb);
+}
diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -634,6 +634,7 @@
unix.4 \
ure.4 \
vale.4 \
+ veb.4 \
veriexec.4 \
vga.4 \
vge.4 \
@@ -851,6 +852,9 @@
MLINKS+=tty.4 cua.4
MLINKS+=tun.4 if_tun.4
MLINKS+=ure.4 if_ure.4
+MLINKS+=veb.4 if_veb.4
+MLINKS+=veb.4 if_vport.4
+MLINKS+=veb.4 vport.4
MLINKS+=vge.4 if_vge.4
MLINKS+=vlan.4 if_vlan.4
MLINKS+=vxlan.4 if_vxlan.4
diff --git a/share/man/man4/veb.4 b/share/man/man4/veb.4
new file mode 100644
--- /dev/null
+++ b/share/man/man4/veb.4
@@ -0,0 +1,264 @@
+.\"-
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Copyright (c) 2026 Aaron Espinoza <acesp25@freebsd.org>
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd November 6, 2026
+.Dt VEB 4
+.Os
+.Sh NAME
+.Nm veb ,
+.Nm vport
+.Nd Virtual Ethernet Bridge pseudo network device
+.Sh SYNOPSIS
+.Cd device veb
+.Pp
+In
+.Xr loader.conf 5 :
+.Cd if_veb_load="YES"
+.Pp
+In
+.Xr loader.conf 5
+or
+.Xr sysctl.conf 5 :
+.Cd net.link.veb.log_mac_flap
+.Sh DESCRIPTION
+The
+.Nm
+driver provides a Layer 2 Ethernet bridge for virtual network
+topologies such as
+.Xr bhyve 8
+VMs and VNET
+.Xr jail 8 Ns s .
+As opposed to
+.Xr bridge 4 ,
+it does not support the Spanning Tree Protocol,
+.Xr pfil 9
+packet filtering, or implicit host membership.
+.Pp
+Each
+.Nm
+interface is created at runtime using interface cloning.
+This is most easily done with the
+.Xr ifconfig 8
+.Cm create
+command or using the
+.Va cloned_interfaces
+variable in
+.Xr rc.conf 5 .
+.Nm vport
+interfaces are created the same way.
+The
+.Nm
+can be configured via
+.Xr netlink 4
+using
+.Xr ifconfig 8 .
+.Ss Host presence
+The
+.Nm
+interface has no host presence.
+It cannot be assigned any
+.Xr inet 4
+or
+.Xr inet6 4
+address, it does not deliver frames to the host network stack, and it
+does not transmit frames originating from the host.
+.Pp
+To connect the host to a
+.Nm ,
+create a
+.Nm vport
+interface and add it to the
+.Nm
+as a port.
+A
+.Nm vport
+acts as an ordinary Ethernet interface to the host;
+addresses assigned to it are reachable from other ports, and traffic
+the host transmits is forwarded by the
+.Nm .
+Addresses can only be assigned to a
+.Nm vport
+while it is a port of a
+.Nm .
+A
+.Nm
+can have at most one
+.Nm vport ,
+and a
+.Nm vport
+can be a port of only one
+.Nm
+at any given time.
+.Ss Ports
+Ethernet and
+.Xr vlan 4
+interfaces can be added to the
+.Nm
+as ports.
+An interface can be a port of only one
+.Nm
+and cannot be a port or member of both a
+.Nm
+and a
+.Xr bridge 4
+at the same time.
+A
+.Xr vlan 4
+interface whose parent is a
+.Xr bridge 4
+or a
+.Nm
+cannot be added.
+.Pp
+An interface must not have an IPv4 address when it is added.
+On addition, if the interface has an IPv6 link-local address, all of its
+IPv6 addresses are removed and a message is logged.
+The interface is then placed into promiscuous mode and isolated from
+the host network stack:
+no addresses can be assigned to it and its capabilities cannot be
+changed.
+Promiscuous mode and capabilities are restored when the port is
+removed.
+.Pp
+The MTU of a
+.Nm
+is set by the first port that is added to it.
+All subsequent ports are set to that MTU.
+If the MTU of a
+.Nm
+is changed, every port of that
+.Nm
+is adjusted to match.
+The MTU of any port, including a
+.Nm vport ,
+cannot be changed directly while being a port.
+.Pp
+The TOE, TSO, TXCSUM, TXCSUM_IPV6, and MEXTPG capabilities remain
+enabled on the ports only if every port has them enabled.
+LRO is always disabled on the ports.
+Because the
+.Nm vport
+supports none of the transmit offloads, adding a
+.Nm vport
+disables them on all ports.
+.Ss Forwarding
+Just like
+.Xr bridge 4 ,
+the
+.Nm
+learns the source address of each frame it receives, indexed by
+Ethernet address and VLAN ID.
+Unicast frames destined for a known address are only forwarded out the
+port on which that address was learned;
+broadcast and multicast frames are forwarded to all other ports.
+Unicast frames destined for an unknown address are forwarded to all
+other ports with the
+.Li DISCOVER
+flag set.
+Frames addressed to the reserved IEEE 802.1D group addresses
+.Li 01:80:c2:00:00:00
+to
+.Li 01:80:c2:00:00:0f
+are not forwarded.
+VLAN tags are passed through unaltered as the
+.Nm
+currently has no VLAN filtering capability.
+If a port is the parent of a
+.Xr vlan 4
+interface, all VLAN-tagged frames received on that port are passed to
+.Xr vlan 4
+rather than being forwarded by the veb.
+By default, the address cache holds 2000 entries, and an entry expires 1200
+seconds after the last frame from that address is seen.
+A sweep removes all expired entries every 300 seconds.
+Both values can be configured per
+.Nm .
+Static entries do not expire.
+.Pp
+Each port has the following flags.
+Newly added ports have
+.Li LEARNING
+and
+.Li DISCOVER
+set automatically.
+.Bl -tag -width "DISCOVER"
+.It Li LEARNING
+Learn source addresses from frames received on the port.
+.It Li DISCOVER
+Receive copies of unicast frames to unknown addresses.
+.It Li STICKY
+Addresses learned on the port do not expire and do not move to another
+port.
+.It Li PRIVATE
+Do not forward frames between this port and any other
+.Li PRIVATE
+port.
+.It Li VPORT
+Read-only; set on the port that is the
+.Nm Ns 's
+.Nm vport .
+.El
+.Pp
+The link state of a
+.Nm
+is up when at least one port other than the
+.Nm vport
+has link, or when the
+.Nm
+has ports but none of them, besides the
+.Nm vport ,
+reports a link state.
+.Sh SYSCTL VARIABLES
+.Bl -tag -width indent
+.It Va net.link.veb.log_mac_flap
+Log a message when an address moves between ports.
+Defaults to 1.
+.El
+.Sh SEE ALSO
+.Xr bridge 4 ,
+.Xr epair 4 ,
+.Xr inet 4 ,
+.Xr inet6 4 ,
+.Xr netlink 4 ,
+.Xr vlan 4 ,
+.Xr loader.conf 5 ,
+.Xr rc.conf 5 ,
+.Xr bhyve 8 ,
+.Xr ifconfig 8 ,
+.Xr jail 8 ,
+.Xr ifnet 9 ,
+.Xr pfil 9
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Fx 16.0 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm veb
+driver and this manual page were written by
+.An Aaron Espinoza Aq Mt acesp25@FreeBSD.org .
diff --git a/sys/conf/NOTES b/sys/conf/NOTES
--- a/sys/conf/NOTES
+++ b/sys/conf/NOTES
@@ -966,6 +966,11 @@
# Ethernet interface
device tuntap
+# The `veb` device implements a minimal L2 Ethernet bridge
+# for virtual topologies. It also includes a `vport` device that can be
+# added to a veb for communication with the host network stack.
+device veb
+
# The `gif' device implements IPv6 over IP4 tunneling,
# IPv4 over IPv6 tunneling, IPv4 over IPv4 tunneling and
# IPv6 over IPv6 tunneling.
diff --git a/sys/conf/files b/sys/conf/files
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -4289,6 +4289,7 @@
net/if_ovpn.c optional ovpn inet | ovpn inet6
net/if_stf.c optional stf inet inet6
net/if_tuntap.c optional tuntap
+net/if_veb.c optional veb
net/if_vlan.c optional vlan
net/if_vxlan.c optional vxlan inet | vxlan inet6
net/if_geneve.c optional geneve inet | geneve inet6
diff --git a/sys/modules/Makefile b/sys/modules/Makefile
--- a/sys/modules/Makefile
+++ b/sys/modules/Makefile
@@ -171,6 +171,7 @@
if_ovpn \
${_if_stf} \
if_tuntap \
+ if_veb \
if_vlan \
if_vxlan \
if_geneve \
diff --git a/sys/modules/if_veb/Makefile b/sys/modules/if_veb/Makefile
new file mode 100644
--- /dev/null
+++ b/sys/modules/if_veb/Makefile
@@ -0,0 +1,5 @@
+.PATH: ${SRCTOP}/sys/net
+KMOD= if_veb
+SRCS= if_veb.c opt_inet6.h
+
+.include <bsd.kmod.mk>
diff --git a/sys/net/if_bridge.c b/sys/net/if_bridge.c
--- a/sys/net/if_bridge.c
+++ b/sys/net/if_bridge.c
@@ -2283,7 +2283,11 @@
struct bridge_iflist *bif = ifp->if_bridge;
struct bridge_softc *sc = NULL;
- if (bif)
+ /*
+ * Veb ports also use ifp->if_bridge,
+ * check that we own this entry (if there is one).
+ */
+ if (bif && ifp->if_bridge_input == bridge_input)
sc = bif->bif_sc;
if (V_bridge_cloner == NULL) {
diff --git a/sys/net/if_veb.c b/sys/net/if_veb.c
new file mode 100644
--- /dev/null
+++ b/sys/net/if_veb.c
@@ -0,0 +1,2925 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause AND BSD-4-Clause
+ *
+ * Copyright (c) 2026 Aaron Espinoza <acesp25@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Derived from if_bridge.c. The original notices follow and apply
+ * to the portions derived from it.
+ *
+ * $NetBSD: if_bridge.c,v 1.31 2005/06/01 19:45:34 jdc Exp $
+ */
+
+/*-
+ *
+ * Copyright 2001 Wasabi Systems, Inc.
+ * All rights reserved.
+ *
+ * Written by Jason R. Thorpe for Wasabi Systems, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed for the NetBSD Project by
+ * Wasabi Systems, Inc.
+ * 4. The name of Wasabi Systems, Inc. may not be used to endorse
+ * or promote products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 1999, 2000 Jason L. Wright (jason@thought.net)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * OpenBSD: if_bridge.c,v 1.60 2001/06/15 03:38:33 itojun Exp
+ */
+
+
+/*
+ * Virtual Ethernet Bridge (veb)
+ *
+ * A stripped L2 bridging device tailored for virtual
+ * topologies (VMs and VNET Jails)
+ *
+ * It has no implicit host-presence, no STP, no pfil,
+ * and explicit host-presence via a vport (see veb.4)
+ *
+ * TODO:
+ * - VLAN filtering via supporting multiple vports
+ * - Per port CPU counters for improved debugging
+ */
+
+#include "opt_inet6.h"
+
+#define EXTERR_CATEGORY EXTERR_CAT_VEB
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/exterrvar.h>
+#include <sys/eventhandler.h>
+#include <sys/callout.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/mbuf.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/priv.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+#include <sys/syslog.h>
+
+#include <net/bpf.h>
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <net/if_dl.h>
+#include <net/if_clone.h>
+#include <net/if_types.h>
+#include <net/if_media.h>
+#include <net/if_var.h>
+#include <net/if_private.h>
+#include <net/if_vlan_var.h>
+#include <net/vnet.h>
+
+#ifdef INET6
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <netinet6/in6_var.h>
+#include <netinet6/ip6_var.h>
+#include <netinet6/in6_ifattach.h>
+#endif
+
+#include <netlink/netlink.h>
+#include <netlink/netlink_ctl.h>
+#include <netlink/netlink_generic.h>
+#include <netlink/netlink_route.h>
+#include <netlink/netlink_var.h>
+#include <netlink/route/interface.h>
+#include <netlink/route/route_var.h>
+
+#include <net/if_veb_nl.h>
+
+
+/*
+ * Size of the route hash table. Must be a power of two.
+ */
+#ifndef VEB_RTHASH_SIZE
+#define VEB_RTHASH_SIZE 1024
+#endif
+
+#define VEB_RTHASH_MASK (VEB_RTHASH_SIZE - 1)
+
+/*
+ * Default maximum number of addresses to cache.
+ */
+#ifndef VEB_RTABLE_MAX
+#define VEB_RTABLE_MAX 2000
+#endif
+
+/*
+ * Timeout (in seconds) for entries learned dynamically.
+ */
+#ifndef VEB_RTABLE_TIMEOUT
+#define VEB_RTABLE_TIMEOUT (20 * 60) /* same as ARP */
+#endif
+
+/*
+ * Number of seconds between walks of the route list.
+ */
+#ifndef VEB_RTABLE_PRUNE_PERIOD
+#define VEB_RTABLE_PRUNE_PERIOD (5 * 60)
+#endif
+
+/*
+ * List of capabilities to possibly mask on the port interface.
+ */
+#define VEB_IFCAPS_MASK (IFCAP_TOE | IFCAP_TSO | IFCAP_TXCSUM |\
+ IFCAP_TXCSUM_IPV6 | IFCAP_MEXTPG)
+
+/*
+ * List of capabilities to strip
+ */
+#define VEB_IFCAPS_STRIP IFCAP_LRO
+
+static const char veb_name[] = "veb";
+static MALLOC_DEFINE(M_VEB, "veb", "Virtual Ethernet Bridge pseudo driver");
+VNET_DEFINE_STATIC(struct if_clone *, veb_cloner);
+#define V_veb_cloner VNET(veb_cloner)
+
+static const char vport_name[] = "vport";
+static MALLOC_DEFINE(M_VPORT, "vport", "Vport network pseudo driver");
+VNET_DEFINE_STATIC(struct if_clone *, vport_cloner);
+#define V_vport_cloner VNET(vport_cloner)
+
+/*
+ * Veb port (member)
+ */
+struct veb_port {
+ CK_LIST_ENTRY(veb_port) vp_next;
+ struct ifnet *vp_ifp;
+ struct veb_softc *vp_sc;
+ uint32_t vp_flags;
+#define VEB_PORT_LEARNING 0x01 /* learn source addresses */
+#define VEB_PORT_DISCOVER 0x02 /* receive unknown-unicast floods */
+#define VEB_PORT_STICKY 0x04 /* learned addresses do not age */
+#define VEB_PORT_PRIVATE 0x08 /* no forwarding between private ports */
+#define VEB_PORT_VPORT 0x10 /* port is the veb's vport; read-only */
+
+ bool vp_dying; /* set under sc_rt_mtx */
+ int vp_savedcaps; /* saved capabilities */
+ uint32_t vp_addrmax; /* max # of addresses */
+ uint32_t vp_addrcnt; /* cur. # of addresses */
+ uint32_t vp_addrexceeded;/* # of address violations */
+ struct epoch_context vp_epoch_ctx;
+ int (*vp_ioctl)(struct ifnet *, u_long, caddr_t);
+};
+#define IS_VPORT(_vp) ((_vp)->vp_flags & VEB_PORT_VPORT)
+
+/*
+ * Veb route node.
+ */
+struct veb_rtnode {
+ CK_LIST_ENTRY(veb_rtnode) vrt_hash; /* hash table linkage */
+ CK_LIST_ENTRY(veb_rtnode) vrt_list; /* list linkage */
+ struct veb_port *vrt_dst; /* destination if */
+ unsigned long vrt_expire; /* expiration time */
+ uint8_t vrt_flags; /* address flags */
+#define VEB_RTNODE_DYNAMIC 0x00 /* dynamically learned address */
+#define VEB_RTNODE_STATIC 0x01 /* static address */
+#define VEB_RTNODE_STICKY 0x02 /* sticky address */
+#define VEB_RTNODE_TYPEMASK 0x03 /* address type mask */
+
+ uint8_t vrt_addr[ETHER_ADDR_LEN];
+ struct vnet *vrt_vnet;
+ ether_vlanid_t vrt_vlan; /* vlan id */
+ struct epoch_context vrt_epoch_ctx;
+};
+#define vrt_ifp vrt_dst->vp_ifp
+
+#define VEB_RTABLE_FLUSHDYN 0x00 /* flush learned addresses only */
+#define VEB_RTABLE_FLUSHALL 0x01 /* flush all addresses */
+
+struct vport_softc {
+ struct ifnet *sc_ifp;
+ struct sx sc_sx;
+ struct veb_port *sc_vp; /* vport's veb_port */
+ struct ether_addr sc_defaddr; /* Default MAC address */
+ struct epoch_context sc_epoch_ctx;
+};
+
+struct veb_softc {
+ struct ifnet *sc_ifp;
+ struct sx sc_sx;
+ struct mtx sc_rt_mtx;
+ uint32_t sc_vrtmax; /* max # of addresses */
+ uint32_t sc_vrtcnt; /* cur. # of addresses */
+ uint32_t sc_vrttimeout; /* rt timeout in seconds */
+ struct callout sc_vebcallout; /* veb callout */
+ struct vport_softc *sc_vportsc; /* our vport */
+ CK_LIST_HEAD(, veb_port) sc_vebport; /* port interface list */
+ CK_LIST_HEAD(, veb_rtnode) *sc_rthash; /* our forwarding table */
+ CK_LIST_HEAD(, veb_rtnode) sc_rtlist; /* list version of above */
+ bool sc_dying; /* set under sc_sx */
+ uint32_t sc_rthash_key; /* key for hash */
+ uint32_t sc_vrtexceeded; /* # of cache drops */
+ struct epoch_context sc_epoch_ctx;
+};
+
+/* Used for dumping routing table */
+struct veb_rtable_snap {
+ char port[IFNAMSIZ];
+ uint8_t mac[ETHER_ADDR_LEN];
+ ether_vlanid_t vlan;
+ uint8_t type;
+ unsigned long expire;
+};
+
+/*
+ * VEB LOCKING
+ *
+ * if_veb uses a mtx for routing table node operations
+ * and a sc lock for all other operations.
+ */
+#define VEB_LOCK_INIT(_sc) do { \
+ sx_init(&(_sc)->sc_sx, "if_veb"); \
+ mtx_init(&(_sc)->sc_rt_mtx, "if_veb rt", NULL, MTX_DEF);\
+} while (0)
+#define VEB_LOCK_DESTROY(_sc) do { \
+ sx_destroy(&(_sc)->sc_sx); \
+ mtx_destroy(&(_sc)->sc_rt_mtx); \
+} while (0)
+#define VEB_LOCK(_sc) sx_xlock(&(_sc)->sc_sx)
+#define VEB_UNLOCK(_sc) sx_xunlock(&(_sc)->sc_sx)
+#define VEB_ASSERT(_sc) sx_assert(&(_sc)->sc_sx, SX_XLOCKED)
+#define VEB_LOCK_OR_NET_EPOCH_ASSERT(_sc) \
+ MPASS(in_epoch(net_epoch_preempt) || sx_xlocked(&(_sc)->sc_sx))
+#define VEB_RT_LOCK(_sc) mtx_lock(&(_sc)->sc_rt_mtx)
+#define VEB_RT_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_rt_mtx)
+#define VEB_RT_LOCK_ASSERT(_sc) mtx_assert(&(_sc)->sc_rt_mtx, MA_OWNED)
+#define VEB_RT_LOCK_OR_NET_EPOCH_ASSERT(_sc) \
+ MPASS(in_epoch(net_epoch_preempt) || mtx_owned(&(_sc)->sc_rt_mtx))
+
+/*
+ * VPORT LOCKING
+ *
+ * vport uses one sc lock,
+ *
+ * sc_vp is written under VEB_LOCK then VPORT_LOCK (lock order VEB -> VPORT),
+ * and read under VPORT_LOCK or the net epoch.
+ *
+ * Never call the vport's if_ioctl while holding VPORT_LOCK
+ */
+#define VPORT_LOCK_INIT(_sc) do { \
+ sx_init(&(_sc)->sc_sx, "if_vport"); \
+} while (0)
+#define VPORT_LOCK_DESTROY(_sc) do { \
+ sx_destroy(&(_sc)->sc_sx); \
+} while (0)
+#define VPORT_LOCK(_sc) sx_xlock(&(_sc)->sc_sx)
+#define VPORT_UNLOCK(_sc) sx_xunlock(&(_sc)->sc_sx)
+#define VPORT_ASSERT(_sc) sx_assert(&(_sc)->sc_sx, SX_XLOCKED)
+
+
+static uint16_t nl_veb_family_id;
+
+struct nl_parsed_veb_port {
+ char *ifla_ifname;
+ uint32_t ifla_maxaddr;
+ bool ifla_learning;
+ bool ifla_discover;
+ bool ifla_sticky;
+ bool ifla_private;
+};
+
+struct nl_parsed_veb {
+ char *ifla_addport_ifname;
+ char *ifla_delport_ifname;
+ struct nlattr *ifla_setport;
+ uint32_t ifla_timeout;
+ uint32_t ifla_maxaddr;
+ bool ifla_flushall;
+};
+
+struct nl_parsed_veb_rtable {
+ char *ifname;
+ char *port;
+ uint8_t mac[ETHER_ADDR_LEN];
+ ether_vlanid_t vlan;
+};
+
+#define _OUT(_field) offsetof(struct nl_parsed_veb_port, _field)
+static const struct nlattr_parser nla_p_veb_port[] = {
+ { .type = IFLA_VEB_PORT_IFNAME, .off = _OUT(ifla_ifname),
+ .cb = nlattr_get_string },
+ { .type = IFLA_VEB_PORT_MAXADDR, .off = _OUT(ifla_maxaddr),
+ .cb = nlattr_get_uint32 },
+ { .type = IFLA_VEB_PORT_LEARNING, .off = _OUT(ifla_learning),
+ .cb = nlattr_get_bool },
+ { .type = IFLA_VEB_PORT_DISCOVER, .off = _OUT(ifla_discover),
+ .cb = nlattr_get_bool },
+ { .type = IFLA_VEB_PORT_STICKY, .off = _OUT(ifla_sticky),
+ .cb = nlattr_get_bool },
+ { .type = IFLA_VEB_PORT_PRIVATE, .off = _OUT(ifla_private),
+ .cb = nlattr_get_bool },
+};
+#undef _OUT
+NL_DECLARE_ATTR_PARSER(veb_port_parser, nla_p_veb_port);
+
+#define _OUT(_field) offsetof(struct nl_parsed_veb, _field)
+static const struct nlattr_parser nla_p_veb[] = {
+ { .type = IFLA_VEB_ADDPORT, .off = _OUT(ifla_addport_ifname),
+ .cb = nlattr_get_string },
+ { .type = IFLA_VEB_DELPORT, .off = _OUT(ifla_delport_ifname),
+ .cb = nlattr_get_string },
+ { .type = IFLA_VEB_SETPORT, .off = _OUT(ifla_setport),
+ .cb = nlattr_get_nla },
+ { .type = IFLA_VEB_RTABLE_FLUSH, .off = _OUT(ifla_flushall),
+ .cb = nlattr_get_bool },
+ { .type = IFLA_VEB_RTABLE_TIMEOUT, .off = _OUT(ifla_timeout),
+ .cb = nlattr_get_uint32 },
+ { .type = IFLA_VEB_RTABLE_MAXADDR, .off = _OUT(ifla_maxaddr),
+ .cb = nlattr_get_uint32 },
+};
+#undef _OUT
+NL_DECLARE_ATTR_PARSER(veb_parser, nla_p_veb);
+
+#define _OUT(_field) offsetof(struct nl_parsed_veb_rtable, _field)
+static const struct nlattr_parser nla_p_veb_rtable[] = {
+ { .type = VEB_NL_RTABLE_MAC, .off = _OUT(mac),
+ .arg = (void *)ETHER_ADDR_LEN, .cb = nlattr_get_bytes },
+ { .type = VEB_NL_RTABLE_VLAN, .off = _OUT(vlan),
+ .cb = nlattr_get_uint16 },
+ { .type = VEB_NL_RTABLE_IFNAME, .off = _OUT(ifname),
+ .cb = nlattr_get_string },
+ { .type = VEB_NL_RTABLE_PORT, .off = _OUT(port),
+ .cb = nlattr_get_string },
+};
+#undef _OUT
+NL_DECLARE_PARSER(veb_rtable_parser, struct genlmsghdr,
+ nlf_p_empty, nla_p_veb_rtable);
+
+static const struct nlhdr_parser *all_parsers[] = {
+ &veb_parser,
+ &veb_port_parser,
+ &veb_rtable_parser,
+};
+
+/* veb method */
+static void vnet_veb_init(const void *);
+static void vnet_veb_uninit(const void *);
+static void veb_mod_load(void);
+static void veb_mod_unload(void);
+static int veb_clone_create(struct if_clone *, char *, size_t,
+ struct ifc_data *, struct ifnet **);
+static int veb_clone_destroy(struct if_clone *, struct ifnet *, uint32_t);
+static void veb_init(void *);
+static void veb_stop(struct ifnet *);
+static int veb_transmit(struct ifnet *, struct mbuf *);
+static void veb_qflush(struct ifnet *);
+static void veb_rttrim(struct veb_softc *);
+static void veb_timer(void *);
+static void veb_mutecaps(struct veb_softc *);
+static void veb_reassign(struct ifnet *, struct vnet *, char *);
+static void veb_set_ifcap(struct veb_softc *, struct veb_port *, int);
+static int veb_ioctl(struct ifnet *, u_long, caddr_t);
+static void veb_delete_port(struct veb_softc *, struct veb_port *, int);
+static int veb_add_port(struct veb_softc *, struct nl_pstate *, char *);
+static struct mbuf *veb_input(struct ifnet *, struct mbuf *);
+static int veb_if_output(struct ifnet *, struct mbuf *,
+ const struct sockaddr *, struct route *);
+static int veb_output(struct ifnet *, struct mbuf *,
+ struct sockaddr *, struct rtentry *);
+static void veb_forward(struct veb_softc *,
+ struct veb_port *, struct mbuf *);
+static void veb_linkstate(struct ifnet *);
+static void veb_linkcheck(struct veb_softc *);
+static int veb_rtaddstatic(struct veb_softc *, const uint8_t *,
+ ether_vlanid_t, struct veb_port *);
+static struct veb_port *veb_lookup_port(struct veb_softc *, const char *);
+static void veb_ifdetach(void *, struct ifnet *);
+static void veb_broadcast(struct veb_softc *, struct veb_port *,
+ struct ifnet *, struct mbuf *);
+static struct veb_port *veb_port_of(struct ifnet *);
+static int veb_enqueue(struct veb_softc *, struct veb_port *,
+ struct mbuf *);
+/* replaced ioctl on ports (members) */
+static int veb_p_ioctl(struct ifnet *, u_long, caddr_t);
+
+/* vport methods */
+static void vnet_vport_init(const void *);
+static int vport_clone_create(struct if_clone *, char *, size_t,
+ struct ifc_data *, struct ifnet **);
+static int vport_clone_destroy(struct if_clone *,
+ struct ifnet *, uint32_t);
+static int vport_ioctl(struct ifnet *, u_long, caddr_t);
+static void vport_qflush(struct ifnet *);
+static void vport_init(void *);
+static void vport_stop(struct ifnet *);
+static int vport_transmit(struct ifnet *, struct mbuf *);
+
+/* RT methods */
+static int veb_rtupdate(struct veb_softc *, const uint8_t *,
+ ether_vlanid_t, struct veb_port *, uint8_t);
+static struct veb_port *veb_rtlookup(struct veb_softc *, const uint8_t *,
+ ether_vlanid_t);
+static struct veb_rtnode *veb_rtnode_lookup(struct veb_softc *,
+ const uint8_t *, ether_vlanid_t);
+static void veb_rtage(struct veb_softc *);
+static void veb_rtflush(struct veb_softc *, int);
+static void veb_rtdelete(struct veb_softc *, struct ifnet *, int);
+static void veb_rtable_init(struct veb_softc *);
+static void veb_rtable_fini(struct veb_softc *);
+static void veb_rtnode_destroy(struct veb_softc *, struct veb_rtnode *);
+static __inline uint32_t veb_rthash(struct veb_softc *, const uint8_t *);
+static int veb_rtnode_addr_cmp(const uint8_t *, const uint8_t *);
+static int veb_rtnode_insert(struct veb_softc *, struct veb_rtnode *);
+
+/* veb nl methods */
+static int veb_clone_create_nl(struct if_clone *, char *,
+ size_t, struct ifc_data_nl *);
+static int veb_clone_modify_nl(struct ifnet *, struct ifc_data_nl *);
+static void veb_clone_dump_nl(struct ifnet *, struct nl_writer *);
+static int veb_addport_nl(struct veb_softc *, struct nl_pstate *, char *);
+static int veb_delport_nl(struct veb_softc *, struct nl_pstate *, char *);
+static int veb_setport_nl(struct veb_softc *,
+ struct nl_pstate *, struct nlattr *);
+static int veb_rtable_flush_nl(struct veb_softc *,
+ struct nl_pstate *, bool);
+static int veb_settimeout_nl(struct veb_softc *,
+ struct nl_pstate *, uint32_t);
+static bool veb_rtable_entry_nl(struct nl_writer *, struct nlmsghdr *,
+ const struct veb_rtable_snap *);
+static int veb_getrtable_nl(struct nlmsghdr *, struct nl_pstate *);
+static int veb_addstatic_nl(struct nlmsghdr *, struct nl_pstate *);
+static int veb_setmaxaddr_nl(struct veb_softc *,
+ struct nl_pstate *, uint32_t);
+static int veb_deladdr_nl(struct nlmsghdr *, struct nl_pstate *);
+
+static const struct genl_cmd veb_cmds[] = {
+ {
+ .cmd_num = VEB_NL_CMD_GETRTABLE,
+ .cmd_name = "GETRTABLE",
+ .cmd_cb = veb_getrtable_nl,
+ .cmd_flags = GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL,
+ .cmd_priv = 0,
+ },
+ {
+ .cmd_num = VEB_NL_CMD_ADDSTATIC,
+ .cmd_name = "ADDSTATIC",
+ .cmd_cb = veb_addstatic_nl,
+ .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_HASPOL,
+ .cmd_priv = PRIV_NET_BRIDGE,
+ },
+ {
+ .cmd_num = VEB_NL_CMD_DELADDR,
+ .cmd_name = "DELADDR",
+ .cmd_cb = veb_deladdr_nl,
+ .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_HASPOL,
+ .cmd_priv = PRIV_NET_BRIDGE,
+ },
+};
+
+#define VLANTAGOF(_m) \
+ ((_m->m_flags & M_VLANTAG) ? \
+ EVL_VLANOFTAG(_m->m_pkthdr.ether_vtag) : DOT1Q_VID_NULL)
+
+static eventhandler_tag veb_detach_cookie;
+
+SYSCTL_DECL(_net_link);
+static SYSCTL_NODE(_net_link, OID_AUTO,
+ veb, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "veb");
+
+/* log MAC address port flapping */
+VNET_DEFINE_STATIC(bool, log_mac_flap) = true;
+#define V_log_mac_flap VNET(log_mac_flap)
+SYSCTL_BOOL(_net_link_veb, OID_AUTO, log_mac_flap,
+ CTLFLAG_RW | CTLFLAG_VNET, &VNET_NAME(log_mac_flap), true,
+ "Log MAC address port flapping");
+
+VNET_DEFINE_STATIC(uma_zone_t, veb_rtnode_zone);
+VNET_DEFINE_STATIC(int, log_interval) = 5;
+VNET_DEFINE_STATIC(int, log_count) = 0;
+VNET_DEFINE_STATIC(struct timeval, log_last) = { 0 };
+
+#define V_veb_rtnode_zone VNET(veb_rtnode_zone)
+#define V_log_interval VNET(log_interval)
+#define V_log_count VNET(log_count)
+#define V_log_last VNET(log_last)
+
+static void
+vnet_vport_init(const void *unused __unused)
+{
+ struct if_clone_addreq req = {
+ .create_f = vport_clone_create,
+ .destroy_f = vport_clone_destroy,
+ .flags = IFC_F_AUTOUNIT,
+ };
+ V_vport_cloner = ifc_attach_cloner(vport_name, &req);
+}
+VNET_SYSINIT(vnet_vport_init, SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_ANY,
+ vnet_vport_init, NULL);
+
+static int
+vport_clone_create(struct if_clone *ifc, char *name, size_t len,
+ struct ifc_data *ifd, struct ifnet **ifpp)
+{
+ struct vport_softc *sc;
+ struct ifnet *ifp;
+
+ sc = malloc(sizeof(*sc), M_VPORT, M_WAITOK | M_ZERO);
+ ifp = sc->sc_ifp = if_alloc(IFT_ETHER);
+
+ VPORT_LOCK_INIT(sc);
+ ifp->if_softc = sc;
+ if_initname(ifp, vport_name, ifd->unit);
+
+ ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
+ ifp->if_capabilities = ifp->if_capenable =
+ IFCAP_VLAN_HWTAGGING | IFCAP_VLAN_MTU;
+ ifp->if_ioctl = vport_ioctl;
+ ifp->if_qflush = vport_qflush;
+ ifp->if_init = vport_init;
+ ifp->if_transmit = vport_transmit;
+ ether_gen_addr(ifp, &sc->sc_defaddr);
+ ether_ifattach(ifp, sc->sc_defaddr.octet);
+ ifp->if_baudrate = IF_Gbps(10); /* "arbitrary max" - if_epair.c */
+
+ *ifpp = ifp;
+
+ return (0);
+}
+
+static void
+vport_clone_destroy_cb(struct epoch_context *ctx)
+{
+ struct vport_softc *sc;
+
+ sc = __containerof(ctx, struct vport_softc, sc_epoch_ctx);
+
+ VPORT_LOCK_DESTROY(sc);
+ free(sc, M_VPORT);
+}
+
+static int
+vport_clone_destroy(struct if_clone *ifc, struct ifnet *ifp, uint32_t flags)
+{
+ struct vport_softc *sc = if_getsoftc(ifp);
+
+ VPORT_LOCK(sc);
+ vport_stop(ifp);
+ ifp->if_flags &= ~IFF_UP;
+ VPORT_UNLOCK(sc);
+
+ ether_ifdetach(ifp);
+
+ KASSERT(sc->sc_vp == NULL,
+ ("%s: %s destroyed while still a veb port",
+ __func__, ifp->if_xname));
+
+ if_free(ifp);
+
+ NET_EPOCH_CALL(vport_clone_destroy_cb, &sc->sc_epoch_ctx);
+
+ return (0);
+}
+
+/*
+ * A vport is the host's port on a veb, so it keeps ordinary interface
+ * semantics; only commands that break a veb invariant are gated on
+ * membership. The default case rejects while the vport is not a veb port.
+ *
+ * Every case sets error and breaks. No case may return directly: the
+ * unlock at the bottom is the only release.
+ */
+static int
+vport_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
+{
+ struct vport_softc *sc = if_getsoftc(ifp);
+ struct ifreq *ifr = (struct ifreq *) data;
+ int error = 0;
+
+ VPORT_LOCK(sc);
+
+ switch (cmd) {
+ case SIOCSIFFLAGS:
+ if (!(ifp->if_flags & IFF_UP) &&
+ (ifp->if_drv_flags & IFF_DRV_RUNNING)) {
+ vport_stop(ifp);
+ } else if ((ifp->if_flags & IFF_UP) &&
+ !(ifp->if_drv_flags & IFF_DRV_RUNNING)) {
+ VPORT_UNLOCK(sc);
+ vport_init(sc);
+ VPORT_LOCK(sc);
+ }
+ break;
+ case SIOCADDMULTI:
+ case SIOCDELMULTI:
+ break;
+ case SIOCSIFMTU:
+ if (sc->sc_vp != NULL) {
+ error = EXTERROR(EBUSY,
+ "MTU is managed by the veb; adjust it on the veb");
+ break;
+ }
+ ifp->if_mtu = ifr->ifr_mtu;
+ break;
+ case SIOCSIFCAP:
+ ifp->if_capenable = ifr->ifr_reqcap;
+ VLAN_CAPABILITIES(ifp);
+ break;
+ default:
+ if (sc->sc_vp == NULL) {
+ error = EXTERROR(EINVAL,
+ "Vport can't be configured when "
+ "it is not a veb port");
+ break;
+ }
+ VPORT_UNLOCK(sc);
+ error = ether_ioctl(ifp, cmd, data);
+ VPORT_LOCK(sc);
+ break;
+ }
+
+ VPORT_UNLOCK(sc);
+
+ return (error);
+}
+
+static void
+vport_qflush(struct ifnet *ifp __unused)
+{
+}
+
+static void
+vport_init(void *xsc)
+{
+ struct vport_softc *sc = (struct vport_softc *) xsc;
+ struct ifnet *ifp = sc->sc_ifp;
+
+ if (ifp->if_drv_flags & IFF_DRV_RUNNING)
+ return;
+
+ VPORT_LOCK(sc);
+ ifp->if_drv_flags |= IFF_DRV_RUNNING;
+ VPORT_UNLOCK(sc);
+}
+
+static void
+vport_stop(struct ifnet *ifp)
+{
+ struct vport_softc *sc __diagused = if_getsoftc(ifp);
+
+ VPORT_ASSERT(sc);
+
+ if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0)
+ return;
+
+ ifp->if_drv_flags &= ~IFF_DRV_RUNNING;
+}
+
+static int
+vport_transmit(struct ifnet *ifp, struct mbuf *m)
+{
+ struct vport_softc *sc = if_getsoftc(ifp);
+ struct veb_softc *vebsc;
+ struct veb_port *vp;
+ struct epoch_tracker et;
+
+ M_ASSERTPKTHDR(m);
+
+ if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) {
+ m_freem(m);
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+ return (ENETDOWN);
+ }
+
+ /* ether_output() does not tap, and veb_forward() taps sc_ifp. */
+ ETHER_BPF_MTAP(ifp, m);
+
+ NET_EPOCH_ENTER(et);
+
+ vp = sc->sc_vp;
+ /* NULL means we were removed from the veb; no carrier. */
+ if (vp == NULL) {
+ NET_EPOCH_EXIT(et);
+ m_freem(m);
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+ return (ENETDOWN);
+ }
+ vebsc = vp->vp_sc;
+
+ if ((vebsc->sc_ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) {
+ NET_EPOCH_EXIT(et);
+ m_freem(m);
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+ return (ENETDOWN);
+ }
+
+ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
+ if_inc_counter(ifp, IFCOUNTER_OBYTES, m->m_pkthdr.len);
+ if (m->m_flags & M_MCAST)
+ if_inc_counter(ifp, IFCOUNTER_OMCASTS, 1);
+
+ veb_forward(vebsc, vp, m); /* consumes m */
+
+ NET_EPOCH_EXIT(et);
+ return (0);
+}
+
+static void
+vnet_veb_init(const void *unused __unused)
+{
+
+ V_veb_rtnode_zone = uma_zcreate("veb_rtnode",
+ sizeof(struct veb_rtnode), NULL, NULL, NULL, NULL,
+ UMA_ALIGN_PTR, 0);
+
+ struct if_clone_addreq_v2 req = {
+ .version = 2,
+ .flags = IFC_F_AUTOUNIT,
+ .create_f = veb_clone_create,
+ .destroy_f = veb_clone_destroy,
+ .create_nl_f = veb_clone_create_nl,
+ .modify_nl_f = veb_clone_modify_nl,
+ .dump_nl_f = veb_clone_dump_nl,
+ };
+ V_veb_cloner = ifc_attach_cloner(veb_name,
+ (struct if_clone_addreq *) &req);
+}
+VNET_SYSINIT(vnet_veb_init, SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_ANY,
+ vnet_veb_init, NULL);
+
+static void
+vnet_veb_uninit(const void *unused __unused)
+{
+ /*
+ * Order matters: detaching a cloner destroys every instance of it.
+ * veb0 must go first so that veb_delete_port() runs while the
+ * vport ifnets and softcs are still alive.
+ */
+ ifc_detach_cloner(V_veb_cloner);
+ V_veb_cloner = NULL;
+ ifc_detach_cloner(V_vport_cloner);
+ V_vport_cloner = NULL;
+
+
+ /* Callbacks may use the UMA zone. */
+ NET_EPOCH_DRAIN_CALLBACKS();
+
+ uma_zdestroy(V_veb_rtnode_zone);
+}
+VNET_SYSUNINIT(vnet_veb_uninit, SI_SUB_PSEUDO, SI_ORDER_ANY,
+ vnet_veb_uninit, NULL);
+
+static int
+veb_modevent(module_t mod, int type, void *data)
+{
+ switch (type) {
+ case MOD_LOAD:
+ veb_mod_load();
+ break;
+ case MOD_UNLOAD:
+ veb_mod_unload();
+ break;
+ default:
+ return (EOPNOTSUPP);
+ }
+ return (0);
+}
+
+static moduledata_t veb_mod = {"if_veb", veb_modevent, 0};
+DECLARE_MODULE(if_veb, veb_mod, SI_SUB_PSEUDO, SI_ORDER_ANY);
+MODULE_VERSION(if_veb, 1);
+
+static void
+veb_mod_load(void)
+{
+ veb_detach_cookie = EVENTHANDLER_REGISTER(ifnet_departure_event,
+ veb_ifdetach, NULL, EVENTHANDLER_PRI_ANY);
+
+ NL_VERIFY_PARSERS(all_parsers);
+
+ nl_veb_family_id = genl_register_family(VEB_NL_FAMILY_NAME,
+ 0, VEB_NL_FAMILY_VERSION, VEB_NL_RTABLE_MAX);
+
+ MPASS(nl_veb_family_id != 0);
+
+ genl_register_cmds(nl_veb_family_id, veb_cmds, nitems(veb_cmds));
+}
+
+static void
+veb_mod_unload(void)
+{
+ EVENTHANDLER_DEREGISTER(ifnet_departure_event, veb_detach_cookie);
+
+ genl_unregister_family(nl_veb_family_id);
+}
+
+static int
+veb_clone_create(struct if_clone *ifc, char *name, size_t len,
+ struct ifc_data *ifd, struct ifnet **ifpp)
+{
+ struct veb_softc *sc;
+ struct ifnet *ifp;
+
+ sc = malloc(sizeof(*sc), M_VEB, M_WAITOK | M_ZERO);
+ ifp = sc->sc_ifp = if_alloc(IFT_BRIDGE);
+
+ VEB_LOCK_INIT(sc);
+ sc->sc_vrtmax = VEB_RTABLE_MAX; /* 2000 */
+ sc->sc_vrttimeout = VEB_RTABLE_TIMEOUT; /* 20 * 60 */
+
+ veb_rtable_init(sc);
+
+ callout_init_mtx(&sc->sc_vebcallout, &sc->sc_rt_mtx, 0);
+
+ CK_LIST_INIT(&sc->sc_vebport);
+
+ ifp->if_softc = sc;
+ if_initname(ifp, veb_name, ifd->unit);
+
+ ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
+ ifp->if_capabilities = ifp->if_capenable = IFCAP_VLAN_HWTAGGING;
+ ifp->if_output = veb_if_output;
+ ifp->if_transmit = veb_transmit;
+ ifp->if_ioctl = veb_ioctl;
+ ifp->if_qflush = veb_qflush;
+ ifp->if_init = veb_init;
+ ifp->if_mtu = ETHERMTU;
+ if_attach(ifp);
+ bpfattach(ifp, DLT_EN10MB, ETHER_HDR_LEN);
+#ifdef VIMAGE
+ ifp->if_reassign = veb_reassign;
+#endif
+
+ *ifpp = ifp;
+
+ return (0);
+}
+
+static void
+veb_clone_destroy_cb(struct epoch_context *ctx)
+{
+ struct veb_softc *sc;
+
+ sc = __containerof(ctx, struct veb_softc, sc_epoch_ctx);
+
+ VEB_LOCK_DESTROY(sc);
+ free(sc->sc_rthash, M_VEB);
+ free(sc, M_VEB);
+}
+
+static int
+veb_clone_destroy(struct if_clone *ifc, struct ifnet *ifp, uint32_t flags)
+{
+ struct veb_softc *sc = if_getsoftc(ifp);
+ struct veb_port *vp;
+
+ VEB_LOCK(sc);
+
+ veb_stop(ifp);
+ ifp->if_flags &= ~IFF_UP;
+
+ while ((vp = CK_LIST_FIRST(&sc->sc_vebport)) != NULL)
+ veb_delete_port(sc, vp, 0);
+
+ veb_rtable_fini(sc);
+
+ VEB_UNLOCK(sc);
+
+ callout_drain(&sc->sc_vebcallout);
+
+ bpfdetach(ifp);
+ if_detach(ifp);
+
+ if_free(ifp);
+
+ NET_EPOCH_CALL(veb_clone_destroy_cb, &sc->sc_epoch_ctx);
+
+ return (0);
+}
+
+static int
+veb_transmit(struct ifnet *ifp, struct mbuf *m)
+{
+ m_freem(m);
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+ return (ENETDOWN);
+}
+
+static int
+veb_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
+{
+ struct veb_softc *sc = if_getsoftc(ifp);
+ struct ifreq *ifr = (struct ifreq *) data;
+ struct veb_port *vp;
+ struct vport_softc *vpsc;
+ int error = 0, oldmtu;
+
+ switch (cmd) {
+ case SIOCADDMULTI:
+ case SIOCDELMULTI:
+ return (0);
+ case SIOCSIFADDR:
+ /* IPv4 and IPv6 send this for an interface's first address */
+ return (EXTERROR(EOPNOTSUPP,
+ "veb has no host presence; assign addresses to a vport"));
+ }
+
+
+ VEB_LOCK(sc);
+
+ switch (cmd) {
+ case SIOCSIFFLAGS:
+ if (!(ifp->if_flags & IFF_UP) &&
+ (ifp->if_drv_flags & IFF_DRV_RUNNING)) {
+ veb_stop(ifp);
+ } else if ((ifp->if_flags & IFF_UP) &&
+ !(ifp->if_drv_flags & IFF_DRV_RUNNING)) {
+ VEB_UNLOCK(sc);
+ veb_init(sc);
+ VEB_LOCK(sc);
+ }
+ break;
+ case SIOCSIFMTU:
+ oldmtu = sc->sc_ifp->if_mtu;
+
+ if (ifr->ifr_mtu < IF_MINMTU) {
+ error = EXTERROR(EINVAL,
+ "Requested MTU is lower than IF_MINMTU");
+ break;
+ }
+ if (CK_LIST_EMPTY(&sc->sc_vebport)) {
+ sc->sc_ifp->if_mtu = ifr->ifr_mtu;
+ break;
+ }
+ CK_LIST_FOREACH(vp, &sc->sc_vebport, vp_next) {
+ if (IS_VPORT(vp)) {
+ /* We own vports, no need for negotiation */
+ vpsc = sc->sc_vportsc;
+ VPORT_LOCK(vpsc);
+ vp->vp_ifp->if_mtu = ifr->ifr_mtu;
+ VPORT_UNLOCK(vpsc);
+ continue;
+ }
+
+ error = (*vp->vp_ifp->if_ioctl)(vp->vp_ifp,
+ SIOCSIFMTU, (caddr_t)ifr);
+ if (error != 0) {
+ log(LOG_NOTICE, "%s: invalid MTU: %u for"
+ " port %s\n", sc->sc_ifp->if_xname,
+ ifr->ifr_mtu,
+ vp->vp_ifp->if_xname);
+ error = EINVAL;
+ break;
+ }
+ }
+ if (error) {
+ /* Restore the previous MTU on all ports */
+ ifr->ifr_mtu = oldmtu;
+ CK_LIST_FOREACH(vp, &sc->sc_vebport, vp_next) {
+ if (IS_VPORT(vp)) {
+ vpsc = sc->sc_vportsc;
+ VPORT_LOCK(vpsc);
+ vp->vp_ifp->if_mtu = ifr->ifr_mtu;
+ VPORT_UNLOCK(vpsc);
+ continue;
+ }
+ (*vp->vp_ifp->if_ioctl)(vp->vp_ifp,
+ SIOCSIFMTU, (caddr_t)ifr);
+ }
+ error = EXTERROR(error,
+ "Failed to set MTU on port interface");
+ } else {
+ sc->sc_ifp->if_mtu = ifr->ifr_mtu;
+ if (sc->sc_vportsc != NULL)
+ if_notifymtu(sc->sc_vportsc->sc_ifp);
+ }
+ break;
+
+ default:
+ error = EINVAL;
+ break;
+ }
+
+ VEB_UNLOCK(sc);
+
+ return (error);
+}
+
+/*
+ * Installed as if_ioctl on every non-vport port while attached to a veb.
+ *
+ * Denied with EBUSY: SIOCSIFADDR, SIOCAIFADDR, other IOC_IN
+ * requests, and SIOCSIFCAP since veb_mutecaps() owns port capabilities.
+ *
+ * IPv4 and IPv6 both issue if_ioctl(SIOCSIFADDR) when adding an
+ * address (in.c, in6.c), which is what keeps ports address-free.
+ *
+ * Passed to the port's original handler: SIOCSIFFLAGS, multicast
+ * membership, every get-style request, and SIOCSIFMTU, which only
+ * veb_ioctl() can send because ifhwioctl() refuses MTU changes on
+ * interfaces with if_bridge set.
+ */
+static int
+veb_p_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
+{
+ struct veb_port *vp;
+ struct epoch_tracker et;
+ int (*ioctl_fn)(struct ifnet *, u_long, caddr_t);
+
+ NET_EPOCH_ENTER(et);
+ vp = veb_port_of(ifp);
+ ioctl_fn = (vp != NULL) ? vp->vp_ioctl : NULL;
+ NET_EPOCH_EXIT(et);
+
+ if (ioctl_fn == NULL)
+ return (EOPNOTSUPP); /* raced with delete */
+
+ switch (cmd) {
+ case SIOCSIFFLAGS:
+ case SIOCSIFMTU:
+ case SIOCADDMULTI:
+ case SIOCDELMULTI:
+ break;
+
+ case SIOCSIFADDR:
+ case SIOCAIFADDR:
+ case SIOCSIFCAP:
+ return (EXTERROR(EBUSY, "Interface is a veb port"));
+
+ default:
+ if ((cmd & IOC_DIRMASK) == IOC_IN)
+ return (EXTERROR(EBUSY, "Interface is a veb port"));
+ break;
+ }
+
+ return ((*ioctl_fn)(ifp, cmd, data));
+}
+
+#ifdef VIMAGE
+static void
+veb_reassign(struct ifnet *ifp, struct vnet *newvnet, char *arg)
+{
+ struct veb_softc *sc = ifp->if_softc;
+ struct veb_port *vp;
+
+ VEB_LOCK(sc);
+
+ while ((vp = CK_LIST_FIRST(&sc->sc_vebport)) != NULL)
+ veb_delete_port(sc, vp, 0);
+
+ VEB_UNLOCK(sc);
+}
+#endif
+
+/*
+ * Resolve an ifnet to its veb_port via if_bridge void pointer
+ *
+ * Caller must be in NET_EPOCH or hold VEB_LOCK.
+ */
+static struct veb_port *
+veb_port_of(struct ifnet *port_ifp)
+{
+ /*
+ * If the ifnet belongs to a vport,
+ * we can't use the if_bridge pointer.
+ */
+ if (port_ifp->if_ioctl == vport_ioctl) {
+ struct vport_softc *vpsc = if_getsoftc(port_ifp);
+ return (vpsc->sc_vp);
+ }
+
+ /*
+ * Check that the ifp in the if_bridge pointer
+ * belongs to a veb and not a bridge
+ */
+ if (port_ifp->if_bridge_input != veb_input)
+ return (NULL);
+
+ return (port_ifp->if_bridge);
+}
+
+static void
+veb_delete_port_cb(struct epoch_context *ctx)
+{
+ struct veb_port *vp;
+
+ vp = __containerof(ctx, struct veb_port, vp_epoch_ctx);
+
+ free(vp, M_VEB);
+}
+
+static void
+veb_delete_port(struct veb_softc *sc, struct veb_port *vp, int gone)
+{
+ struct ifnet *ifs = vp->vp_ifp;
+ struct vport_softc *vpsc;
+
+ VEB_ASSERT(sc);
+
+ if (IS_VPORT(vp)) {
+ vpsc = if_getsoftc(ifs);
+ sc->sc_vportsc = NULL;
+ VPORT_LOCK(vpsc);
+ vpsc->sc_vp = NULL;
+ VPORT_UNLOCK(vpsc);
+ }
+
+ CK_LIST_REMOVE(vp, vp_next);
+
+ veb_linkcheck(sc);
+ veb_mutecaps(sc);
+
+ VEB_RT_LOCK(sc);
+ vp->vp_dying = true;
+ veb_rtdelete(sc, ifs, VEB_RTABLE_FLUSHALL);
+ VEB_RT_UNLOCK(sc);
+
+ KASSERT(vp->vp_addrcnt == 0,
+ ("%s: %u veb routes referenced", __func__, vp->vp_addrcnt));
+
+
+ if (!IS_VPORT(vp)) {
+ ifs->if_bridge = NULL;
+ ifs->if_bridge_output = NULL;
+ ifs->if_bridge_input = NULL;
+ ifs->if_bridge_linkstate = NULL;
+ /* Restore original ioctl function on ports interface */
+ ifs->if_ioctl = vp->vp_ioctl;
+ vp->vp_ioctl = NULL;
+ }
+
+ if (!gone && !IS_VPORT(vp)) {
+ switch (ifs->if_type) {
+ case IFT_ETHER:
+ case IFT_L2VLAN:
+ /*
+ * Take the interface out of promiscuous mode, but only
+ * if it was promiscuous in the first place. It might
+ * not be if we're in the veb_add_port() error path.
+ */
+ if (ifs->if_flags & IFF_PROMISC)
+ (void) ifpromisc(ifs, 0);
+ break;
+
+ default:
+ break;
+ }
+ /* Re-enable any interface capabilities */
+ veb_set_ifcap(sc, vp, vp->vp_savedcaps);
+ }
+
+ NET_EPOCH_CALL(veb_delete_port_cb, &vp->vp_epoch_ctx);
+}
+
+static void
+veb_ifdetach(void *arg __unused, struct ifnet *ifp)
+{
+ struct veb_port *vp = NULL;
+ struct veb_softc *sc = NULL;
+ struct epoch_tracker et;
+
+ NET_EPOCH_ENTER(et);
+ vp = veb_port_of(ifp);
+ sc = (vp != NULL) ? vp->vp_sc : NULL;
+ NET_EPOCH_EXIT(et);
+
+ if (sc == NULL || V_veb_cloner == NULL)
+ return;
+
+ /* Check if the interface is a veb port */
+ VEB_LOCK(sc);
+ /* revalidate under VEB_LOCK */
+ vp = veb_port_of(ifp);
+ if (vp != NULL && vp->vp_sc == sc)
+ veb_delete_port(sc, vp, (ifp->if_flags & IFF_DYING) != 0);
+ VEB_UNLOCK(sc);
+}
+
+static int
+veb_if_output(struct ifnet *ifp, struct mbuf *m,
+ const struct sockaddr *dst, struct route *ro)
+{
+ m_freem(m);
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+ return (ENETDOWN);
+}
+
+static void
+veb_qflush(struct ifnet *ifp __unused)
+{
+}
+
+static void
+veb_init(void *xsc)
+{
+ struct veb_softc *sc = (struct veb_softc *) xsc;
+ struct ifnet *ifp = sc->sc_ifp;
+
+ if (ifp->if_drv_flags & IFF_DRV_RUNNING)
+ return;
+
+ VEB_LOCK(sc);
+
+ callout_reset(&sc->sc_vebcallout,
+ VEB_RTABLE_PRUNE_PERIOD * hz, veb_timer, sc);
+
+ ifp->if_drv_flags |= IFF_DRV_RUNNING;
+ VEB_UNLOCK(sc);
+}
+
+static void
+veb_stop(struct ifnet *ifp)
+{
+ struct veb_softc *sc = ifp->if_softc;
+
+ VEB_ASSERT(sc);
+
+ sc->sc_dying = true;
+
+ if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0)
+ return;
+
+ VEB_RT_LOCK(sc);
+
+ callout_stop(&sc->sc_vebcallout);
+
+ veb_rtflush(sc, VEB_RTABLE_FLUSHDYN);
+ VEB_RT_UNLOCK(sc);
+
+ ifp->if_drv_flags &= ~IFF_DRV_RUNNING;
+}
+
+static void
+veb_set_ifcap(struct veb_softc *sc, struct veb_port *vp, int set)
+{
+ struct ifnet *ifp = vp->vp_ifp;
+ int (*ioctl_fn)(struct ifnet *, u_long, caddr_t);
+ struct ifreq ifr;
+ int error, mask, stuck;
+
+ bzero(&ifr, sizeof(ifr));
+ ifr.ifr_reqcap = set;
+
+ if (ifp->if_capenable != set) {
+ /*
+ * We bypass veb_p_ioctl() here
+ * as it denies SIOCSIFCAP to ports.
+ */
+ ioctl_fn = (vp->vp_ioctl != NULL)
+ ? vp->vp_ioctl : ifp->if_ioctl;
+
+ error = (*ioctl_fn)(ifp, SIOCSIFCAP, (caddr_t)&ifr);
+ if (error)
+ if_printf(sc->sc_ifp,
+ "error setting capabilities on %s: %d\n",
+ ifp->if_xname, error);
+ mask = VEB_IFCAPS_MASK | VEB_IFCAPS_STRIP;
+ stuck = ifp->if_capenable & mask & ~set;
+ if (stuck != 0)
+ if_printf(sc->sc_ifp,
+ "can't disable some capabilities on %s: 0x%x\n",
+ ifp->if_xname, stuck);
+ }
+}
+
+static void
+veb_mutecaps(struct veb_softc *sc)
+{
+ struct veb_port *vp;
+ int enabled, mask;
+
+ VEB_ASSERT(sc);
+
+ /* Initial bitmask of capabilities to test */
+ mask = VEB_IFCAPS_MASK;
+
+ CK_LIST_FOREACH(vp, &sc->sc_vebport, vp_next) {
+ /* Every port must support it or it's disabled */
+ mask &= vp->vp_savedcaps;
+ }
+
+ /*
+ * Adding a Vport strips TSO/TXCSUM from every port
+ * as a vport does not advertise them.
+ */
+ CK_LIST_FOREACH(vp, &sc->sc_vebport, vp_next) {
+ enabled = vp->vp_ifp->if_capenable;
+ enabled &= ~VEB_IFCAPS_STRIP;
+ /* Strip off mask bits and enable them again if allowed */
+ enabled &= ~VEB_IFCAPS_MASK;
+ enabled |= mask;
+ veb_set_ifcap(sc, vp, enabled);
+ }
+}
+
+static void
+veb_rttrim(struct veb_softc *sc)
+{
+ struct veb_rtnode *vrt, *nvrt;
+
+ NET_EPOCH_ASSERT();
+ VEB_RT_LOCK_ASSERT(sc);
+
+ /* Make sure we actually need to do this. */
+ if (sc->sc_vrtcnt <= sc->sc_vrtmax)
+ return;
+
+ /* Force an aging cycle; this might trim enough addresses. */
+ veb_rtage(sc);
+ if (sc->sc_vrtcnt <= sc->sc_vrtmax)
+ return;
+
+ CK_LIST_FOREACH_SAFE(vrt, &sc->sc_rtlist, vrt_list, nvrt) {
+ if ((vrt->vrt_flags & VEB_RTNODE_TYPEMASK)
+ == VEB_RTNODE_DYNAMIC) {
+ veb_rtnode_destroy(sc, vrt);
+ if (sc->sc_vrtcnt <= sc->sc_vrtmax)
+ return;
+ }
+ }
+}
+
+static void
+veb_timer(void *arg)
+{
+ struct veb_softc *sc = arg;
+
+ VEB_RT_LOCK_ASSERT(sc);
+
+ /* Destruction of rtnodes requires a proper vnet context */
+ CURVNET_SET(sc->sc_ifp->if_vnet);
+ veb_rtage(sc);
+
+ if (sc->sc_ifp->if_drv_flags & IFF_DRV_RUNNING)
+ callout_reset(&sc->sc_vebcallout,
+ VEB_RTABLE_PRUNE_PERIOD * hz, veb_timer, sc);
+ CURVNET_RESTORE();
+}
+
+static void
+veb_rtable_init(struct veb_softc *sc)
+{
+ int i;
+
+ sc->sc_rthash = malloc(sizeof(*sc->sc_rthash) * VEB_RTHASH_SIZE,
+ M_VEB, M_WAITOK);
+
+ for (i = 0; i < VEB_RTHASH_SIZE; i++)
+ CK_LIST_INIT(&sc->sc_rthash[i]);
+
+ sc->sc_rthash_key = arc4random();
+ CK_LIST_INIT(&sc->sc_rtlist);
+}
+
+static void
+veb_rtable_fini(struct veb_softc *sc)
+{
+ KASSERT(sc->sc_vrtcnt == 0,
+ ("%s: %u veb routes referenced", __func__, sc->sc_vrtcnt));
+}
+
+static void
+veb_rtage(struct veb_softc *sc)
+{
+ struct veb_rtnode *vrt, *nvrt;
+
+ VEB_RT_LOCK_ASSERT(sc);
+
+ CK_LIST_FOREACH_SAFE(vrt, &sc->sc_rtlist, vrt_list, nvrt) {
+ if ((vrt->vrt_flags &
+ VEB_RTNODE_TYPEMASK) == VEB_RTNODE_DYNAMIC) {
+ if (time_uptime >= vrt->vrt_expire)
+ veb_rtnode_destroy(sc, vrt);
+ }
+ }
+}
+
+static void
+veb_rtnode_destroy_cb(struct epoch_context *ctx)
+{
+ struct veb_rtnode *vrt;
+
+ vrt = __containerof(ctx, struct veb_rtnode, vrt_epoch_ctx);
+
+ CURVNET_SET(vrt->vrt_vnet);
+ uma_zfree(V_veb_rtnode_zone, vrt);
+ CURVNET_RESTORE();
+}
+
+static void
+veb_rtnode_destroy(struct veb_softc *sc, struct veb_rtnode *vrt)
+{
+ VEB_RT_LOCK_ASSERT(sc);
+
+ CK_LIST_REMOVE(vrt, vrt_hash);
+ CK_LIST_REMOVE(vrt, vrt_list);
+
+ sc->sc_vrtcnt--;
+ vrt->vrt_dst->vp_addrcnt--;
+
+ NET_EPOCH_CALL(veb_rtnode_destroy_cb, &vrt->vrt_epoch_ctx);
+}
+
+static void
+veb_rtdelete(struct veb_softc *sc, struct ifnet *ifp, int full)
+{
+ struct veb_rtnode *vrt, *nvrt;
+
+ VEB_RT_LOCK_ASSERT(sc);
+
+ CK_LIST_FOREACH_SAFE(vrt, &sc->sc_rtlist, vrt_list, nvrt) {
+ if (vrt->vrt_ifp == ifp && (full || (vrt->vrt_flags &
+ VEB_RTNODE_TYPEMASK) == VEB_RTNODE_DYNAMIC))
+ veb_rtnode_destroy(sc, vrt);
+ }
+}
+
+static void
+veb_rtflush(struct veb_softc *sc, int full)
+{
+ struct veb_rtnode *vrt, *nvrt;
+
+ VEB_RT_LOCK_ASSERT(sc);
+
+ CK_LIST_FOREACH_SAFE(vrt, &sc->sc_rtlist, vrt_list, nvrt) {
+ if (full || (vrt->vrt_flags &
+ VEB_RTNODE_TYPEMASK) == VEB_RTNODE_DYNAMIC)
+ veb_rtnode_destroy(sc, vrt);
+ }
+}
+
+static struct veb_port *
+veb_rtlookup(struct veb_softc *sc, const uint8_t *addr, ether_vlanid_t vlan)
+{
+ struct veb_rtnode *vrt;
+
+ NET_EPOCH_ASSERT();
+
+ if ((vrt = veb_rtnode_lookup(sc, addr, vlan)) == NULL)
+ return (NULL);
+
+ return (vrt->vrt_dst);
+}
+
+static struct veb_rtnode *
+veb_rtnode_lookup(struct veb_softc *sc, const uint8_t *addr,
+ ether_vlanid_t vlan)
+{
+ struct veb_rtnode *vrt;
+ uint32_t hash;
+ int dir;
+
+ VEB_RT_LOCK_OR_NET_EPOCH_ASSERT(sc);
+
+ hash = veb_rthash(sc, addr);
+ CK_LIST_FOREACH(vrt, &sc->sc_rthash[hash], vrt_hash) {
+ dir = veb_rtnode_addr_cmp(addr, vrt->vrt_addr);
+ if (dir == 0 && vrt->vrt_vlan == vlan)
+ return (vrt);
+ if (dir > 0)
+ return (NULL);
+ }
+
+ return (NULL);
+}
+
+/*
+ * The following hash function is adapted from "Hash Functions" by Bob Jenkins
+ * ("Algorithm Alley", Dr. Dobbs Journal, September 1997).
+ */
+#define mix(a, b, c) \
+do { \
+ a -= b; a -= c; a ^= (c >> 13); \
+ b -= c; b -= a; b ^= (a << 8); \
+ c -= a; c -= b; c ^= (b >> 13); \
+ a -= b; a -= c; a ^= (c >> 12); \
+ b -= c; b -= a; b ^= (a << 16); \
+ c -= a; c -= b; c ^= (b >> 5); \
+ a -= b; a -= c; a ^= (c >> 3); \
+ b -= c; b -= a; b ^= (a << 10); \
+ c -= a; c -= b; c ^= (b >> 15); \
+} while (/*CONSTCOND*/0)
+
+static __inline uint32_t
+veb_rthash(struct veb_softc *sc, const uint8_t *addr)
+{
+ uint32_t a = 0x9e3779b9, b = 0x9e3779b9, c = sc->sc_rthash_key;
+
+ b += addr[5] << 8;
+ b += addr[4];
+ a += addr[3] << 24;
+ a += addr[2] << 16;
+ a += addr[1] << 8;
+ a += addr[0];
+
+ mix(a, b, c);
+
+ return (c & VEB_RTHASH_MASK);
+}
+
+#undef mix
+
+static int
+veb_rtnode_addr_cmp(const uint8_t *a, const uint8_t *b)
+{
+ int i, d;
+
+ for (i = 0, d = 0; i < ETHER_ADDR_LEN && d == 0; i++) {
+ d = ((int)a[i]) - ((int)b[i]);
+ }
+
+ return (d);
+}
+
+static int
+veb_rtupdate(struct veb_softc *sc, const uint8_t *dst,
+ ether_vlanid_t vlan, struct veb_port *vp, uint8_t flags)
+{
+ struct veb_rtnode *vrt;
+ struct veb_port *ovp;
+ int error;
+
+ NET_EPOCH_ASSERT();
+
+ /* Check the source address is valid and not multicast. */
+ if (ETHER_IS_MULTICAST(dst))
+ return (EINVAL);
+ if (dst[0] == 0 && dst[1] == 0 && dst[2] == 0 &&
+ dst[3] == 0 && dst[4] == 0 && dst[5] == 0)
+ return (EINVAL);
+
+ /*
+ * A route for this destination might already exist. If so,
+ * update it, otherwise create a new one.
+ */
+ if ((vrt = veb_rtnode_lookup(sc, dst, vlan)) == NULL) {
+ VEB_RT_LOCK(sc);
+
+ if (vp->vp_dying) {
+ VEB_RT_UNLOCK(sc);
+ return (0);
+ }
+ /*
+ * Check again, now that we have the lock. There could have
+ * been a race and we only want to insert this once.
+ */
+ if (veb_rtnode_lookup(sc, dst, vlan) != NULL) {
+ VEB_RT_UNLOCK(sc);
+ return (0);
+ }
+
+ if (sc->sc_vrtcnt >= sc->sc_vrtmax) {
+ sc->sc_vrtexceeded++;
+ VEB_RT_UNLOCK(sc);
+ return (ENOSPC);
+ }
+ /* Check per interface address limits (if enabled) */
+ if (vp->vp_addrmax && vp->vp_addrcnt >= vp->vp_addrmax) {
+ vp->vp_addrexceeded++;
+ VEB_RT_UNLOCK(sc);
+ return (ENOSPC);
+ }
+
+ /*
+ * Allocate a new bridge forwarding node, and
+ * initialize the expiration time and Ethernet
+ * address.
+ */
+ vrt = uma_zalloc(V_veb_rtnode_zone, M_NOWAIT | M_ZERO);
+ if (vrt == NULL) {
+ VEB_RT_UNLOCK(sc);
+ return (ENOMEM);
+ }
+ vrt->vrt_vnet = curvnet;
+
+ if (vp->vp_flags & VEB_PORT_STICKY)
+ vrt->vrt_flags = VEB_RTNODE_STICKY;
+ else
+ vrt->vrt_flags = VEB_RTNODE_DYNAMIC;
+
+ memcpy(vrt->vrt_addr, dst, ETHER_ADDR_LEN);
+ vrt->vrt_vlan = vlan;
+
+ vrt->vrt_dst = vp;
+ if ((error = veb_rtnode_insert(sc, vrt)) != 0) {
+ uma_zfree(V_veb_rtnode_zone, vrt);
+ VEB_RT_UNLOCK(sc);
+ return (error);
+ }
+ vp->vp_addrcnt++;
+
+ VEB_RT_UNLOCK(sc);
+ }
+
+ if ((vrt->vrt_flags & VEB_RTNODE_TYPEMASK) == VEB_RTNODE_DYNAMIC &&
+ (ovp = vrt->vrt_dst) != vp) {
+ MPASS(ovp != NULL);
+
+ VEB_RT_LOCK(sc);
+ /* Node may have been destroyed or moved since lookup. */
+ if (vp->vp_dying || veb_rtnode_lookup(sc, dst, vlan) != vrt ||
+ (ovp = vrt->vrt_dst) == vp) {
+ VEB_RT_UNLOCK(sc);
+ return (0);
+ }
+ vrt->vrt_dst->vp_addrcnt--;
+ vrt->vrt_dst = vp;
+ vrt->vrt_dst->vp_addrcnt++;
+ VEB_RT_UNLOCK(sc);
+
+ if (V_log_mac_flap &&
+ ppsratecheck(&V_log_last, &V_log_count, V_log_interval)) {
+ log(LOG_NOTICE,
+ "%s: mac address %6D vlan %d moved from %s to %s\n",
+ sc->sc_ifp->if_xname,
+ &vrt->vrt_addr[0], ":",
+ vrt->vrt_vlan,
+ ovp->vp_ifp->if_xname,
+ vp->vp_ifp->if_xname);
+ }
+ }
+
+ if ((flags & VEB_RTNODE_TYPEMASK) == VEB_RTNODE_DYNAMIC)
+ vrt->vrt_expire = time_uptime + sc->sc_vrttimeout;
+
+ return (0);
+}
+
+static int
+veb_rtnode_insert(struct veb_softc *sc, struct veb_rtnode *vrt)
+{
+ struct veb_rtnode *lvrt;
+ uint32_t hash;
+ int dir;
+
+ VEB_RT_LOCK_ASSERT(sc);
+
+ hash = veb_rthash(sc, vrt->vrt_addr);
+
+ lvrt = CK_LIST_FIRST(&sc->sc_rthash[hash]);
+ if (lvrt == NULL) {
+ CK_LIST_INSERT_HEAD(&sc->sc_rthash[hash], vrt, vrt_hash);
+ goto out;
+ }
+
+ do {
+ dir = veb_rtnode_addr_cmp(vrt->vrt_addr, lvrt->vrt_addr);
+ if (dir == 0 && vrt->vrt_vlan == lvrt->vrt_vlan)
+ return (EEXIST);
+ if (dir > 0) {
+ CK_LIST_INSERT_BEFORE(lvrt, vrt, vrt_hash);
+ goto out;
+ }
+ if (CK_LIST_NEXT(lvrt, vrt_hash) == NULL) {
+ CK_LIST_INSERT_AFTER(lvrt, vrt, vrt_hash);
+ goto out;
+ }
+ lvrt = CK_LIST_NEXT(lvrt, vrt_hash);
+ } while (lvrt != NULL);
+
+#ifdef DIAGNOSTIC
+ panic("veb_rtnode_insert: impossible");
+#endif
+
+out:
+ CK_LIST_INSERT_HEAD(&sc->sc_rtlist, vrt, vrt_list);
+ sc->sc_vrtcnt++;
+
+ return (0);
+}
+
+static int
+veb_rtaddstatic(struct veb_softc *sc, const uint8_t *dst,
+ ether_vlanid_t vlan, struct veb_port *vp)
+{
+ struct veb_rtnode *vrt;
+ int error;
+
+ NET_EPOCH_ASSERT();
+
+ VEB_RT_LOCK(sc);
+ if (vp->vp_dying) {
+ VEB_RT_UNLOCK(sc);
+ return (ENOENT);
+ }
+
+ vrt = veb_rtnode_lookup(sc, dst, vlan);
+ if (vrt != NULL) {
+ if (vrt->vrt_dst != vp) {
+ vrt->vrt_dst->vp_addrcnt--;
+ vrt->vrt_dst = vp;
+ vp->vp_addrcnt++;
+ }
+ vrt->vrt_flags = VEB_RTNODE_STATIC;
+ VEB_RT_UNLOCK(sc);
+ return (0);
+ }
+ if (sc->sc_vrtcnt >= sc->sc_vrtmax ||
+ (vp->vp_addrmax && vp->vp_addrcnt >= vp->vp_addrmax)) {
+ VEB_RT_UNLOCK(sc);
+ return (ENOSPC);
+ }
+
+ vrt = uma_zalloc(V_veb_rtnode_zone, M_NOWAIT | M_ZERO);
+ if (vrt == NULL) {
+ VEB_RT_UNLOCK(sc);
+ return (ENOMEM);
+ }
+ vrt->vrt_vnet = curvnet;
+ vrt->vrt_flags = VEB_RTNODE_STATIC;
+ memcpy(vrt->vrt_addr, dst, ETHER_ADDR_LEN);
+ vrt->vrt_vlan = vlan;
+ vrt->vrt_dst = vp;
+
+ if ((error = veb_rtnode_insert(sc, vrt)) != 0) {
+ uma_zfree(V_veb_rtnode_zone, vrt);
+ VEB_RT_UNLOCK(sc);
+ return (error);
+ }
+ vp->vp_addrcnt++;
+
+ VEB_RT_UNLOCK(sc);
+
+ return (error);
+}
+
+static struct veb_port *
+veb_lookup_port(struct veb_softc *sc, const char *name)
+{
+ struct veb_port *vp;
+ struct ifnet *ifp;
+
+ VEB_LOCK_OR_NET_EPOCH_ASSERT(sc);
+
+ CK_LIST_FOREACH(vp, &sc->sc_vebport, vp_next) {
+ ifp = vp->vp_ifp;
+ if (strcmp(ifp->if_xname, name) == 0)
+ return (vp);
+ }
+
+ return (NULL);
+}
+
+static struct mbuf *
+veb_input(struct ifnet *ifp, struct mbuf *m)
+{
+ struct veb_softc *sc;
+ struct veb_port *vp;
+ struct ifnet *vpp;
+ ether_vlanid_t vlan;
+
+ NET_EPOCH_ASSERT();
+
+ /* We need the Ethernet header later, so make sure we have it now. */
+ if (m->m_len < ETHER_HDR_LEN) {
+ m = m_pullup(m, ETHER_HDR_LEN);
+ if (m == NULL) {
+ return (NULL);
+ }
+ }
+
+ vlan = VLANTAGOF(m);
+
+ /*
+ * If this frame has a VLAN tag and the receiving interface has a
+ * vlan(4) trunk, then it is destined for vlan(4), not for us.
+ * This means if vlan(4) and veb(4) are configured on the same
+ * interface, vlan(4) is preferred, which is what users typically
+ * expect.
+ */
+ if (vlan != DOT1Q_VID_NULL && ifp->if_vlantrunk != NULL)
+ return (m);
+
+ vp = ifp->if_bridge;
+
+ /*
+ * If there is no vp, the port has been deleted,
+ * we can carry on as usual
+ */
+ if (__predict_false(vp == NULL))
+ return (m);
+ sc = vp->vp_sc;
+
+ vpp = sc->sc_ifp;
+ if ((vpp->if_drv_flags & IFF_DRV_RUNNING) == 0) {
+ m_freem(m);
+ return (NULL);
+ }
+
+ /*
+ * Implement support for veb monitoring if this
+ * flag has been set on this interface.
+ * Before we discard the packet, increment the
+ * byte and packet counters associated with this interface.
+ */
+ if ((vpp->if_flags & IFF_MONITOR) != 0) {
+ m->m_pkthdr.rcvif = vpp;
+ ETHER_BPF_MTAP(vpp, m);
+ if_inc_counter(vpp, IFCOUNTER_IPACKETS, 1);
+ if_inc_counter(vpp, IFCOUNTER_IBYTES, m->m_pkthdr.len);
+ m_freem(m);
+ return (NULL);
+ }
+
+ /* Perform the bridge forwarding function. */
+ veb_forward(sc, vp, m);
+
+ return (NULL);
+}
+
+/*
+ * Blank veb_output method so if_bridge_output is non-NULL
+ */
+static int
+veb_output(struct ifnet *ifp, struct mbuf *m,
+ struct sockaddr *sa, struct rtentry *rt)
+{
+ m_freem(m);
+ return (EINVAL);
+}
+
+static void
+veb_forward(struct veb_softc *sc, struct veb_port *src_vp, struct mbuf *m)
+{
+ struct veb_port *dst_vp;
+ struct ifnet *src_if, *ifp;
+ struct ether_header *eh;
+ uint8_t *dst;
+ int error;
+ ether_vlanid_t vlan;
+
+ NET_EPOCH_ASSERT();
+
+ vlan = VLANTAGOF(m);
+ if (__predict_false(vlan > DOT1Q_VID_MAX))
+ goto drop;
+
+ src_if = src_vp->vp_ifp;
+ ifp = sc->sc_ifp;
+
+ if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1);
+ if_inc_counter(ifp, IFCOUNTER_IBYTES, m->m_pkthdr.len);
+
+ eh = mtod(m, struct ether_header *);
+ dst = eh->ether_dhost;
+
+ /* If the interface is learning, record the address. */
+ if (src_vp->vp_flags & VEB_PORT_LEARNING) {
+ error = veb_rtupdate(sc, eh->ether_shost,
+ vlan, src_vp, VEB_RTNODE_DYNAMIC);
+ /*
+ * If the interface has addresses limits then deny any source
+ * that is not in the cache.
+ */
+ if (error && src_vp->vp_addrmax)
+ goto drop;
+ }
+
+ /*
+ * If the packet is a multicast, or we don't know a better way to
+ * get there, send to all interfaces.
+ */
+ if (!ETHER_IS_MULTICAST(dst)) {
+ dst_vp = veb_rtlookup(sc, dst, vlan);
+ if (src_vp == dst_vp)
+ goto drop;
+ } else {
+ if ((m->m_flags & (M_BCAST | M_MCAST)) == 0)
+ m->m_flags |= ETHER_IS_BROADCAST(dst)
+ ? M_BCAST : M_MCAST;
+ /*
+ * Check if its a reserved multicast address, any address
+ * listed in 802.1D section 7.12.6 may not be forwarded
+ * by the veb.
+ * This is currently 01-80-C2-00-00-00 to 01-80-C2-00-00-0F
+ */
+ if (dst[0] == 0x01 && dst[1] == 0x80 &&
+ dst[2] == 0xc2 && dst[3] == 0x00 &&
+ dst[4] == 0x00 && dst[5] <= 0x0f)
+ goto drop;
+
+ /* ...flood it to all interfaces. */
+ if_inc_counter(ifp, IFCOUNTER_IMCASTS, 1);
+ dst_vp = NULL;
+ }
+
+
+ /*
+ * Since we don't reinject into ether_input, we tap unconditionally
+ */
+ ETHER_BPF_MTAP(ifp, m);
+
+ if (dst_vp == NULL) {
+ veb_broadcast(sc, src_vp, src_if, m);
+ return;
+ }
+
+ /*
+ * At this point, we're dealing with a unicast frame
+ * going to a different interface.
+ */
+ if ((dst_vp->vp_ifp->if_drv_flags & IFF_DRV_RUNNING) == 0)
+ goto drop;
+
+ if (dst_vp->vp_dying)
+ goto drop;
+
+ /* Private segments can not talk to each other */
+ if (src_vp->vp_flags & dst_vp->vp_flags & VEB_PORT_PRIVATE)
+ goto drop;
+
+ veb_enqueue(sc, dst_vp, m);
+ return;
+
+drop:
+ m_freem(m);
+}
+
+static int
+veb_enqueue(struct veb_softc *sc, struct veb_port *dst_vp,
+ struct mbuf *m)
+{
+ struct ifnet *dst_ifp = dst_vp->vp_ifp;
+ int len, err = 0;
+ short mflags;
+
+ MPASS(m->m_nextpkt == NULL);
+
+ len = m->m_pkthdr.len;
+ mflags = m->m_flags;
+
+ /*
+ * If the port interface doesn't support hardware tagging,
+ * we have to insert our own tag
+ */
+ if ((m->m_flags & M_VLANTAG) &&
+ ((dst_ifp->if_capenable & IFCAP_VLAN_HWTAGGING) == 0)) {
+ m = ether_vlanencap_proto(m,
+ m->m_pkthdr.ether_vtag, ETHERTYPE_VLAN);
+
+ if (m == NULL) {
+ if_printf(dst_ifp,
+ "unable to prepend VLAN header\n");
+ if_inc_counter(dst_ifp, IFCOUNTER_OERRORS, 1);
+
+ return (ENOBUFS);
+ }
+ m->m_flags &= ~M_VLANTAG;
+ }
+
+ /* We shouldn't transmit mbuf without pkthdr */
+ M_ASSERTPKTHDR(m);
+
+ /*
+ * Host delivery goes to if_input(), not if_transmit(): for a
+ * vport if_transmit() is the host *sending*.
+ *
+ * snd_tag and rcvif share a union in struct pkthdr, and
+ * CSUM_SND_TAG says which is live, so the tag must be released
+ * before rcvif is written or we leak the driver's reference.
+ *
+ * Counters go before if_input(), mbuf is the stack's
+ * afterwards. ether_input() bumps IMCASTS and IBYTES but
+ * never IPACKETS.
+ */
+ if (IS_VPORT(dst_vp)) {
+ if (m->m_pkthdr.csum_flags & CSUM_SND_TAG) {
+ m_snd_tag_rele(m->m_pkthdr.snd_tag);
+ m->m_pkthdr.snd_tag = NULL;
+ m->m_pkthdr.csum_flags &= ~CSUM_SND_TAG;
+ }
+ m_tag_delete_nonpersistent(m);
+ m->m_pkthdr.rcvif = dst_ifp;
+ M_SETFIB(m, dst_ifp->if_fib);
+
+ if_inc_counter(sc->sc_ifp, IFCOUNTER_OPACKETS, 1);
+ if_inc_counter(sc->sc_ifp, IFCOUNTER_OBYTES, len);
+
+ if (mflags & M_MCAST)
+ if_inc_counter(sc->sc_ifp,
+ IFCOUNTER_OMCASTS, 1);
+
+ if_inc_counter(dst_ifp, IFCOUNTER_IPACKETS, 1);
+
+ dst_ifp->if_input(dst_ifp, m);
+
+ return (0);
+ }
+
+ err = dst_ifp->if_transmit(dst_ifp, m);
+ if (err != 0) {
+ if_inc_counter(sc->sc_ifp, IFCOUNTER_OERRORS, 1);
+ return (err);
+ }
+
+ if_inc_counter(sc->sc_ifp, IFCOUNTER_OPACKETS, 1);
+ if_inc_counter(sc->sc_ifp, IFCOUNTER_OBYTES, len);
+ if (mflags & M_MCAST)
+ if_inc_counter(sc->sc_ifp, IFCOUNTER_OMCASTS, 1);
+
+ return (0);
+}
+
+static void
+veb_broadcast(struct veb_softc *sc, struct veb_port *src_vp,
+ struct ifnet *exclude_if, struct mbuf *m)
+{
+ struct veb_port *dst_vp;
+ struct mbuf *mc;
+ struct ifnet *dst_if;
+ int used = 0;
+
+ NET_EPOCH_ASSERT();
+
+ CK_LIST_FOREACH(dst_vp, &sc->sc_vebport, vp_next) {
+ dst_if = dst_vp->vp_ifp;
+ if (dst_if == exclude_if)
+ continue;
+
+ if (src_vp->vp_flags & dst_vp->vp_flags & VEB_PORT_PRIVATE)
+ continue;
+
+ if ((dst_vp->vp_flags & VEB_PORT_DISCOVER) == 0 &&
+ (m->m_flags & (M_BCAST | M_MCAST)) == 0)
+ continue;
+
+ if ((dst_if->if_drv_flags & IFF_DRV_RUNNING) == 0)
+ continue;
+
+ if (CK_LIST_NEXT(dst_vp, vp_next) == NULL) {
+ /* Last dst uses original mbuf */
+ mc = m;
+ used = 1;
+ } else {
+ mc = m_dup(m, M_NOWAIT);
+ if (mc == NULL) {
+ if_inc_counter(sc->sc_ifp,
+ IFCOUNTER_OERRORS, 1);
+ continue;
+ }
+ }
+
+ veb_enqueue(sc, dst_vp, mc);
+ }
+ if (used == 0)
+ m_freem(m);
+}
+
+static int
+veb_add_port(struct veb_softc *sc, struct nl_pstate *npt, char *ifname)
+{
+ struct veb_port *vp = NULL;
+ struct vport_softc *vpsc = NULL;
+ struct ifnet *ifs;
+ struct epoch_tracker et;
+ int error = 0;
+
+ ifs = ifunit_ref(ifname);
+ if (ifs == NULL) {
+ nlmsg_report_err_msg(npt, "No such interface");
+ return (ENOENT);
+ }
+
+ if (ifs->if_ioctl == NULL) {
+ if_rele(ifs);
+ nlmsg_report_err_msg(npt, "Interface must support ioctl(2)");
+ return (EINVAL);
+ }
+
+ /*
+ * If the new interface is a vlan(4), it could be a bridge SVI.
+ * Don't allow such things to be added to vebs.
+ */
+ if (ifs->if_type == IFT_L2VLAN) {
+ struct ifnet *parent;
+ bool is_bridge;
+
+ /*
+ * Entering NET_EPOCH with VEB_LOCK held, but this is okay
+ * since we don't sleep here.
+ */
+ NET_EPOCH_ENTER(et);
+ parent = VLAN_TRUNKDEV(ifs);
+ is_bridge = (parent != NULL && parent->if_type == IFT_BRIDGE);
+ NET_EPOCH_EXIT(et);
+
+ if (is_bridge) {
+ if_rele(ifs);
+ nlmsg_report_err_msg(npt,
+ "SVI cannot be added to a veb");
+ return (EINVAL);
+ }
+ }
+
+ /* Walk our own port list rather than dereferencing if_bridge. */
+ CK_LIST_FOREACH(vp, &sc->sc_vebport, vp_next) {
+ if (vp->vp_ifp == ifs) {
+ if_rele(ifs);
+ nlmsg_report_err_msg(npt,
+ "Interface is already a port of this veb");
+ return (EEXIST);
+ }
+ }
+
+ if (ifs->if_bridge != NULL) {
+ if_rele(ifs);
+ nlmsg_report_err_msg(npt,
+ "Interface is already a port of another veb/bridge");
+ return (EBUSY);
+ }
+
+ /* Needed before the veb_port exists, so IS_VPORT() is unavailable. */
+ bool is_vport = (strcmp(ifs->if_dname, vport_name) == 0);
+ if (is_vport) {
+ vpsc = if_getsoftc(ifs);
+ VPORT_LOCK(vpsc);
+ if (vpsc->sc_vp != NULL) {
+ VPORT_UNLOCK(vpsc);
+ if_rele(ifs);
+ nlmsg_report_err_msg(npt,
+ "vport is already a port of an existing veb");
+ return (EBUSY);
+ }
+ VPORT_UNLOCK(vpsc);
+
+ if (sc->sc_vportsc != NULL) {
+ if_rele(ifs);
+ nlmsg_report_err_msg(npt,
+ "Veb cannot have multiple vports");
+ return (EINVAL);
+ }
+ }
+
+ switch (ifs->if_type) {
+ case IFT_ETHER:
+ case IFT_L2VLAN:
+ /* permitted interface types */
+ break;
+ default:
+ if_rele(ifs);
+ nlmsg_report_err_msg(npt, "Unsupported interface type");
+ return (EINVAL);
+ }
+
+#ifdef INET6
+ /*
+ * If the incoming interface has an inet6 address,
+ * remove it by default
+ */
+ if (in6ifa_llaonifp(ifs)) {
+ in6_ifdetach(ifs);
+ if_printf(sc->sc_ifp,
+ "IPv6 addresses on %s have been removed "
+ "before adding as a veb port\n",
+ ifs->if_xname);
+ }
+#endif
+
+ /*
+ * Ports can't have IP's when adding no matter what,
+ * Vport IP can only be configured AFTER adding to a veb.
+ */
+ struct ifaddr *ifa;
+ NET_EPOCH_ENTER(et);
+ CK_STAILQ_FOREACH(ifa, &ifs->if_addrhead, ifa_link) {
+ if (ifa->ifa_addr->sa_family != AF_INET &&
+ ifa->ifa_addr->sa_family != AF_INET6)
+ continue;
+ NET_EPOCH_EXIT(et);
+ if_rele(ifs);
+ nlmsg_report_err_msg(npt,
+ "Ports may not have an IP address assigned");
+ return (EINVAL);
+ }
+ NET_EPOCH_EXIT(et);
+
+ /* Allow the first Ethernet port to define the MTU */
+ if (CK_LIST_EMPTY(&sc->sc_vebport))
+ sc->sc_ifp->if_mtu = ifs->if_mtu;
+ else if (!is_vport && sc->sc_ifp->if_mtu != ifs->if_mtu) {
+ struct ifreq ifr;
+
+ snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s",
+ ifs->if_xname);
+ ifr.ifr_mtu = sc->sc_ifp->if_mtu;
+
+ error = (*ifs->if_ioctl)(ifs,
+ SIOCSIFMTU, (caddr_t)&ifr);
+ if (error != 0) {
+ log(LOG_NOTICE, "%s: invalid MTU: %u for"
+ " new port %s\n", sc->sc_ifp->if_xname,
+ ifr.ifr_mtu,
+ ifs->if_xname);
+ if_rele(ifs);
+ nlmsg_report_err_msg(npt,
+ "Failed to set MTU on new port");
+ return (EINVAL);
+ }
+ }
+
+ vp = malloc(sizeof(*vp), M_VEB, M_WAITOK | M_ZERO);
+
+ vp->vp_sc = sc;
+ vp->vp_ifp = ifs;
+ vp->vp_flags = VEB_PORT_LEARNING | VEB_PORT_DISCOVER;
+
+ /*
+ * If Interface is a vport,
+ * we add the proper flags and initialize our backpointers
+ */
+ if (is_vport) {
+ /*
+ * Check that vport wasn't added anywhere
+ * else since we last checked
+ */
+ vpsc = if_getsoftc(ifs);
+ VPORT_LOCK(vpsc);
+ if (vpsc->sc_vp != NULL) {
+ VPORT_UNLOCK(vpsc);
+ free(vp, M_VEB);
+ if_rele(ifs);
+ nlmsg_report_err_msg(npt,
+ "vport is already a port of an existing veb");
+ return (EBUSY);
+ }
+ vp->vp_flags |= VEB_PORT_VPORT;
+ vpsc->sc_vp = vp;
+ ifs->if_mtu = sc->sc_ifp->if_mtu;
+ VPORT_UNLOCK(vpsc);
+ sc->sc_vportsc = vpsc;
+ }
+ vp->vp_savedcaps = ifs->if_capenable;
+
+ /*
+ * Reuse ifs->if_bridge as the back-pointer to our port. This isn't only
+ * for convenience: ether_output() and ether_input_internal() both
+ * dispatch on if_bridge != NULL, so a port cannot reach the
+ * forwarding path without it. Setting the field commits us to having
+ * both hooks installed.
+ *
+ * vport is excluded as it needs ordinary host semantics
+ * (addressing, lladdr changes) that veb_p_ioctl() denies
+ * to ports.
+ *
+ * One consequence: ifhwioctl()'s "no MTU changes on
+ * bridge members" guard keys off if_bridge, so it does not cover the
+ * vport and vport_ioctl() enforces that itself.
+ */
+ if (!IS_VPORT(vp)) {
+ ifs->if_bridge_output = veb_output;
+ ifs->if_bridge_input = veb_input;
+ ifs->if_bridge_linkstate = veb_linkstate;
+ ifs->if_bridge = vp;
+ }
+
+ CK_LIST_INSERT_HEAD(&sc->sc_vebport, vp, vp_next);
+
+ veb_mutecaps(sc);
+ veb_linkcheck(sc);
+
+ switch (ifs->if_type) {
+ case IFT_ETHER:
+ case IFT_L2VLAN:
+ if (!IS_VPORT(vp))
+ error = ifpromisc(ifs, 1);
+ break;
+ }
+
+ /*
+ * Replace interface's ioctl method
+ * with our own to stray away from host
+ */
+ vp->vp_ioctl = ifs->if_ioctl;
+ if (!IS_VPORT(vp))
+ ifs->if_ioctl = veb_p_ioctl;
+
+ if (error)
+ veb_delete_port(sc, vp, 0);
+
+ if_rele(ifs);
+
+ return (error);
+}
+
+static void
+veb_linkstate(struct ifnet *ifp)
+{
+ struct veb_softc *sc = NULL;
+ struct veb_port *vp;
+ struct epoch_tracker et;
+
+ NET_EPOCH_ENTER(et);
+
+ vp = veb_port_of(ifp);
+
+ if (vp)
+ sc = vp->vp_sc;
+
+ if (sc != NULL)
+ veb_linkcheck(sc);
+
+ NET_EPOCH_EXIT(et);
+}
+
+static void
+veb_linkcheck(struct veb_softc *sc)
+{
+ struct veb_port *vp;
+ int new_link, hasls;
+
+ VEB_LOCK_OR_NET_EPOCH_ASSERT(sc);
+
+ new_link = LINK_STATE_DOWN;
+ hasls = 0;
+ /* Our link is considered up if at least one of our ports is active */
+ CK_LIST_FOREACH(vp, &sc->sc_vebport, vp_next) {
+ /* vport does not contribute to linkstate */
+ if (IS_VPORT(vp))
+ continue;
+ if (vp->vp_ifp->if_capabilities & IFCAP_LINKSTATE)
+ hasls++;
+ if (vp->vp_ifp->if_link_state == LINK_STATE_UP) {
+ new_link = LINK_STATE_UP;
+ break;
+ }
+ }
+ if (!CK_LIST_EMPTY(&sc->sc_vebport) && !hasls) {
+ /* If no interfaces support link-state then we default to up */
+ new_link = LINK_STATE_UP;
+ }
+ if_link_state_change(sc->sc_ifp, new_link);
+}
+
+static int
+veb_clone_create_nl(struct if_clone *ifc, char *name, size_t len,
+ struct ifc_data_nl *ifd)
+{
+ struct ifc_data ifd_new = {
+ .flags = IFC_F_SYSSPACE,
+ .unit = ifd->unit,
+ };
+
+ return (veb_clone_create(ifc, name, len, &ifd_new, &ifd->ifp));
+}
+
+static int
+veb_clone_modify_nl(struct ifnet *ifp, struct ifc_data_nl *ifd)
+{
+ struct veb_softc *sc = ifp->if_softc;
+ struct nl_parsed_link *lattrs = ifd->lattrs;
+ struct nl_pstate *npt = ifd->npt;
+ struct nl_parsed_veb params;
+ struct nlattr *attrs = lattrs->ifla_idata;
+ struct nlattr_bmask bm;
+ int error = 0;
+
+ if ((attrs == NULL) ||
+ (nl_has_attr(ifd->bm, IFLA_LINKINFO) == 0)) {
+ error = nl_modify_ifp_generic(ifp, lattrs, ifd->bm, npt);
+ return (error);
+ }
+
+ error = priv_check(curthread, PRIV_NET_BRIDGE);
+ if (error)
+ return (error);
+
+ /* make sure ignored attributes by nl_parse will not cause panics */
+ memset(¶ms, 0, sizeof(params));
+
+ nl_get_attrs_bmask_raw(NLA_DATA(attrs), NLA_DATA_LEN(attrs), &bm);
+ error = nl_parse_nested(attrs, &veb_parser, npt, ¶ms);
+
+ VEB_LOCK(sc);
+
+ if (sc->sc_dying) {
+ VEB_UNLOCK(sc);
+ return (ENXIO);
+ }
+
+ if (error == 0 && nl_has_attr(&bm, IFLA_VEB_RTABLE_TIMEOUT))
+ error = veb_settimeout_nl(sc, npt, params.ifla_timeout);
+
+ if (error == 0 && nl_has_attr(&bm, IFLA_VEB_RTABLE_MAXADDR))
+ error = veb_setmaxaddr_nl(sc, npt, params.ifla_maxaddr);
+
+ if (error == 0 && nl_has_attr(&bm, IFLA_VEB_RTABLE_FLUSH))
+ error = veb_rtable_flush_nl(sc, npt, params.ifla_flushall);
+
+ if (error == 0 && nl_has_attr(&bm, IFLA_VEB_ADDPORT))
+ error = veb_addport_nl(sc, npt, params.ifla_addport_ifname);
+
+ if (error == 0 && nl_has_attr(&bm, IFLA_VEB_DELPORT))
+ error = veb_delport_nl(sc, npt, params.ifla_delport_ifname);
+
+ if (error == 0 && nl_has_attr(&bm, IFLA_VEB_SETPORT))
+ error = veb_setport_nl(sc, npt, params.ifla_setport);
+
+ VEB_UNLOCK(sc);
+
+ return (error);
+}
+
+static void
+veb_clone_dump_nl(struct ifnet *ifp, struct nl_writer *nw)
+{
+ struct veb_softc *sc = if_getsoftc(ifp);
+ struct veb_port *vp;
+ int off, off2, poff;
+
+
+ off = nlattr_add_nested(nw, IFLA_LINKINFO);
+ if (off == 0)
+ return;
+
+ nlattr_add_string(nw, IFLA_INFO_KIND, "veb");
+ off2 = nlattr_add_nested(nw, IFLA_INFO_DATA);
+ if (off2 == 0) {
+ nlattr_set_len(nw, off);
+ return;
+ }
+
+ VEB_LOCK(sc);
+
+ nlattr_add_u32(nw, IFLA_VEB_RTABLE_TIMEOUT, sc->sc_vrttimeout);
+ nlattr_add_u32(nw, IFLA_VEB_RTABLE_MAXADDR, sc->sc_vrtmax);
+ nlattr_add_u32(nw, IFLA_VEB_RTABLE_EXCEEDED, sc->sc_vrtexceeded);
+
+ CK_LIST_FOREACH(vp, &sc->sc_vebport, vp_next) {
+ poff = nlattr_add_nested(nw, IFLA_VEB_PORT);
+ if (poff == 0)
+ break;
+ nlattr_add_string(nw, IFLA_VEB_PORT_IFNAME,
+ vp->vp_ifp->if_xname);
+ nlattr_add_bool(nw, IFLA_VEB_PORT_LEARNING,
+ vp->vp_flags & VEB_PORT_LEARNING);
+ nlattr_add_bool(nw, IFLA_VEB_PORT_DISCOVER,
+ vp->vp_flags & VEB_PORT_DISCOVER);
+ nlattr_add_bool(nw, IFLA_VEB_PORT_STICKY,
+ vp->vp_flags & VEB_PORT_STICKY);
+ nlattr_add_bool(nw, IFLA_VEB_PORT_PRIVATE,
+ vp->vp_flags & VEB_PORT_PRIVATE);
+ nlattr_add_bool(nw, IFLA_VEB_PORT_VPORT,
+ IS_VPORT(vp));
+ nlattr_add_u32(nw, IFLA_VEB_PORT_MAXADDR,
+ vp->vp_addrmax);
+ nlattr_set_len(nw, poff);
+ }
+
+ nlattr_set_len(nw, off2);
+ nlattr_set_len(nw, off);
+
+ VEB_UNLOCK(sc);
+}
+
+static int
+veb_addport_nl(struct veb_softc *sc, struct nl_pstate *npt, char *ifname)
+{
+ int error = 0;
+
+ VEB_ASSERT(sc);
+
+ error = veb_add_port(sc, npt, ifname);
+
+ return (error);
+}
+
+static int
+veb_delport_nl(struct veb_softc *sc, struct nl_pstate *npt, char *ifname)
+{
+ struct veb_port *vp;
+
+ VEB_ASSERT(sc);
+
+ vp = veb_lookup_port(sc, ifname);
+ if (vp == NULL) {
+ nlmsg_report_err_msg(npt, "Interface is not a veb port");
+ return (ENOENT);
+ }
+
+ veb_delete_port(sc, vp, 0);
+
+ return (0);
+}
+
+static inline uint32_t
+veb_port_apply_flag(uint32_t flags, uint32_t flag, bool on)
+{
+ return (on ? (flags | flag) : (flags & ~flag));
+}
+
+static int
+veb_setport_nl(struct veb_softc *sc, struct nl_pstate *npt, struct nlattr *nla)
+{
+ struct nl_parsed_veb_port params;
+ struct nlattr_bmask bm;
+ struct veb_port *vp;
+ uint32_t flags;
+ int error;
+
+ VEB_ASSERT(sc);
+
+ memset(¶ms, 0, sizeof(params));
+ nl_get_attrs_bmask_raw(NLA_DATA(nla), NLA_DATA_LEN(nla), &bm);
+ error = nl_parse_nested(nla, &veb_port_parser, npt, ¶ms);
+ if (error != 0)
+ return (error);
+
+ if (params.ifla_ifname == NULL) {
+ nlmsg_report_err_msg(npt, "Missing port interface name");
+ return (EINVAL);
+ }
+ if (nl_has_attr(&bm, IFLA_VEB_PORT_VPORT)) {
+ nlmsg_report_err_msg(npt, "vport flag is read-only");
+ return (EINVAL);
+ }
+
+ vp = veb_lookup_port(sc, params.ifla_ifname);
+ if (vp == NULL) {
+ nlmsg_report_err_msg(npt, "Interface is not a veb port");
+ return (ENOENT);
+ }
+
+ if (nl_has_attr(&bm, IFLA_VEB_PORT_MAXADDR)) {
+ vp->vp_addrmax = params.ifla_maxaddr;
+ }
+
+ flags = vp->vp_flags;
+ if (nl_has_attr(&bm, IFLA_VEB_PORT_LEARNING))
+ flags = veb_port_apply_flag(flags, VEB_PORT_LEARNING,
+ params.ifla_learning);
+ if (nl_has_attr(&bm, IFLA_VEB_PORT_DISCOVER))
+ flags = veb_port_apply_flag(flags, VEB_PORT_DISCOVER,
+ params.ifla_discover);
+ if (nl_has_attr(&bm, IFLA_VEB_PORT_STICKY))
+ flags = veb_port_apply_flag(flags, VEB_PORT_STICKY,
+ params.ifla_sticky);
+ if (nl_has_attr(&bm, IFLA_VEB_PORT_PRIVATE))
+ flags = veb_port_apply_flag(flags, VEB_PORT_PRIVATE,
+ params.ifla_private);
+ vp->vp_flags = flags;
+
+ return (0);
+}
+
+static int
+veb_rtable_flush_nl(struct veb_softc *sc, struct nl_pstate *npt, bool flushall)
+{
+ VEB_ASSERT(sc);
+
+ VEB_RT_LOCK(sc);
+ veb_rtflush(sc, flushall ? VEB_RTABLE_FLUSHALL : VEB_RTABLE_FLUSHDYN);
+ VEB_RT_UNLOCK(sc);
+
+ return (0);
+}
+
+static int
+veb_settimeout_nl(struct veb_softc *sc, struct nl_pstate *npt, uint32_t timeout)
+{
+ VEB_ASSERT(sc);
+
+ sc->sc_vrttimeout = timeout;
+
+ return (0);
+}
+
+static bool
+veb_rtable_entry_nl(struct nl_writer *nw, struct nlmsghdr *hdr,
+ const struct veb_rtable_snap *vrts)
+{
+ struct genlmsghdr *g;
+
+ if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr)))
+ return (false);
+
+ g = nlmsg_reserve_object(nw, struct genlmsghdr);
+ g->cmd = VEB_NL_CMD_GETRTABLE;
+ g->version = VEB_NL_FAMILY_VERSION;
+ g->reserved = 0;
+ if (!nlattr_add_string(nw, VEB_NL_RTABLE_PORT, vrts->port) ||
+ !nlattr_add(nw, VEB_NL_RTABLE_MAC, ETHER_ADDR_LEN, vrts->mac) ||
+ !nlattr_add_u16(nw, VEB_NL_RTABLE_VLAN, vrts->vlan) ||
+ !nlattr_add_u8(nw, VEB_NL_RTABLE_TYPE, vrts->type) ||
+ !nlattr_add_u32(nw, VEB_NL_RTABLE_EXPIRE, vrts->expire))
+ goto fail;
+
+ if (nlmsg_end(nw))
+ return (true);
+fail:
+ nlmsg_abort(nw);
+ return (false);
+}
+
+static int
+veb_getrtable_nl(struct nlmsghdr *hdr, struct nl_pstate *npt)
+{
+ struct nl_parsed_veb_rtable attrs = {};
+ struct veb_rtable_snap *snap = NULL;
+ struct veb_rtnode *vrt;
+ struct veb_softc *sc;
+ struct ifnet *ifp;
+ struct epoch_tracker et;
+ size_t vrtcnt, i = 0;
+ int error;
+
+ error = nl_parse_nlmsg(hdr, &veb_rtable_parser, npt, &attrs);
+ if (error != 0)
+ return (error);
+ if (attrs.ifname == NULL) {
+ nlmsg_report_err_msg(npt, "Missing veb interface name");
+ return (EINVAL);
+ }
+
+ ifp = ifunit_ref(attrs.ifname);
+ if (ifp == NULL) {
+ nlmsg_report_err_msg(npt, "No such interface");
+ return (ENOENT);
+ }
+
+ /*
+ * We go in NET_EPOCH here so veb_softc
+ * can't be deleted mid-snapshot.
+ */
+ NET_EPOCH_ENTER(et);
+
+ if (ifp->if_ioctl != veb_ioctl) {
+ NET_EPOCH_EXIT(et);
+ if_rele(ifp);
+ nlmsg_report_err_msg(npt, "Interface is not a veb");
+ return (EINVAL);
+ }
+ sc = ifp->if_softc;
+
+ /*
+ * We also acquire VEB_RT_LOCK to ensure our snapshot
+ * doesn't record anything mid-change.
+ */
+ VEB_RT_LOCK(sc);
+
+ vrtcnt = sc->sc_vrtcnt;
+
+ if (vrtcnt != 0) {
+ snap = mallocarray(vrtcnt,
+ sizeof(*snap), M_TEMP, M_NOWAIT | M_ZERO);
+ if (snap == NULL) {
+ VEB_RT_UNLOCK(sc);
+ NET_EPOCH_EXIT(et);
+ nlmsg_report_err_msg(npt,
+ "Unable to allocate rtable snapshot");
+ error = ENOMEM;
+ goto done;
+ }
+ CK_LIST_FOREACH(vrt, &sc->sc_rtlist, vrt_list) {
+ KASSERT(i < vrtcnt,
+ ("%s: rtable grew under lock", __func__));
+ strlcpy(snap[i].port, vrt->vrt_ifp->if_xname,
+ sizeof(snap[i].port));
+ memcpy(snap[i].mac, vrt->vrt_addr, ETHER_ADDR_LEN);
+
+ snap[i].vlan = vrt->vrt_vlan;
+
+ switch (vrt->vrt_flags & VEB_RTNODE_TYPEMASK) {
+ case VEB_RTNODE_STATIC:
+ snap[i].type = VEB_NL_RTABLE_TYPE_STATIC;
+ break;
+ case VEB_RTNODE_STICKY:
+ snap[i].type = VEB_NL_RTABLE_TYPE_STICKY;
+ break;
+ default:
+ snap[i].type = VEB_NL_RTABLE_TYPE_DYNAMIC;
+ break;
+ }
+
+ if ((vrt->vrt_flags & VEB_RTNODE_TYPEMASK)
+ == VEB_RTNODE_DYNAMIC &&
+ (time_uptime < vrt->vrt_expire))
+ snap[i].expire = vrt->vrt_expire - time_uptime;
+ else
+ snap[i].expire = 0;
+
+ i++;
+ }
+ }
+
+ VEB_RT_UNLOCK(sc);
+
+ NET_EPOCH_EXIT(et);
+
+ /*
+ * We got our desired information, released the lock,
+ * and left NET_EPOCH.
+ *
+ * We can now send the snapshot's information
+ */
+ hdr->nlmsg_flags |= NLM_F_MULTI;
+ for (size_t j = 0; j < i; j++) {
+ if (!veb_rtable_entry_nl(npt->nw, hdr, &snap[j])) {
+ error = ENOMEM;
+ break;
+ }
+ }
+ free(snap, M_TEMP);
+
+done:
+ if_rele(ifp);
+ if (!nlmsg_end_dump(npt->nw, error, hdr))
+ return (ENOMEM);
+ return (error);
+}
+
+static int
+veb_setmaxaddr_nl(struct veb_softc *sc, struct nl_pstate *npt, uint32_t maxaddr)
+{
+ struct epoch_tracker et;
+
+ VEB_ASSERT(sc);
+ NET_EPOCH_ENTER(et);
+ VEB_RT_LOCK(sc);
+
+ sc->sc_vrtmax = maxaddr;
+ veb_rttrim(sc);
+
+ VEB_RT_UNLOCK(sc);
+ NET_EPOCH_EXIT(et);
+
+ return (0);
+}
+
+static int
+veb_addstatic_nl(struct nlmsghdr *hdr, struct nl_pstate *npt)
+{
+ struct nl_parsed_veb_rtable attrs = {};
+ struct veb_softc *sc;
+ struct veb_port *vp;
+ struct ifnet *ifp;
+ struct epoch_tracker et;
+ int error;
+
+ error = nl_parse_nlmsg(hdr, &veb_rtable_parser, npt, &attrs);
+ if (error != 0)
+ return (error);
+ if (attrs.ifname == NULL || attrs.port == NULL) {
+ nlmsg_report_err_msg(npt, "Missing veb or port interface name");
+ return (EINVAL);
+ }
+ if (ETHER_IS_ZERO(attrs.mac)) {
+ nlmsg_report_err_msg(npt, "Static address cannot be zero");
+ return (EINVAL);
+ }
+ if (ETHER_IS_MULTICAST(attrs.mac)) {
+ nlmsg_report_err_msg(npt, "Static address must be unicast");
+ return (EINVAL);
+ }
+ if (attrs.vlan > DOT1Q_VID_MAX) {
+ nlmsg_report_err_msg(npt, "Invalid VLAN ID");
+ return (EINVAL);
+ }
+
+ ifp = ifunit_ref(attrs.ifname);
+ if (ifp == NULL) {
+ nlmsg_report_err_msg(npt, "No such interface");
+ return (ENOENT);
+ }
+ NET_EPOCH_ENTER(et);
+ if (ifp->if_ioctl != veb_ioctl) {
+ NET_EPOCH_EXIT(et);
+ if_rele(ifp);
+ nlmsg_report_err_msg(npt, "Interface is not a veb");
+ return (EINVAL);
+ }
+
+ sc = ifp->if_softc;
+ vp = veb_lookup_port(sc, attrs.port);
+
+ error = (vp == NULL) ? ENOENT :
+ veb_rtaddstatic(sc, attrs.mac, attrs.vlan, vp);
+
+ NET_EPOCH_EXIT(et);
+
+ if_rele(ifp);
+
+ if (error == ENOENT)
+ nlmsg_report_err_msg(npt, "Interface is not a veb port");
+ else if (error == ENOSPC)
+ nlmsg_report_err_msg(npt, "Address table is full");
+
+ return (error);
+}
+
+static int
+veb_deladdr_nl(struct nlmsghdr *hdr, struct nl_pstate *npt)
+{
+ struct nl_parsed_veb_rtable attrs = {};
+ struct epoch_tracker et;
+ struct veb_rtnode *vrt;
+ struct veb_softc *sc;
+ struct ifnet *ifp;
+ int error;
+
+ error = nl_parse_nlmsg(hdr, &veb_rtable_parser, npt, &attrs);
+ if (error != 0)
+ return (error);
+ if (attrs.ifname == NULL) {
+ nlmsg_report_err_msg(npt, "Missing veb interface name");
+ return (EINVAL);
+ }
+ if (attrs.vlan > DOT1Q_VID_MAX) {
+ nlmsg_report_err_msg(npt, "Invalid VLAN ID");
+ return (EINVAL);
+ }
+
+ ifp = ifunit_ref(attrs.ifname);
+ if (ifp == NULL) {
+ nlmsg_report_err_msg(npt, "No such interface");
+ return (ENOENT);
+ }
+
+ NET_EPOCH_ENTER(et);
+ if (ifp->if_ioctl != veb_ioctl) {
+ NET_EPOCH_EXIT(et);
+ if_rele(ifp);
+ nlmsg_report_err_msg(npt, "Interface is not a veb");
+ return (EINVAL);
+ }
+ sc = ifp->if_softc;
+
+ VEB_RT_LOCK(sc);
+ vrt = veb_rtnode_lookup(sc, attrs.mac, attrs.vlan);
+ if (vrt != NULL)
+ veb_rtnode_destroy(sc, vrt);
+ VEB_RT_UNLOCK(sc);
+
+ NET_EPOCH_EXIT(et);
+
+ if_rele(ifp);
+ if (vrt == NULL) {
+ nlmsg_report_err_msg(npt, "No such address");
+ return (ENOENT);
+ }
+ return (0);
+}
diff --git a/sys/net/if_veb_nl.h b/sys/net/if_veb_nl.h
new file mode 100644
--- /dev/null
+++ b/sys/net/if_veb_nl.h
@@ -0,0 +1,62 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Aaron Espinoza <acesp25@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _NET_IF_VEB_NL_H_
+#define _NET_IF_VEB_NL_H_
+
+#define VEB_NL_FAMILY_NAME "veb"
+#define VEB_NL_FAMILY_VERSION 1
+
+enum veb_nl_rtable_type {
+ VEB_NL_RTABLE_TYPE_DYNAMIC = 0,
+ VEB_NL_RTABLE_TYPE_STATIC = 1,
+ VEB_NL_RTABLE_TYPE_STICKY = 2,
+};
+
+enum veb_nl_cmd {
+ VEB_NL_CMD_UNSPEC,
+ VEB_NL_CMD_GETRTABLE,
+ VEB_NL_CMD_ADDSTATIC,
+ VEB_NL_CMD_DELADDR,
+ __VEB_NL_CMD_MAX,
+};
+#define VEB_NL_CMD_MAX (__VEB_NL_CMD_MAX - 1)
+
+enum veb_nl_rtable_attr {
+ VEB_NL_RTABLE_UNSPEC,
+ VEB_NL_RTABLE_MAC, /* binary, ETHER_ADDR_LEN */
+ VEB_NL_RTABLE_VLAN, /* u16 */
+ VEB_NL_RTABLE_IFNAME, /* string, request-only */
+ VEB_NL_RTABLE_PORT, /* string */
+ VEB_NL_RTABLE_TYPE, /* u8 */
+ VEB_NL_RTABLE_EXPIRE, /* u32, reply-only */
+ __VEB_NL_RTABLE_MAX,
+};
+#define VEB_NL_RTABLE_MAX (__VEB_NL_RTABLE_MAX - 1)
+
+#endif /* !_NET_IF_VEB_NL_H_ */
diff --git a/sys/netlink/route/interface.h b/sys/netlink/route/interface.h
--- a/sys/netlink/route/interface.h
+++ b/sys/netlink/route/interface.h
@@ -422,4 +422,33 @@
uint16_t high;
};
+/* IFLA_INFO_DATA veb attributes */
+enum {
+ IFLA_VEB_UNSPEC,
+ IFLA_VEB_PORT, /* nested, dump-only */
+ IFLA_VEB_ADDPORT, /* string, request-only */
+ IFLA_VEB_DELPORT, /* string, request-only */
+ IFLA_VEB_SETPORT, /* nested, request-only */
+ IFLA_VEB_RTABLE_FLUSH, /* bool (flush all), request-only */
+ IFLA_VEB_RTABLE_TIMEOUT, /* u32 */
+ IFLA_VEB_RTABLE_MAXADDR, /* u32 */
+ IFLA_VEB_RTABLE_EXCEEDED, /* u32, dump-only */
+ __IFLA_VEB_MAX,
+};
+#define IFLA_VEB_MAX (__IFLA_VEB_MAX - 1)
+
+/* IFLA_VEB_PORT attributes */
+enum {
+ IFLA_VEB_PORT_UNSPEC,
+ IFLA_VEB_PORT_IFNAME, /* string */
+ IFLA_VEB_PORT_MAXADDR, /* u32 */
+ IFLA_VEB_PORT_LEARNING, /* bool */
+ IFLA_VEB_PORT_DISCOVER, /* bool */
+ IFLA_VEB_PORT_STICKY, /* bool */
+ IFLA_VEB_PORT_PRIVATE, /* bool */
+ IFLA_VEB_PORT_VPORT, /* bool, dump-only */
+ __IFLA_VEB_PORT_MAX,
+};
+#define IFLA_VEB_PORT_MAX (__IFLA_VEB_PORT_MAX - 1)
+
#endif
diff --git a/sys/sys/exterr_cat.h b/sys/sys/exterr_cat.h
--- a/sys/sys/exterr_cat.h
+++ b/sys/sys/exterr_cat.h
@@ -58,5 +58,6 @@
#define EXTERR_CAT_HWPMC_MOD 21
#define EXTERR_CAT_TTY 22
#define EXTERR_CAT_GEOMZONED 23
+#define EXTERR_CAT_VEB 24
#endif
diff --git a/tests/sys/net/Makefile b/tests/sys/net/Makefile
--- a/tests/sys/net/Makefile
+++ b/tests/sys/net/Makefile
@@ -15,6 +15,7 @@
ATF_TESTS_SH+= if_vlan
ATF_TESTS_SH+= if_wg
ATF_TESTS_SH+= if_geneve
+ATF_TESTS_SH+= if_veb
TEST_METADATA+= execenv="jail"
TEST_METADATA+= execenv_jail_params="vnet allow.raw_sockets"
diff --git a/tests/sys/net/if_veb.sh b/tests/sys/net/if_veb.sh
new file mode 100644
--- /dev/null
+++ b/tests/sys/net/if_veb.sh
@@ -0,0 +1,793 @@
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2026 Aaron Espinoza <acesp25@FreeBSD.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+. $(atf_get_srcdir)/../common/vnet.subr
+
+atf_test_case "create_destroy" "cleanup"
+create_destroy_head()
+{
+ atf_set descr 'Create and destroy a veb(4) and check its default state'
+ atf_set require.user root
+ atf_set require.kmods if_veb
+}
+
+create_destroy_body()
+{
+ vnet_init
+ vnet_mkjail vebtest
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 create
+ atf_check -s exit:0 -o match:"timeout: 1200" ifconfig -j vebtest veb0
+ atf_check -s exit:0 -o not-match:"port:" ifconfig -j vebtest veb0
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 destroy
+ atf_check -s not-exit:0 -e ignore ifconfig -j vebtest veb0
+}
+
+create_destroy_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "port_add_del" "cleanup"
+port_add_del_head()
+{
+ atf_set descr 'Verify vebportadd and vebportdel alongside their respective errors'
+ atf_set require.user root
+ atf_set require.kmods if_veb
+}
+
+port_add_del_body()
+{
+ local epair
+
+ vnet_init
+ epair=$(vnet_mkepair)
+ vnet_mkjail vebtest ${epair}a ${epair}b
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 create up
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb1 create
+
+ atf_check -s exit:1 -e ignore \
+ ifconfig -j vebtest veb0 vebportadd nonexistent0
+ atf_check -s exit:1 -e ignore \
+ ifconfig -j vebtest veb0 vebportdel ${epair}a
+
+ ifconfig -j vebtest ${epair}a inet 192.0.2.5/24
+ atf_check -s exit:1 -e ignore \
+ ifconfig -j vebtest veb0 vebportadd ${epair}a
+
+ ifconfig -j vebtest ${epair}a inet 192.0.2.5 -alias
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebportadd ${epair}a
+ # Check that we are still UP after adding a port
+ atf_check -s exit:0 -o match:"flags=[0-9a-f]+<UP," ifconfig -j vebtest veb0
+ atf_check -s exit:0 -o match:"port: ${epair}a flags=<LEARNING,DISCOVER>" \
+ ifconfig -j vebtest veb0
+
+ atf_check -s exit:1 -e ignore \
+ ifconfig -j vebtest veb0 vebportadd ${epair}a
+
+ atf_check -s exit:1 -e ignore \
+ ifconfig -j vebtest veb1 vebportadd ${epair}a
+
+ atf_check -s exit:0 -o match:"port: ${epair}a " \
+ ifconfig -j vebtest veb0
+ atf_check -s exit:0 -o not-match:"port:" ifconfig -j vebtest veb1
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebportdel ${epair}a
+ atf_check -s exit:0 -o not-match:"port:" ifconfig -j vebtest veb0
+}
+
+port_add_del_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "delete_with_ports" "cleanup"
+delete_with_ports_head()
+{
+ atf_set descr 'Destroy a veb(4) that still has ports and reuse them'
+ atf_set require.user root
+ atf_set require.kmods if_veb
+}
+
+delete_with_ports_body()
+{
+ local epair1 epair2
+
+ vnet_init
+ epair1=$(vnet_mkepair)
+ epair2=$(vnet_mkepair)
+ vnet_mkjail vebtest ${epair1}a ${epair2}a
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 create
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebportadd ${epair1}a
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebportadd ${epair2}a
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 destroy
+
+ # ports must be fully released; readding with no issues
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb1 create
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb1 vebportadd ${epair1}a
+ atf_check -s exit:0 -o match:"port: ${epair1}a " \
+ ifconfig -j vebtest veb1
+}
+
+delete_with_ports_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "port_departure" "cleanup"
+port_departure_head()
+{
+ atf_set descr 'Destroy a veb(4) port and check it leaves the veb'
+ atf_set require.user root
+ atf_set require.kmods if_veb
+}
+
+port_departure_body()
+{
+ local epair1 epair2
+
+ vnet_init
+ epair1=$(vnet_mkepair)
+ epair2=$(vnet_mkepair)
+ vnet_mkjail vebtest ${epair1}a ${epair2}a
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 create
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebportadd ${epair1}a
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest ${epair1}a destroy
+ atf_check -s exit:0 -o not-match:"port:" ifconfig -j vebtest veb0
+
+ # veb must remain usable afterwards.
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebportadd ${epair2}a
+}
+
+port_departure_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "flags" "cleanup"
+flags_head()
+{
+ atf_set descr 'Set and clear veb(4) per-port flags'
+ atf_set require.user root
+ atf_set require.kmods if_veb
+}
+
+flags_body()
+{
+ local epair
+
+ vnet_init
+ epair=$(vnet_mkepair)
+ vnet_mkjail vebtest ${epair}a ${epair}b
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 create
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebportadd ${epair}a
+ atf_check -s exit:0 -o match:"port: ${epair}a flags=<LEARNING,DISCOVER>" \
+ ifconfig -j vebtest veb0
+
+ atf_check -s exit:0 -o ignore \
+ ifconfig -j vebtest veb0 vebsticky ${epair}a -vebdiscover ${epair}a
+ atf_check -s exit:0 -o match:"port: ${epair}a flags=<LEARNING,STICKY>" \
+ ifconfig -j vebtest veb0
+ atf_check -s exit:0 -o ignore \
+ ifconfig -j vebtest veb0 vebprivate ${epair}a -veblearn ${epair}a
+ atf_check -s exit:0 -o match:"port: ${epair}a flags=<STICKY,PRIVATE>" \
+ ifconfig -j vebtest veb0
+ atf_check -s exit:0 -o ignore \
+ ifconfig -j vebtest veb0 -vebsticky ${epair}a -vebprivate ${epair}a
+ atf_check -s exit:0 -o match:"port: ${epair}a flags=<>" \
+ ifconfig -j vebtest veb0
+ atf_check -s exit:0 -o ignore \
+ ifconfig -j vebtest veb0 veblearn ${epair}a vebdiscover ${epair}a
+ atf_check -s exit:0 -o match:"port: ${epair}a flags=<LEARNING,DISCOVER>" \
+ ifconfig -j vebtest veb0
+
+ atf_check -s exit:1 -e ignore \
+ ifconfig -j vebtest veb0 veblearn ${epair}b
+ atf_check -s exit:1 -e ignore \
+ ifconfig -j vebtest veb0 veblearn nonexistent0
+}
+
+flags_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "learning" "cleanup"
+learning_head()
+{
+ atf_set descr 'Verify that veb(4) learns source addresses on the receiving port'
+ atf_set require.user root
+ atf_set require.kmods if_veb
+}
+
+learning_body()
+{
+ local epair1 epair2 ip1 ip2 mac1 mac2
+
+ ip1=192.0.2.1
+ ip2=192.0.2.2
+
+ vnet_init
+ epair1=$(vnet_mkepair)
+ epair2=$(vnet_mkepair)
+ vnet_mkjail vebtest ${epair1}a ${epair2}a
+ vnet_mkjail vebtest1 ${epair1}b
+ vnet_mkjail vebtest2 ${epair2}b
+
+ ifconfig -j vebtest1 ${epair1}b inet ${ip1}/24 up
+ ifconfig -j vebtest2 ${epair2}b inet ${ip2}/24 up
+ ifconfig -j vebtest ${epair1}a up
+ ifconfig -j vebtest ${epair2}a up
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 create up
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebportadd ${epair1}a
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebportadd ${epair2}a
+
+ atf_check -s exit:0 -o ignore jexec vebtest1 ping -nc 1 -t 1 ${ip2}
+
+ mac1=$(ifconfig -j vebtest1 ${epair1}b | awk '/ether/ { print $2 }')
+ mac2=$(ifconfig -j vebtest2 ${epair2}b | awk '/ether/ { print $2 }')
+ atf_check -s exit:0 -o match:"${mac1} Vlan0 ${epair1}a [0-9]+ dynamic" \
+ ifconfig -j vebtest veb0 vebaddr
+ atf_check -s exit:0 -o match:"${mac2} Vlan0 ${epair2}a [0-9]+ dynamic" \
+ ifconfig -j vebtest veb0 vebaddr
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebflush
+ atf_check -s exit:0 -o not-match:"${mac1}" ifconfig -j vebtest veb0 vebaddr
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebsticky ${epair1}a
+ atf_check -s exit:0 -o ignore jexec vebtest1 ping -nc 1 -t 1 ${ip2}
+ atf_check -s exit:0 -o match:"${mac1} Vlan0 ${epair1}a 0 sticky" \
+ ifconfig -j vebtest veb0 vebaddr
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebflush
+ atf_check -s exit:0 -o match:"${mac1} Vlan0 ${epair1}a 0 sticky" \
+ ifconfig -j vebtest veb0 vebaddr
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebflushall
+ atf_check -s exit:0 -o not-match:"${mac1}" ifconfig -j vebtest veb0 vebaddr
+}
+
+learning_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "mtu" "cleanup"
+mtu_head()
+{
+ atf_set descr 'Verify that the first veb(4) port sets the MTU and later ports follow it'
+ atf_set require.user root
+ atf_set require.kmods if_veb
+}
+
+mtu_body()
+{
+ local epair1 epair2
+
+ vnet_init
+ epair1=$(vnet_mkepair)
+ epair2=$(vnet_mkepair)
+ vnet_mkjail vebtest ${epair1}a ${epair2}a
+
+ ifconfig -j vebtest ${epair1}a mtu 9000
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 create
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebportadd ${epair1}a
+ atf_check -s exit:0 -o match:"mtu 9000" ifconfig -j vebtest veb0
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebportadd ${epair2}a
+ atf_check -s exit:0 -o match:"mtu 9000" ifconfig -j vebtest ${epair2}a
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest vport0 create
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebportadd vport0
+ atf_check -s exit:0 -o match:"mtu 9000" ifconfig -j vebtest vport0
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 mtu 1400
+ atf_check -s exit:0 -o match:"mtu 1400" ifconfig -j vebtest vport0
+ atf_check -s exit:0 -o match:"mtu 1400" ifconfig -j vebtest ${epair2}a
+
+ # The veb owns a member vport's MTU
+ atf_check -s exit:1 -e ignore ifconfig -j vebtest vport0 mtu 1500
+}
+
+mtu_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "timeout" "cleanup"
+timeout_head()
+{
+ atf_set descr 'Set and dump the veb(4) address cache timeout'
+ atf_set require.user root
+ atf_set require.kmods if_veb
+}
+
+timeout_body()
+{
+ vnet_init
+ vnet_mkjail vebtest
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 create
+ atf_check -s exit:0 -o match:"timeout: 1200" ifconfig -j vebtest veb0
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebtimeout 240
+ atf_check -s exit:0 -o match:"timeout: 240" ifconfig -j vebtest veb0
+
+ atf_check -s exit:1 -e ignore ifconfig -j vebtest veb0 vebtimeout -1
+ atf_check -s exit:1 -e ignore ifconfig -j vebtest veb0 vebtimeout abc
+ atf_check -s exit:0 -o match:"timeout: 240" ifconfig -j vebtest veb0
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb1 create
+ atf_check -s exit:0 -o match:"timeout: 1200" ifconfig -j vebtest veb1
+}
+
+timeout_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "private_ports" "cleanup"
+private_ports_head()
+{
+ atf_set descr 'Verify that private ports do not exchange traffic'
+ atf_set require.user root
+ atf_set require.kmods if_veb
+}
+
+private_ports_body()
+{
+ local epair1 epair2 epair3 ip1 ip2 ip3
+
+ ip1=192.0.2.1
+ ip2=192.0.2.2
+ ip3=192.0.2.3
+
+ vnet_init
+ epair1=$(vnet_mkepair)
+ epair2=$(vnet_mkepair)
+ epair3=$(vnet_mkepair)
+ vnet_mkjail vebtest ${epair1}a ${epair2}a ${epair3}a
+ vnet_mkjail vebtest1 ${epair1}b
+ vnet_mkjail vebtest2 ${epair2}b
+ vnet_mkjail vebtest3 ${epair3}b
+
+ ifconfig -j vebtest1 ${epair1}b inet ${ip1}/24 up
+ ifconfig -j vebtest2 ${epair2}b inet ${ip2}/24 up
+ ifconfig -j vebtest3 ${epair3}b inet ${ip3}/24 up
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 create up
+ for port in ${epair1}a ${epair2}a ${epair3}a; do
+ ifconfig -j vebtest ${port} up
+ atf_check -s exit:0 -o ignore \
+ ifconfig -j vebtest veb0 vebportadd ${port}
+ done
+
+ atf_check -s exit:0 -o ignore \
+ ifconfig -j vebtest veb0 vebprivate ${epair1}a
+ atf_check -s exit:0 -o ignore \
+ ifconfig -j vebtest veb0 vebprivate ${epair2}a
+
+ atf_check -s exit:2 -o ignore jexec vebtest1 ping -nc 1 -t 1 ${ip2}
+ atf_check -s exit:2 -o ignore jexec vebtest2 ping -nc 1 -t 1 ${ip1}
+
+ atf_check -s exit:0 -o ignore jexec vebtest1 ping -nc 1 -t 1 ${ip3}
+ atf_check -s exit:0 -o ignore jexec vebtest2 ping -nc 1 -t 1 ${ip3}
+}
+
+private_ports_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "unprivileged" "cleanup"
+unprivileged_head()
+{
+ atf_set descr 'Verify that unprivileged users can read but not modify veb(4)'
+ atf_set require.user root
+ atf_set require.kmods if_veb
+}
+
+unprivileged_body()
+{
+ local epair
+
+ vnet_init
+ epair=$(vnet_mkepair)
+ vnet_mkjail vebtest ${epair}a
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 create
+
+ atf_check -s exit:0 -o ignore jexec -U nobody vebtest ifconfig veb0
+
+ atf_check -s exit:0 -o ignore jexec -U nobody vebtest ifconfig veb0 vebaddr
+
+ atf_check -s exit:1 -e ignore \
+ jexec -U nobody vebtest ifconfig veb0 vebportadd ${epair}a
+ atf_check -s exit:1 -e ignore \
+ jexec -U nobody vebtest ifconfig veb0 vebtimeout 60
+ atf_check -s exit:1 -e ignore \
+ jexec -U nobody vebtest ifconfig veb0 vebstatic ${epair}a 02:aa:bb:cc:dd:01
+
+ atf_check -s exit:0 -o not-match:"port:" ifconfig -j vebtest veb0
+ atf_check -s exit:0 -o match:"timeout: 1200" ifconfig -j vebtest veb0
+}
+
+unprivileged_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "bridge_mutual_exclusion" "cleanup"
+bridge_mutual_exclusion_head()
+{
+ atf_set descr 'Verify that an if_bridge(4) member cannot be a veb(4) port and vice versa'
+ atf_set require.user root
+ atf_set require.kmods if_veb
+}
+
+bridge_mutual_exclusion_body()
+{
+ local epair1 epair2
+
+ vnet_init
+ vnet_init_bridge
+ epair1=$(vnet_mkepair)
+ epair2=$(vnet_mkepair)
+ vnet_mkjail vebtest ${epair1}a ${epair2}a
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 create
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest bridge0 create
+
+ atf_check -s exit:0 -o ignore \
+ ifconfig -j vebtest bridge0 addm ${epair1}a
+ atf_check -s exit:1 -e ignore \
+ ifconfig -j vebtest veb0 vebportadd ${epair1}a
+
+ atf_check -s exit:0 -o ignore \
+ ifconfig -j vebtest veb0 vebportadd ${epair2}a
+ atf_check -s exit:1 -e ignore \
+ ifconfig -j vebtest bridge0 addm ${epair2}a
+}
+
+bridge_mutual_exclusion_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "no_host_presence" "cleanup"
+no_host_presence_head()
+{
+ atf_set descr 'Verify that veb(4) refuses addresses and still forwards'
+ atf_set require.user root
+ atf_set require.kmods if_veb
+}
+
+no_host_presence_body()
+{
+ local epair1 epair2 ip0 ip1 ip2
+
+ ip0=192.0.2.1
+ ip1=192.0.2.2
+ ip2=192.0.2.3
+
+ vnet_init
+ epair1=$(vnet_mkepair)
+ epair2=$(vnet_mkepair)
+ vnet_mkjail vebtest ${epair1}a ${epair2}a
+ vnet_mkjail vebtest1 ${epair1}b
+ vnet_mkjail vebtest2 ${epair2}b
+
+ ifconfig -j vebtest1 ${epair1}b inet ${ip1}/24 up
+ ifconfig -j vebtest2 ${epair2}b inet ${ip2}/24 up
+ ifconfig -j vebtest ${epair1}a up
+ ifconfig -j vebtest ${epair2}a up
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 create up
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebportadd ${epair1}a
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebportadd ${epair2}a
+
+ atf_check -s exit:1 -e match:"Operation not supported" \
+ ifconfig -j vebtest veb0 inet ${ip0}/24
+ atf_check -s exit:0 -o not-match:"inet " ifconfig -j vebtest veb0
+
+ atf_check -s exit:0 -o ignore jexec vebtest1 ping -nc 1 -t 1 ${ip2}
+ atf_check -s not-exit:0 -o ignore -e ignore \
+ jexec vebtest1 ping -nc 1 -t 1 ${ip0}
+}
+
+no_host_presence_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "transmit_ipv4_unicast" "cleanup"
+transmit_ipv4_unicast_head()
+{
+ atf_set descr 'Create a veb(4) with two epair ports and pass traffic between jails'
+ atf_set require.user root
+ atf_set require.kmods if_veb
+}
+
+transmit_ipv4_unicast_body()
+{
+ local epair1 epair2 ip1 ip2
+
+ ip1=192.0.2.1
+ ip2=192.0.2.2
+
+ vnet_init
+ epair1=$(vnet_mkepair)
+ epair2=$(vnet_mkepair)
+ vnet_mkjail vebtest ${epair1}a ${epair2}a
+ vnet_mkjail vebtest1 ${epair1}b
+ vnet_mkjail vebtest2 ${epair2}b
+
+ ifconfig -j vebtest1 ${epair1}b inet ${ip1}/24 up
+ ifconfig -j vebtest2 ${epair2}b inet ${ip2}/24 up
+ ifconfig -j vebtest ${epair1}a up
+ ifconfig -j vebtest ${epair2}a up
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 create up
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebportadd ${epair1}a
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebportadd ${epair2}a
+
+ atf_check -s exit:0 -o ignore jexec vebtest1 ping -nc 1 -t 1 ${ip2}
+ atf_check -s exit:0 -o ignore jexec vebtest2 ping -nc 1 -t 1 ${ip1}
+}
+
+transmit_ipv4_unicast_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "transmit_ipv4_multicast" "cleanup"
+transmit_ipv4_multicast_head()
+{
+ atf_set descr 'Create a veb(4) with three epair ports and check that multicast reaches every other port'
+ atf_set require.user root
+ atf_set require.kmods if_veb
+}
+
+transmit_ipv4_multicast_body()
+{
+ local epair1 epair2 epair3 ip1 ip2 ip3 mc_group
+
+ ip1=192.0.2.1
+ ip2=192.0.2.2
+ ip3=192.0.2.3
+ mc_group=224.0.0.1
+
+ vnet_init
+ epair1=$(vnet_mkepair)
+ epair2=$(vnet_mkepair)
+ epair3=$(vnet_mkepair)
+ vnet_mkjail vebtest ${epair1}a ${epair2}a ${epair3}a
+ vnet_mkjail vebtest1 ${epair1}b
+ vnet_mkjail vebtest2 ${epair2}b
+ vnet_mkjail vebtest3 ${epair3}b
+
+ ifconfig -j vebtest1 ${epair1}b inet ${ip1}/24 up
+ ifconfig -j vebtest2 ${epair2}b inet ${ip2}/24 up
+ ifconfig -j vebtest3 ${epair3}b inet ${ip3}/24 up
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 create up
+ for port in ${epair1}a ${epair2}a ${epair3}a; do
+ ifconfig -j vebtest ${port} up
+ atf_check -s exit:0 -o ignore \
+ ifconfig -j vebtest veb0 vebportadd ${port}
+ done
+
+ route -j vebtest1 add -net 224.0.0.0/4 -interface ${epair1}b
+
+ atf_check -s exit:0 -o ignore sysctl -j vebtest2 net.inet.icmp.bmcastecho=1
+ atf_check -s exit:0 -o ignore sysctl -j vebtest3 net.inet.icmp.bmcastecho=1
+
+ atf_check -s exit:0 -o save:ping.out \
+ jexec vebtest1 ping -nc 2 -t 3 ${mc_group}
+ atf_check -s exit:0 -o match:"from ${ip2}:" cat ping.out
+ atf_check -s exit:0 -o match:"from ${ip3}:" cat ping.out
+}
+
+transmit_ipv4_multicast_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "static_addresses" "cleanup"
+static_addresses_head()
+{
+ atf_set descr 'Add, show, flush and delete static veb(4) addresses'
+ atf_set require.user root
+ atf_set require.kmods if_veb
+}
+
+static_addresses_body()
+{
+ local epair1 epair2 mac
+
+ mac=02:aa:bb:cc:dd:01
+
+ vnet_init
+ epair1=$(vnet_mkepair)
+ epair2=$(vnet_mkepair)
+ vnet_mkjail vebtest ${epair1}a ${epair2}a
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 create
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebportadd ${epair1}a
+
+ atf_check -s exit:0 -o ignore \
+ ifconfig -j vebtest veb0 vebstatic ${epair1}a ${mac}
+ atf_check -s exit:0 -o ignore \
+ ifconfig -j vebtest veb0 vebstatic ${epair1}a ${mac} vlan 10
+ atf_check -s exit:0 -o match:"${mac} Vlan0 ${epair1}a 0 static" \
+ ifconfig -j vebtest veb0 vebaddr
+ atf_check -s exit:0 -o match:"${mac} Vlan10 ${epair1}a 0 static" \
+ ifconfig -j vebtest veb0 vebaddr
+
+ atf_check -s exit:1 -e ignore \
+ ifconfig -j vebtest veb0 vebstatic ${epair2}a ${mac}
+ atf_check -s exit:1 -e ignore \
+ ifconfig -j vebtest veb0 vebstatic ${epair1}a 01:00:5e:00:00:01
+ atf_check -s exit:1 -e ignore \
+ ifconfig -j vebtest veb0 vebstatic ${epair1}a ${mac} vlan 4095
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebflush
+ atf_check -s exit:0 -o match:"${mac} Vlan0 " \
+ ifconfig -j vebtest veb0 vebaddr
+
+ atf_check -s exit:0 -o ignore \
+ ifconfig -j vebtest veb0 vebdeladdr ${mac}
+ atf_check -s exit:0 -o not-match:"${mac} Vlan0 " \
+ ifconfig -j vebtest veb0 vebaddr
+ atf_check -s exit:0 -o match:"${mac} Vlan10 " \
+ ifconfig -j vebtest veb0 vebaddr
+ atf_check -s exit:1 -e ignore \
+ ifconfig -j vebtest veb0 vebdeladdr ${mac}
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebportdel ${epair1}a
+ atf_check -s exit:0 -o not-match:"${mac}" \
+ ifconfig -j vebtest veb0 vebaddr
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebportadd ${epair1}a
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebmaxaddr 0
+ atf_check -s exit:1 -e ignore \
+ ifconfig -j vebtest veb0 vebstatic ${epair1}a ${mac}
+}
+
+static_addresses_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "vport_constraints" "cleanup"
+vport_constraints_head()
+{
+ atf_set descr 'Check that veb(4) has at most one vport and a vport joins at most one veb'
+ atf_set require.user root
+ atf_set require.kmods if_veb
+}
+
+vport_constraints_body()
+{
+ vnet_init
+ vnet_mkjail vebtest
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 create
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb1 create
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest vport0 create
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest vport1 create
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebportadd vport0
+
+ atf_check -s exit:1 -e ignore ifconfig -j vebtest veb0 vebportadd vport1
+
+ atf_check -s exit:1 -e ignore ifconfig -j vebtest veb1 vebportadd vport0
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebportdel vport0
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb1 vebportadd vport0
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebportadd vport1
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest vport0 destroy
+ atf_check -s exit:0 -o not-match:"port:" ifconfig -j vebtest veb1
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest vport2 create
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb1 vebportadd vport2
+}
+
+vport_constraints_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "vport_host_path" "cleanup"
+vport_host_path_head()
+{
+ atf_set descr 'Check we can pass traffic between a vport and a jail'
+ atf_set require.user root
+ atf_set require.kmods if_veb
+}
+
+vport_host_path_body()
+{
+ local epair ip0 ip1
+
+ ip0=192.0.2.1
+ ip1=192.0.2.2
+
+ vnet_init
+ epair=$(vnet_mkepair)
+ vnet_mkjail vebtest ${epair}a
+ vnet_mkjail vebtest1 ${epair}b
+
+ ifconfig -j vebtest1 ${epair}b inet ${ip1}/24 up
+ ifconfig -j vebtest ${epair}a up
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 create up
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest vport0 create
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebportadd ${epair}a
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest veb0 vebportadd vport0
+
+ atf_check -s exit:0 -o ignore ifconfig -j vebtest vport0 inet ${ip0}/24 up
+
+ atf_check -s exit:0 -o match:"port: vport0 flags=<[A-Z,]*VPORT>" \
+ ifconfig -j vebtest veb0
+ atf_check -s exit:0 -o not-match:"port: ${epair}a flags=<[A-Z,]*VPORT" \
+ ifconfig -j vebtest veb0
+
+ atf_check -s exit:0 -o ignore jexec vebtest ping -nc 1 -t 1 ${ip1}
+ atf_check -s exit:0 -o ignore jexec vebtest1 ping -nc 1 -t 1 ${ip0}
+}
+
+vport_host_path_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case "create_destroy"
+ atf_add_test_case "port_add_del"
+ atf_add_test_case "delete_with_ports"
+ atf_add_test_case "port_departure"
+ atf_add_test_case "flags"
+ atf_add_test_case "learning"
+ atf_add_test_case "mtu"
+ atf_add_test_case "timeout"
+ atf_add_test_case "private_ports"
+ atf_add_test_case "unprivileged"
+ atf_add_test_case "bridge_mutual_exclusion"
+ atf_add_test_case "no_host_presence"
+ atf_add_test_case "transmit_ipv4_unicast"
+ atf_add_test_case "transmit_ipv4_multicast"
+ atf_add_test_case "static_addresses"
+ atf_add_test_case "vport_constraints"
+ atf_add_test_case "vport_host_path"
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Nov 14, 5:27 PM (11 h, 28 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41728482
Default Alt Text
D60834.1794677260.diff (143 KB)
Attached To
Mode
D60834: if_veb: Add veb(4) pseudo network device
Attached
Detach File
Event Timeline