1#!/usr/bin/env python3 2# SPDX-License-Identifier: GPL-2.0 3 4import re 5import time 6import threading 7from os import path 8from lib.py import ( 9 ksft_run, 10 ksft_exit, 11 ksft_eq, 12 ksft_in, 13 ksft_not_in, 14 ksft_raises, 15) 16from lib.py import ( 17 NetDrvContEnv, 18 NetNSEnter, 19 EthtoolFamily, 20 NetdevFamily, 21) 22from lib.py import ( 23 bkg, 24 cmd, 25 defer, 26 ethtool, 27 ip, 28 rand_port, 29 wait_port_listen, 30) 31from lib.py import KsftSkipEx, CmdExitFailure 32 33 34def set_flow_rule(cfg): 35 output = ethtool( 36 f"-N {cfg.ifname} flow-type tcp6 dst-port {cfg.port} action {cfg.src_queue}" 37 ).stdout 38 values = re.search(r"ID (\d+)", output).group(1) 39 return int(values) 40 41 42def test_iou_zcrx(cfg) -> None: 43 cfg.require_ipver("6") 44 ethnl = EthtoolFamily() 45 46 rings = ethnl.rings_get({"header": {"dev-index": cfg.ifindex}}) 47 rx_rings = rings["rx"] 48 hds_thresh = rings.get("hds-thresh", 0) 49 50 ethnl.rings_set( 51 { 52 "header": {"dev-index": cfg.ifindex}, 53 "tcp-data-split": "enabled", 54 "hds-thresh": 0, 55 "rx": 64, 56 } 57 ) 58 defer( 59 ethnl.rings_set, 60 { 61 "header": {"dev-index": cfg.ifindex}, 62 "tcp-data-split": "unknown", 63 "hds-thresh": hds_thresh, 64 "rx": rx_rings, 65 }, 66 ) 67 68 ethtool(f"-X {cfg.ifname} equal {cfg.src_queue}") 69 defer(ethtool, f"-X {cfg.ifname} default") 70 71 flow_rule_id = set_flow_rule(cfg) 72 defer(ethtool, f"-N {cfg.ifname} delete {flow_rule_id}") 73 74 rx_cmd = f"ip netns exec {cfg.netns.name} {cfg.bin_local} -s -p {cfg.port} -i {cfg._nk_guest_ifname} -q {cfg.nk_queue}" 75 tx_cmd = f"{cfg.bin_remote} -c -h {cfg.nk_guest_ipv6} -p {cfg.port} -l 12840" 76 with bkg(rx_cmd, exit_wait=True): 77 wait_port_listen(cfg.port, proto="tcp", ns=cfg.netns) 78 cmd(tx_cmd, host=cfg.remote) 79 80 81def test_attrs(cfg) -> None: 82 cfg.require_ipver("6") 83 netdevnl = NetdevFamily() 84 queue_info = netdevnl.queue_get( 85 {"ifindex": cfg.ifindex, "id": cfg.src_queue, "type": "rx"} 86 ) 87 88 ksft_eq(queue_info["id"], cfg.src_queue) 89 ksft_eq(queue_info["type"], "rx") 90 ksft_eq(queue_info["ifindex"], cfg.ifindex) 91 92 ksft_in("lease", queue_info) 93 lease = queue_info["lease"] 94 ksft_eq(lease["ifindex"], cfg.nk_guest_ifindex) 95 ksft_eq(lease["queue"]["id"], cfg.nk_queue) 96 ksft_eq(lease["queue"]["type"], "rx") 97 ksft_in("netns-id", lease) 98 99 100def test_attach_xdp_with_mp(cfg) -> None: 101 cfg.require_ipver("6") 102 ethnl = EthtoolFamily() 103 104 rings = ethnl.rings_get({"header": {"dev-index": cfg.ifindex}}) 105 rx_rings = rings["rx"] 106 hds_thresh = rings.get("hds-thresh", 0) 107 108 ethnl.rings_set( 109 { 110 "header": {"dev-index": cfg.ifindex}, 111 "tcp-data-split": "enabled", 112 "hds-thresh": 0, 113 "rx": 64, 114 } 115 ) 116 defer( 117 ethnl.rings_set, 118 { 119 "header": {"dev-index": cfg.ifindex}, 120 "tcp-data-split": "unknown", 121 "hds-thresh": hds_thresh, 122 "rx": rx_rings, 123 }, 124 ) 125 126 ethtool(f"-X {cfg.ifname} equal {cfg.src_queue}") 127 defer(ethtool, f"-X {cfg.ifname} default") 128 129 netdevnl = NetdevFamily() 130 131 rx_cmd = f"ip netns exec {cfg.netns.name} {cfg.bin_local} -s -p {cfg.port} -i {cfg._nk_guest_ifname} -q {cfg.nk_queue}" 132 with bkg(rx_cmd): 133 wait_port_listen(cfg.port, proto="tcp", ns=cfg.netns) 134 135 time.sleep(0.1) 136 queue_info = netdevnl.queue_get( 137 {"ifindex": cfg.ifindex, "id": cfg.src_queue, "type": "rx"} 138 ) 139 ksft_in("io-uring", queue_info) 140 141 prog = cfg.net_lib_dir / "xdp_dummy.bpf.o" 142 with ksft_raises(CmdExitFailure): 143 ip(f"link set dev {cfg.ifname} xdp obj {prog} sec xdp.frags") 144 145 time.sleep(0.1) 146 queue_info = netdevnl.queue_get( 147 {"ifindex": cfg.ifindex, "id": cfg.src_queue, "type": "rx"} 148 ) 149 ksft_not_in("io-uring", queue_info) 150 151 152def test_destroy(cfg) -> None: 153 cfg.require_ipver("6") 154 ethnl = EthtoolFamily() 155 156 rings = ethnl.rings_get({"header": {"dev-index": cfg.ifindex}}) 157 rx_rings = rings["rx"] 158 hds_thresh = rings.get("hds-thresh", 0) 159 160 ethnl.rings_set( 161 { 162 "header": {"dev-index": cfg.ifindex}, 163 "tcp-data-split": "enabled", 164 "hds-thresh": 0, 165 "rx": 64, 166 } 167 ) 168 defer( 169 ethnl.rings_set, 170 { 171 "header": {"dev-index": cfg.ifindex}, 172 "tcp-data-split": "unknown", 173 "hds-thresh": hds_thresh, 174 "rx": rx_rings, 175 }, 176 ) 177 178 ethtool(f"-X {cfg.ifname} equal {cfg.src_queue}") 179 defer(ethtool, f"-X {cfg.ifname} default") 180 181 rx_cmd = f"ip netns exec {cfg.netns.name} {cfg.bin_local} -s -p {cfg.port} -i {cfg._nk_guest_ifname} -q {cfg.nk_queue}" 182 rx_proc = cmd(rx_cmd, background=True) 183 wait_port_listen(cfg.port, proto="tcp", ns=cfg.netns) 184 185 netdevnl = NetdevFamily() 186 queue_info = netdevnl.queue_get( 187 {"ifindex": cfg.ifindex, "id": cfg.src_queue, "type": "rx"} 188 ) 189 ksft_in("io-uring", queue_info) 190 191 # ip link del will wait for all refs to drop first, but iou-zcrx is holding 192 # onto a ref. Terminate iou-zcrx async via a thread after a delay. 193 kill_timer = threading.Timer(1, rx_proc.proc.terminate) 194 kill_timer.start() 195 196 ip(f"link del dev {cfg._nk_host_ifname}") 197 kill_timer.join() 198 cfg._nk_host_ifname = None 199 cfg._nk_guest_ifname = None 200 201 queue_info = netdevnl.queue_get( 202 {"ifindex": cfg.ifindex, "id": cfg.src_queue, "type": "rx"} 203 ) 204 ksft_not_in("io-uring", queue_info) 205 206 cmd(f"tc filter del dev {cfg.ifname} ingress pref {cfg._bpf_prog_pref}") 207 cfg._tc_attached = False 208 209 flow_rule_id = set_flow_rule(cfg) 210 defer(ethtool, f"-N {cfg.ifname} delete {flow_rule_id}") 211 212 rx_cmd = f"{cfg.bin_local} -s -p {cfg.port} -i {cfg.ifname} -q {cfg.src_queue}" 213 tx_cmd = f"{cfg.bin_remote} -c -h {cfg.addr_v['6']} -p {cfg.port} -l 12840" 214 with bkg(rx_cmd, exit_wait=True): 215 wait_port_listen(cfg.port, proto="tcp") 216 cmd(tx_cmd, host=cfg.remote) 217 # Short delay since iou cleanup is async and takes a bit of time. 218 time.sleep(0.1) 219 queue_info = netdevnl.queue_get( 220 {"ifindex": cfg.ifindex, "id": cfg.src_queue, "type": "rx"} 221 ) 222 ksft_not_in("io-uring", queue_info) 223 224 225def main() -> None: 226 with NetDrvContEnv(__file__, rxqueues=2) as cfg: 227 cfg.bin_local = path.abspath( 228 path.dirname(__file__) + "/../../../drivers/net/hw/iou-zcrx" 229 ) 230 cfg.bin_remote = cfg.remote.deploy(cfg.bin_local) 231 cfg.port = rand_port() 232 233 ethnl = EthtoolFamily() 234 channels = ethnl.channels_get({"header": {"dev-index": cfg.ifindex}}) 235 channels = channels["combined-count"] 236 if channels < 2: 237 raise KsftSkipEx("Test requires NETIF with at least 2 combined channels") 238 239 cfg.src_queue = channels - 1 240 241 with NetNSEnter(str(cfg.netns)): 242 netdevnl = NetdevFamily() 243 bind_result = netdevnl.queue_create( 244 { 245 "ifindex": cfg.nk_guest_ifindex, 246 "type": "rx", 247 "lease": { 248 "ifindex": cfg.ifindex, 249 "queue": {"id": cfg.src_queue, "type": "rx"}, 250 "netns-id": 0, 251 }, 252 } 253 ) 254 cfg.nk_queue = bind_result["id"] 255 256 # test_destroy must be last because it destroys the netkit devices 257 ksft_run( 258 [test_iou_zcrx, test_attrs, test_attach_xdp_with_mp, test_destroy], 259 args=(cfg,), 260 ) 261 ksft_exit() 262 263 264if __name__ == "__main__": 265 main() 266