Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'add-selftests-for-ntuple-nfc-rules'

Dimitri Daskalakis says:

====================
Add selftests for ntuple (NFC) rules

Thoroughly testing a device's NFC implementation can be tedious. The more
features a device supports, the more combinations to validate.

This series aims to ease that burden, validating the most common NFC rule
combinations.
====================

Link: https://patch.msgid.link/20260407164954.2977820-1-dimitri.daskalakis1@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

+163
+1
tools/testing/selftests/drivers/net/hw/Makefile
··· 36 36 nic_timestamp.py \ 37 37 nk_netns.py \ 38 38 nk_qlease.py \ 39 + ntuple.py \ 39 40 pp_alloc_fail.py \ 40 41 rss_api.py \ 41 42 rss_ctx.py \
+162
tools/testing/selftests/drivers/net/hw/ntuple.py
··· 1 + #!/usr/bin/env python3 2 + # SPDX-License-Identifier: GPL-2.0 3 + """Test ethtool NFC (ntuple) flow steering rules.""" 4 + 5 + import random 6 + from enum import Enum, auto 7 + from lib.py import ksft_run, ksft_exit 8 + from lib.py import ksft_eq, ksft_ge 9 + from lib.py import ksft_variants, KsftNamedVariant 10 + from lib.py import EthtoolFamily, NetDrvEpEnv, NetdevFamily 11 + from lib.py import KsftSkipEx 12 + from lib.py import cmd, ethtool, defer, rand_ports, bkg, wait_port_listen 13 + 14 + 15 + class NtupleField(Enum): 16 + SRC_IP = auto() 17 + DST_IP = auto() 18 + SRC_PORT = auto() 19 + DST_PORT = auto() 20 + 21 + 22 + def _require_ntuple(cfg): 23 + features = ethtool(f"-k {cfg.ifname}", json=True)[0] 24 + if not features["ntuple-filters"]["active"]: 25 + raise KsftSkipEx("Ntuple filters not enabled on the device: " + str(features["ntuple-filters"])) 26 + 27 + 28 + def _get_rx_cnts(cfg, prev=None): 29 + """Get Rx packet counts for all queues, as a simple list of integers 30 + if @prev is specified the prev counts will be subtracted""" 31 + cfg.wait_hw_stats_settle() 32 + data = cfg.netdevnl.qstats_get({"ifindex": cfg.ifindex, "scope": ["queue"]}, dump=True) 33 + data = [x for x in data if x['queue-type'] == "rx"] 34 + max_q = max([x["queue-id"] for x in data]) 35 + queue_stats = [0] * (max_q + 1) 36 + for q in data: 37 + queue_stats[q["queue-id"]] = q["rx-packets"] 38 + if prev and q["queue-id"] < len(prev): 39 + queue_stats[q["queue-id"]] -= prev[q["queue-id"]] 40 + return queue_stats 41 + 42 + 43 + def _ntuple_rule_add(cfg, flow_spec): 44 + """Install an NFC rule via ethtool.""" 45 + 46 + output = ethtool(f"-N {cfg.ifname} {flow_spec}").stdout 47 + rule_id = int(output.split()[-1]) 48 + defer(ethtool, f"-N {cfg.ifname} delete {rule_id}") 49 + 50 + 51 + def _setup_isolated_queue(cfg): 52 + """Default all traffic to queue 0, and pick a random queue to 53 + steer NFC traffic to.""" 54 + 55 + channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}}) 56 + ch_max = channels['combined-max'] 57 + qcnt = channels['combined-count'] 58 + 59 + if ch_max < 2: 60 + raise KsftSkipEx(f"Need at least 2 combined channels, max is {ch_max}") 61 + 62 + desired_queues = min(ch_max, 4) 63 + if qcnt >= desired_queues: 64 + desired_queues = qcnt 65 + else: 66 + ethtool(f"-L {cfg.ifname} combined {desired_queues}") 67 + defer(ethtool, f"-L {cfg.ifname} combined {qcnt}") 68 + 69 + ethtool(f"-X {cfg.ifname} equal 1") 70 + defer(ethtool, f"-X {cfg.ifname} default") 71 + 72 + return random.randint(1, desired_queues - 1) 73 + 74 + 75 + def _send_traffic(cfg, ipver, proto, dst_port, src_port, pkt_cnt=40): 76 + """Generate traffic with the desired flow signature.""" 77 + 78 + cfg.require_cmd("socat", remote=True) 79 + 80 + socat_proto = proto.upper() 81 + dst_addr = f"[{cfg.addr_v['6']}]" if ipver == '6' else cfg.addr_v['4'] 82 + 83 + extra_opts = ",nodelay" if proto == "tcp" else ",shut-null" 84 + 85 + listen_cmd = (f"socat -{ipver} -t 2 -u " 86 + f"{socat_proto}-LISTEN:{dst_port},reuseport /dev/null") 87 + with bkg(listen_cmd, exit_wait=True): 88 + wait_port_listen(dst_port, proto=proto) 89 + send_cmd = f""" 90 + bash -c 'for i in $(seq {pkt_cnt}); do echo msg; sleep 0.02; done' | 91 + socat -{ipver} -u - \ 92 + {socat_proto}:{dst_addr}:{dst_port},sourceport={src_port},reuseaddr{extra_opts} 93 + """ 94 + cmd(send_cmd, shell=True, host=cfg.remote) 95 + 96 + 97 + def _add_ntuple_rule_and_send_traffic(cfg, ipver, proto, fields, test_queue): 98 + ports = rand_ports(2) 99 + src_port = ports[0] 100 + dst_port = ports[1] 101 + flow_parts = [f"flow-type {proto}{ipver}"] 102 + 103 + for field in fields: 104 + if field == NtupleField.SRC_IP: 105 + flow_parts.append(f"src-ip {cfg.remote_addr_v[ipver]}") 106 + elif field == NtupleField.DST_IP: 107 + flow_parts.append(f"dst-ip {cfg.addr_v[ipver]}") 108 + elif field == NtupleField.SRC_PORT: 109 + flow_parts.append(f"src-port {src_port}") 110 + elif field == NtupleField.DST_PORT: 111 + flow_parts.append(f"dst-port {dst_port}") 112 + 113 + flow_parts.append(f"action {test_queue}") 114 + _ntuple_rule_add(cfg, " ".join(flow_parts)) 115 + _send_traffic(cfg, ipver, proto, dst_port=dst_port, src_port=src_port) 116 + 117 + 118 + def _ntuple_variants(): 119 + for ipver in ["4", "6"]: 120 + for proto in ["tcp", "udp"]: 121 + for fields in [[NtupleField.SRC_IP], 122 + [NtupleField.DST_IP], 123 + [NtupleField.SRC_PORT], 124 + [NtupleField.DST_PORT], 125 + [NtupleField.SRC_IP, NtupleField.DST_IP], 126 + [NtupleField.SRC_IP, NtupleField.DST_IP, 127 + NtupleField.SRC_PORT, NtupleField.DST_PORT]]: 128 + name = ".".join(f.name.lower() for f in fields) 129 + yield KsftNamedVariant(f"{proto}{ipver}.{name}", 130 + ipver, proto, fields) 131 + 132 + 133 + @ksft_variants(_ntuple_variants()) 134 + def queue(cfg, ipver, proto, fields): 135 + """Test that an NFC rule steers traffic to the correct queue.""" 136 + 137 + cfg.require_ipver(ipver) 138 + _require_ntuple(cfg) 139 + 140 + test_queue = _setup_isolated_queue(cfg) 141 + 142 + cnts = _get_rx_cnts(cfg) 143 + _add_ntuple_rule_and_send_traffic(cfg, ipver, proto, fields, test_queue) 144 + cnts = _get_rx_cnts(cfg, prev=cnts) 145 + 146 + ksft_ge(cnts[test_queue], 40, f"Traffic on test queue {test_queue}: {cnts}") 147 + sum_idle = sum(cnts) - cnts[0] - cnts[test_queue] 148 + ksft_eq(sum_idle, 0, f"Traffic on idle queues: {cnts}") 149 + 150 + 151 + def main() -> None: 152 + """Ksft boilerplate main.""" 153 + 154 + with NetDrvEpEnv(__file__, nsim_test=False) as cfg: 155 + cfg.ethnl = EthtoolFamily() 156 + cfg.netdevnl = NetdevFamily() 157 + ksft_run([queue], args=(cfg,)) 158 + ksft_exit() 159 + 160 + 161 + if __name__ == "__main__": 162 + main()