Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F177380271
D58938.1794250284.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
13 KB
Referenced Files
None
Subscribers
None
D58938.1794250284.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D58938: [FOR TESTING ONLY] ifconfig: report and set the RSS configuration
Attached
Detach File
Event Timeline