Page MenuHomeFreeBSD

D58938.1794250284.diff
No OneTemporary

Authored By
Unknown
Size
13 KB
Referenced Files
None
Subscribers
None

D58938.1794250284.diff

diff --git a/sbin/ifconfig/Makefile b/sbin/ifconfig/Makefile
--- a/sbin/ifconfig/Makefile
+++ b/sbin/ifconfig/Makefile
@@ -28,6 +28,7 @@
SRCS+= ifmac.c # MAC support
SRCS+= ifmedia.c # SIOC[GS]IFMEDIA support
SRCS+= iffib.c # non-default FIB support
+SRCS+= ifrss.c # SIOC[GS]IFRSS* support
SRCS+= ifvlan.c # SIOC[GS]ETVLAN support
SRCS+= ifvxlan.c # VXLAN support
SRCS+= ifgre.c # GRE keys etc
diff --git a/sbin/ifconfig/ifconfig.8 b/sbin/ifconfig/ifconfig.8
--- a/sbin/ifconfig/ifconfig.8
+++ b/sbin/ifconfig/ifconfig.8
@@ -28,7 +28,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd September 10, 2026
+.Dd September 19, 2026
.Dt IFCONFIG 8
.Os
.Sh NAME
@@ -325,6 +325,11 @@
Display only the interfaces that are up.
.It Fl v
Get more verbose status for an interface.
+When the driver reports them, this includes the receive-side scaling hash
+function and types, the hash key and the indirection table; see
+.Cm rsskey
+and
+.Cm rsstable .
When supported, this includes the status of configured SR-IOV virtual
functions.
Fields that the PF driver cannot observe are omitted; an omitted field does
@@ -705,6 +710,40 @@
The
.Ar name
may not be longer than 15 characters.
+.It Cm rsskey Ar key
+Program the receive-side scaling hash key of the interface.
+.Ar key
+is an even number of hexadecimal digits; the driver decides which key
+lengths its hardware accepts.
+An interface nobody has configured uses the key the kernel programs into
+every device, so setting a different key makes this interface alone
+diverge from it.
+The kernel's key is reported by the
+.Va net.inet.rss.key
+sysctl, and programming that value back undoes the divergence.
+A programmed key is reapplied whenever the driver reinitialises the
+device.
+.It Cm rsstable Ar queues
+Program the receive-side scaling indirection table of the interface,
+which selects the receive queue for each hash value.
+.Ar queues
+is
+.Cm default
+to spread the table evenly over every receive queue in turn, or a comma
+separated list of receive queue numbers and
+.Ar first Ns - Ns Ar last
+ranges, which the table entries are assigned to in turn.
+Naming a single queue therefore sends every hashed flow to that queue,
+which is useful when testing; traffic a driver steers outside the table
+is unaffected, see the driver's manual page.
+A programmed table is reapplied whenever the driver reinitialises the
+device, so the driver's own table is not restored by a down and up.
+.Pp
+Both commands require
+.Xr priv 9
+.Dv PRIV_NET_HWIOCTL ,
+which a jail receives only with its own network stack, and neither
+changes how the network stack itself hashes received packets.
.It Cm rxcsum , txcsum , rxcsum6 , txcsum6
If the driver supports user-configurable checksum offloading,
enable receive (or transmit) checksum offloading on the interface.
@@ -3638,6 +3677,14 @@
.Pp
Set a randomly-generated MAC address on tap0:
.Dl # ifconfig tap0 ether random
+.Pp
+Send every hashed flow received on
+.Li aq0
+to receive queue 3, then spread flows over every queue again:
+.Bd -literal -offset indent
+ifconfig aq0 rsstable 3
+ifconfig aq0 rsstable default
+.Ed
.Sh DIAGNOSTICS
Messages indicating the specified interface does not exist, the
requested address is unknown, or the user is not privileged and
diff --git a/sbin/ifconfig/ifrss.c b/sbin/ifconfig/ifrss.c
new file mode 100644
--- /dev/null
+++ b/sbin/ifconfig/ifrss.c
@@ -0,0 +1,282 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Nick Price <nprice@FreeBSD.org>
+ */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+
+#include <net/if.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ifconfig.h"
+
+static const struct {
+ uint32_t type;
+ const char *name;
+} rss_type_names[] = {
+ { RSS_TYPE_IPV4, "ipv4" },
+ { RSS_TYPE_TCP_IPV4, "tcp4" },
+ { RSS_TYPE_UDP_IPV4, "udp4" },
+ { RSS_TYPE_IPV6, "ipv6" },
+ { RSS_TYPE_IPV6_EX, "ipv6ex" },
+ { RSS_TYPE_TCP_IPV6, "tcp6" },
+ { RSS_TYPE_TCP_IPV6_EX, "tcp6ex" },
+ { RSS_TYPE_UDP_IPV6, "udp6" },
+ { RSS_TYPE_UDP_IPV6_EX, "udp6ex" },
+};
+
+static const char *
+rss_func_name(uint8_t func)
+{
+ switch (func) {
+ case RSS_FUNC_NONE:
+ return ("none");
+ case RSS_FUNC_PRIVATE:
+ return ("private");
+ case RSS_FUNC_TOEPLITZ:
+ return ("toeplitz");
+ default:
+ return ("unknown");
+ }
+}
+
+static bool
+rss_print_hash(if_ctx *ctx)
+{
+ struct ifrsshash ifrh = {};
+ const char *sep;
+ size_t i;
+
+ strlcpy(ifrh.ifrh_name, ctx->ifname, sizeof(ifrh.ifrh_name));
+ if (ioctl_ctx(ctx, SIOCGIFRSSHASH, (caddr_t)&ifrh) < 0)
+ return (false);
+
+ printf("\trss: %s", rss_func_name(ifrh.ifrh_func));
+ sep = " types ";
+ for (i = 0; i < nitems(rss_type_names); i++) {
+ if ((ifrh.ifrh_types & rss_type_names[i].type) == 0)
+ continue;
+ printf("%s%s", sep, rss_type_names[i].name);
+ sep = ",";
+ }
+ printf("\n");
+ return (true);
+}
+
+static void
+rss_print_key(if_ctx *ctx)
+{
+ struct ifrsskey ifrk = {};
+ uint16_t i;
+
+ strlcpy(ifrk.ifrk_name, ctx->ifname, sizeof(ifrk.ifrk_name));
+ if (ioctl_ctx(ctx, SIOCGIFRSSKEY, (caddr_t)&ifrk) < 0)
+ return;
+ if (ifrk.ifrk_keylen > sizeof(ifrk.ifrk_key))
+ return;
+
+ printf("\trss key:");
+ for (i = 0; i < ifrk.ifrk_keylen; i++) {
+ if (i % 20 == 0)
+ printf("\n\t ");
+ printf("%02x", ifrk.ifrk_key[i]);
+ }
+ printf("\n");
+}
+
+static void
+rss_print_table(if_ctx *ctx)
+{
+ struct ifrsstable ifrt = {};
+ unsigned int max;
+ uint16_t i;
+ int width;
+
+ strlcpy(ifrt.ifrt_name, ctx->ifname, sizeof(ifrt.ifrt_name));
+ if (ioctl_ctx(ctx, SIOCGIFRSSTABLE, (caddr_t)&ifrt) < 0)
+ return;
+ if (ifrt.ifrt_nentries > nitems(ifrt.ifrt_table))
+ return;
+
+ printf("\trss table: %u entries, %u queue%s", ifrt.ifrt_nentries,
+ ifrt.ifrt_nqueues, ifrt.ifrt_nqueues == 1 ? "" : "s");
+ width = 2;
+ for (max = ifrt.ifrt_nqueues > 0 ? ifrt.ifrt_nqueues - 1 : 0;
+ max >= 10; max /= 10)
+ width++;
+ for (i = 0; i < ifrt.ifrt_nentries; i++) {
+ if (i % 32 == 0)
+ printf("\n\t ");
+ printf("%*u", width, ifrt.ifrt_table[i]);
+ }
+ printf("\n");
+}
+
+static void
+rss_status(if_ctx *ctx)
+{
+
+ if (ctx->args->verbose == 0)
+ return;
+
+ if (!rss_print_hash(ctx))
+ return;
+ rss_print_key(ctx);
+ rss_print_table(ctx);
+}
+
+static u_int
+rss_parse_queues(const char *spec, uint16_t *queues, u_int maxqueues)
+{
+ char *copy, *tok, *next;
+ u_int count;
+
+ copy = strdup(spec);
+ if (copy == NULL)
+ err(1, "strdup");
+
+ count = 0;
+ next = copy;
+ while ((tok = strsep(&next, ",")) != NULL) {
+ u_long first, last;
+ char *ep;
+
+ if (*tok == '\0')
+ errx(1, "rsstable: empty queue in \"%s\"", spec);
+ /* strtoul() takes a sign or blanks; a queue is digits. */
+ if (!isdigit((unsigned char)*tok))
+ errx(1, "rsstable: \"%s\" is not a queue number", tok);
+ first = strtoul(tok, &ep, 10);
+ last = first;
+ if (*ep == '-') {
+ tok = ep + 1;
+ if (!isdigit((unsigned char)*tok))
+ errx(1, "rsstable: bad range \"%s\"", spec);
+ last = strtoul(tok, &ep, 10);
+ }
+ if (*ep != '\0')
+ errx(1, "rsstable: trailing junk in \"%s\"", tok);
+ if (first > last)
+ errx(1, "rsstable: range %lu-%lu is reversed", first,
+ last);
+ if (last > UINT16_MAX)
+ errx(1, "rsstable: queue %lu is too large", last);
+ for (; first <= last; first++) {
+ if (count >= maxqueues)
+ errx(1, "rsstable: too many queues listed");
+ queues[count++] = (uint16_t)first;
+ }
+ }
+ free(copy);
+
+ if (count == 0)
+ errx(1, "rsstable: no queues given");
+ return (count);
+}
+
+static void
+setrsstable(if_ctx *ctx, const char *val, int dummy __unused)
+{
+ struct ifrsstable ifrt = {};
+ uint16_t queues[RSS_TABLELEN];
+ u_int nqueues;
+ uint16_t i;
+
+ nqueues = 0;
+ if (strcmp(val, "default") != 0)
+ nqueues = rss_parse_queues(val, queues, nitems(queues));
+
+ strlcpy(ifrt.ifrt_name, ctx->ifname, sizeof(ifrt.ifrt_name));
+ if (ioctl_ctx(ctx, SIOCGIFRSSTABLE, (caddr_t)&ifrt) < 0)
+ err(1, "ioctl (SIOCGIFRSSTABLE)");
+ if (ifrt.ifrt_nqueues == 0)
+ errx(1, "rsstable: %s has no receive queues", ctx->ifname);
+ if (ifrt.ifrt_nentries > nitems(ifrt.ifrt_table))
+ errx(1, "rsstable: kernel reported %u entries",
+ ifrt.ifrt_nentries);
+
+ if (nqueues == 0) {
+ nqueues = MIN(ifrt.ifrt_nqueues, nitems(queues));
+ for (i = 0; i < nqueues; i++)
+ queues[i] = i;
+ } else {
+ for (i = 0; i < nqueues; i++) {
+ if (queues[i] >= ifrt.ifrt_nqueues)
+ errx(1, "rsstable: queue %u out of range "
+ "(0..%u)", queues[i],
+ ifrt.ifrt_nqueues - 1);
+ }
+ }
+
+ for (i = 0; i < ifrt.ifrt_nentries; i++)
+ ifrt.ifrt_table[i] = queues[i % nqueues];
+
+ if (ioctl_ctx(ctx, SIOCSIFRSSTABLE, (caddr_t)&ifrt) < 0)
+ err(1, "ioctl (SIOCSIFRSSTABLE)");
+}
+
+static uint8_t
+rss_hexval(char digit)
+{
+
+ if (!isxdigit((unsigned char)digit))
+ errx(1, "rsskey: \"%c\" is not a hex digit", digit);
+ return (digittoint((unsigned char)digit));
+}
+
+static void
+setrsskey(if_ctx *ctx, const char *val, int dummy __unused)
+{
+ struct ifrsskey ifrk = {};
+ size_t digits, i;
+
+ digits = strlen(val);
+ if (digits == 0 || (digits % 2) != 0)
+ errx(1, "rsskey: expected an even number of hex digits");
+ if (digits / 2 > sizeof(ifrk.ifrk_key))
+ errx(1, "rsskey: key exceeds %zu bytes",
+ sizeof(ifrk.ifrk_key));
+
+ for (i = 0; i < digits / 2; i++) {
+ ifrk.ifrk_key[i] = (uint8_t)((rss_hexval(val[i * 2]) << 4) |
+ rss_hexval(val[i * 2 + 1]));
+ }
+ ifrk.ifrk_func = RSS_FUNC_TOEPLITZ;
+ ifrk.ifrk_keylen = (uint16_t)(digits / 2);
+
+ strlcpy(ifrk.ifrk_name, ctx->ifname, sizeof(ifrk.ifrk_name));
+ if (ioctl_ctx(ctx, SIOCSIFRSSKEY, (caddr_t)&ifrk) < 0)
+ err(1, "ioctl (SIOCSIFRSSKEY)");
+}
+
+static struct cmd rss_cmds[] = {
+ DEF_CMD_ARG("rsstable", setrsstable),
+ DEF_CMD_ARG("rsskey", setrsskey),
+};
+
+static struct afswtch af_rss = {
+ .af_name = "af_rss",
+ .af_af = AF_UNSPEC,
+ .af_other_status = rss_status,
+};
+
+static __constructor void
+rss_ctor(void)
+{
+ size_t i;
+
+ for (i = 0; i < nitems(rss_cmds); i++)
+ cmd_register(&rss_cmds[i]);
+ af_register(&af_rss);
+}
diff --git a/sbin/ifconfig/tests/Makefile b/sbin/ifconfig/tests/Makefile
--- a/sbin/ifconfig/tests/Makefile
+++ b/sbin/ifconfig/tests/Makefile
@@ -1,6 +1,7 @@
NETBSD_ATF_TESTS_SH= nonexistent_test
ATF_TESTS_SH+= ifconfig \
- inet6
+ inet6 \
+ rss
TEST_METADATA+= execenv="jail"
TEST_METADATA+= execenv_jail_params="vnet allow.raw_sockets"
diff --git a/sbin/ifconfig/tests/rss.sh b/sbin/ifconfig/tests/rss.sh
new file mode 100644
--- /dev/null
+++ b/sbin/ifconfig/tests/rss.sh
@@ -0,0 +1,138 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2026 Nick Price <nprice@FreeBSD.org>
+
+. $(atf_get_srcdir)/../../sys/common/vnet.subr
+
+atf_test_case "rss_status" "cleanup"
+rss_status_head()
+{
+ atf_set descr "Verbose status is silent for an interface without RSS"
+ atf_set require.user root
+}
+rss_status_body()
+{
+ local epair
+
+ vnet_init
+
+ epair=$(vnet_mkepair)
+ atf_check -s exit:0 -o not-match:'rss' ifconfig -v ${epair}a
+}
+rss_status_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "rsskey_syntax" "cleanup"
+rsskey_syntax_head()
+{
+ atf_set descr "rsskey rejects malformed keys before any ioctl"
+ atf_set require.user root
+}
+rsskey_syntax_body()
+{
+ local epair longkey
+
+ vnet_init
+
+ epair=$(vnet_mkepair)
+ atf_check -s not-exit:0 -e match:'even number of hex digits' \
+ ifconfig ${epair}a rsskey abc
+ atf_check -s not-exit:0 -e match:'even number of hex digits' \
+ ifconfig ${epair}a rsskey ''
+ atf_check -s not-exit:0 -e match:'not a hex digit' \
+ ifconfig ${epair}a rsskey zz
+ longkey=$(jot -b 0 -s '' 258)
+ atf_check -s not-exit:0 -e match:'exceeds 128 bytes' \
+ ifconfig ${epair}a rsskey ${longkey}
+}
+rsskey_syntax_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "rsstable_syntax" "cleanup"
+rsstable_syntax_head()
+{
+ atf_set descr "rsstable rejects malformed queue lists before any ioctl"
+ atf_set require.user root
+}
+rsstable_syntax_body()
+{
+ local epair
+
+ vnet_init
+
+ epair=$(vnet_mkepair)
+ atf_check -s not-exit:0 -e match:'not a queue number' \
+ ifconfig ${epair}a rsstable q
+ atf_check -s not-exit:0 -e match:'empty queue' \
+ ifconfig ${epair}a rsstable '0,,1'
+ atf_check -s not-exit:0 -e match:'is reversed' \
+ ifconfig ${epair}a rsstable '3-1'
+ atf_check -s not-exit:0 -e match:'trailing junk' \
+ ifconfig ${epair}a rsstable '0x'
+ atf_check -s not-exit:0 -e match:'is too large' \
+ ifconfig ${epair}a rsstable 65536
+ # strtoul(3) would take a sign, and a large negative wraps to a small
+ # queue number.
+ atf_check -s not-exit:0 -e match:'not a queue number' \
+ ifconfig ${epair}a rsstable -18446744073709551615
+ atf_check -s not-exit:0 -e match:'not a queue number' \
+ ifconfig ${epair}a rsstable +1
+ atf_check -s not-exit:0 -e match:'bad range' \
+ ifconfig ${epair}a rsstable '0--1'
+}
+rsstable_syntax_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "rss_unsupported" "cleanup"
+rss_unsupported_head()
+{
+ atf_set descr "A well formed request fails on an interface without RSS"
+ atf_set require.user root
+}
+rss_unsupported_body()
+{
+ local epair
+
+ vnet_init
+
+ epair=$(vnet_mkepair)
+ atf_check -s not-exit:0 -e match:'SIOCSIFRSSKEY' \
+ ifconfig ${epair}a rsskey 00112233
+ atf_check -s not-exit:0 -e match:'SIOCGIFRSSTABLE' \
+ ifconfig ${epair}a rsstable 0
+}
+rss_unsupported_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "rss_unprivileged"
+rss_unprivileged_head()
+{
+ atf_set descr "An unprivileged caller may not program RSS"
+ atf_set require.user root
+ atf_set require.config unprivileged_user
+}
+rss_unprivileged_body()
+{
+ local user
+
+ user=$(atf_config_get unprivileged_user)
+ atf_check -s not-exit:0 -e match:'Operation not permitted' \
+ su -m ${user} -c 'ifconfig lo0 rsskey 00112233'
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case rss_status
+ atf_add_test_case rsskey_syntax
+ atf_add_test_case rsstable_syntax
+ atf_add_test_case rss_unsupported
+ atf_add_test_case rss_unprivileged
+}

File Metadata

Mime Type
text/plain
Expires
Mon, Nov 9, 6:51 PM (12 h, 51 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41618044
Default Alt Text
D58938.1794250284.diff (13 KB)

Event Timeline