Files
linux-stable-mirror/tools/testing/selftests/drivers/net/hw/ntuple.py
T
Dimitri Daskalakis c301658dfe selftests: drv-net: Enable ntuple-filters if supported
Certain devices which support ntuple-filters do not enable the feature
by default. The existing tests will skip (if they check for the feature),
or fail if they blindly attempt to install rules. Therefore, attempt to turn
on ntuple-filters if the device supports them.

Signed-off-by: Dimitri Daskalakis <daskald@meta.com>
Reviewed-by: Joe Damato <joe@dama.to>
Link: https://patch.msgid.link/20260430165217.3700469-1-dimitri.daskalakis1@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2026-05-01 17:10:06 -07:00

166 lines
5.6 KiB
Python
Executable File

#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
"""Test ethtool NFC (ntuple) flow steering rules."""
import random
from enum import Enum, auto
from lib.py import ksft_run, ksft_exit
from lib.py import ksft_eq, ksft_ge
from lib.py import ksft_variants, KsftNamedVariant
from lib.py import EthtoolFamily, NetDrvEpEnv, NetdevFamily
from lib.py import KsftSkipEx
from lib.py import cmd, ethtool, defer, rand_ports, bkg, wait_port_listen
class NtupleField(Enum):
SRC_IP = auto()
DST_IP = auto()
SRC_PORT = auto()
DST_PORT = auto()
def _require_ntuple(cfg):
features = ethtool(f"-k {cfg.ifname}", json=True)[0]
if not features["ntuple-filters"]["active"]:
if features["ntuple-filters"]["fixed"]:
raise KsftSkipEx("Device does not support ntuple-filters")
ethtool(f"-K {cfg.ifname} ntuple-filters on")
defer(ethtool, f"-K {cfg.ifname} ntuple-filters off")
def _get_rx_cnts(cfg, prev=None):
"""Get Rx packet counts for all queues, as a simple list of integers
if @prev is specified the prev counts will be subtracted"""
cfg.wait_hw_stats_settle()
data = cfg.netdevnl.qstats_get({"ifindex": cfg.ifindex, "scope": ["queue"]}, dump=True)
data = [x for x in data if x['queue-type'] == "rx"]
max_q = max([x["queue-id"] for x in data])
queue_stats = [0] * (max_q + 1)
for q in data:
queue_stats[q["queue-id"]] = q["rx-packets"]
if prev and q["queue-id"] < len(prev):
queue_stats[q["queue-id"]] -= prev[q["queue-id"]]
return queue_stats
def _ntuple_rule_add(cfg, flow_spec):
"""Install an NFC rule via ethtool."""
output = ethtool(f"-N {cfg.ifname} {flow_spec}").stdout
rule_id = int(output.split()[-1])
defer(ethtool, f"-N {cfg.ifname} delete {rule_id}")
def _setup_isolated_queue(cfg):
"""Default all traffic to queue 0, and pick a random queue to
steer NFC traffic to."""
channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
ch_max = channels['combined-max']
qcnt = channels['combined-count']
if ch_max < 2:
raise KsftSkipEx(f"Need at least 2 combined channels, max is {ch_max}")
desired_queues = min(ch_max, 4)
if qcnt >= desired_queues:
desired_queues = qcnt
else:
ethtool(f"-L {cfg.ifname} combined {desired_queues}")
defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
ethtool(f"-X {cfg.ifname} equal 1")
defer(ethtool, f"-X {cfg.ifname} default")
return random.randint(1, desired_queues - 1)
def _send_traffic(cfg, ipver, proto, dst_port, src_port, pkt_cnt=40):
"""Generate traffic with the desired flow signature."""
cfg.require_cmd("socat", remote=True)
socat_proto = proto.upper()
dst_addr = f"[{cfg.addr_v['6']}]" if ipver == '6' else cfg.addr_v['4']
extra_opts = ",nodelay" if proto == "tcp" else ",shut-null"
listen_cmd = (f"socat -{ipver} -t 2 -u "
f"{socat_proto}-LISTEN:{dst_port},reuseport /dev/null")
with bkg(listen_cmd, exit_wait=True):
wait_port_listen(dst_port, proto=proto)
send_cmd = f"""
bash -c 'for i in $(seq {pkt_cnt}); do echo msg; sleep 0.02; done' |
socat -{ipver} -u - \
{socat_proto}:{dst_addr}:{dst_port},sourceport={src_port},reuseaddr{extra_opts}
"""
cmd(send_cmd, shell=True, host=cfg.remote)
def _add_ntuple_rule_and_send_traffic(cfg, ipver, proto, fields, test_queue):
ports = rand_ports(2)
src_port = ports[0]
dst_port = ports[1]
flow_parts = [f"flow-type {proto}{ipver}"]
for field in fields:
if field == NtupleField.SRC_IP:
flow_parts.append(f"src-ip {cfg.remote_addr_v[ipver]}")
elif field == NtupleField.DST_IP:
flow_parts.append(f"dst-ip {cfg.addr_v[ipver]}")
elif field == NtupleField.SRC_PORT:
flow_parts.append(f"src-port {src_port}")
elif field == NtupleField.DST_PORT:
flow_parts.append(f"dst-port {dst_port}")
flow_parts.append(f"action {test_queue}")
_ntuple_rule_add(cfg, " ".join(flow_parts))
_send_traffic(cfg, ipver, proto, dst_port=dst_port, src_port=src_port)
def _ntuple_variants():
for ipver in ["4", "6"]:
for proto in ["tcp", "udp"]:
for fields in [[NtupleField.SRC_IP],
[NtupleField.DST_IP],
[NtupleField.SRC_PORT],
[NtupleField.DST_PORT],
[NtupleField.SRC_IP, NtupleField.DST_IP],
[NtupleField.SRC_IP, NtupleField.DST_IP,
NtupleField.SRC_PORT, NtupleField.DST_PORT]]:
name = ".".join(f.name.lower() for f in fields)
yield KsftNamedVariant(f"{proto}{ipver}.{name}",
ipver, proto, fields)
@ksft_variants(_ntuple_variants())
def queue(cfg, ipver, proto, fields):
"""Test that an NFC rule steers traffic to the correct queue."""
cfg.require_ipver(ipver)
_require_ntuple(cfg)
test_queue = _setup_isolated_queue(cfg)
cnts = _get_rx_cnts(cfg)
_add_ntuple_rule_and_send_traffic(cfg, ipver, proto, fields, test_queue)
cnts = _get_rx_cnts(cfg, prev=cnts)
ksft_ge(cnts[test_queue], 40, f"Traffic on test queue {test_queue}: {cnts}")
sum_idle = sum(cnts) - cnts[0] - cnts[test_queue]
ksft_eq(sum_idle, 0, f"Traffic on idle queues: {cnts}")
def main() -> None:
"""Ksft boilerplate main."""
with NetDrvEpEnv(__file__, nsim_test=False) as cfg:
cfg.ethnl = EthtoolFamily()
cfg.netdevnl = NetdevFamily()
ksft_run([queue], args=(cfg,))
ksft_exit()
if __name__ == "__main__":
main()