Page MenuHomeFreeBSD

D60834.1794677266.diff
No OneTemporary

Authored By
Unknown
Size
132 KB
Referenced Files
None
Subscribers
None

D60834.1794677266.diff

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/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,701 @@
+/*-
+ * 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 <sys/socket.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 <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <err.h>
+
+#include "ifconfig.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;
+};
+static const char *const veb_rtable_types[] = {
+ [0] = "dynamic",
+ [1] = "static",
+ [2] = "sticky"
+};
+
+#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 const struct snl_hdr_parser *all_parsers[] = {
+ &veb_parser,
+ &veb_port_parser,
+ &veb_link_parser,
+ &veb_linkinfo_parser,
+ &veb_rtable_parser,
+};
+
+static void
+veb_print_port(const struct nl_parsed_veb_port *port)
+{
+ const struct {
+ bool on;
+ const char *name;
+ } f[] = {
+ { port->ifla_learning, "LEARNING" },
+ { port->ifla_discover, "DISCOVER" },
+ { port->ifla_sticky, "STICKY" },
+ { port->ifla_private, "PRIVATE" },
+ { port->ifla_vport, "VPORT" },
+ };
+ const char *sep = "";
+
+ printf("\tport: %s flags=<", port->ifla_ifname);
+ for (size_t i = 0; i < nitems(f); i++) {
+ if (f[i].on) {
+ printf("%s%s", sep, f[i].name);
+ sep = ",";
+ }
+ }
+ printf("> maxaddr: %u\n", port->ifla_maxaddr);
+}
+
+static inline void
+veb_nl_init(if_ctx *ctx, struct snl_writer *nw, uint32_t flags)
+{
+ struct nlmsghdr *hdr;
+
+ snl_init_writer(ctx->io_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, ctx->ifname);
+}
+
+static inline void
+veb_nl_fini(if_ctx *ctx, struct snl_writer *nw)
+{
+ struct nlmsghdr *hdr;
+ struct snl_errmsg_data errmsg = {};
+
+ hdr = snl_finalize_msg(nw);
+ if (hdr == NULL || !snl_send_message(ctx->io_ss, hdr))
+ err(1, "unable to send netlink message");
+
+ if (!snl_read_reply_code(ctx->io_ss, hdr->nlmsg_seq, &errmsg)) {
+ errx(errmsg.error, "%s", errmsg.error_str);
+ }
+}
+
+static inline int
+veb_genl_init(struct snl_state *ss)
+{
+ int family_id;
+
+ if (!snl_init(ss, NETLINK_GENERIC))
+ err(1, "unable to open netlink socket");
+
+ family_id = snl_get_genl_family(ss, VEB_NL_FAMILY_NAME);
+ if (family_id == 0)
+ errx(1, "veb netlink family not found (is if_veb loaded?)");
+
+ return (family_id);
+}
+
+static void
+veb_status_nl(if_ctx *ctx)
+{
+ struct snl_writer nw;
+ struct nlmsghdr *hdr;
+ struct nla_veb_link veb_link = {};
+ uint32_t i;
+
+ snl_init_writer(ctx->io_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, ctx->ifname);
+
+ if ((hdr = snl_finalize_msg(&nw)) == NULL ||
+ !snl_send_message(ctx->io_ss, hdr))
+ return;
+
+ hdr = snl_read_reply(ctx->io_ss, hdr->nlmsg_seq);
+ if (hdr == NULL || hdr->nlmsg_type != NL_RTM_NEWLINK)
+ return;
+ if (!snl_parse_nlmsg(ctx->io_ss, hdr, &veb_link_parser, &veb_link))
+ return;
+
+ if (veb_link.linkinfo.kind == NULL ||
+ strcmp(veb_link.linkinfo.kind, "veb") != 0)
+ return;
+
+ printf("\tmaxaddr: %u", veb_link.linkinfo.data.ifla_maxaddr);
+ printf("\ttimeout: %u", veb_link.linkinfo.data.ifla_timeout);
+ printf("\texceeded: %u\n", veb_link.linkinfo.data.ifla_exceeded);
+
+ for (i = 0; i < veb_link.linkinfo.data.ifla_ports.count; i++)
+ veb_print_port(veb_link.linkinfo.data.ifla_ports.items[i]);
+}
+
+static void
+veb_create_nl(if_ctx *ctx, struct ifreq *ifr)
+{
+ struct snl_writer nw = {};
+ struct nlmsghdr *hdr;
+ int off;
+
+ snl_init_writer(ctx->io_ss, &nw);
+ hdr = snl_create_msg_request(&nw, NL_RTM_NEWLINK);
+ hdr->nlmsg_flags |= (NLM_F_CREATE | NLM_F_EXCL);
+
+ snl_reserve_msg_object(&nw, struct ifinfomsg);
+ snl_add_msg_attr_string(&nw, IFLA_IFNAME, ifr->ifr_name);
+
+ off = snl_add_msg_attr_nested(&nw, IFLA_LINKINFO);
+ snl_add_msg_attr_string(&nw, IFLA_INFO_KIND, "veb");
+ snl_end_attr_nested(&nw, off);
+
+ veb_nl_fini(ctx, &nw);
+}
+
+static void
+veb_port_cmd(if_ctx *ctx, int attr, const char *ifname)
+{
+ struct snl_writer nw = {};
+ int off, off2;
+
+ veb_nl_init(ctx, &nw, 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, ifname);
+
+ snl_end_attr_nested(&nw, off2);
+ snl_end_attr_nested(&nw, off);
+
+ veb_nl_fini(ctx, &nw);
+}
+
+static void
+setveb_addport(if_ctx *ctx, const char *ifname, int dummy __unused)
+{
+ veb_port_cmd(ctx, IFLA_VEB_ADDPORT, ifname);
+}
+
+static void
+setveb_delport(if_ctx *ctx, const char *ifname, int dummy __unused)
+{
+ veb_port_cmd(ctx, IFLA_VEB_DELPORT, ifname);
+}
+
+static void
+setveb_setportmaxaddr(if_ctx *ctx, const char *ifname, const char *arg)
+{
+ struct snl_writer nw = {};
+ const char *errstr;
+ uint32_t maxaddr;
+ int off, off2, off3;
+
+ maxaddr = strtonum(arg, 0, UINT32_MAX, &errstr);
+ if (errstr != NULL)
+ errx(1, "portmaxaddr %s: %s", arg, errstr);
+
+ veb_nl_init(ctx, &nw, 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, ifname);
+ 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);
+
+ veb_nl_fini(ctx, &nw);
+}
+
+static void
+veb_port_flag(if_ctx *ctx, const char *ifname, int attr, bool on)
+{
+ struct snl_writer nw = {};
+ int off, off2, off3;
+
+ veb_nl_init(ctx, &nw, 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, ifname);
+ 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);
+
+ veb_nl_fini(ctx, &nw);
+}
+
+static void
+setveb_learn(if_ctx *ctx, const char *ifname, int dummy __unused)
+{
+ veb_port_flag(ctx, ifname, IFLA_VEB_PORT_LEARNING, true);
+}
+
+static void
+setveb_unlearn(if_ctx *ctx, const char *ifname, int dummy __unused)
+{
+ veb_port_flag(ctx, ifname, IFLA_VEB_PORT_LEARNING, false);
+}
+
+static void
+setveb_discover(if_ctx *ctx, const char *ifname, int dummy __unused)
+{
+ veb_port_flag(ctx, ifname, IFLA_VEB_PORT_DISCOVER, true);
+}
+
+static void
+setveb_undiscover(if_ctx *ctx, const char *ifname, int dummy __unused)
+{
+ veb_port_flag(ctx, ifname, IFLA_VEB_PORT_DISCOVER, false);
+}
+
+static void
+setveb_sticky(if_ctx *ctx, const char *ifname, int dummy __unused)
+{
+ veb_port_flag(ctx, ifname, IFLA_VEB_PORT_STICKY, true);
+}
+
+static void
+setveb_unsticky(if_ctx *ctx, const char *ifname, int dummy __unused)
+{
+ veb_port_flag(ctx, ifname, IFLA_VEB_PORT_STICKY, false);
+}
+
+static void
+setveb_private(if_ctx *ctx, const char *ifname, int dummy __unused)
+{
+ veb_port_flag(ctx, ifname, IFLA_VEB_PORT_PRIVATE, true);
+}
+
+static void
+setveb_unprivate(if_ctx *ctx, const char *ifname, int dummy __unused)
+{
+ veb_port_flag(ctx, ifname, IFLA_VEB_PORT_PRIVATE, false);
+}
+
+static void
+setveb_settimeout(if_ctx *ctx, const char *arg, int dummy __unused)
+{
+ struct snl_writer nw = {};
+ const char *errstr;
+ uint32_t timeout;
+ int off, off2;
+
+ timeout = strtonum(arg, 0, UINT32_MAX, &errstr);
+ if (errstr != NULL)
+ errx(1, "timeout %s: %s", arg, errstr);
+
+ veb_nl_init(ctx, &nw, 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, IFLA_VEB_RTABLE_TIMEOUT, timeout);
+ snl_end_attr_nested(&nw, off2);
+
+ snl_end_attr_nested(&nw, off);
+
+ veb_nl_fini(ctx, &nw);
+}
+
+static void
+setveb_setmaxaddr(if_ctx *ctx, const char *arg, int dummy __unused)
+{
+ struct snl_writer nw = {};
+ const char *errstr;
+ uint32_t maxaddr;
+ int off, off2;
+
+ maxaddr = strtonum(arg, 0, UINT32_MAX, &errstr);
+ if (errstr != NULL)
+ errx(1, "maxaddr %s: %s", arg, errstr);
+
+ veb_nl_init(ctx, &nw, 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, IFLA_VEB_RTABLE_MAXADDR, maxaddr);
+ snl_end_attr_nested(&nw, off2);
+
+ snl_end_attr_nested(&nw, off);
+
+ veb_nl_fini(ctx, &nw);
+}
+
+static int
+setveb_static(if_ctx *ctx, int argc, const char *const *argv)
+{
+ struct snl_state ss = {};
+ struct snl_errmsg_data e = {};
+ struct snl_writer nw;
+ struct nlmsghdr *hdr;
+ struct ether_addr *ea;
+ const char *port, *errstr;
+ ether_vlanid_t vlan = DOT1Q_VID_NULL;
+ int family_id, 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;
+ }
+
+ family_id = veb_genl_init(&ss);
+
+ snl_init_writer(&ss, &nw);
+ hdr = snl_create_genl_msg_request(&nw, family_id, VEB_NL_CMD_ADDSTATIC);
+ snl_add_msg_attr_string(&nw, VEB_NL_RTABLE_IFNAME, ctx->ifname);
+ 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);
+
+ hdr = snl_finalize_msg(&nw);
+ if (hdr == NULL || !snl_send_message(&ss, hdr)) {
+ errx(1, "unable to send netlink message");
+ }
+
+ if (!snl_read_reply_code(&ss, hdr->nlmsg_seq, &e)) {
+ if (e.error_str != NULL)
+ errx(e.error, "%s", e.error_str);
+ errc(e.error, e.error, "vebstatic");
+ }
+
+ snl_free(&ss);
+
+ return (arg);
+}
+
+
+static int
+setveb_deladdr(if_ctx *ctx, int argc, const char *const *argv)
+{
+ struct snl_state ss = {};
+ struct snl_errmsg_data e = {};
+ struct snl_writer nw;
+ struct nlmsghdr *hdr;
+ struct ether_addr *ea;
+ const char *errstr;
+ ether_vlanid_t vlan = DOT1Q_VID_NULL;
+ int family_id, 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;
+ }
+
+ family_id = veb_genl_init(&ss);
+
+ snl_init_writer(&ss, &nw);
+ hdr = snl_create_genl_msg_request(&nw, family_id, VEB_NL_CMD_DELADDR);
+ snl_add_msg_attr_string(&nw, VEB_NL_RTABLE_IFNAME, ctx->ifname);
+ 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);
+
+ hdr = snl_finalize_msg(&nw);
+ if (hdr == NULL || !snl_send_message(&ss, hdr)) {
+ errx(1, "unable to send netlink message");
+ }
+
+ if (!snl_read_reply_code(&ss, hdr->nlmsg_seq, &e)) {
+ if (e.error_str != NULL)
+ errx(e.error, "%s", e.error_str);
+ errc(e.error, e.error, "vebdeladdr");
+ }
+
+ snl_free(&ss);
+
+ return (arg);
+}
+
+static void
+setveb_flush(if_ctx *ctx, const char *val __unused, int d)
+{
+ struct snl_writer nw = {};
+ int off, off2;
+
+ veb_nl_init(ctx, &nw, 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, d != 0);
+
+ snl_end_attr_nested(&nw, off2);
+ snl_end_attr_nested(&nw, off);
+
+ veb_nl_fini(ctx, &nw);
+}
+
+static void
+setveb_showaddr(if_ctx *ctx, const char *val __unused, int dummy __unused)
+{
+ struct snl_state ss = {};
+ struct snl_errmsg_data e = {};
+ struct snl_writer nw;
+ struct nlmsghdr *hdr;
+ struct ether_addr ea;
+ uint32_t seq_id;
+ int family_id;
+
+ family_id = veb_genl_init(&ss);
+
+ 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, ctx->ifname);
+
+ hdr = snl_finalize_msg(&nw);
+ if (hdr == NULL) {
+ errx(1, "unable to build rtable request");
+ }
+ seq_id = hdr->nlmsg_seq;
+ if (!snl_send_message(&ss, hdr)) {
+ err(1, "unable to send rtable request");
+ }
+
+ while ((hdr = snl_read_reply_multi(&ss, seq_id, &e)) != NULL) {
+ struct veb_rt_entry rt = {};
+
+ 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;
+
+ memcpy(ea.octet, NLA_DATA(rt.mac), ETHER_ADDR_LEN);
+ printf("%s Vlan%u %s %u %s\n", ether_ntoa(&ea),
+ rt.vlan, rt.port, rt.expire,
+ rt.type < nitems(veb_rtable_types)
+ ? veb_rtable_types[rt.type] : "?");
+ }
+
+ if (e.error != 0)
+ errc(1, e.error, "%s",
+ e.error_str != NULL ? e.error_str : "rtable dump failed");
+ snl_free(&ss);
+}
+
+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_nl,
+};
+
+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);
+ clone_setdefcallback_prefix("veb", veb_create_nl);
+ SNL_VERIFY_PARSERS(all_parsers);
+}
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,266 @@
+.\"-
+.\" 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 October 18, 2026
+.Dt VEB 4
+.Os
+.Sh NAME
+.Nm veb ,
+.Nm vport
+.Nd Virtual Ethernet Bridge pseudo network device
+.Sh SYNOPSIS
+To compile this driver into the kernel,
+place the following line in your
+kernel configuration file:
+.Bd -ragged -offset indent
+.Cd "device veb"
+.Ed
+.Pp
+Alternatively, to load the driver as a
+module at boot time, place the following line in
+.Xr loader.conf 5 :
+.Bd -literal -offset indent
+if_veb_load="YES"
+.Ed
+.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(&params, 0, sizeof(params));
+
+ nl_get_attrs_bmask_raw(NLA_DATA(attrs), NLA_DATA_LEN(attrs), &bm);
+ error = nl_parse_nested(attrs, &veb_parser, npt, &params);
+
+ 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(&params, 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, &params);
+ 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:2 -e ignore \
+ ifconfig -j vebtest veb0 vebportadd nonexistent0
+ atf_check -s exit:2 -e ignore \
+ ifconfig -j vebtest veb0 vebportdel ${epair}a
+
+ ifconfig -j vebtest ${epair}a inet 192.0.2.5/24
+ atf_check -s exit:22 -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:17 -e ignore \
+ ifconfig -j vebtest veb0 vebportadd ${epair}a
+
+ atf_check -s exit:16 -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:2 -e ignore \
+ ifconfig -j vebtest veb0 veblearn ${epair}b
+ atf_check -s exit:2 -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:16 -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:2 -e ignore \
+ ifconfig -j vebtest veb0 vebstatic ${epair2}a ${mac}
+ atf_check -s exit:22 -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:2 -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:28 -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:22 -e ignore ifconfig -j vebtest veb0 vebportadd vport1
+
+ atf_check -s exit:16 -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

Mime Type
text/plain
Expires
Sat, Nov 14, 5:27 PM (4 h, 36 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41728485
Default Alt Text
D60834.1794677266.diff (132 KB)

Event Timeline