mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-05-17 11:55:00 -04:00
Extend the ntuple flow steering test to cover dst-ip, src-port, and dst-port fields. The test supports arbitrary combinations of the fields, for now we test src_ip/dst_ip, and src_ip/dst_ip/src_port/dst_port. The tests currently match full fields, but we can consider adding support for masked fields in the future. TAP version 13 1..24 ok 1 ntuple.queue.tcp4.src_ip ok 2 ntuple.queue.tcp4.dst_ip ok 3 ntuple.queue.tcp4.src_port ok 4 ntuple.queue.tcp4.dst_port ok 5 ntuple.queue.tcp4.src_ip.dst_ip ok 6 ntuple.queue.tcp4.src_ip.dst_ip.src_port.dst_port ok 7 ntuple.queue.udp4.src_ip ok 8 ntuple.queue.udp4.dst_ip ok 9 ntuple.queue.udp4.src_port ok 10 ntuple.queue.udp4.dst_port ok 11 ntuple.queue.udp4.src_ip.dst_ip ok 12 ntuple.queue.udp4.src_ip.dst_ip.src_port.dst_port ok 13 ntuple.queue.tcp6.src_ip ok 14 ntuple.queue.tcp6.dst_ip ok 15 ntuple.queue.tcp6.src_port ok 16 ntuple.queue.tcp6.dst_port ok 17 ntuple.queue.tcp6.src_ip.dst_ip ok 18 ntuple.queue.tcp6.src_ip.dst_ip.src_port.dst_port ok 19 ntuple.queue.udp6.src_ip ok 20 ntuple.queue.udp6.dst_ip ok 21 ntuple.queue.udp6.src_port ok 22 ntuple.queue.udp6.dst_port ok 23 ntuple.queue.udp6.src_ip.dst_ip ok 24 ntuple.queue.udp6.src_ip.dst_ip.src_port.dst_port # Totals: pass:24 fail:0 xfail:0 xpass:0 skip:0 error:0 Signed-off-by: Dimitri Daskalakis <daskald@meta.com> Link: https://patch.msgid.link/20260407164954.2977820-3-dimitri.daskalakis1@gmail.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
163 lines
5.5 KiB
Python
Executable File
163 lines
5.5 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"]:
|
|
raise KsftSkipEx("Ntuple filters not enabled on the device: " + str(features["ntuple-filters"]))
|
|
|
|
|
|
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()
|