xref: /linux/tools/testing/selftests/drivers/net/gro.py (revision 37a93dd5c49b5fda807fd204edf2547c3493319c)
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