| 1 | |
| 2 | |
| 3 |
|
| 4 |
|
| 5 | import os
|
| 6 | import re
|
| 7 |
|
| 8 | from lib.py import (bkg, cmd, defer, ethtool, rand_port, wait_port_listen,
|
| 9 | ksft_eq, KsftSkipEx, NetNSEnter, EthtoolFamily,
|
| 10 | NetdevFamily)
|
| 11 |
|
| 12 |
|
| 13 | RX_PAGE_SIZE_DEFAULT = 0
|
| 14 | RX_PAGE_SIZE_16K = 16384
|
| 15 |
|
| 16 | PROBE_RX_PAGE_SIZES = (RX_PAGE_SIZE_DEFAULT, RX_PAGE_SIZE_16K)
|
| 17 |
|
| 18 | NR_HUGEPAGES_FILE = "/proc/sys/vm/nr_hugepages"
|
| 19 |
|
| 20 |
|
| 21 | def _is_aligned(value, alignment):
|
| 22 | |
| 23 |
|
| 24 | alignment must be a power of two.
|
| 25 |
|
| 26 | return (value & (alignment - 1)) == 0
|
| 27 |
|
| 28 |
|
| 29 | def _restore_nr_hugepages(nr_hugepages):
|
| 30 | with open(NR_HUGEPAGES_FILE, 'w', encoding='utf-8') as f:
|
| 31 | f.write(str(nr_hugepages))
|
| 32 |
|
| 33 |
|
| 34 | def _reserve_hugepages(want=64):
|
| 35 |
|
| 36 | with open(NR_HUGEPAGES_FILE, 'r+', encoding='utf-8') as f:
|
| 37 | nr_hugepages = int(f.read().strip())
|
| 38 | if nr_hugepages >= want:
|
| 39 | return
|
| 40 | f.seek(0)
|
| 41 | f.write(str(want))
|
| 42 | defer(_restore_nr_hugepages, nr_hugepages)
|
| 43 |
|
| 44 |
|
| 45 | def _probe_devmem(cfg, rx_page_size):
|
| 46 |
|
| 47 | probe_command = f"{cfg.bin_local} -f {cfg.ifname}"
|
| 48 | if rx_page_size != RX_PAGE_SIZE_DEFAULT:
|
| 49 | probe_command += f" -b {rx_page_size}"
|
| 50 | return cmd(probe_command, fail=False, shell=True).ret == 0
|
| 51 |
|
| 52 |
|
| 53 | def require_devmem(cfg, rx_page_size=RX_PAGE_SIZE_DEFAULT):
|
| 54 |
|
| 55 | if rx_page_size not in PROBE_RX_PAGE_SIZES:
|
| 56 | raise RuntimeError(
|
| 57 | f"rx-page-size={rx_page_size} is missing from "
|
| 58 | f"PROBE_RX_PAGE_SIZES, so it was never probed.")
|
| 59 |
|
| 60 | if not hasattr(cfg, "devmem_supported"):
|
| 61 | _reserve_hugepages()
|
| 62 | |
| 63 | |
| 64 | cfg.devmem_supported = {size: _probe_devmem(cfg, size)
|
| 65 | for size in PROBE_RX_PAGE_SIZES}
|
| 66 |
|
| 67 | if not cfg.devmem_supported[RX_PAGE_SIZE_DEFAULT]:
|
| 68 | raise KsftSkipEx("Test requires devmem support")
|
| 69 |
|
| 70 | if rx_page_size != RX_PAGE_SIZE_DEFAULT:
|
| 71 | page_size = os.sysconf("SC_PAGE_SIZE")
|
| 72 | if not _is_aligned(rx_page_size, page_size):
|
| 73 | raise KsftSkipEx(
|
| 74 | f"rx-page-size={rx_page_size} is invalid for this platform "
|
| 75 | f"(must be a multiple of PAGE_SIZE={page_size})")
|
| 76 |
|
| 77 | if not cfg.devmem_supported[rx_page_size]:
|
| 78 | raise KsftSkipEx(
|
| 79 | f"Test requires devmem rx-page-size={rx_page_size} support")
|
| 80 |
|
| 81 |
|
| 82 | def configure_nic(cfg):
|
| 83 |
|
| 84 | if not hasattr(cfg, "devmem_supported"):
|
| 85 | raise RuntimeError(
|
| 86 | "require_devmem() must be called before configure_nic(), which "
|
| 87 | "may lease a queue away and make later probes fail.")
|
| 88 |
|
| 89 | if not hasattr(cfg, 'netns'):
|
| 90 | return
|
| 91 |
|
| 92 | cfg.require_ipver('6')
|
| 93 | ethnl = EthtoolFamily()
|
| 94 |
|
| 95 | channels = ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
|
| 96 | channels = channels['combined-count']
|
| 97 | if channels < 2:
|
| 98 | raise KsftSkipEx(
|
| 99 | 'Test requires NETIF with at least 2 combined channels'
|
| 100 | )
|
| 101 |
|
| 102 | rings = ethnl.rings_get({'header': {'dev-index': cfg.ifindex}})
|
| 103 | orig_rx_rings = rings['rx']
|
| 104 | orig_hds_thresh = rings.get('hds-thresh', 0)
|
| 105 |
|
| 106 | ethnl.rings_set({'header': {'dev-index': cfg.ifindex},
|
| 107 | 'tcp-data-split': 'enabled',
|
| 108 | 'hds-thresh': 0,
|
| 109 | 'rx': min(64, orig_rx_rings)})
|
| 110 | defer(ethnl.rings_set, {'header': {'dev-index': cfg.ifindex},
|
| 111 | 'tcp-data-split': 'unknown',
|
| 112 | 'hds-thresh': orig_hds_thresh,
|
| 113 | 'rx': orig_rx_rings})
|
| 114 |
|
| 115 | cfg.src_queue = channels - 1
|
| 116 | ethtool(f"-X {cfg.ifname} equal {cfg.src_queue}")
|
| 117 | defer(ethtool, f"-X {cfg.ifname} default")
|
| 118 |
|
| 119 | if not hasattr(cfg, 'nk_queue'):
|
| 120 | with NetNSEnter(str(cfg.netns)):
|
| 121 | netdevnl = NetdevFamily()
|
| 122 | lease_result = netdevnl.queue_create({
|
| 123 | "ifindex": cfg.nk_guest_ifindex,
|
| 124 | "type": "rx",
|
| 125 | "lease": {
|
| 126 | "ifindex": cfg.ifindex,
|
| 127 | "queue": {"id": cfg.src_queue, "type": "rx"},
|
| 128 | "netns-id": 0,
|
| 129 | },
|
| 130 | })
|
| 131 | cfg.nk_queue = lease_result['id']
|
| 132 |
|
| 133 |
|
| 134 | def set_flow_rule(cfg, port):
|
| 135 |
|
| 136 | output = ethtool(
|
| 137 | f"-N {cfg.ifname} flow-type tcp6 dst-port {port}"
|
| 138 | f" action {cfg.src_queue}"
|
| 139 | ).stdout
|
| 140 | return int(re.search(r'ID (\d+)', output).group(1))
|
| 141 |
|
| 142 |
|
| 143 | def ncdevmem_rx(cfg, port, verify=True, fail_on_linear=False, flow_steer=False,
|
| 144 | rx_page_size=RX_PAGE_SIZE_DEFAULT):
|
| 145 |
|
| 146 | if hasattr(cfg, 'netns'):
|
| 147 | flow_rule_id = set_flow_rule(cfg, port)
|
| 148 | defer(ethtool, f"-N {cfg.ifname} delete {flow_rule_id}")
|
| 149 |
|
| 150 | ifname = cfg.nk_guest_ifname
|
| 151 | addr = cfg.nk_guest_ipv6
|
| 152 | extras = [f"-t {cfg.nk_queue}", "-q 1", "-n"]
|
| 153 | else:
|
| 154 | ifname = cfg.ifname
|
| 155 | addr = cfg.addr
|
| 156 | extras = []
|
| 157 | if flow_steer:
|
| 158 | extras.append(f"-c {cfg.remote_addr}")
|
| 159 |
|
| 160 | if verify:
|
| 161 | extras.append("-v 7")
|
| 162 | if fail_on_linear:
|
| 163 | extras.append("-L")
|
| 164 | if rx_page_size != RX_PAGE_SIZE_DEFAULT:
|
| 165 | extras.append(f"-b {rx_page_size}")
|
| 166 |
|
| 167 | parts = [cfg.bin_local, "-l", f"-f {ifname}", f"-s {addr}",
|
| 168 | f"-p {port}", *extras]
|
| 169 | return " ".join(parts)
|
| 170 |
|
| 171 |
|
| 172 | def ncdevmem_tx(cfg, port, chunk_size=0):
|
| 173 |
|
| 174 | if hasattr(cfg, 'netns'):
|
| 175 | ifname = cfg.nk_guest_ifname
|
| 176 | addr = cfg.remote_addr_v['6']
|
| 177 | extras = ["-t 0", "-q 1", "-n"]
|
| 178 | else:
|
| 179 | ifname = cfg.ifname
|
| 180 | addr = cfg.remote_addr
|
| 181 | extras = []
|
| 182 |
|
| 183 | if chunk_size:
|
| 184 | extras.append(f"-z {chunk_size}")
|
| 185 |
|
| 186 | parts = [cfg.bin_local, f"-f {ifname}", f"-s {addr}",
|
| 187 | f"-p {port}", *extras]
|
| 188 | return " ".join(parts)
|
| 189 |
|
| 190 |
|
| 191 | def socat_send(cfg, port, buf_size=0):
|
| 192 | |
| 193 |
|
| 194 | When buf_size > 0, force one TCP segment per write of exactly that size by
|
| 195 | setting socat's buffer (-b) and disabling Nagle (TCP_NODELAY).
|
| 196 |
|
| 197 | proto = f"TCP{cfg.addr_ipver}"
|
| 198 |
|
| 199 | if hasattr(cfg, 'netns'):
|
| 200 | addr = f"[{cfg.nk_guest_ipv6}]"
|
| 201 | else:
|
| 202 | addr = cfg.baddr
|
| 203 |
|
| 204 | suffix = f",bind={cfg.remote_baddr}:{port}"
|
| 205 |
|
| 206 | buf = ""
|
| 207 | if buf_size:
|
| 208 | buf = f"-b {buf_size}"
|
| 209 | suffix += ",nodelay"
|
| 210 |
|
| 211 | return f"socat {buf} -u - {proto}:{addr}:{port}{suffix}"
|
| 212 |
|
| 213 |
|
| 214 | def socat_listen(cfg, port):
|
| 215 |
|
| 216 | return f"socat -U - TCP{cfg.addr_ipver}-LISTEN:{port}"
|
| 217 |
|
| 218 |
|
| 219 | def setup_test(cfg, bin_local):
|
| 220 |
|
| 221 | cfg.bin_local = bin_local
|
| 222 | cfg.bin_remote = cfg.remote.deploy(cfg.bin_local)
|
| 223 |
|
| 224 |
|
| 225 | def run_rx(cfg):
|
| 226 |
|
| 227 | require_devmem(cfg)
|
| 228 | configure_nic(cfg)
|
| 229 | port = rand_port()
|
| 230 | socat = socat_send(cfg, port)
|
| 231 | data_pipe = (f"yes $(echo -e \x01\x02\x03\x04\x05\x06) | head -c 1K"
|
| 232 | f" | {socat}")
|
| 233 | netns = getattr(cfg, "netns", None)
|
| 234 |
|
| 235 | listen_cmd = ncdevmem_rx(cfg, port, flow_steer=not hasattr(cfg, 'netns'))
|
| 236 | with bkg(listen_cmd, exit_wait=True, ns=netns) as ncdevmem:
|
| 237 | wait_port_listen(port, proto="tcp", ns=netns)
|
| 238 | cmd(data_pipe, host=cfg.remote, shell=True)
|
| 239 | ksft_eq(ncdevmem.ret, 0)
|
| 240 |
|
| 241 |
|
| 242 | def run_tx(cfg):
|
| 243 |
|
| 244 | require_devmem(cfg)
|
| 245 | configure_nic(cfg)
|
| 246 | netns = getattr(cfg, "netns", None)
|
| 247 | port = rand_port()
|
| 248 | tx_cmd = ncdevmem_tx(cfg, port)
|
| 249 | listen_cmd = socat_listen(cfg, port)
|
| 250 |
|
| 251 | with bkg(listen_cmd, host=cfg.remote, exit_wait=True) as socat:
|
| 252 | wait_port_listen(port, host=cfg.remote)
|
| 253 | cmd(f"bash -c 'echo -e \"hello\\nworld\" | {tx_cmd}'", ns=netns, shell=True)
|
| 254 | ksft_eq(socat.stdout.strip(), "hello\nworld")
|
| 255 |
|
| 256 |
|
| 257 | def run_tx_chunks(cfg):
|
| 258 |
|
| 259 | require_devmem(cfg)
|
| 260 | configure_nic(cfg)
|
| 261 | netns = getattr(cfg, "netns", None)
|
| 262 | port = rand_port()
|
| 263 | tx_cmd = ncdevmem_tx(cfg, port, chunk_size=3)
|
| 264 | listen_cmd = socat_listen(cfg, port)
|
| 265 |
|
| 266 | with bkg(listen_cmd, host=cfg.remote, exit_wait=True) as socat:
|
| 267 | wait_port_listen(port, host=cfg.remote)
|
| 268 | cmd(f"bash -c 'echo -e \"hello\\nworld\" | {tx_cmd}'", ns=netns, shell=True)
|
| 269 | ksft_eq(socat.stdout.strip(), "hello\nworld")
|
| 270 |
|
| 271 |
|
| 272 | def run_rx_large_niov(cfg):
|
| 273 | |
| 274 |
|
| 275 | Sweep payload sizes that straddle the niov boundary: below, equal to,
|
| 276 | and above rx_page_size, to exercise sub-niov, exact-niov, and multi-niov
|
| 277 | RX paths.
|
| 278 |
|
| 279 | require_devmem(cfg, rx_page_size=RX_PAGE_SIZE_16K)
|
| 280 | _reserve_hugepages()
|
| 281 | configure_nic(cfg)
|
| 282 | netns = getattr(cfg, "netns", None)
|
| 283 |
|
| 284 | for size in [1024, 4096, 8192, 16384, 32768, 65536]:
|
| 285 | port = rand_port()
|
| 286 | socat = socat_send(cfg, port)
|
| 287 | listen_cmd = ncdevmem_rx(cfg, port,
|
| 288 | flow_steer=not netns,
|
| 289 | rx_page_size=RX_PAGE_SIZE_16K)
|
| 290 | data_pipe = (f"yes $(echo -e \x01\x02\x03\x04\x05\x06) | "
|
| 291 | f"head -c {size} | {socat}")
|
| 292 | with bkg(listen_cmd, exit_wait=True, ns=netns) as ncdevmem:
|
| 293 | wait_port_listen(port, proto="tcp", ns=netns)
|
| 294 | cmd(data_pipe, host=cfg.remote, shell=True)
|
| 295 | ksft_eq(ncdevmem.ret, 0,
|
| 296 | f"large-niov failed for payload size {size}")
|
| 297 |
|
| 298 |
|
| 299 | def run_rx_hds(cfg):
|
| 300 |
|
| 301 | require_devmem(cfg)
|
| 302 | configure_nic(cfg)
|
| 303 | netns = getattr(cfg, "netns", None)
|
| 304 |
|
| 305 | for size in [1, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192]:
|
| 306 | port = rand_port()
|
| 307 |
|
| 308 | listen_cmd = ncdevmem_rx(cfg, port, verify=False,
|
| 309 | fail_on_linear=True)
|
| 310 | socat = socat_send(cfg, port, buf_size=size)
|
| 311 |
|
| 312 | with bkg(listen_cmd, exit_wait=True, ns=netns) as ncdevmem:
|
| 313 | wait_port_listen(port, proto="tcp", ns=netns)
|
| 314 | cmd(f"dd if=/dev/zero bs={size} count=1 2>/dev/null | "
|
| 315 | f"{socat}", host=cfg.remote, shell=True)
|
| 316 | ksft_eq(ncdevmem.ret, 0, f"HDS failed for payload size {size}")
|