Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'selftests-net-page_poll-allocation-error-injection'

Jakub Kicinski says:

====================
selftests: net: page_poll allocation error injection

Add a test for exercising driver memory allocation failure paths.
page pool is a bit tricky to inject errors into at the page allocator
level because of the bulk alloc and recycling, so add explicit error
injection support "in front" of the caches.

Add a test to exercise that using only the standard APIs.
This is the first useful test for the new tests with an endpoint.
There's no point testing netdevsim here, so this is also the first
HW-only test in Python.

I'm not super happy with the traffic generation using iperf3,
my initial approach was to use mausezahn. But it turned out to be
5x slower in terms of PPS. Hopefully this is good enough for now.

v1: https://lore.kernel.org/all/20240426232400.624864-1-kuba@kernel.org/
====================

Link: https://lore.kernel.org/r/20240429144426.743476-1-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

+214 -7
+2
net/core/page_pool.c
··· 5 5 * Copyright (C) 2016 Red Hat, Inc. 6 6 */ 7 7 8 + #include <linux/error-injection.h> 8 9 #include <linux/types.h> 9 10 #include <linux/kernel.h> 10 11 #include <linux/slab.h> ··· 551 550 return page; 552 551 } 553 552 EXPORT_SYMBOL(page_pool_alloc_pages); 553 + ALLOW_ERROR_INJECTION(page_pool_alloc_pages, NULL); 554 554 555 555 /* Calculate distance between two u32 values, valid if distance is below 2^(31) 556 556 * https://en.wikipedia.org/wiki/Serial_number_arithmetic#General_Solution
+1 -1
tools/testing/selftests/Makefile
··· 119 119 TARGETS_HOTPLUG += memory-hotplug 120 120 121 121 # Networking tests want the net/lib target, include it automatically 122 - ifneq ($(filter net drivers/net,$(TARGETS)),) 122 + ifneq ($(filter net drivers/net drivers/net/hw,$(TARGETS)),) 123 123 ifeq ($(filter net/lib,$(TARGETS)),) 124 124 INSTALL_DEP_TARGETS := net/lib 125 125 endif
+2
tools/testing/selftests/drivers/net/hw/Makefile
··· 9 9 hw_stats_l3.sh \ 10 10 hw_stats_l3_gre.sh \ 11 11 loopback.sh \ 12 + pp_alloc_fail.py \ 12 13 # 13 14 14 15 TEST_FILES := \ ··· 17 16 # 18 17 19 18 TEST_INCLUDES := \ 19 + $(wildcard lib/py/*.py ../lib/py/*.py) \ 20 20 ../../../net/lib.sh \ 21 21 ../../../net/forwarding/lib.sh \ 22 22 ../../../net/forwarding/ipip_lib.sh \
+16
tools/testing/selftests/drivers/net/hw/lib/py/__init__.py
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + 3 + import sys 4 + from pathlib import Path 5 + 6 + KSFT_DIR = (Path(__file__).parent / "../../../../..").resolve() 7 + 8 + try: 9 + sys.path.append(KSFT_DIR.as_posix()) 10 + from net.lib.py import * 11 + from drivers.net.lib.py import * 12 + except ModuleNotFoundError as e: 13 + ksft_pr("Failed importing `net` library from kernel sources") 14 + ksft_pr(str(e)) 15 + ktap_result(True, comment="SKIP") 16 + sys.exit(4)
+129
tools/testing/selftests/drivers/net/hw/pp_alloc_fail.py
··· 1 + #!/usr/bin/env python3 2 + # SPDX-License-Identifier: GPL-2.0 3 + 4 + import time 5 + import os 6 + from lib.py import ksft_run, ksft_exit, ksft_pr 7 + from lib.py import KsftSkipEx, KsftFailEx 8 + from lib.py import NetdevFamily, NlError 9 + from lib.py import NetDrvEpEnv 10 + from lib.py import cmd, tool, GenerateTraffic 11 + 12 + 13 + def _write_fail_config(config): 14 + for key, value in config.items(): 15 + with open("/sys/kernel/debug/fail_function/" + key, "w") as fp: 16 + fp.write(str(value) + "\n") 17 + 18 + 19 + def _enable_pp_allocation_fail(): 20 + if not os.path.exists("/sys/kernel/debug/fail_function"): 21 + raise KsftSkipEx("Kernel built without function error injection (or DebugFS)") 22 + 23 + if not os.path.exists("/sys/kernel/debug/fail_function/page_pool_alloc_pages"): 24 + with open("/sys/kernel/debug/fail_function/inject", "w") as fp: 25 + fp.write("page_pool_alloc_pages\n") 26 + 27 + _write_fail_config({ 28 + "verbose": 0, 29 + "interval": 511, 30 + "probability": 100, 31 + "times": -1, 32 + }) 33 + 34 + 35 + def _disable_pp_allocation_fail(): 36 + if not os.path.exists("/sys/kernel/debug/fail_function"): 37 + return 38 + 39 + if os.path.exists("/sys/kernel/debug/fail_function/page_pool_alloc_pages"): 40 + with open("/sys/kernel/debug/fail_function/inject", "w") as fp: 41 + fp.write("\n") 42 + 43 + _write_fail_config({ 44 + "probability": 0, 45 + "times": 0, 46 + }) 47 + 48 + 49 + def test_pp_alloc(cfg, netdevnl): 50 + def get_stats(): 51 + return netdevnl.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0] 52 + 53 + def check_traffic_flowing(): 54 + stat1 = get_stats() 55 + time.sleep(1) 56 + stat2 = get_stats() 57 + if stat2['rx-packets'] - stat1['rx-packets'] < 15000: 58 + raise KsftFailEx("Traffic seems low:", stat2['rx-packets'] - stat1['rx-packets']) 59 + 60 + 61 + try: 62 + stats = get_stats() 63 + except NlError as e: 64 + if e.nl_msg.error == -95: 65 + stats = {} 66 + else: 67 + raise 68 + if 'rx-alloc-fail' not in stats: 69 + raise KsftSkipEx("Driver does not report 'rx-alloc-fail' via qstats") 70 + 71 + set_g = False 72 + traffic = None 73 + try: 74 + traffic = GenerateTraffic(cfg) 75 + 76 + check_traffic_flowing() 77 + 78 + _enable_pp_allocation_fail() 79 + 80 + s1 = get_stats() 81 + time.sleep(3) 82 + s2 = get_stats() 83 + 84 + if s2['rx-alloc-fail'] - s1['rx-alloc-fail'] < 1: 85 + raise KsftSkipEx("Allocation failures not increasing") 86 + if s2['rx-alloc-fail'] - s1['rx-alloc-fail'] < 100: 87 + raise KsftSkipEx("Allocation increasing too slowly", s2['rx-alloc-fail'] - s1['rx-alloc-fail'], 88 + "packets:", s2['rx-packets'] - s1['rx-packets']) 89 + 90 + # Basic failures are fine, try to wobble some settings to catch extra failures 91 + check_traffic_flowing() 92 + g = tool("ethtool", "-g " + cfg.ifname, json=True)[0] 93 + if 'rx' in g and g["rx"] * 2 <= g["rx-max"]: 94 + new_g = g['rx'] * 2 95 + elif 'rx' in g: 96 + new_g = g['rx'] // 2 97 + else: 98 + new_g = None 99 + 100 + if new_g: 101 + set_g = cmd(f"ethtool -G {cfg.ifname} rx {new_g}", fail=False).ret == 0 102 + if set_g: 103 + ksft_pr("ethtool -G change retval: success") 104 + else: 105 + ksft_pr("ethtool -G change retval: did not succeed", new_g) 106 + else: 107 + ksft_pr("ethtool -G change retval: did not try") 108 + 109 + time.sleep(0.1) 110 + check_traffic_flowing() 111 + finally: 112 + _disable_pp_allocation_fail() 113 + if traffic: 114 + traffic.stop() 115 + time.sleep(0.1) 116 + if set_g: 117 + cmd(f"ethtool -G {cfg.ifname} rx {g['rx']}") 118 + 119 + 120 + def main() -> None: 121 + netdevnl = NetdevFamily() 122 + with NetDrvEpEnv(__file__, nsim_test=False) as cfg: 123 + 124 + ksft_run([test_pp_alloc], args=(cfg, netdevnl, )) 125 + ksft_exit() 126 + 127 + 128 + if __name__ == "__main__": 129 + main()
+1
tools/testing/selftests/drivers/net/lib/py/__init__.py
··· 15 15 sys.exit(4) 16 16 17 17 from .env import * 18 + from .load import * 18 19 from .remote import Remote
+8 -2
tools/testing/selftests/drivers/net/lib/py/env.py
··· 2 2 3 3 import os 4 4 from pathlib import Path 5 - from lib.py import KsftSkipEx 5 + from lib.py import KsftSkipEx, KsftXfailEx 6 6 from lib.py import cmd, ip 7 7 from lib.py import NetNS, NetdevSimDev 8 8 from .remote import Remote ··· 76 76 nsim_v4_pfx = "192.0.2." 77 77 nsim_v6_pfx = "2001:db8::" 78 78 79 - def __init__(self, src_path): 79 + def __init__(self, src_path, nsim_test=None): 80 80 81 81 self.env = _load_env_file(src_path) 82 82 ··· 88 88 self._ns_peer = None 89 89 90 90 if "NETIF" in self.env: 91 + if nsim_test is True: 92 + raise KsftXfailEx("Test only works on netdevsim") 91 93 self._check_env() 94 + 92 95 self.dev = ip("link show dev " + self.env['NETIF'], json=True)[0] 93 96 94 97 self.v4 = self.env.get("LOCAL_V4") ··· 101 98 kind = self.env["REMOTE_TYPE"] 102 99 args = self.env["REMOTE_ARGS"] 103 100 else: 101 + if nsim_test is False: 102 + raise KsftXfailEx("Test does not work on netdevsim") 103 + 104 104 self.create_local() 105 105 106 106 self.dev = self._ns.nsims[0].dev
+41
tools/testing/selftests/drivers/net/lib/py/load.py
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + 3 + import time 4 + 5 + from lib.py import ksft_pr, cmd, ip, rand_port, wait_port_listen 6 + 7 + class GenerateTraffic: 8 + def __init__(self, env): 9 + env.require_cmd("iperf3", remote=True) 10 + 11 + self.env = env 12 + 13 + port = rand_port() 14 + self._iperf_server = cmd(f"iperf3 -s -p {port}", background=True) 15 + wait_port_listen(port) 16 + time.sleep(0.1) 17 + self._iperf_client = cmd(f"iperf3 -c {env.addr} -P 16 -p {port} -t 86400", 18 + background=True, host=env.remote) 19 + 20 + # Wait for traffic to ramp up 21 + pkt = ip("-s link show dev " + env.ifname, json=True)[0]["stats64"]["rx"]["packets"] 22 + for _ in range(50): 23 + time.sleep(0.1) 24 + now = ip("-s link show dev " + env.ifname, json=True)[0]["stats64"]["rx"]["packets"] 25 + if now - pkt > 1000: 26 + return 27 + pkt = now 28 + self.stop(verbose=True) 29 + raise Exception("iperf3 traffic did not ramp up") 30 + 31 + def stop(self, verbose=None): 32 + self._iperf_client.process(terminate=True) 33 + if verbose: 34 + ksft_pr(">> Client:") 35 + ksft_pr(self._iperf_client.stdout) 36 + ksft_pr(self._iperf_client.stderr) 37 + self._iperf_server.process(terminate=True) 38 + if verbose: 39 + ksft_pr(">> Server:") 40 + ksft_pr(self._iperf_server.stdout) 41 + ksft_pr(self._iperf_server.stderr)
+4
tools/testing/selftests/net/lib/py/ksft.py
··· 11 11 KSFT_RESULT_ALL = True 12 12 13 13 14 + class KsftFailEx(Exception): 15 + pass 16 + 17 + 14 18 class KsftSkipEx(Exception): 15 19 pass 16 20
+10 -4
tools/testing/selftests/net/lib/py/utils.py
··· 56 56 return self.process(terminate=self.terminate) 57 57 58 58 59 - def ip(args, json=None, ns=None, host=None): 60 - cmd_str = "ip " 59 + def tool(name, args, json=None, ns=None, host=None): 60 + cmd_str = name + ' ' 61 61 if json: 62 - cmd_str += '-j ' 62 + cmd_str += '--json ' 63 63 cmd_str += args 64 64 cmd_obj = cmd(cmd_str, ns=ns, host=host) 65 65 if json: ··· 67 67 return cmd_obj 68 68 69 69 70 + def ip(args, json=None, ns=None, host=None): 71 + if ns: 72 + args = f'-netns {ns} ' + args 73 + return tool('ip', args, json=json, host=host) 74 + 75 + 70 76 def rand_port(): 71 77 """ 72 78 Get unprivileged port, for now just random, one day we may decide to check if used. 73 79 """ 74 - return random.randint(1024, 65535) 80 + return random.randint(10000, 65535) 75 81 76 82 77 83 def wait_port_listen(port, proto="tcp", ns=None, host=None, sleep=0.005, deadline=5):