Merge branch 'net-ethtool-make-netdev_rss_key_fill-spread-flows-over-all-queues'

Eric Dumazet says:

====================
net: ethtool: make netdev_rss_key_fill() spread flows over all queues

netdev_rss_key_fill() hands drivers a uniformly random key. Because the
Toeplitz hash is linear over GF(2), a random key is singular for a given
header field and queue count with probability 1/2: flows differing only
in the low order bits of that field (such as a burst of consecutive
ephemeral ports) then cannot reach all RX queues. On one affected 16-queue
host, only 4 queues received traffic until the key was replaced.

Patch 1 synchronizes proc_do_rss_key() with netdev_rss_key_fill() using an
smp_wmb()/smp_rmb() pair and a boolean flag so readers of
/proc/sys/net/core/netdev_rss_key print zero bytes until the key is fully
populated.

Patch 2 keeps the key random but constrains 1008 of its 2048 bits so that,
at every 16-bit aligned position and for every power-of-two queue count up
to 256, consecutive values of a field ending there visit every queue once.
Applying the fixup on the 16-bit grid covers standard 2-tuple/4-tuple
hashes as well as encapsulated or bitmap-selected layouts (such as PSP
inner TCP ports at byte 78) without enumerating them in the core. The
fixup is a bijection onto the set of valid keys, and redraws in the ~1 in
5 case where two 32-bit windows a multiple of 8 bits apart alias.

Patch 3 adds RSS key, indirection table, and flow-hash reporting to
netdevsim so the selftest can exercise both the host key and ethtool
netlink RSS reporting in a virtual machine without hardware.

Patch 4 adds a selftest under tools/testing/selftests/drivers/net/rss_key.py
checking /proc/sys/net/core/netdev_rss_key and the live RSS key,
indirection table, and flow-hash layouts reported by a device via ethtool
netlink.
====================

Link: https://patch.msgid.link/20260922163458.3900996-1-edumazet@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-09-25 16:29:57 -07:00
commit cee5db680d
10 changed files with 591 additions and 11 deletions

View File

@ -48,6 +48,15 @@ count is not a power of two. NICs should provide an indirection table
at least 4 times larger than the queue count. 4x table results in ~16%
imbalance between the queues, which is acceptable for most applications.
The Toeplitz hash is linear over GF(2), so the quality of the hash key
matters as much as its randomness. For a key made of uniformly random
bytes, the q lowest order bits of a given header field fail to spread
flows over all 2^q queues with probability 1/2, and a burst of connections
picking nearly consecutive ephemeral ports then lands on a fraction of the
queues while the others stay idle. The netdev_rss_key_fill() helper draws
a random key that is free of this defect; drivers should use it rather
than seeding a key of their own.
Some NICs support symmetric RSS hashing where, if the IP (source address,
destination address) and TCP/UDP (source port, destination port) tuples
are swapped, the computed hash is the same. This is beneficial in some

View File

@ -205,7 +205,59 @@ static int nsim_get_ts_info(struct net_device *dev,
return 0;
}
static u32 nsim_get_rxfh_key_size(struct net_device *dev)
{
return NETDEV_RSS_KEY_LEN;
}
static u32 nsim_get_rxfh_indir_size(struct net_device *dev)
{
return NSIM_RSS_INDIR_SIZE;
}
static int nsim_get_rxfh(struct net_device *dev,
struct ethtool_rxfh_param *rxfh)
{
struct netdevsim *ns = netdev_priv(dev);
u32 i;
rxfh->hfunc = ETH_RSS_HASH_TOP;
if (rxfh->indir) {
for (i = 0; i < NSIM_RSS_INDIR_SIZE; i++)
rxfh->indir[i] = ethtool_rxfh_indir_default(i, ns->ethtool.channels);
}
if (rxfh->key)
memcpy(rxfh->key, ns->ethtool.rss_key,
sizeof(ns->ethtool.rss_key));
return 0;
}
static int nsim_get_rxfh_fields(struct net_device *dev,
struct ethtool_rxfh_fields *info)
{
switch (info->flow_type) {
case TCP_V4_FLOW:
case UDP_V4_FLOW:
case TCP_V6_FLOW:
case UDP_V6_FLOW:
info->data = RXH_IP_SRC | RXH_IP_DST |
RXH_L4_B_0_1 | RXH_L4_B_2_3;
return 0;
case IPV4_FLOW:
case IPV6_FLOW:
info->data = RXH_IP_SRC | RXH_IP_DST;
return 0;
default:
return -EOPNOTSUPP;
}
}
static const struct ethtool_ops nsim_ethtool_ops = {
.get_rxfh_key_size = nsim_get_rxfh_key_size,
.get_rxfh_indir_size = nsim_get_rxfh_indir_size,
.get_rxfh = nsim_get_rxfh,
.get_rxfh_fields = nsim_get_rxfh_fields,
.supported_coalesce_params = ETHTOOL_COALESCE_ALL_PARAMS,
.supported_ring_params = ETHTOOL_RING_USE_TCP_DATA_SPLIT |
ETHTOOL_RING_USE_HDS_THRS,
@ -250,6 +302,7 @@ void nsim_ethtool_init(struct netdevsim *ns)
ns->ethtool.fec.active_fec = ETHTOOL_FEC_NONE;
ns->ethtool.channels = ns->nsim_bus_dev->num_queues;
netdev_rss_key_fill(ns->ethtool.rss_key, sizeof(ns->ethtool.rss_key));
ethtool = debugfs_create_dir("ethtool", ns->nsim_dev_port->ddir);
ns->ethtool_ddir = ethtool;

View File

@ -87,10 +87,13 @@ struct nsim_ethtool_pauseparam {
bool report_stats_tx;
};
#define NSIM_RSS_INDIR_SIZE 128
struct nsim_ethtool {
u32 get_err;
u32 set_err;
u32 channels;
u8 rss_key[NETDEV_RSS_KEY_LEN];
struct nsim_ethtool_pauseparam pauseparam;
struct ethtool_coalesce coalesce;
struct ethtool_ringparam ring;

View File

@ -5323,7 +5323,6 @@ void netdev_lower_state_changed(struct net_device *lower_dev,
void *lower_state_info);
#define NETDEV_RSS_KEY_LEN 256
extern u8 netdev_rss_key[NETDEV_RSS_KEY_LEN] __read_mostly;
void netdev_rss_key_fill(void *buffer, size_t len);
int skb_checksum_help(struct sk_buff *skb);

View File

@ -95,6 +95,8 @@ extern int netdev_unregister_timeout_secs;
extern int weight_p;
extern int dev_weight_rx_bias;
extern int dev_weight_tx_bias;
extern u8 netdev_rss_key[NETDEV_RSS_KEY_LEN] __read_mostly;
extern bool netdev_rss_key_initialized;
extern struct rw_semaphore dev_addr_sem;

View File

@ -337,10 +337,15 @@ static int proc_do_rss_key(const struct ctl_table *table, int write,
{
char buf[NETDEV_RSS_KEY_LEN * 3];
struct ctl_table fake_table;
bool initialized;
char *pos = buf;
initialized = READ_ONCE(netdev_rss_key_initialized);
/* Pair with smp_wmb() in netdev_rss_key_fill(). */
smp_rmb();
for (int i = 0; i < NETDEV_RSS_KEY_LEN; i++) {
pos = hex_byte_pack(pos, netdev_rss_key[i]);
pos = hex_byte_pack(pos, initialized ? netdev_rss_key[i] : 0);
*pos++ = ':';
}
*(--pos) = 0;

View File

@ -2,6 +2,9 @@
#include <linux/ethtool_netlink.h>
#include <linux/net_tstamp.h>
#include <linux/once.h>
#include <linux/random.h>
#include <linux/unaligned.h>
#include <linux/phy.h>
#include <linux/rtnetlink.h>
#include <linux/ptp_clock_kernel.h>
@ -1400,3 +1403,174 @@ enum ethtool_link_medium ethtool_str_to_medium(const char *str)
return ETHTOOL_LINK_MEDIUM_NONE;
}
EXPORT_SYMBOL_GPL(ethtool_str_to_medium);
u8 netdev_rss_key[NETDEV_RSS_KEY_LEN] __read_mostly;
bool netdev_rss_key_initialized __read_mostly;
/* Toeplitz is linear over GF(2): the hash is the XOR of the 32-bit key
* windows selected by the set bits of the input, and hardware indexes the
* indirection table with the low order bits of the hash. Only the tail of
* each window therefore matters for queue selection:
*
* v(i) = key bits [i + 32 - q .. i + 31]
*
* for input bit @i, with q = log2(number of RX queues). Consecutive input
* bits give windows overlapping in q - 1 positions, so the matrix formed by
* the windows of the q lowest bits of a header field is a Toeplitz matrix
* built from 2 * q - 1 key bits, not from q * q independent ones. A random
* Toeplitz matrix over GF(2) is singular with probability 1/2, whatever its
* size, and when it is singular the flows of a burst differing only in the
* low order bits of that field (consecutive ephemeral ports, typically)
* cannot reach all the RX queues no matter how many of them are configured.
*
* Keep the key random, but constrain the few bits that decide this. The
* fixup is applied at every 16-bit aligned position of the key, rather than
* at the offsets of the one hash input layout the software happens to know
* about: hardware is free to hash whatever it wants, but the fields it picks
* are 16 bits wide at the smallest and are not expected to straddle that
* grid, so an encapsulated or offloaded layout is covered like the usual
* 2-tuple and 4-tuple ones. Each position constrains 2 * q - 1 bits, so
* NETDEV_RSS_KEY_QMAX is both enough for 256 queues and the largest value
* keeping the ranges of two adjacent positions disjoint.
*/
#define NETDEV_RSS_KEY_QMAX 8
#define NETDEV_RSS_KEY_SPAN (2 * NETDEV_RSS_KEY_QMAX - 1)
static bool netdev_rss_key_bit(const u8 *key, unsigned int bit)
{
return key[bit / BITS_PER_BYTE] & (0x80 >> (bit % BITS_PER_BYTE));
}
static void netdev_rss_key_assign_bit(u8 *key, unsigned int bit, bool value)
{
u8 mask = 0x80 >> (bit % BITS_PER_BYTE);
if (value)
key[bit / BITS_PER_BYTE] |= mask;
else
key[bit / BITS_PER_BYTE] &= ~mask;
}
/* Writing d[t] for key bit (@lsb + 31 - t), the matrices of all the q values
* up to NETDEV_RSS_KEY_QMAX are non singular if and only if
*
* d[2 * i] = 1 ^ d[i] ^ d[i + 1] ^ ... ^ d[2 * i - 1]
*
* The odd positions stay free, so this is a one pass fixup rather than a
* search. It is also a bijection onto the set of the values having the
* property, so the key stays uniformly distributed over that set. It costs
* NETDEV_RSS_KEY_QMAX bits of entropy per position.
*/
static void netdev_rss_key_fixup_field(u8 *key, unsigned int lsb)
{
bool d[NETDEV_RSS_KEY_SPAN];
unsigned int i, t;
for (t = 0; t < NETDEV_RSS_KEY_SPAN; t++)
d[t] = netdev_rss_key_bit(key, lsb + 31 - t);
for (i = 0; 2 * i < NETDEV_RSS_KEY_SPAN; i++) {
bool value = true;
for (t = i; t < 2 * i; t++)
value ^= d[t];
d[2 * i] = value;
}
for (t = 0; t < NETDEV_RSS_KEY_SPAN; t++)
netdev_rss_key_assign_bit(key, lsb + 31 - t, d[t]);
}
/* The 32 key bits starting at @bit, which is what input bit @bit contributes
* to the hash. @bit + 32 must fit in the key.
*/
static u32 netdev_rss_key_window(const u8 *key, unsigned int bit)
{
unsigned int byte = bit / BITS_PER_BYTE;
unsigned int shift = bit % BITS_PER_BYTE;
u32 window = get_unaligned_be32(key + byte);
if (shift)
window = (window << shift) |
(key[byte + 4] >> (BITS_PER_BYTE - shift));
return window;
}
/* Two input bits contributing the same window are indistinguishable to the
* hash, since flipping both of them leaves it unchanged. For a uniformly
* random key that is a 2 ** -32 event per pair of positions, but the fixup
* makes it likelier: it derives 8 of every 16 bits from the 8 others, so a
* 16-bit aligned word only takes 2 ** 8 values and two windows a whole
* number of words apart collide with probability 2 ** -16 instead. Half a
* word apart is less affected but still well clear of the random odds, so
* cover every distance that is a multiple of 8. What is left after that is
* below what a plain random key gives.
*
* Two windows at a distance that is a multiple of 8 are two windows at the
* same offset modulo 8. Caching a whole class would put 253 u32 on the
* stack, so cache one class modulo 16 and stream the class 8 bits above it
* against it.
*/
static bool netdev_rss_key_aliases(const u8 *key, unsigned int bits)
{
u32 windows[NETDEV_RSS_KEY_LEN * BITS_PER_BYTE / 16];
unsigned int i, j, n, r;
for (r = 0; r < 16; r++) {
n = 0;
for (i = r; i + 32 <= bits; i += 16)
windows[n++] = netdev_rss_key_window(key, i);
for (i = 0; i < n; i++)
for (j = i + 1; j < n; j++)
if (windows[i] == windows[j])
return true;
if (r >= 8)
continue;
for (i = r + 8; i + 32 <= bits; i += 16) {
u32 window = netdev_rss_key_window(key, i);
for (j = 0; j < n; j++)
if (windows[j] == window)
return true;
}
}
return false;
}
static void netdev_rss_key_init(u8 *key, size_t len)
{
unsigned int lsb, bits = len * BITS_PER_BYTE;
/* Four keys out of five come out of the fixup free of aliases, so
* drawing another one is both simpler and cheaper than repairing.
*/
do {
get_random_bytes(key, len);
/* A field ending at bit @lsb uses key bits [.. , @lsb + 31],
* so stop as soon as a 32-bit window no longer fits in the
* key.
*/
for (lsb = 15; lsb + 32 <= bits; lsb += 16)
netdev_rss_key_fixup_field(key, lsb);
} while (netdev_rss_key_aliases(key, bits));
/* Pair with smp_rmb() in proc_do_rss_key(). */
smp_wmb();
WRITE_ONCE(netdev_rss_key_initialized, true);
}
void netdev_rss_key_fill(void *buffer, size_t len)
{
if (WARN_ON_ONCE(len > sizeof(netdev_rss_key)))
len = sizeof(netdev_rss_key);
DO_ONCE(netdev_rss_key_init, netdev_rss_key, sizeof(netdev_rss_key));
memcpy(buffer, netdev_rss_key, len);
}
EXPORT_SYMBOL(netdev_rss_key_fill);

View File

@ -1301,15 +1301,6 @@ static int ethtool_copy_validate_indir(u32 *indir, void __user *useraddr,
return 0;
}
u8 netdev_rss_key[NETDEV_RSS_KEY_LEN] __read_mostly;
void netdev_rss_key_fill(void *buffer, size_t len)
{
BUG_ON(len > sizeof(netdev_rss_key));
net_get_random_once(netdev_rss_key, sizeof(netdev_rss_key));
memcpy(buffer, netdev_rss_key, len);
}
EXPORT_SYMBOL(netdev_rss_key_fill);
static noinline_for_stack int ethtool_get_rxfh_indir(struct net_device *dev,
void __user *useraddr)

View File

@ -25,6 +25,7 @@ TEST_PROGS := \
psp.py \
queues.py \
ring_reconfig.py \
rss_key.py \
shaper.py \
so_txtime.py \
stats.py \

View File

@ -0,0 +1,343 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
"""
Check the quality of the host RSS key (/proc/sys/net/core/netdev_rss_key)
and that the RSS key a device actually uses spreads flows over all of the
entries of its indirection table.
The Toeplitz hash is linear over GF(2): the hash is the XOR of the 32 bit key
windows selected by the set bits of the input, and hardware indexes the
indirection table with the low order bits of the hash. The windows belonging
to the q lowest bits of a header field therefore form a Toeplitz matrix, and
when that matrix is singular the flows of a burst differing only in those
bits, consecutive ephemeral ports typically, can not reach all of the 2 ** q
entries of the table. A key drawn uniformly at random is singular for a given
field and a given q with probability 1/2.
netdev_rss_key_fill() generates keys that are non singular for every field of
the hash input and every q up to RSS_KEY_QMAX.
"""
import errno
import random
from lib.py import ksft_run, ksft_exit, ksft_pr
from lib.py import ksft_eq, ksft_ge
from lib.py import KsftSkipEx
from lib.py import NetDrvEnv, EthtoolFamily, NlError
KEY_PATH = "/proc/sys/net/core/netdev_rss_key"
# Shortest key able to hash an IPv6 4-tuple.
MIN_KEY_LEN = 40
# Matches NETDEV_RSS_KEY_QMAX, that is up to 256 entries of the table.
RSS_KEY_QMAX = 8
# "define" for the ID of the Toeplitz hash function
ETH_RSS_HASH_TOP = 1
FLOW_TYPES = ("tcp4", "udp4", "tcp6", "udp6")
# Map ethtool netlink rxfh-fields flag names to rss_key_layout() codes.
FIELD_NAMES = {
"ip-src": "s",
"ip-dst": "d",
"l3-proto": "t",
"l4-b-0-1": "f",
"l4-b-2-3": "n",
"ip6-fl": "l",
}
def rss_key_bit(buf, bit):
"""Bit @bit of @buf, counting from the most significant bit of byte 0."""
return (buf[bit // 8] >> (7 - bit % 8)) & 1
def rss_key_assign_bit(buf, bit, value):
mask = 0x80 >> (bit % 8)
if value:
buf[bit // 8] |= mask
else:
buf[bit // 8] &= ~mask
def rss_key_window(key, bit):
"""The 32 key bits starting at @bit, what input bit @bit contributes."""
value = 0
for i in range(32):
value = (value << 1) | rss_key_bit(key, bit + i)
return value
def rss_key_toeplitz(key, inp, nbits):
"""The Toeplitz hash of the @nbits long input @inp under @key."""
value = 0
for i in range(nbits):
if rss_key_bit(inp, i):
value ^= rss_key_window(key, i)
return value
def rss_key_full_rank(key, lsb, q):
"""Do the q low order bits of the field at @lsb reach all 2 ** q entries?
Gaussian elimination over GF(2) on the q windows involved, reduced to
their q low order bits, which are the ones indexing the table.
"""
basis = {}
for j in range(q):
vector = rss_key_window(key, lsb - j) & ((1 << q) - 1)
while vector:
low = vector & -vector
if low not in basis:
basis[low] = vector
break
vector ^= basis[low]
if not vector:
return False
return True
def rss_key_layout(fields, ipv6):
"""Describe the hash input built from @fields.
@fields is the flow hash configuration, "sdfn" for a 4-tuple or "sd" for
a 2-tuple. Returns the list of (name, position of the least significant
bit) and the length of the input in bits, or None if the layout involves
something this does not know how to place.
"""
addr_bits = 128 if ipv6 else 32
known = (("s", "saddr", addr_bits),
("d", "daddr", addr_bits),
("f", "sport", 16),
("n", "dport", 16))
if set(fields) - {flag for flag, _, _ in known}:
return None, 0
layout = []
nbits = 0
for flag, name, width in known:
if flag not in fields:
continue
nbits += width
layout.append((name, nbits - 1))
return layout, nbits
def _read_host_key():
"""Return the host RSS key, skipping if it has not been generated."""
try:
with open(KEY_PATH, "r", encoding="ascii") as fp:
text = fp.read().strip()
except FileNotFoundError as exc:
raise KsftSkipEx(f"{KEY_PATH} is not available") from exc
key = bytes(int(byte, 16) for byte in text.split(":")) if text else b""
if not any(key):
raise KsftSkipEx("the host RSS key has not been generated yet, "
"no driver has called netdev_rss_key_fill()")
return key
def _get_rss(cfg):
"""The key, indirection table, and flow-hash config of @cfg's device."""
try:
rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
except NlError as exc:
if exc.error == errno.EOPNOTSUPP:
raise KsftSkipEx(f"{cfg.ifname} does not support RSS") from exc
raise
hkey = rss.get("hkey")
if not hkey or not any(hkey):
raise KsftSkipEx(f"{cfg.ifname} does not report an RSS key")
if rss.get("hfunc") != ETH_RSS_HASH_TOP:
raise KsftSkipEx(f"{cfg.ifname} does not use the Toeplitz hash")
if rss.get("input-xfrm"):
raise KsftSkipEx(f"{cfg.ifname} transforms the hash input")
indir = rss.get("indir")
if not indir:
raise KsftSkipEx(f"{cfg.ifname} does not report an indirection table")
if len(indir) & (len(indir) - 1):
raise KsftSkipEx(f"{cfg.ifname} has {len(indir)} indirection table "
"entries, which is not a power of two")
return bytes(hkey), indir, rss.get("flow-hash", {})
def _get_layouts(flow_hash):
"""The hash input layouts in use, mapped to the flow types sharing them."""
layouts = {}
for fl_type in FLOW_TYPES:
nl_fields = flow_hash.get(fl_type)
if not nl_fields:
continue
fields = "".join(FIELD_NAMES.get(name, "?") for name in nl_fields)
layout, nbits = rss_key_layout(fields, fl_type.endswith("6"))
if layout is None:
ksft_pr(f"{fl_type}: not checked, hashes fields we can not place "
f"({nl_fields})")
continue
layouts.setdefault((tuple(layout), nbits), []).append(fl_type)
if not layouts:
raise KsftSkipEx("no flow type with a hash input we can describe")
return layouts
def test_host_rss_key_length(cfg) -> None:
key = _read_host_key()
ksft_pr(f"host RSS key is {len(key)} bytes")
ksft_ge(len(key), MIN_KEY_LEN, "key too short to hash an IPv6 4-tuple")
def test_host_rss_key_spread(cfg) -> None:
key = _read_host_key()
degenerate = []
for ipv6 in (False, True):
layout, _ = rss_key_layout("sdfn", ipv6)
family = "IPv6" if ipv6 else "IPv4"
for name, lsb in layout:
if lsb + 32 > len(key) * 8:
continue
for q in range(1, RSS_KEY_QMAX + 1):
if not rss_key_full_rank(key, lsb, q):
degenerate.append(f"{family} {name} over {1 << q} queues")
for bad in degenerate:
ksft_pr(f"degenerate: {bad}")
ksft_eq(len(degenerate), 0,
"the host RSS key does not spread flows over all the queues")
def test_host_rss_key_grid(cfg) -> None:
"""Sweep the whole key, not only the fields of the usual layouts."""
key = _read_host_key()
bits = len(key) * 8
positions = 0
degenerate = []
for lsb in range(15, bits - 31, 16):
positions += 1
for q in range(1, RSS_KEY_QMAX + 1):
if not rss_key_full_rank(key, lsb, q):
degenerate.append(f"field ending at bit {lsb} "
f"over {1 << q} queues")
ksft_pr(f"checked {positions} positions of the {len(key)} byte key")
for bad in degenerate[:8]:
ksft_pr(f"degenerate: {bad}")
ksft_eq(len(degenerate), 0,
"the host RSS key does not spread flows over all the queues "
"at every 16-bit aligned position")
def test_dev_rss_key_rank(cfg) -> None:
"""The key has to be non singular for the size of the table."""
hkey, indir, flow_hash = _get_rss(cfg)
q = min((len(indir) - 1).bit_length(), RSS_KEY_QMAX)
degenerate = []
if not q:
raise KsftSkipEx("the indirection table has a single entry")
for (layout, _), fl_types in _get_layouts(flow_hash).items():
for name, lsb in layout:
if lsb + 32 > len(hkey) * 8:
ksft_pr(f"{name}: not checked, the key is {len(hkey)} bytes")
continue
if not rss_key_full_rank(hkey, lsb, q):
degenerate.append(f"{'/'.join(fl_types)} {name}")
for bad in degenerate:
ksft_pr(f"degenerate: {bad}")
ksft_eq(len(degenerate), 0,
f"the key of {cfg.ifname} does not spread flows over the "
f"{1 << q} entries of its indirection table")
def test_dev_rss_key_spread(cfg) -> None:
"""Hash bursts differing in one field only, and place them in the table."""
hkey, indir, flow_hash = _get_rss(cfg)
q = (len(indir) - 1).bit_length()
collisions = []
if q > RSS_KEY_QMAX:
raise KsftSkipEx(f"{len(indir)} indirection table entries is more "
"than the kernel guarantees")
if not q:
raise KsftSkipEx("the indirection table has a single entry")
for (layout, nbits), fl_types in _get_layouts(flow_hash).items():
if nbits + 31 > len(hkey) * 8:
ksft_pr(f"{'/'.join(fl_types)}: not checked, the key is "
f"{len(hkey)} bytes, input needs {(nbits + 31 + 7) // 8}")
continue
for name, lsb in layout:
inp = bytearray(random.randbytes(nbits // 8))
entries = set()
for value in range(1 << q):
for bit in range(q):
rss_key_assign_bit(inp, lsb - bit, value & (1 << bit))
hash_ = rss_key_toeplitz(hkey, inp, nbits)
entries.add(hash_ & (len(indir) - 1))
if len(entries) != 1 << q:
collisions.append(f"{'/'.join(fl_types)} {name} reaches "
f"{len(entries)} of the {1 << q} entries")
for bad in collisions:
ksft_pr(bad)
ksft_eq(len(collisions), 0,
f"flows differing in one field only do not fill the "
f"indirection table of {cfg.ifname}")
def main() -> None:
""" Ksft boiler plate main """
with NetDrvEnv(__file__, queue_count=4) as cfg:
cfg.ethnl = EthtoolFamily()
ksft_run(globs=globals(), case_pfx={"test_"}, args=(cfg, ))
ksft_exit()
if __name__ == "__main__":
main()