1#!/usr/bin/env python3 2# SPDX-License-Identifier: GPL-2.0 3 4""" 5Tests for RSS hashing on IPv6 Flow Label. 6""" 7 8import glob 9import os 10import socket 11from lib.py import CmdExitFailure 12from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_ge, ksft_in, \ 13 ksft_not_in, ksft_raises, KsftSkipEx 14from lib.py import bkg, cmd, defer, fd_read_timeout, rand_port 15from lib.py import NetDrvEpEnv 16 17 18def _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 43def _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 62def _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 91def 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 122def _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 133def 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 158def 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 166if __name__ == "__main__": 167 main() 168