summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/drivers/net/hw/ntuple.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/testing/selftests/drivers/net/hw/ntuple.py')
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/ntuple.py162
1 files changed, 162 insertions, 0 deletions
diff --git a/tools/testing/selftests/drivers/net/hw/ntuple.py b/tools/testing/selftests/drivers/net/hw/ntuple.py
new file mode 100755
index 000000000000..232733142c02
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/ntuple.py
@@ -0,0 +1,162 @@
+#!/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()