Add a simple test for checking that RSS on flow label works, and that its rejected for IPv4 flows. # ./tools/testing/selftests/drivers/net/hw/rss_flow_label.py TAP version 13 1..2 ok 1 rss_flow_label.test_rss_flow_label ok 2 rss_flow_label.test_rss_flow_label_6only # Totals: pass:2 fail:0 xfail:0 xpass:0 skip:0 error:0
Unified · Split
| @@ -18,6 +18,7 @@ TEST_PROGS = \ · 17 unmodified lines | ||
| 18 | 18 | pp_alloc_fail.py \ |
| 19 | 19 | rss_api.py \ |
| 20 | 20 | rss_ctx.py \ |
| 21 | + rss_flow_label.py \ | |
| 21 | 22 | rss_input_xfrm.py \ |
| 22 | 23 | tso.py \ |
| 23 | 24 | xsk_reconfig.py \ |
| @@ -0,0 +1,167 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +# SPDX-License-Identifier: GPL-2.0 | |
| 3 | + | |
| 4 | +""" | |
| 5 | +Tests for RSS hashing on IPv6 Flow Label. | |
| 6 | +""" | |
| 7 | + | |
| 8 | +import glob | |
| 9 | +import os | |
| 10 | +import socket | |
| 11 | +from lib.py import CmdExitFailure | |
| 12 | +from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_ge, ksft_in, \ | |
| 13 | + ksft_not_in, ksft_raises, KsftSkipEx | |
| 14 | +from lib.py import bkg, cmd, defer, fd_read_timeout, rand_port | |
| 15 | +from lib.py import NetDrvEpEnv | |
| 16 | + | |
| 17 | + | |
| 18 | +def _check_system(cfg): | |
| 19 | + if not hasattr(socket, "SO_INCOMING_CPU"): | |
| 20 | + raise KsftSkipEx("socket.SO_INCOMING_CPU was added in Python 3.11") | |
| 21 | + | |
| 22 | + qcnt = len(glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*")) | |
| 23 | + if qcnt < 2: | |
| 24 | + raise KsftSkipEx(f"Local has only {qcnt} queues") | |
| 25 | + | |
| 26 | + for f in [f"/sys/class/net/{cfg.ifname}/queues/rx-0/rps_flow_cnt", | |
| 27 | + f"/sys/class/net/{cfg.ifname}/queues/rx-0/rps_cpus"]: | |
| 28 | + try: | |
| 29 | + with open(f, 'r') as fp: | |
| 30 | + setting = fp.read().strip() | |
| 31 | + # CPU mask will be zeros and commas | |
| 32 | + if setting.replace("0", "").replace(",", ""): | |
| 33 | + raise KsftSkipEx(f"RPS/RFS is configured: {f}: {setting}") | |
| 34 | + except FileNotFoundError: | |
| 35 | + pass | |
| 36 | + | |
| 37 | + # 1 is the default, if someone changed it we probably shouldn"t mess with it | |
| 38 | + af = cmd("cat /proc/sys/net/ipv6/auto_flowlabels", host=cfg.remote).stdout | |
| 39 | + if af.strip() != "1": | |
| 40 | + raise KsftSkipEx("Remote does not have auto_flowlabels enabled") | |
| 41 | + | |
| 42 | + | |
| 43 | +def _ethtool_get_cfg(cfg, fl_type): | |
| 44 | + descr = cmd(f"ethtool -n {cfg.ifname} rx-flow-hash {fl_type}").stdout | |
| 45 | + | |
| 46 | + converter = { | |
| 47 | + "IP SA": "s", | |
| 48 | + "IP DA": "d", | |
| 49 | + "L3 proto": "t", | |
| 50 | + "L4 bytes 0 & 1 [TCP/UDP src port]": "f", | |
| 51 | + "L4 bytes 2 & 3 [TCP/UDP dst port]": "n", | |
| 52 | + "IPv6 Flow Label": "l", | |
| 53 | + } | |
| 54 | + | |
| 55 | + ret = "" | |
| 56 | + for line in descr.split("\n")[1:-2]: | |
| 57 | + # if this raises we probably need to add more keys to converter above | |
| 58 | + ret += converter[line] | |
| 59 | + return ret | |
| 60 | + | |
| 61 | + | |
| 62 | +def _traffic(cfg, one_sock, one_cpu): | |
| 63 | + local_port = rand_port(socket.SOCK_DGRAM) | |
| 64 | + remote_port = rand_port(socket.SOCK_DGRAM) | |
| 65 | + | |
| 66 | + sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) | |
| 67 | + sock.bind(("", local_port)) | |
| 68 | + sock.connect((cfg.remote_addr_v["6"], 0)) | |
| 69 | + if one_sock: | |
| 70 | + send = f"exec 5<>/dev/udp/{cfg.addr_v['6']}/{local_port}; " \ | |
| 71 | + "for i in `seq 20`; do echo a >&5; sleep 0.02; done; exec 5>&-" | |
| 72 | + else: | |
| 73 | + send = "for i in `seq 20`; do echo a | socat -t0.02 - UDP6:" \ | |
| 74 | + f"[{cfg.addr_v['6']}]:{local_port},sourceport={remote_port}; done" | |
| 75 | + | |
| 76 | + cpus = set() | |
| 77 | + with bkg(send, shell=True, host=cfg.remote, exit_wait=True): | |
| 78 | + for _ in range(20): | |
| 79 | + fd_read_timeout(sock.fileno(), 1) | |
| 80 | + cpu = sock.getsockopt(socket.SOL_SOCKET, socket.SO_INCOMING_CPU) | |
| 81 | + cpus.add(cpu) | |
| 82 | + | |
| 83 | + if one_cpu: | |
| 84 | + ksft_eq(len(cpus), 1, | |
| 85 | + f"{one_sock=} - expected one CPU, got traffic on: {cpus=}") | |
| 86 | + else: | |
| 87 | + ksft_ge(len(cpus), 2, | |
| 88 | + f"{one_sock=} - expected many CPUs, got traffic on: {cpus=}") | |
| 89 | + | |
| 90 | + | |
| 91 | +def test_rss_flow_label(cfg): | |
| 92 | + """ | |
| 93 | + Test hashing on IPv6 flow label. Send traffic over a single socket | |
| 94 | + and over multiple sockets. Depend on the remote having auto-label | |
| 95 | + enabled so that it randomizes the label per socket. | |
| 96 | + """ | |
| 97 | + | |
| 98 | + cfg.require_ipver("6") | |
| 99 | + cfg.require_cmd("socat", remote=True) | |
| 100 | + _check_system(cfg) | |
| 101 | + | |
| 102 | + # Enable flow label hashing for UDP6 | |
| 103 | + initial = _ethtool_get_cfg(cfg, "udp6") | |
| 104 | + no_lbl = initial.replace("l", "") | |
| 105 | + if "l" not in initial: | |
| 106 | + try: | |
| 107 | + cmd(f"ethtool -N {cfg.ifname} rx-flow-hash udp6 l{no_lbl}") | |
| 108 | + except CmdExitFailure as exc: | |
| 109 | + raise KsftSkipEx("Device doesn't support Flow Label for UDP6") from exc | |
| 110 | + | |
| 111 | + defer(cmd, f"ethtool -N {cfg.ifname} rx-flow-hash udp6 {initial}") | |
| 112 | + | |
| 113 | + _traffic(cfg, one_sock=True, one_cpu=True) | |
| 114 | + _traffic(cfg, one_sock=False, one_cpu=False) | |
| 115 | + | |
| 116 | + # Disable it, we should see no hashing (reset was already defer()ed) | |
| 117 | + cmd(f"ethtool -N {cfg.ifname} rx-flow-hash udp6 {no_lbl}") | |
| 118 | + | |
| 119 | + _traffic(cfg, one_sock=False, one_cpu=True) | |
| 120 | + | |
| 121 | + | |
| 122 | +def _check_v4_flow_types(cfg): | |
| 123 | + for fl_type in ["tcp4", "udp4", "ah4", "esp4", "sctp4"]: | |
| 124 | + try: | |
| 125 | + cur = cmd(f"ethtool -n {cfg.ifname} rx-flow-hash {fl_type}").stdout | |
| 126 | + ksft_not_in("Flow Label", cur, | |
| 127 | + comment=f"{fl_type=} has Flow Label:" + cur) | |
| 128 | + except CmdExitFailure: | |
| 129 | + # Probably does not support this flow type | |
| 130 | + pass | |
| 131 | + | |
| 132 | + | |
| 133 | +def test_rss_flow_label_6only(cfg): | |
| 134 | + """ | |
| 135 | + Test interactions with IPv4 flow types. It should not be possible to set | |
| 136 | + IPv6 Flow Label hashing for an IPv4 flow type. The Flow Label should also | |
| 137 | + not appear in the IPv4 "current config". | |
| 138 | + """ | |
| 139 | + | |
| 140 | + with ksft_raises(CmdExitFailure) as cm: | |
| 141 | + cmd(f"ethtool -N {cfg.ifname} rx-flow-hash tcp4 sdfnl") | |
| 142 | + ksft_in("Invalid argument", cm.exception.cmd.stderr) | |
| 143 | + | |
| 144 | + _check_v4_flow_types(cfg) | |
| 145 | + | |
| 146 | + # Try to enable Flow Labels and check again, in case it leaks thru | |
| 147 | + initial = _ethtool_get_cfg(cfg, "udp6") | |
| 148 | + changed = initial.replace("l", "") if "l" in initial else initial + "l" | |
| 149 | + | |
| 150 | + cmd(f"ethtool -N {cfg.ifname} rx-flow-hash udp6 {changed}") | |
| 151 | + restore = defer(cmd, f"ethtool -N {cfg.ifname} rx-flow-hash udp6 {initial}") | |
| 152 | + | |
| 153 | + _check_v4_flow_types(cfg) | |
| 154 | + restore.exec() | |
| 155 | + _check_v4_flow_types(cfg) | |
| 156 | + | |
| 157 | + | |
| 158 | +def main() -> None: | |
| 159 | + with NetDrvEpEnv(__file__, nsim_test=False) as cfg: | |
| 160 | + ksft_run([test_rss_flow_label, | |
| 161 | + test_rss_flow_label_6only], | |
| 162 | + args=(cfg, )) | |
| 163 | + ksft_exit() | |
| 164 | + | |
| 165 | + | |
| 166 | +if __name__ == "__main__": | |
| 167 | + main() | |