1#!/usr/bin/env python3 2# SPDX-License-Identifier: GPL-2.0 3 4""" 5GRO (Generic Receive Offload) conformance tests. 6 7Validates that GRO coalescing works correctly by running the gro 8binary in different configurations and checking for correct packet 9coalescing behavior. 10 11Test cases: 12 - data_same: Same size data packets coalesce 13 - data_lrg_sml: Large packet followed by smaller one coalesces 14 - data_sml_lrg: Small packet followed by larger one doesn't coalesce 15 - ack: Pure ACK packets do not coalesce 16 - flags_psh: Packets with PSH flag don't coalesce 17 - flags_syn: Packets with SYN flag don't coalesce 18 - flags_rst: Packets with RST flag don't coalesce 19 - flags_urg: Packets with URG flag don't coalesce 20 - flags_cwr: Packets with CWR flag don't coalesce 21 - tcp_csum: Packets with incorrect checksum don't coalesce 22 - tcp_seq: Packets with non-consecutive seqno don't coalesce 23 - tcp_ts: Packets with different timestamp options don't coalesce 24 - tcp_opt: Packets with different TCP options don't coalesce 25 - ip_ecn: Packets with different ECN don't coalesce 26 - ip_tos: Packets with different TOS don't coalesce 27 - ip_ttl: (IPv4) Packets with different TTL don't coalesce 28 - ip_opt: (IPv4) Packets with IP options don't coalesce 29 - ip_frag4: (IPv4) IPv4 fragments don't coalesce 30 - ip_id_df*: (IPv4) IP ID field coalescing tests 31 - ip_frag6: (IPv6) IPv6 fragments don't coalesce 32 - ip_v6ext_same: (IPv6) IPv6 ext header with same payload coalesces 33 - ip_v6ext_diff: (IPv6) IPv6 ext header with different payload doesn't coalesce 34 - large_max: Packets exceeding GRO_MAX_SIZE don't coalesce 35 - large_rem: Large packet remainder handling 36""" 37 38import os 39from lib.py import ksft_run, ksft_exit, ksft_pr 40from lib.py import NetDrvEpEnv, KsftXfailEx 41from lib.py import bkg, cmd, defer, ethtool, ip 42from lib.py import ksft_variants 43 44 45def _resolve_dmac(cfg, ipver): 46 """ 47 Find the destination MAC address remote host should use to send packets 48 towards the local host. It may be a router / gateway address. 49 """ 50 51 attr = "dmac" + ipver 52 # Cache the response across test cases 53 if hasattr(cfg, attr): 54 return getattr(cfg, attr) 55 56 route = ip(f"-{ipver} route get {cfg.addr_v[ipver]}", 57 json=True, host=cfg.remote)[0] 58 gw = route.get("gateway") 59 # Local L2 segment, address directly 60 if not gw: 61 setattr(cfg, attr, cfg.dev['address']) 62 return getattr(cfg, attr) 63 64 # ping to make sure neighbor is resolved, 65 # bind to an interface, for v6 the GW is likely link local 66 cmd(f"ping -c1 -W0 -I{cfg.remote_ifname} {gw}", host=cfg.remote) 67 68 neigh = ip(f"neigh get {gw} dev {cfg.remote_ifname}", 69 json=True, host=cfg.remote)[0] 70 setattr(cfg, attr, neigh['lladdr']) 71 return getattr(cfg, attr) 72 73 74def _write_defer_restore(cfg, path, val, defer_undo=False): 75 with open(path, "r", encoding="utf-8") as fp: 76 orig_val = fp.read().strip() 77 if str(val) == orig_val: 78 return 79 with open(path, "w", encoding="utf-8") as fp: 80 fp.write(val) 81 if defer_undo: 82 defer(_write_defer_restore, cfg, path, orig_val) 83 84 85def _set_mtu_restore(dev, mtu, host): 86 if dev['mtu'] < mtu: 87 ip(f"link set dev {dev['ifname']} mtu {mtu}", host=host) 88 defer(ip, f"link set dev {dev['ifname']} mtu {dev['mtu']}", host=host) 89 90 91def _set_ethtool_feat(dev, current, feats, host=None): 92 s2n = {True: "on", False: "off"} 93 94 new = ["-K", dev] 95 old = ["-K", dev] 96 no_change = True 97 for name, state in feats.items(): 98 new += [name, s2n[state]] 99 old += [name, s2n[current[name]["active"]]] 100 101 if current[name]["active"] != state: 102 no_change = False 103 if current[name]["fixed"]: 104 raise KsftXfailEx(f"Device does not support {name}") 105 if no_change: 106 return 107 108 eth_cmd = ethtool(" ".join(new), host=host) 109 defer(ethtool, " ".join(old), host=host) 110 111 # If ethtool printed something kernel must have modified some features 112 if eth_cmd.stdout: 113 ksft_pr(eth_cmd) 114 115 116def _setup(cfg, mode, test_name): 117 """ Setup hardware loopback mode for GRO testing. """ 118 119 if not hasattr(cfg, "bin_remote"): 120 cfg.bin_local = cfg.test_dir / "gro" 121 cfg.bin_remote = cfg.remote.deploy(cfg.bin_local) 122 123 if not hasattr(cfg, "feat"): 124 cfg.feat = ethtool(f"-k {cfg.ifname}", json=True)[0] 125 cfg.remote_feat = ethtool(f"-k {cfg.remote_ifname}", 126 host=cfg.remote, json=True)[0] 127 128 # "large_*" tests need at least 4k MTU 129 if test_name.startswith("large_"): 130 _set_mtu_restore(cfg.dev, 4096, None) 131 _set_mtu_restore(cfg.remote_dev, 4096, cfg.remote) 132 133 if mode == "sw": 134 flush_path = f"/sys/class/net/{cfg.ifname}/gro_flush_timeout" 135 irq_path = f"/sys/class/net/{cfg.ifname}/napi_defer_hard_irqs" 136 137 _write_defer_restore(cfg, flush_path, "200000", defer_undo=True) 138 _write_defer_restore(cfg, irq_path, "10", defer_undo=True) 139 140 _set_ethtool_feat(cfg.ifname, cfg.feat, 141 {"generic-receive-offload": True, 142 "rx-gro-hw": False, 143 "large-receive-offload": False}) 144 elif mode == "hw": 145 _set_ethtool_feat(cfg.ifname, cfg.feat, 146 {"generic-receive-offload": False, 147 "rx-gro-hw": True, 148 "large-receive-offload": False}) 149 150 # Some NICs treat HW GRO as a GRO sub-feature so disabling GRO 151 # will also clear HW GRO. Use a hack of installing XDP generic 152 # to skip SW GRO, even when enabled. 153 feat = ethtool(f"-k {cfg.ifname}", json=True)[0] 154 if not feat["rx-gro-hw"]["active"]: 155 ksft_pr("Driver clears HW GRO and SW GRO is cleared, using generic XDP workaround") 156 prog = cfg.net_lib_dir / "xdp_dummy.bpf.o" 157 ip(f"link set dev {cfg.ifname} xdpgeneric obj {prog} sec xdp") 158 defer(ip, f"link set dev {cfg.ifname} xdpgeneric off") 159 160 # Attaching XDP may change features, fetch the latest state 161 feat = ethtool(f"-k {cfg.ifname}", json=True)[0] 162 163 _set_ethtool_feat(cfg.ifname, feat, 164 {"generic-receive-offload": True, 165 "rx-gro-hw": True, 166 "large-receive-offload": False}) 167 elif mode == "lro": 168 # netdevsim advertises LRO for feature inheritance testing with 169 # bonding/team tests but it doesn't actually perform the offload 170 cfg.require_nsim(nsim_test=False) 171 172 _set_ethtool_feat(cfg.ifname, cfg.feat, 173 {"generic-receive-offload": False, 174 "rx-gro-hw": False, 175 "large-receive-offload": True}) 176 177 try: 178 # Disable TSO for local tests 179 cfg.require_nsim() # will raise KsftXfailEx if not running on nsim 180 181 _set_ethtool_feat(cfg.remote_ifname, cfg.remote_feat, 182 {"tcp-segmentation-offload": False}, 183 host=cfg.remote) 184 except KsftXfailEx: 185 pass 186 187 188def _gro_variants(): 189 """Generator that yields all combinations of protocol and test types.""" 190 191 # Tests that work for all protocols 192 common_tests = [ 193 "data_same", "data_lrg_sml", "data_sml_lrg", 194 "ack", 195 "flags_psh", "flags_syn", "flags_rst", "flags_urg", "flags_cwr", 196 "tcp_csum", "tcp_seq", "tcp_ts", "tcp_opt", 197 "ip_ecn", "ip_tos", 198 "large_max", "large_rem", 199 ] 200 201 # Tests specific to IPv4 202 ipv4_tests = [ 203 "ip_ttl", "ip_opt", "ip_frag4", 204 "ip_id_df1_inc", "ip_id_df1_fixed", 205 "ip_id_df0_inc", "ip_id_df0_fixed", 206 "ip_id_df1_inc_fixed", "ip_id_df1_fixed_inc", 207 ] 208 209 # Tests specific to IPv6 210 ipv6_tests = [ 211 "ip_frag6", "ip_v6ext_same", "ip_v6ext_diff", 212 ] 213 214 for mode in ["sw", "hw", "lro"]: 215 for protocol in ["ipv4", "ipv6", "ipip"]: 216 for test_name in common_tests: 217 yield mode, protocol, test_name 218 219 if protocol in ["ipv4", "ipip"]: 220 for test_name in ipv4_tests: 221 yield mode, protocol, test_name 222 elif protocol == "ipv6": 223 for test_name in ipv6_tests: 224 yield mode, protocol, test_name 225 226 227@ksft_variants(_gro_variants()) 228def test(cfg, mode, protocol, test_name): 229 """Run a single GRO test with retries.""" 230 231 ipver = "6" if protocol[-1] == "6" else "4" 232 cfg.require_ipver(ipver) 233 234 _setup(cfg, mode, test_name) 235 236 base_cmd_args = [ 237 f"--{protocol}", 238 f"--dmac {_resolve_dmac(cfg, ipver)}", 239 f"--smac {cfg.remote_dev['address']}", 240 f"--daddr {cfg.addr_v[ipver]}", 241 f"--saddr {cfg.remote_addr_v[ipver]}", 242 f"--test {test_name}", 243 "--verbose" 244 ] 245 base_args = " ".join(base_cmd_args) 246 247 # Each test is run 6 times to deflake, because given the receive timing, 248 # not all packets that should coalesce will be considered in the same flow 249 # on every try. 250 max_retries = 6 251 for attempt in range(max_retries): 252 rx_cmd = f"{cfg.bin_local} {base_args} --rx --iface {cfg.ifname}" 253 tx_cmd = f"{cfg.bin_remote} {base_args} --iface {cfg.remote_ifname}" 254 255 fail_now = attempt >= max_retries - 1 256 257 with bkg(rx_cmd, ksft_ready=True, exit_wait=True, 258 fail=fail_now) as rx_proc: 259 cmd(tx_cmd, host=cfg.remote) 260 261 if rx_proc.ret == 0: 262 return 263 264 ksft_pr(rx_proc) 265 266 if test_name.startswith("large_") and os.environ.get("KSFT_MACHINE_SLOW"): 267 ksft_pr(f"Ignoring {protocol}/{test_name} failure due to slow environment") 268 return 269 270 ksft_pr(f"Attempt {attempt + 1}/{max_retries} failed, retrying...") 271 272 273def main() -> None: 274 """ Ksft boiler plate main """ 275 276 with NetDrvEpEnv(__file__) as cfg: 277 ksft_run(cases=[test], args=(cfg,)) 278 ksft_exit() 279 280 281if __name__ == "__main__": 282 main() 283