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