diff options
| author | Lexi Winter <ivy@FreeBSD.org> | 2025-05-29 07:58:04 +0100 |
|---|---|---|
| committer | Lexi Winter <ivy@FreeBSD.org> | 2025-05-31 05:33:28 +0100 |
| commit | 177b4442a8b027ca757aeeded324519554447139 (patch) | |
| tree | c9347ed9b5ad9fc03f7bba28d4cf5ee711463b99 | |
| parent | 9f28d8db26a65f7ac845a8edd5c6455900184640 (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.h | 14 | ||||
| -rw-r--r-- | lib/libifconfig/libifconfig_bridge.c | 99 | ||||
| -rw-r--r-- | sbin/ifconfig/ifbridge.c | 89 | ||||
| -rw-r--r-- | sbin/ifconfig/ifconfig.8 | 9 | ||||
| -rw-r--r-- | sys/net/if_bridge.c | 413 | ||||
| -rw-r--r-- | sys/net/if_bridgevar.h | 106 |
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. */ |
