aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLexi Winter <ivy@FreeBSD.org>2025-05-29 07:58:04 +0100
committerLexi Winter <ivy@FreeBSD.org>2025-05-31 05:33:28 +0100
commit177b4442a8b027ca757aeeded324519554447139 (patch)
treec9347ed9b5ad9fc03f7bba28d4cf5ee711463b99
parent9f28d8db26a65f7ac845a8edd5c6455900184640 (diff)
bridge: support default vlan filteringlf/dev/bridge-vlan
Allow a default VLAN filtering configuration to be applied to a bridge: # ifconfig bridge0 defvlanfilter defuntagged 1 This causes newly-added bridge members to attain the VLANFILTER flag and/or the specified pvid by default, obviating the need to configure this for every bridge member. The configured options are displayed in ifconfig: bridge flags=1<DEFVLANFILTER> defuntagged=1 This is implemented using a new bridge ioctl interface, BRDGNVCALL, which is based on nv(9). This new interface will eventually replace the existing ioctl-based configuration interface for bridge(4); it's more flexible, extensible, easier to use, and it allows us to finally return proper error messages, especially for things like 'addm'.
-rw-r--r--lib/libifconfig/libifconfig.h14
-rw-r--r--lib/libifconfig/libifconfig_bridge.c99
-rw-r--r--sbin/ifconfig/ifbridge.c89
-rw-r--r--sbin/ifconfig/ifconfig.89
-rw-r--r--sys/net/if_bridge.c413
-rw-r--r--sys/net/if_bridgevar.h106
6 files changed, 729 insertions, 1 deletions
diff --git a/lib/libifconfig/libifconfig.h b/lib/libifconfig/libifconfig.h
index fc835485a51e..a1ed96790571 100644
--- a/lib/libifconfig/libifconfig.h
+++ b/lib/libifconfig/libifconfig.h
@@ -27,6 +27,7 @@
#pragma once
#include <sys/types.h>
+#include <sys/nv.h>
#include <net/if.h>
#include <net/if_bridgevar.h> /* for ifbvlan_set_t */
@@ -69,6 +70,8 @@ struct ifconfig_bridge_status {
size_t members_count; /**< how many member interfaces */
uint32_t cache_size; /**< size of address cache */
uint32_t cache_lifetime; /**< address cache entry lifetime */
+ ifbr_flags_t flags; /**< bridge flags */
+ ether_vlanid_t defpvid; /**< default pvid */
};
struct ifconfig_capabilities {
@@ -333,6 +336,17 @@ int ifconfig_bridge_get_bridge_status(ifconfig_handle_t *h,
*/
void ifconfig_bridge_free_bridge_status(struct ifconfig_bridge_status *bridge);
+/** Perform a BRDGNVCALL request on the given bridge.
+ * @param h An open ifconfig state object
+ * @param name The bridge interface name
+ * @param set True if this is a set request.
+ * @param request The nv request
+ * @param reply Where the nv reply will be stored
+ * @return 0 on success, nonzero on failure
+ */
+int ifconfig_bridge_nvcall(ifconfig_handle_t *h, const char *name, bool set,
+ const nvlist_t *request, nvlist_t **reply);
+
/** Retrieve additional information about a lagg(4) interface */
int ifconfig_lagg_get_lagg_status(ifconfig_handle_t *h,
const char *name, struct ifconfig_lagg_status **lagg_status);
diff --git a/lib/libifconfig/libifconfig_bridge.c b/lib/libifconfig/libifconfig_bridge.c
index 0dc989ec178d..1d3fb6ead986 100644
--- a/lib/libifconfig/libifconfig_bridge.c
+++ b/lib/libifconfig/libifconfig_bridge.c
@@ -22,8 +22,10 @@
* 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/nv.h>
#include <net/ethernet.h>
#include <net/if.h>
@@ -60,6 +62,76 @@ ifconfig_bridge_ioctlwrap(ifconfig_handle_t *h, const char *name,
return (ifconfig_ioctlwrap(h, AF_LOCAL, req, &ifd));
}
+/*
+ * Make a BRDGNVCALL request and return the response.
+ */
+int
+ifconfig_bridge_nvcall(ifconfig_handle_t *h, const char *name, bool set,
+ const nvlist_t *request, nvlist_t **reply)
+{
+ struct ifb_nvcall nvcall;
+ void *request_buf;
+ size_t request_size;
+ int error;
+
+ request_buf = NULL;
+ error = 0;
+
+ /* Pack the request */
+ if ((request_buf = nvlist_pack(request, &request_size)) == NULL) {
+ error = errno;
+ goto out;
+ }
+
+ if (request_size > BRNV_MAXSIZE) {
+ error = ENOSPC;
+ goto out;
+ }
+
+ memset(&nvcall, 0, sizeof(nvcall));
+ nvcall.ifbnv_inlen = request_size;
+ nvcall.ifbnv_buflen = BRNV_MAXSIZE;
+
+ /* Try the nvcall, increasing the buffer size if needed */
+ for (;;) {
+ if (nvcall.ifbnv_buflen < request_size) {
+ error = ENOSPC;
+ goto out;
+ }
+
+ free(nvcall.ifbnv_data);
+ nvcall.ifbnv_data = malloc(nvcall.ifbnv_buflen);
+ if (nvcall.ifbnv_data == NULL) {
+ error = ENOMEM;
+ goto out;
+ }
+
+ memcpy(nvcall.ifbnv_data, request_buf, request_size);
+ if (ifconfig_bridge_ioctlwrap(h, name, BRDGNVCALL,
+ &nvcall, sizeof(nvcall), set) == 0)
+ break;
+
+ if (errno == ENOSPC) {
+ nvcall.ifbnv_buflen = nvcall.ifbnv_outlen;
+ continue;
+ }
+
+ error = errno;
+ goto out;
+ }
+
+ *reply = nvlist_unpack(nvcall.ifbnv_data, nvcall.ifbnv_outlen, 0);
+ if (*reply == NULL) {
+ error = errno;
+ goto out;
+ }
+
+out:
+ free(nvcall.ifbnv_data);
+ free(request_buf);
+ return (error);
+}
+
int
ifconfig_bridge_get_bridge_status(ifconfig_handle_t *h,
const char *name, struct ifconfig_bridge_status **bridgep)
@@ -68,9 +140,13 @@ ifconfig_bridge_get_bridge_status(ifconfig_handle_t *h,
struct ifbrparam cache_param;
struct _ifconfig_bridge_status *bridge = NULL;
char *buf = NULL;
+ nvlist_t *nvrequest, *nvreply;
+ const nvlist_t *nvret;
+ int err;
members.ifbic_buf = NULL;
*bridgep = NULL;
+ nvrequest = nvreply = NULL;
bridge = calloc(1, sizeof(struct _ifconfig_bridge_status));
if (bridge == NULL) {
@@ -97,6 +173,25 @@ ifconfig_bridge_get_bridge_status(ifconfig_handle_t *h,
goto err;
}
+ /*
+ * Fetch the nv-based parameters.
+ */
+ nvrequest = nvlist_create(0);
+ if (nvrequest == NULL)
+ goto err;
+ nvlist_add_null(nvrequest, BRNV_GETDEFPVID);
+ nvlist_add_null(nvrequest, BRNV_GETBRFLAGS);
+
+ err = ifconfig_bridge_nvcall(h, name, false, nvrequest, &nvreply);
+ nvlist_destroy(nvrequest);
+ if (err)
+ goto err;
+
+ nvret = nvlist_get_nvlist(nvreply, BRNV_GETDEFPVID);
+ bridge->inner.defpvid = nvlist_get_number(nvret, BRNV_DEFPVID);
+ nvret = nvlist_get_nvlist(nvreply, BRNV_GETBRFLAGS);
+ bridge->inner.flags = nvlist_get_number(nvret, BRNV_BRFLAGS);
+
for (size_t len = 8192;
(buf = realloc(members.ifbic_buf, len)) != NULL;
len *= 2) {
@@ -145,6 +240,10 @@ ifconfig_bridge_get_bridge_status(ifconfig_handle_t *h,
return (0);
err:
+ if (nvrequest)
+ nvlist_destroy(nvrequest);
+ if (nvreply)
+ nvlist_destroy(nvreply);
free(members.ifbic_buf);
if (bridge)
free(bridge->inner.member_vlans);
diff --git a/sbin/ifconfig/ifbridge.c b/sbin/ifconfig/ifbridge.c
index 4356798f4158..aa9a90a78f51 100644
--- a/sbin/ifconfig/ifbridge.c
+++ b/sbin/ifconfig/ifbridge.c
@@ -185,8 +185,11 @@ bridge_status(if_ctx *ctx)
uint8_t lladdr[ETHER_ADDR_LEN];
uint16_t bprio;
- if (ifconfig_bridge_get_bridge_status(lifh, ctx->ifname, &bridge) == -1)
+ if (ifconfig_bridge_get_bridge_status(lifh, ctx->ifname,
+ &bridge) == -1) {
+ errx(1, "ifconfig_bridge_get_bridge_status failed");
return;
+ }
params = bridge->params;
@@ -209,6 +212,11 @@ bridge_status(if_ctx *ctx)
params->ifbop_root_path_cost,
params->ifbop_root_port & 0xfff);
+ printb("\tbridge flags", bridge->flags, IFBRFBITS);
+ if (bridge->defpvid)
+ printf(" defuntagged=%d", (int) bridge->defpvid);
+ printf("\n");
+
prefix = "\tmember: ";
pad = "\t ";
for (size_t i = 0; i < bridge->members_count; ++i) {
@@ -795,6 +803,81 @@ delbridge_tagged(if_ctx *ctx, const char *ifn, const char *vlans)
set_bridge_vlanset(ctx, ifn, vlans, BRDG_VLAN_OP_DEL);
}
+static void
+change_bridge_flags(if_ctx *ctx, ifbr_flags_t set, ifbr_flags_t clr)
+{
+ nvlist_t *request, *args, *reply;
+ int error;
+
+ if ((request = nvlist_create(0)) == NULL)
+ err(1, "nvlist_create");
+ if ((args = nvlist_create(0)) == NULL)
+ err(1, "nvlist_create");
+
+ nvlist_add_number(args, BRNV_SETFLAGS, set);
+ nvlist_add_number(args, BRNV_CLRFLAGS, clr);
+ nvlist_add_nvlist(request, BRNV_MODBRFLAGS, args);
+
+ error = ifconfig_bridge_nvcall(lifh, ctx->ifname, true, request,
+ &reply);
+ if (error)
+ errx(1, "BRDGNVCALL: %s", strerror(errno));
+}
+
+static void
+setbridge_defvlanfilter(if_ctx *ctx, const char *ifn __unused, int set)
+{
+ if (set)
+ change_bridge_flags(ctx, IFBRF_DEFVLANFILTER, 0);
+ else
+ change_bridge_flags(ctx, 0, IFBRF_DEFVLANFILTER);
+}
+
+static void
+set_bridge_defuntagged(if_ctx *ctx, ether_vlanid_t vid)
+{
+ nvlist_t *request, *args, *reply;
+ const nvlist_t *status;
+ int error;
+
+ if ((request = nvlist_create(0)) == NULL)
+ err(1, "nvlist_create");
+ if ((args = nvlist_create(0)) == NULL)
+ err(1, "nvlist_create");
+
+ nvlist_add_number(args, BRNV_DEFPVID, vid);
+ nvlist_add_nvlist(request, BRNV_SETDEFPVID, args);
+
+ error = ifconfig_bridge_nvcall(lifh, ctx->ifname, true, request,
+ &reply);
+ if (error)
+ errx(1, "BRDGNVCALL: %s", strerror(error));
+
+ if (!nvlist_exists_nvlist(reply, BRNV_SETDEFPVID))
+ errx(1, "BRDGNVCALL: reply is missing?");
+ status = nvlist_get_nvlist(reply, BRNV_SETDEFPVID);
+ if (nvlist_exists_string(status, BRNV_ERROR))
+ errx(1, "%s", nvlist_get_string(status, BRNV_ERROR));
+}
+
+static void
+setbridge_defuntagged(if_ctx *ctx, const char *vidstr, int dummy __unused)
+{
+ u_long vid;
+
+ if (get_val(vidstr, &vid) < 0 || vid > DOT1Q_VID_MAX)
+ errx(1, "invalid vlan id: %s", vidstr);
+
+ set_bridge_defuntagged(ctx, (ether_vlanid_t)vid);
+}
+
+static void
+unsetbridge_defuntagged(if_ctx *ctx, const char *val __unused,
+ int dummy __unused)
+{
+ set_bridge_defuntagged(ctx, 0);
+}
+
static struct cmd bridge_cmds[] = {
DEF_CMD_ARG("addm", setbridge_add),
DEF_CMD_ARG("deletem", setbridge_delete),
@@ -838,6 +921,10 @@ static struct cmd bridge_cmds[] = {
DEF_CMD_ARG2("tagged", setbridge_tagged),
DEF_CMD_ARG2("+tagged", addbridge_tagged),
DEF_CMD_ARG2("-tagged", delbridge_tagged),
+ DEF_CMD_ARG("defuntagged", setbridge_defuntagged),
+ DEF_CMD("-defuntagged", 0, unsetbridge_defuntagged),
+ DEF_CMD("defvlanfilter", 1, setbridge_defvlanfilter),
+ DEF_CMD("-defvlanfilter", 0, setbridge_defvlanfilter),
DEF_CMD_ARG("timeout", setbridge_timeout),
DEF_CMD_ARG("private", setbridge_private),
DEF_CMD_ARG("-private", unsetbridge_private),
diff --git a/sbin/ifconfig/ifconfig.8 b/sbin/ifconfig/ifconfig.8
index 0086fad1fc21..1aadf1d0bc08 100644
--- a/sbin/ifconfig/ifconfig.8
+++ b/sbin/ifconfig/ifconfig.8
@@ -2702,6 +2702,15 @@ The behaviour of these options is described in the
section of
.Xr bridge 4 .
.Bl -tag -width indent
+.It Cm defvlanfilter
+Enable VLAN filtering by default on newly-added interfaces.
+.It Cm -defvlanfilter
+Don't enable VLAN filtering by default on newly-added interfaces.
+.It Cm defuntagged Ar vlan-id
+Set the untagged VLAN identifier for newly added interfaces to
+.Ar vlan-id .
+.It Cm -defuntagged
+Don't set an untagged VLAN identifier for newly added interfaces.
.It Cm vlanfilter Ar interface
Enable VLAN filtering on an interface.
.It Cm -vlanfilter Ar interface
diff --git a/sys/net/if_bridge.c b/sys/net/if_bridge.c
index 4fc3ad6d2326..3a1a39a4b085 100644
--- a/sys/net/if_bridge.c
+++ b/sys/net/if_bridge.c
@@ -101,6 +101,8 @@
#include <sys/proc.h>
#include <sys/lock.h>
#include <sys/mutex.h>
+#include <sys/nv.h>
+#include <sys/cnv.h>
#include <net/bpf.h>
#include <net/if.h>
@@ -297,6 +299,8 @@ struct bridge_softc {
struct ether_addr sc_defaddr; /* Default MAC address */
if_input_fn_t sc_if_input; /* Saved copy of if_input */
struct epoch_context sc_epoch_ctx;
+ ether_vlanid_t sc_defpvid; /* default pvid */
+ ifbr_flags_t sc_flags; /* bridge flags */
};
VNET_DEFINE_STATIC(struct sx, bridge_list_sx);
@@ -414,6 +418,8 @@ static int bridge_ioctl_grte(struct bridge_softc *, void *);
static int bridge_ioctl_gifsstp(struct bridge_softc *, void *);
static int bridge_ioctl_sproto(struct bridge_softc *, void *);
static int bridge_ioctl_stxhc(struct bridge_softc *, void *);
+static int bridge_nvcall(struct bridge_softc *, const struct ifdrv *,
+ bool is_priv);
static int bridge_pfil(struct mbuf **, struct ifnet *, struct ifnet *,
int);
#ifdef INET
@@ -986,6 +992,26 @@ bridge_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
case SIOCGDRVSPEC:
case SIOCSDRVSPEC:
+ if (ifd->ifd_cmd == BRDGNVCALL) {
+ bool is_priv;
+
+ /*
+ * The new nv-based configuration interface.
+ *
+ * SIOCGDRVSPEC means a read-only request which any user
+ * can perform. SIOCSDRVSPEC requires PRIV_NET_BRIDGE.
+ */
+ is_priv = (cmd == SIOCSDRVSPEC);
+ if (is_priv) {
+ error = priv_check(td, PRIV_NET_BRIDGE);
+ if (error)
+ break;
+ }
+
+ error = bridge_nvcall(sc, ifd, is_priv);
+ break;
+ }
+
if (ifd->ifd_cmd >= bridge_control_table_size) {
error = EINVAL;
break;
@@ -1433,6 +1459,10 @@ bridge_ioctl_add(struct bridge_softc *sc, void *arg)
bif->bif_ifp = ifs;
bif->bif_flags = IFBIF_LEARNING | IFBIF_DISCOVER;
bif->bif_savedcaps = ifs->if_capenable;
+ if (sc->sc_flags & IFBRF_DEFVLANFILTER) {
+ bif->bif_flags |= IFBIF_VLANFILTER;
+ bif->bif_untagged = sc->sc_defpvid;
+ }
/*
* Assign the interface's MAC address to the bridge if it's the first
@@ -4295,3 +4325,386 @@ bridge_linkcheck(struct bridge_softc *sc)
}
if_link_state_change(sc->sc_ifp, new_link);
}
+
+/*
+ * The nv-based configuration API. The user provides a request as a packed
+ * nvlist, which we perform, then we return a new nvlist with the result of
+ * the request. Each request is like a little program and may contain any
+ * number of configuration requests, up to the predefined size limit.
+ *
+ * We distinguish 'get' and 'set' requests based on the ioctl: SIOCGDRVSPEC
+ * is an unprivileged 'get' request and SIOCSDRVSPEC is a privileged 'set'
+ * request. The permission check is done in bridge_ioctl(), so once we get
+ * here we know SIOCSDRVSPEC means the caller is allowed to do that.
+ */
+
+#define ERROUT(e) do { error = (e); goto out; } while (0)
+
+/* Get bridge defpvid.
+ * Arguments: none.
+ * Response: BRNV_DEFPVID: (number) the default pvid.
+ */
+static void
+brnv_getdefpvid(struct bridge_softc *sc, const nvlist_t *args, nvlist_t *reply)
+{
+ if (args != NULL && !nvlist_empty(args)) {
+ nvlist_add_stringf(reply, BRNV_ERROR, "invalid arguments");
+ return;
+ }
+
+ nvlist_add_number(reply, BRNV_DEFPVID, sc->sc_defpvid);
+}
+
+/* Set bridge defpvid (privileged).
+ * Arguments: BRNV_DEFPVID: (number) the new default pvid
+ * Response: none.
+ */
+static void
+brnv_setdefpvid(struct bridge_softc *sc, const nvlist_t *args, nvlist_t *reply)
+{
+ uint64_t defpvid;
+
+ if (args == NULL) {
+ nvlist_add_stringf(reply, BRNV_ERROR, "missing arguments");
+ return;
+ }
+
+ if (!nvlist_exists_number(args, BRNV_DEFPVID)) {
+ nvlist_add_stringf(reply, BRNV_ERROR,
+ "missing %s", BRNV_DEFPVID);
+ return;
+ }
+
+ defpvid = nvlist_get_number(args, BRNV_DEFPVID);
+
+ if (defpvid > DOT1Q_VID_MAX) {
+ nvlist_add_stringf(reply, BRNV_ERROR,
+ "defpvid may not be larger than %d", DOT1Q_VID_MAX);
+ return;
+ }
+
+ sc->sc_defpvid = (ether_vlanid_t)defpvid;
+}
+
+/* Get bridge flags.
+ * Arguments: none.
+ * Response: BRNV_BRFLAGS (number): the bridge flags.
+ */
+static void
+brnv_getbrflags(struct bridge_softc *sc, const nvlist_t *args, nvlist_t *reply)
+{
+ if (args != NULL && !nvlist_empty(args)) {
+ nvlist_add_stringf(reply, BRNV_ERROR, "invalid request");
+ return;
+ }
+
+ nvlist_add_number(reply, BRNV_BRFLAGS, sc->sc_flags);
+}
+
+/* Modify bridge flags (privileged).
+ * Arguments: BRNV_SETFLAGS: (number) flags to be set
+ * BRNV_CLRFLAGS: (number) flags to be cleared
+ * It is an error to specify the same flag in both arguments.
+ * Response: none.
+ */
+static void
+brnv_modbrflags(struct bridge_softc *sc, const nvlist_t *args, nvlist_t *reply)
+{
+ uint64_t i;
+ ifbr_flags_t setflags, clrflags;
+
+ setflags = clrflags = 0;
+
+ /*
+ * Technically we could allow a null or empty args, but since it would
+ * always be a no-op, it's user error, so reject it.
+ */
+ if (args == NULL || nvlist_empty(args)) {
+ nvlist_add_stringf(reply, BRNV_ERROR, "missing arguments");
+ return;
+ }
+
+ if (nvlist_exists_number(args, BRNV_SETFLAGS)) {
+ i = nvlist_get_number(args, BRNV_SETFLAGS);
+ if (i > (ifbr_flags_t)-1) {
+ nvlist_add_stringf(reply, BRNV_ERROR, "%s: too large",
+ BRNV_SETFLAGS);
+ return;
+ }
+
+ setflags = (ifbr_flags_t)i;
+ }
+
+ if (nvlist_exists_number(args, BRNV_CLRFLAGS)) {
+ i = nvlist_get_number(args, BRNV_CLRFLAGS);
+ if (i > (ifbr_flags_t)-1) {
+ nvlist_add_stringf(reply, BRNV_ERROR, "%s: too large",
+ BRNV_CLRFLAGS);
+ return;
+ }
+
+ clrflags = (ifbr_flags_t)i;
+ }
+
+ if ((setflags & clrflags) != 0) {
+ nvlist_add_stringf(reply, BRNV_ERROR,
+ "'%s' and '%s' must be a disjoint set",
+ BRNV_SETFLAGS, BRNV_CLRFLAGS);
+ return;
+ }
+
+ sc->sc_flags &= ~clrflags;
+ sc->sc_flags |= setflags;
+}
+
+/*
+ * A list of requests we support.
+ */
+typedef void (*bridge_nvhdlr_t) (struct bridge_softc *sc,
+ const nvlist_t *args, nvlist_t *reply);
+
+static const struct bridge_nvreq {
+ const char *nvr_name; /* Request name, BRNV_RQ_* */
+ bool nvr_is_priv; /* If the request requires privs */
+ bridge_nvhdlr_t nvr_handler; /* Request handler */
+} bridge_nvreqs[] = {
+ { BRNV_GETDEFPVID, false, brnv_getdefpvid },
+ { BRNV_SETDEFPVID, true, brnv_setdefpvid },
+ { BRNV_GETBRFLAGS, false, brnv_getbrflags },
+ { BRNV_MODBRFLAGS, true, brnv_modbrflags },
+};
+
+/*
+ * Find a request by name. If no request of the given name is found,
+ * return ENOENT.
+ */
+static int
+bridge_nv_findreq(const char *name, const struct bridge_nvreq **ret)
+{
+ printf("bridge_nv_findreq: looking for [%s]\n", name);
+ for (size_t i = 0; i < nitems(bridge_nvreqs); ++i) {
+ const struct bridge_nvreq *nvreq = &bridge_nvreqs[i];
+
+ if (strcmp(name, nvreq->nvr_name) == 0) {
+ printf("found it\n");
+ *ret = nvreq;
+ return (0);
+ }
+ }
+
+ printf("did not find it\n");
+ return (ENOENT);
+}
+
+/*
+ * Find and invoke the handler for the given request name. The response
+ * will be added to the given reply with the same key. If a reply already
+ * exists, return an error and the request is not processed.
+ */
+static int
+bridge_nv_invoke(struct bridge_softc *sc, const char *name,
+ const nvlist_t *args, nvlist_t *reply, bool is_priv)
+{
+ const struct bridge_nvreq *nvreq;
+ nvlist_t *ret = NULL;
+ int error;
+
+ /*
+ * If the request name already exists in the response, then this is a
+ * duplicate request, which is an error: each distinct request may
+ * only be specified once.
+ */
+ if (nvlist_exists(reply, name))
+ ERROUT(EINVAL);
+
+ /* Find the handler */
+ error = bridge_nv_findreq(name, &nvreq);
+ if (error)
+ goto out;
+
+ /* Check if this request requires privilege */
+ if (nvreq->nvr_is_priv && !is_priv)
+ ERROUT(EPERM);
+
+ /* Create the response nv */
+ if ((ret = nvlist_create(0)) == NULL)
+ ERROUT(ENOMEM);
+
+ /*
+ * Invoke the handler and attach its output to the reply list. This
+ * never fails, because the handler indicates an error by setting a
+ * key in the reply.
+ */
+ nvreq->nvr_handler(sc, args, ret);
+
+ nvlist_add_nvlist(reply, name, ret);
+ return (0);
+
+out:
+ if (ret)
+ nvlist_destroy(ret);
+ return (error);
+}
+
+/* Find the handler(s) for the request and invoke them */
+static int
+bridge_nv_dispatch(struct bridge_softc *sc, const nvlist_t *request,
+ nvlist_t *reply, bool is_priv)
+{
+ void *cookie;
+ const char *name;
+ int type;
+
+ cookie = NULL;
+ while ((name = nvlist_next(request, &type, &cookie)) != NULL) {
+ const nvlist_t *args;
+ int error;
+
+ /*
+ * Extract the request. To avoid the caller having to create
+ * a large number of empty nvlists, we allow null to mean no
+ * request arguments.
+ */
+ if (type == NV_TYPE_NVLIST)
+ args = cnvlist_get_nvlist(cookie);
+ else if (type == NV_TYPE_NULL)
+ args = NULL;
+ else
+ return (EINVAL);
+
+ /* Dispatch the handler */
+ error = bridge_nv_invoke(sc, name, args, reply, is_priv);
+
+ if (error == 0)
+ error = nvlist_error(reply);
+ if (error)
+ return (error);
+ }
+
+ return (0);
+}
+
+/* Fetch the nvcall from the ioctl data */
+static int
+bridge_nv_getnvcall(const struct ifdrv *ifd, struct ifb_nvcall *nvcall)
+{
+ if (ifd->ifd_len != sizeof(*nvcall))
+ return (EINVAL);
+
+ return (copyin(ifd->ifd_data, nvcall, sizeof(*nvcall)));
+}
+
+/* Fetch the request from the nvcall */
+static int
+bridge_nv_getrequest(const struct ifb_nvcall *nvcall, nvlist_t **reqp)
+{
+ void *nvbuf;
+ int error;
+
+ nvbuf = NULL;
+ error = 0;
+
+ /* Check the request size */
+ if (nvcall->ifbnv_inlen > BRNV_MAXSIZE)
+ ERROUT(E2BIG);
+
+ /* Copy the request in */
+ nvbuf = malloc(nvcall->ifbnv_inlen, M_NVLIST, M_WAITOK);
+ error = copyin(nvcall->ifbnv_data, nvbuf, nvcall->ifbnv_inlen);
+ if (error)
+ goto out;
+
+ /* Unpack the request */
+ *reqp = nvlist_unpack(nvbuf, nvcall->ifbnv_inlen, 0);
+ if (*reqp == NULL)
+ ERROUT(EINVAL);
+
+out:
+ free(nvbuf, M_NVLIST);
+ return (error);
+}
+
+/* Write the reply back to the nvcall */
+static int
+bridge_nv_setreply(const struct ifdrv *ifd, struct ifb_nvcall *nvcall,
+ nvlist_t *reply)
+{
+ void *nvbuf;
+ int error;
+
+ nvbuf = NULL;
+ error = 0;
+
+ /* Check the reply size */
+ nvcall->ifbnv_outlen = nvlist_size(reply);
+ if (nvcall->ifbnv_outlen > nvcall->ifbnv_buflen) {
+ /*
+ * Tell the caller how much space they need, but add some
+ * padding to the size as it might change before the next
+ * call.
+ */
+ nvcall->ifbnv_outlen = (10 * nvcall->ifbnv_outlen) / 8;
+ error = copyout(nvcall, ifd->ifd_data, sizeof(*nvcall));
+ if (error)
+ goto out;
+ ERROUT(ENOSPC);
+ }
+
+ /* Pack the reply */
+ nvbuf = nvlist_pack(reply, &nvcall->ifbnv_outlen);
+ if (nvbuf == NULL)
+ ERROUT(ENOMEM);
+
+ /* Copy out the reply, then copy out the new nvcall */
+ error = copyout(nvbuf, nvcall->ifbnv_data, nvcall->ifbnv_outlen);
+ if (error)
+ goto out;
+
+ error = copyout(nvcall, ifd->ifd_data, sizeof(*nvcall));
+ if (error)
+ goto out;
+
+out:
+ free(nvbuf, M_NVLIST);
+ return (error);
+}
+
+/*
+ * The actual nvcall handler. If is_priv, then the caller is privileged.
+ */
+static int
+bridge_nvcall(struct bridge_softc *sc, const struct ifdrv *ifd, bool is_priv)
+{
+ struct ifb_nvcall nvcall;
+ nvlist_t *request, *reply;
+ int error;
+
+ error = 0;
+ request = reply = NULL;
+ /* Get the nvcall struct */
+ if ((error = bridge_nv_getnvcall(ifd, &nvcall)) != 0)
+ ERROUT(error);
+
+ /* Unpack the request */
+ if ((error = bridge_nv_getrequest(&nvcall, &request)) != 0)
+ ERROUT(error);
+
+ /* Handle the request */
+ reply = nvlist_create(0);
+ if (reply == NULL)
+ ERROUT(ENOMEM);
+
+ if ((error = bridge_nv_dispatch(sc, request, reply, is_priv)) != 0)
+ ERROUT(error);
+
+ /* Copy the reply out */
+ if ((error = bridge_nv_setreply(ifd, &nvcall, reply)) != 0)
+ ERROUT(error);
+
+out:
+ if (request)
+ nvlist_destroy(request);
+ if (reply)
+ nvlist_destroy(reply);
+ return (error);
+}
diff --git a/sys/net/if_bridgevar.h b/sys/net/if_bridgevar.h
index 97b63e3d4416..9b477609695b 100644
--- a/sys/net/if_bridgevar.h
+++ b/sys/net/if_bridgevar.h
@@ -127,6 +127,110 @@
#define BRDGSIFUNTAGGED 31 /* set if untagged vlan */
#define BRDGSIFVLANSET 32 /* set if vlan set */
#define BRDGGIFVLANSET 33 /* get if vlan set */
+#define BRDGNVCALL 34 /* nv(9) based configuration request */
+
+/*
+ * Maximum size of an nv call. 16kB should be more than enough; larger
+ * requests can be split into multiple calls.
+ */
+#define BRNV_MAXSIZE (1024 * 16)
+
+/* Request/reply structure for BRDGNVCALL. */
+struct ifb_nvcall {
+ /*
+ * Address of the buffer. The request is read from here, and the
+ * reply is written back here.
+ */
+ void *ifbnv_data;
+
+ /* Size of the buffer. This should be set by the caller. */
+ size_t ifbnv_buflen;
+
+ /*
+ * Size of the request. This should be set by the caller, and may
+ * not be larger than BRNV_MAXSIZE. If the request is too large,
+ * E2BIG will be returned.
+ */
+ size_t ifbnv_inlen;
+
+ /*
+ * Size of the response. This is set by the bridge. If the response
+ * would require more than ifbnv_buflen bytes, ENOSPC will be returned
+ * and ifbnv_outlen will be set to the required buffer size.
+ *
+ * If ifbnv_outlen is returned as 0, then the reply was empty and the
+ * contents of ifbnv_data are unchanged.
+ */
+ size_t ifbnv_outlen;
+};
+
+/*
+ * Requests for BRDGNVCALL.
+ *
+ * Each request should be provided as an nvlist key, with the arguments
+ * as the value. If the request takes no arguments, the argument will
+ * be null.
+ *
+ * Each named request generates one key in the reply with the same name,
+ * containing the result of the operation. If the operation does not
+ * have any output (e.g., set-only operations) then the result may be
+ * null, otherwise it will be an nvlist.
+ *
+ * If the operation failed, the nvlist will contain a string key called
+ * 'error' with a human-readable error value. At this point, processing
+ * will stop and and subsequent requests will not be reflected in the
+ * output.
+ *
+ * Example:
+ *
+ * Request: { "get-defpvid": null,
+ * "set-defpvid": 5000,
+ * "get-brflags": null }
+ * Response: { "get-defpvid": { "defpvid": 1 },
+ * "set-defpvid": { "error": "invalid pvid" }}
+ *
+ * "get-brflags" is missing from the output since processing stopped when
+ * "set-defpvid" failed.
+ */
+
+#define BRNV_ERROR "error" /* error key name */
+
+/* Get bridge defpvid.
+ * Arguments: none
+ * Response: BRNV_DEFPVID: (number) the default pvid.
+ */
+#define BRNV_GETDEFPVID "get-defpvid"
+#define BRNV_DEFPVID "defpvid"
+
+/* Set bridge defpvid (privileged).
+ * Arguments: BRNV_DEFPVID: (number) the new default pvid
+ * Response: none.
+ */
+#define BRNV_SETDEFPVID "set-defpvid"
+
+/* Get bridge flags.
+ * Arguments: none.
+ * Response: BRNV_BRFLAGS: (number) the bridge flags.
+ */
+#define BRNV_GETBRFLAGS "get-brflags"
+#define BRNV_BRFLAGS "brflags"
+
+/* Modify bridge flags (privileged).
+ * Arguments: BRNV_SETFLAGS: (number) flags to be set
+ * BRNV_CLRFLAGS: (number) flags to be cleared
+ * It is an error to specify the same flag in both arguments.
+ * Response: none.
+ */
+#define BRNV_MODBRFLAGS "modify-brflags"
+#define BRNV_SETFLAGS "set"
+#define BRNV_CLRFLAGS "clear"
+
+/* Bridge flags */
+#define IFBRF_DEFVLANFILTER 0x0001 /* set vlan filtering on new ports */
+
+#define IFBRFBITS "\020\01DEFVLANFILTER"
+
+typedef uint32_t ifbr_flags_t;
/*
* Generic bridge control request.
@@ -238,6 +342,8 @@ struct ifbrparam {
#define ifbrp_maxage ifbrp_ifbrpu.ifbrpu_int8 /* max age (sec) */
#define ifbrp_cexceeded ifbrp_ifbrpu.ifbrpu_int32 /* # of cache dropped
* adresses */
+#define ifbrp_flags ifbrp_ifbrpu.ifbrpu_int32 /* flags */
+
/*
* Bridge current operational parameters structure.
*/