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