1#!/usr/bin/env python3 2# SPDX-License-Identifier: GPL-2.0 3 4import multiprocessing 5import socket 6from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_ge, cmd, fd_read_timeout 7from lib.py import NetDrvEpEnv 8from lib.py import EthtoolFamily, NetdevFamily, NlError 9from lib.py import KsftSkipEx, KsftFailEx 10from lib.py import defer, ksft_pr, rand_port 11 12 13def traffic(cfg, local_port, remote_port, ipver): 14 af_inet = socket.AF_INET if ipver == "4" else socket.AF_INET6 15 sock = socket.socket(af_inet, socket.SOCK_DGRAM) 16 sock.bind(("", local_port)) 17 sock.connect((cfg.remote_addr_v[ipver], remote_port)) 18 tgt = f"{ipver}:[{cfg.addr_v[ipver]}]:{local_port},sourceport={remote_port}" 19 cmd("echo a | socat - UDP" + tgt, host=cfg.remote) 20 fd_read_timeout(sock.fileno(), 5) 21 return sock.getsockopt(socket.SOL_SOCKET, socket.SO_INCOMING_CPU) 22 23 24def _rss_input_xfrm_try_enable(cfg): 25 """ 26 Check if symmetric input-xfrm is already enabled, if not try to enable it 27 and register a cleanup. 28 """ 29 rss = cfg.ethnl.rss_get({'header': {'dev-name': cfg.ifname}}) 30 orig_xfrm = rss.get('input-xfrm', set()) 31 sym_xfrm = set(filter(lambda x: 'sym' in x, orig_xfrm)) 32 33 if sym_xfrm: 34 ksft_pr("Sym input xfrm already enabled:", sym_xfrm) 35 return sym_xfrm 36 37 for xfrm in cfg.ethnl.consts["input-xfrm"].entries: 38 # Skip non-symmetric transforms 39 if "sym" not in xfrm: 40 continue 41 42 try_xfrm = {xfrm} | orig_xfrm 43 try: 44 cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex}, 45 "input-xfrm": try_xfrm}) 46 except NlError: 47 continue 48 49 ksft_pr("Sym input xfrm configured:", try_xfrm) 50 defer(cfg.ethnl.rss_set, 51 {"header": {"dev-index": cfg.ifindex}, 52 "input-xfrm": orig_xfrm}) 53 return {xfrm} 54 55 return set() 56 57 58def test_rss_input_xfrm(cfg, ipver): 59 """ 60 Test symmetric input_xfrm. 61 If symmetric RSS hash is configured, send traffic twice, swapping the 62 src/dst UDP ports, and verify that the same queue is receiving the traffic 63 in both cases (IPs are constant). 64 """ 65 66 if multiprocessing.cpu_count() < 2: 67 raise KsftSkipEx("Need at least two CPUs to test symmetric RSS hash") 68 69 cfg.require_cmd("socat", local=False, remote=True) 70 71 if not hasattr(socket, "SO_INCOMING_CPU"): 72 raise KsftSkipEx("socket.SO_INCOMING_CPU was added in Python 3.11") 73 74 # Check for symmetric xor/or-xor 75 input_xfrm = _rss_input_xfrm_try_enable(cfg) 76 if not input_xfrm: 77 raise KsftSkipEx("Symmetric RSS hash not supported by device") 78 79 cpus = set() 80 successful = 0 81 for _ in range(100): 82 try: 83 port1 = rand_port(socket.SOCK_DGRAM) 84 port2 = rand_port(socket.SOCK_DGRAM) 85 cpu1 = traffic(cfg, port1, port2, ipver) 86 cpu2 = traffic(cfg, port2, port1, ipver) 87 cpus.update([cpu1, cpu2]) 88 ksft_eq( 89 cpu1, cpu2, comment=f"Received traffic on different cpus with ports ({port1 = }, {port2 = }) while symmetric hash is configured") 90 91 successful += 1 92 if successful == 10: 93 break 94 except: 95 continue 96 else: 97 raise KsftFailEx("Failed to run traffic") 98 99 ksft_ge(len(cpus), 2, 100 comment=f"Received traffic on less than two cpus {cpus = }") 101 102 103def test_rss_input_xfrm_ipv4(cfg): 104 cfg.require_ipver("4") 105 test_rss_input_xfrm(cfg, "4") 106 107 108def test_rss_input_xfrm_ipv6(cfg): 109 cfg.require_ipver("6") 110 test_rss_input_xfrm(cfg, "6") 111 112 113def main() -> None: 114 with NetDrvEpEnv(__file__, nsim_test=False) as cfg: 115 cfg.ethnl = EthtoolFamily() 116 cfg.netdevnl = NetdevFamily() 117 118 ksft_run([test_rss_input_xfrm_ipv4, test_rss_input_xfrm_ipv6], 119 args=(cfg, )) 120 ksft_exit() 121 122 123if __name__ == "__main__": 124 main() 125