1# SPDX-License-Identifier: GPL-2.0 2"""Shared helpers for devmem TCP selftests.""" 3 4import re 5 6from lib.py import (bkg, cmd, defer, ethtool, rand_port, wait_port_listen, 7 ksft_eq, KsftSkipEx, NetNSEnter, EthtoolFamily, 8 NetdevFamily) 9 10 11def require_devmem(cfg): 12 """Probe ncdevmem on cfg.ifname and SKIP the test if devmem isn't supported.""" 13 if not hasattr(cfg, "devmem_probed"): 14 probe_command = f"{cfg.bin_local} -f {cfg.ifname}" 15 cfg.devmem_supported = cmd(probe_command, fail=False, shell=True).ret == 0 16 cfg.devmem_probed = True 17 18 if not cfg.devmem_supported: 19 raise KsftSkipEx("Test requires devmem support") 20 21 22def configure_nic(cfg): 23 """Channels, rings, RSS, queue lease for netkit devmem.""" 24 if not hasattr(cfg, 'netns'): 25 return 26 27 cfg.require_ipver('6') 28 ethnl = EthtoolFamily() 29 30 channels = ethnl.channels_get({'header': {'dev-index': cfg.ifindex}}) 31 channels = channels['combined-count'] 32 if channels < 2: 33 raise KsftSkipEx( 34 'Test requires NETIF with at least 2 combined channels' 35 ) 36 37 rings = ethnl.rings_get({'header': {'dev-index': cfg.ifindex}}) 38 orig_rx_rings = rings['rx'] 39 orig_hds_thresh = rings.get('hds-thresh', 0) 40 orig_data_split = rings.get('tcp-data-split', 'unknown') 41 42 ethnl.rings_set({'header': {'dev-index': cfg.ifindex}, 43 'tcp-data-split': 'enabled', 44 'hds-thresh': 0, 45 'rx': min(64, orig_rx_rings)}) 46 defer(ethnl.rings_set, {'header': {'dev-index': cfg.ifindex}, 47 'tcp-data-split': orig_data_split, 48 'hds-thresh': orig_hds_thresh, 49 'rx': orig_rx_rings}) 50 51 cfg.src_queue = channels - 1 52 ethtool(f"-X {cfg.ifname} equal {cfg.src_queue}") 53 defer(ethtool, f"-X {cfg.ifname} default") 54 55 if not hasattr(cfg, 'nk_queue'): 56 with NetNSEnter(str(cfg.netns)): 57 netdevnl = NetdevFamily() 58 lease_result = netdevnl.queue_create({ 59 "ifindex": cfg.nk_guest_ifindex, 60 "type": "rx", 61 "lease": { 62 "ifindex": cfg.ifindex, 63 "queue": {"id": cfg.src_queue, "type": "rx"}, 64 "netns-id": 0, 65 }, 66 }) 67 cfg.nk_queue = lease_result['id'] 68 69 70def set_flow_rule(cfg, port): 71 """Install a flow rule steering to src_queue and return the flow rule ID.""" 72 output = ethtool( 73 f"-N {cfg.ifname} flow-type tcp6 dst-port {port}" 74 f" action {cfg.src_queue}" 75 ).stdout 76 return int(re.search(r'ID (\d+)', output).group(1)) 77 78 79def ncdevmem_rx(cfg, port, verify=True, fail_on_linear=False, flow_steer=False): 80 """Build the ncdevmem RX listener command.""" 81 if hasattr(cfg, 'netns'): 82 flow_rule_id = set_flow_rule(cfg, port) 83 defer(ethtool, f"-N {cfg.ifname} delete {flow_rule_id}") 84 85 ifname = cfg.nk_guest_ifname 86 addr = cfg.nk_guest_ipv6 87 extras = [f"-t {cfg.nk_queue}", "-q 1", "-n"] 88 else: 89 ifname = cfg.ifname 90 addr = cfg.addr 91 extras = [] 92 if flow_steer: 93 extras.append(f"-c {cfg.remote_addr}") 94 95 if verify: 96 extras.append("-v 7") 97 if fail_on_linear: 98 extras.append("-L") 99 100 parts = [cfg.bin_local, "-l", f"-f {ifname}", f"-s {addr}", 101 f"-p {port}", *extras] 102 return " ".join(parts) 103 104 105def ncdevmem_tx(cfg, port, chunk_size=0): 106 """Build the ncdevmem TX send command.""" 107 if hasattr(cfg, 'netns'): 108 ifname = cfg.nk_guest_ifname 109 addr = cfg.remote_addr_v['6'] 110 extras = ["-t 0", "-q 1", "-n"] 111 else: 112 ifname = cfg.ifname 113 addr = cfg.remote_addr 114 extras = [] 115 116 if chunk_size: 117 extras.append(f"-z {chunk_size}") 118 119 parts = [cfg.bin_local, f"-f {ifname}", f"-s {addr}", 120 f"-p {port}", *extras] 121 return " ".join(parts) 122 123 124def socat_send(cfg, port, buf_size=0): 125 """Socat command for sending to the devmem listener. 126 127 When buf_size > 0, force one TCP segment per write of exactly that size by 128 setting socat's buffer (-b) and disabling Nagle (TCP_NODELAY). 129 """ 130 proto = f"TCP{cfg.addr_ipver}" 131 132 if hasattr(cfg, 'netns'): 133 addr = f"[{cfg.nk_guest_ipv6}]" 134 else: 135 addr = cfg.baddr 136 137 suffix = f",bind={cfg.remote_baddr}:{port}" 138 139 buf = "" 140 if buf_size: 141 buf = f"-b {buf_size}" 142 suffix += ",nodelay" 143 144 return f"socat {buf} -u - {proto}:{addr}:{port}{suffix}" 145 146 147def socat_listen(cfg, port): 148 """Socat listen command for TX tests.""" 149 return f"socat -U - TCP{cfg.addr_ipver}-LISTEN:{port}" 150 151 152def setup_test(cfg, bin_local): 153 """Stash the local ncdevmem path on cfg and deploy it to the remote.""" 154 cfg.bin_local = bin_local 155 cfg.bin_remote = cfg.remote.deploy(cfg.bin_local) 156 157 158def run_rx(cfg): 159 """Run the devmem RX test.""" 160 require_devmem(cfg) 161 configure_nic(cfg) 162 port = rand_port() 163 socat = socat_send(cfg, port) 164 data_pipe = (f"yes $(echo -e \x01\x02\x03\x04\x05\x06) | head -c 1K" 165 f" | {socat}") 166 netns = getattr(cfg, "netns", None) 167 168 listen_cmd = ncdevmem_rx(cfg, port, flow_steer=not hasattr(cfg, 'netns')) 169 with bkg(listen_cmd, exit_wait=True, ns=netns) as ncdevmem: 170 wait_port_listen(port, proto="tcp", ns=netns) 171 cmd(data_pipe, host=cfg.remote, shell=True) 172 ksft_eq(ncdevmem.ret, 0) 173 174 175def run_tx(cfg): 176 """Run the devmem TX test.""" 177 require_devmem(cfg) 178 configure_nic(cfg) 179 netns = getattr(cfg, "netns", None) 180 port = rand_port() 181 tx_cmd = ncdevmem_tx(cfg, port) 182 listen_cmd = socat_listen(cfg, port) 183 184 with bkg(listen_cmd, host=cfg.remote, exit_wait=True) as socat: 185 wait_port_listen(port, host=cfg.remote) 186 cmd(f"bash -c 'echo -e \"hello\\nworld\" | {tx_cmd}'", ns=netns, shell=True) 187 ksft_eq(socat.stdout.strip(), "hello\nworld") 188 189 190def run_tx_chunks(cfg): 191 """Run the devmem TX chunking test.""" 192 require_devmem(cfg) 193 configure_nic(cfg) 194 netns = getattr(cfg, "netns", None) 195 port = rand_port() 196 tx_cmd = ncdevmem_tx(cfg, port, chunk_size=3) 197 listen_cmd = socat_listen(cfg, port) 198 199 with bkg(listen_cmd, host=cfg.remote, exit_wait=True) as socat: 200 wait_port_listen(port, host=cfg.remote) 201 cmd(f"bash -c 'echo -e \"hello\\nworld\" | {tx_cmd}'", ns=netns, shell=True) 202 ksft_eq(socat.stdout.strip(), "hello\nworld") 203 204 205def run_rx_hds(cfg): 206 """Run the HDS test by running devmem RX across a segment size sweep.""" 207 require_devmem(cfg) 208 configure_nic(cfg) 209 netns = getattr(cfg, "netns", None) 210 211 for size in [1, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192]: 212 port = rand_port() 213 214 listen_cmd = ncdevmem_rx(cfg, port, verify=False, 215 fail_on_linear=True) 216 socat = socat_send(cfg, port, buf_size=size) 217 218 with bkg(listen_cmd, exit_wait=True, ns=netns) as ncdevmem: 219 wait_port_listen(port, proto="tcp", ns=netns) 220 cmd(f"dd if=/dev/zero bs={size} count=1 2>/dev/null | " 221 f"{socat}", host=cfg.remote, shell=True) 222 ksft_eq(ncdevmem.ret, 0, f"HDS failed for payload size {size}") 223