Merge branch 'selftests-drv-net-convert-gro-and-toeplitz-tests-to-work-for-drivers-in-nipa'
Jakub Kicinski says: ==================== selftests: drv-net: convert GRO and Toeplitz tests to work for drivers in NIPA Main objective of this series is to convert the gro.sh and toeplitz.sh tests to be "NIPA-compatible" - meaning make use of the Python env, which lets us run the tests against either netdevsim or a real device. The tests seem to have been written with a different flow in mind. Namely they source different bash "setup" scripts depending on arguments passed to the test. While I have nothing against the use of bash and the overall architecture - the existing code needs quite a bit of work (don't assume MAC/IP addresses, support remote endpoint over SSH). If I'm the one fixing it, I'd rather convert them to our "simplistic" Python. This series rewrites the tests in Python while addressing their shortcomings. The functionality of running the test over loopback on a real device is retained but with a different method of invocation (see the last patch). Once again we are dealing with a script which run over a variety of protocols (combination of [ipv4, ipv6, ipip] x [tcp, udp]). The first 4 patches add support for test variants to our scripts. We use the term "variant" in the same sense as the C kselftest_harness.h - variant is just a set of static input arguments. Note that neither GRO nor the Toeplitz test fully passes for me on any HW I have access to. But this is unrelated to the conversion. This series is not making any real functional changes to the tests, it is limited to improving the "test harness" scripts. ==================== Link: https://patch.msgid.link/20251120021024.2944527-1-kuba@kernel.org Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
@@ -133,15 +133,21 @@ static netdev_tx_t nsim_start_xmit(struct sk_buff *skb, struct net_device *dev)
|
||||
if (!nsim_ipsec_tx(ns, skb))
|
||||
goto out_drop_any;
|
||||
|
||||
peer_ns = rcu_dereference(ns->peer);
|
||||
if (!peer_ns)
|
||||
goto out_drop_any;
|
||||
/* Check if loopback mode is enabled */
|
||||
if (dev->features & NETIF_F_LOOPBACK) {
|
||||
peer_ns = ns;
|
||||
peer_dev = dev;
|
||||
} else {
|
||||
peer_ns = rcu_dereference(ns->peer);
|
||||
if (!peer_ns)
|
||||
goto out_drop_any;
|
||||
peer_dev = peer_ns->netdev;
|
||||
}
|
||||
|
||||
dr = nsim_do_psp(skb, ns, peer_ns, &psp_ext);
|
||||
if (dr)
|
||||
goto out_drop_free;
|
||||
|
||||
peer_dev = peer_ns->netdev;
|
||||
rxq = skb_get_queue_mapping(skb);
|
||||
if (rxq >= peer_dev->num_rx_queues)
|
||||
rxq = rxq % peer_dev->num_rx_queues;
|
||||
@@ -433,13 +439,8 @@ static int nsim_rcv(struct nsim_rq *rq, int budget)
|
||||
}
|
||||
|
||||
/* skb might be discard at netif_receive_skb, save the len */
|
||||
skblen = skb->len;
|
||||
skb_mark_napi_id(skb, &rq->napi);
|
||||
ret = netif_receive_skb(skb);
|
||||
if (ret == NET_RX_SUCCESS)
|
||||
dev_dstats_rx_add(dev, skblen);
|
||||
else
|
||||
dev_dstats_rx_dropped(dev);
|
||||
dev_dstats_rx_add(dev, skb->len);
|
||||
napi_gro_receive(&rq->napi, skb);
|
||||
}
|
||||
|
||||
nsim_start_peer_tx_queue(dev, rq);
|
||||
@@ -981,7 +982,8 @@ static void nsim_setup(struct net_device *dev)
|
||||
NETIF_F_FRAGLIST |
|
||||
NETIF_F_HW_CSUM |
|
||||
NETIF_F_LRO |
|
||||
NETIF_F_TSO;
|
||||
NETIF_F_TSO |
|
||||
NETIF_F_LOOPBACK;
|
||||
dev->pcpu_stat_type = NETDEV_PCPU_STAT_DSTATS;
|
||||
dev->max_mtu = ETH_MAX_MTU;
|
||||
dev->xdp_features = NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_HW_OFFLOAD;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
gro
|
||||
napi_id_helper
|
||||
psp_responder
|
||||
|
||||
@@ -6,10 +6,12 @@ TEST_INCLUDES := $(wildcard lib/py/*.py) \
|
||||
../../net/lib.sh \
|
||||
|
||||
TEST_GEN_FILES := \
|
||||
gro \
|
||||
napi_id_helper \
|
||||
# end of TEST_GEN_FILES
|
||||
|
||||
TEST_PROGS := \
|
||||
gro.py \
|
||||
hds.py \
|
||||
napi_id.py \
|
||||
napi_threaded.py \
|
||||
|
||||
@@ -57,7 +57,8 @@
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../kselftest.h"
|
||||
#include "../../kselftest.h"
|
||||
#include "../../net/lib/ksft.h"
|
||||
|
||||
#define DPORT 8000
|
||||
#define SPORT 1500
|
||||
@@ -1127,6 +1128,8 @@ static void gro_receiver(void)
|
||||
set_timeout(rxfd);
|
||||
bind_packetsocket(rxfd);
|
||||
|
||||
ksft_ready();
|
||||
|
||||
memset(correct_payload, 0, sizeof(correct_payload));
|
||||
|
||||
if (strcmp(testname, "data") == 0) {
|
||||
164
tools/testing/selftests/drivers/net/gro.py
Executable file
164
tools/testing/selftests/drivers/net/gro.py
Executable file
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
"""
|
||||
GRO (Generic Receive Offload) conformance tests.
|
||||
|
||||
Validates that GRO coalescing works correctly by running the gro
|
||||
binary in different configurations and checking for correct packet
|
||||
coalescing behavior.
|
||||
|
||||
Test cases:
|
||||
- data: Data packets with same size/headers and correct seq numbers coalesce
|
||||
- ack: Pure ACK packets do not coalesce
|
||||
- flags: Packets with PSH, SYN, URG, RST flags do not coalesce
|
||||
- tcp: Packets with incorrect checksum, non-consecutive seqno don't coalesce
|
||||
- ip: Packets with different ECN, TTL, TOS, or IP options don't coalesce
|
||||
- large: Packets larger than GRO_MAX_SIZE don't coalesce
|
||||
"""
|
||||
|
||||
import os
|
||||
from lib.py import ksft_run, ksft_exit, ksft_pr
|
||||
from lib.py import NetDrvEpEnv, KsftXfailEx
|
||||
from lib.py import cmd, defer, bkg, ip
|
||||
from lib.py import ksft_variants
|
||||
|
||||
|
||||
def _resolve_dmac(cfg, ipver):
|
||||
"""
|
||||
Find the destination MAC address remote host should use to send packets
|
||||
towards the local host. It may be a router / gateway address.
|
||||
"""
|
||||
|
||||
attr = "dmac" + ipver
|
||||
# Cache the response across test cases
|
||||
if hasattr(cfg, attr):
|
||||
return getattr(cfg, attr)
|
||||
|
||||
route = ip(f"-{ipver} route get {cfg.addr_v[ipver]}",
|
||||
json=True, host=cfg.remote)[0]
|
||||
gw = route.get("gateway")
|
||||
# Local L2 segment, address directly
|
||||
if not gw:
|
||||
setattr(cfg, attr, cfg.dev['address'])
|
||||
return getattr(cfg, attr)
|
||||
|
||||
# ping to make sure neighbor is resolved,
|
||||
# bind to an interface, for v6 the GW is likely link local
|
||||
cmd(f"ping -c1 -W0 -I{cfg.remote_ifname} {gw}", host=cfg.remote)
|
||||
|
||||
neigh = ip(f"neigh get {gw} dev {cfg.remote_ifname}",
|
||||
json=True, host=cfg.remote)[0]
|
||||
setattr(cfg, attr, neigh['lladdr'])
|
||||
return getattr(cfg, attr)
|
||||
|
||||
|
||||
def _write_defer_restore(cfg, path, val, defer_undo=False):
|
||||
with open(path, "r", encoding="utf-8") as fp:
|
||||
orig_val = fp.read().strip()
|
||||
if str(val) == orig_val:
|
||||
return
|
||||
with open(path, "w", encoding="utf-8") as fp:
|
||||
fp.write(val)
|
||||
if defer_undo:
|
||||
defer(_write_defer_restore, cfg, path, orig_val)
|
||||
|
||||
|
||||
def _set_mtu_restore(dev, mtu, host):
|
||||
if dev['mtu'] < mtu:
|
||||
ip(f"link set dev {dev['ifname']} mtu {mtu}", host=host)
|
||||
defer(ip, f"link set dev {dev['ifname']} mtu {dev['mtu']}", host=host)
|
||||
|
||||
|
||||
def _setup(cfg, test_name):
|
||||
""" Setup hardware loopback mode for GRO testing. """
|
||||
|
||||
if not hasattr(cfg, "bin_remote"):
|
||||
cfg.bin_local = cfg.test_dir / "gro"
|
||||
cfg.bin_remote = cfg.remote.deploy(cfg.bin_local)
|
||||
|
||||
# "large" test needs at least 4k MTU
|
||||
if test_name == "large":
|
||||
_set_mtu_restore(cfg.dev, 4096, None)
|
||||
_set_mtu_restore(cfg.remote_dev, 4096, cfg.remote)
|
||||
|
||||
flush_path = f"/sys/class/net/{cfg.ifname}/gro_flush_timeout"
|
||||
irq_path = f"/sys/class/net/{cfg.ifname}/napi_defer_hard_irqs"
|
||||
|
||||
_write_defer_restore(cfg, flush_path, "200000", defer_undo=True)
|
||||
_write_defer_restore(cfg, irq_path, "10", defer_undo=True)
|
||||
|
||||
try:
|
||||
# Disable TSO for local tests
|
||||
cfg.require_nsim() # will raise KsftXfailEx if not running on nsim
|
||||
|
||||
cmd(f"ethtool -K {cfg.ifname} gro on tso off")
|
||||
cmd(f"ethtool -K {cfg.remote_ifname} gro on tso off", host=cfg.remote)
|
||||
except KsftXfailEx:
|
||||
pass
|
||||
|
||||
def _gro_variants():
|
||||
"""Generator that yields all combinations of protocol and test types."""
|
||||
|
||||
for protocol in ["ipv4", "ipv6", "ipip"]:
|
||||
for test_name in ["data", "ack", "flags", "tcp", "ip", "large"]:
|
||||
yield protocol, test_name
|
||||
|
||||
|
||||
@ksft_variants(_gro_variants())
|
||||
def test(cfg, protocol, test_name):
|
||||
"""Run a single GRO test with retries."""
|
||||
|
||||
ipver = "6" if protocol[-1] == "6" else "4"
|
||||
cfg.require_ipver(ipver)
|
||||
|
||||
_setup(cfg, test_name)
|
||||
|
||||
base_cmd_args = [
|
||||
f"--{protocol}",
|
||||
f"--dmac {_resolve_dmac(cfg, ipver)}",
|
||||
f"--smac {cfg.remote_dev['address']}",
|
||||
f"--daddr {cfg.addr_v[ipver]}",
|
||||
f"--saddr {cfg.remote_addr_v[ipver]}",
|
||||
f"--test {test_name}",
|
||||
"--verbose"
|
||||
]
|
||||
base_args = " ".join(base_cmd_args)
|
||||
|
||||
# Each test is run 6 times to deflake, because given the receive timing,
|
||||
# not all packets that should coalesce will be considered in the same flow
|
||||
# on every try.
|
||||
max_retries = 6
|
||||
for attempt in range(max_retries):
|
||||
rx_cmd = f"{cfg.bin_local} {base_args} --rx --iface {cfg.ifname}"
|
||||
tx_cmd = f"{cfg.bin_remote} {base_args} --iface {cfg.remote_ifname}"
|
||||
|
||||
fail_now = attempt >= max_retries - 1
|
||||
|
||||
with bkg(rx_cmd, ksft_ready=True, exit_wait=True,
|
||||
fail=fail_now) as rx_proc:
|
||||
cmd(tx_cmd, host=cfg.remote)
|
||||
|
||||
if rx_proc.ret == 0:
|
||||
return
|
||||
|
||||
ksft_pr(rx_proc.stdout.strip().replace('\n', '\n# '))
|
||||
ksft_pr(rx_proc.stderr.strip().replace('\n', '\n# '))
|
||||
|
||||
if test_name == "large" and os.environ.get("KSFT_MACHINE_SLOW"):
|
||||
ksft_pr(f"Ignoring {protocol}/{test_name} failure due to slow environment")
|
||||
return
|
||||
|
||||
ksft_pr(f"Attempt {attempt + 1}/{max_retries} failed, retrying...")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
""" Ksft boiler plate main """
|
||||
|
||||
with NetDrvEpEnv(__file__) as cfg:
|
||||
ksft_run(cases=[test], args=(cfg,))
|
||||
ksft_exit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,3 +1,4 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
iou-zcrx
|
||||
ncdevmem
|
||||
toeplitz
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+ OR MIT
|
||||
|
||||
TEST_GEN_FILES = iou-zcrx
|
||||
TEST_GEN_FILES := \
|
||||
iou-zcrx \
|
||||
toeplitz \
|
||||
# end of TEST_GEN_FILES
|
||||
|
||||
TEST_PROGS = \
|
||||
csum.py \
|
||||
@@ -21,6 +24,7 @@ TEST_PROGS = \
|
||||
rss_ctx.py \
|
||||
rss_flow_label.py \
|
||||
rss_input_xfrm.py \
|
||||
toeplitz.py \
|
||||
tso.py \
|
||||
xsk_reconfig.py \
|
||||
#
|
||||
|
||||
@@ -25,7 +25,7 @@ try:
|
||||
fd_read_timeout, ip, rand_port, wait_port_listen, wait_file
|
||||
from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx
|
||||
from net.lib.py import ksft_disruptive, ksft_exit, ksft_pr, ksft_run, \
|
||||
ksft_setup
|
||||
ksft_setup, ksft_variants, KsftNamedVariant
|
||||
from net.lib.py import ksft_eq, ksft_ge, ksft_in, ksft_is, ksft_lt, \
|
||||
ksft_ne, ksft_not_in, ksft_raises, ksft_true, ksft_gt, ksft_not_none
|
||||
from drivers.net.lib.py import GenerateTraffic, Remote
|
||||
@@ -40,7 +40,7 @@ try:
|
||||
"wait_port_listen", "wait_file",
|
||||
"KsftSkipEx", "KsftFailEx", "KsftXfailEx",
|
||||
"ksft_disruptive", "ksft_exit", "ksft_pr", "ksft_run",
|
||||
"ksft_setup",
|
||||
"ksft_setup", "ksft_variants", "KsftNamedVariant",
|
||||
"ksft_eq", "ksft_ge", "ksft_in", "ksft_is", "ksft_lt",
|
||||
"ksft_ne", "ksft_not_in", "ksft_raises", "ksft_true", "ksft_gt",
|
||||
"ksft_not_none", "ksft_not_none",
|
||||
|
||||
@@ -52,7 +52,8 @@
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../kselftest.h"
|
||||
#include "../../../kselftest.h"
|
||||
#include "../../../net/lib/ksft.h"
|
||||
|
||||
#define TOEPLITZ_KEY_MIN_LEN 40
|
||||
#define TOEPLITZ_KEY_MAX_LEN 60
|
||||
@@ -576,6 +577,10 @@ int main(int argc, char **argv)
|
||||
fd_sink = setup_sink();
|
||||
|
||||
setup_rings();
|
||||
|
||||
/* Signal to test framework that we're ready to receive */
|
||||
ksft_ready();
|
||||
|
||||
process_rings();
|
||||
cleanup_rings();
|
||||
|
||||
209
tools/testing/selftests/drivers/net/hw/toeplitz.py
Executable file
209
tools/testing/selftests/drivers/net/hw/toeplitz.py
Executable file
@@ -0,0 +1,209 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
"""
|
||||
Toeplitz Rx hashing test:
|
||||
- rxhash (the hash value calculation itself);
|
||||
- RSS mapping from rxhash to rx queue;
|
||||
- RPS mapping from rxhash to cpu.
|
||||
"""
|
||||
|
||||
import glob
|
||||
import os
|
||||
import socket
|
||||
from lib.py import ksft_run, ksft_exit, ksft_pr
|
||||
from lib.py import NetDrvEpEnv, EthtoolFamily, NetdevFamily
|
||||
from lib.py import cmd, bkg, rand_port, defer
|
||||
from lib.py import ksft_in
|
||||
from lib.py import ksft_variants, KsftNamedVariant, KsftSkipEx, KsftFailEx
|
||||
|
||||
|
||||
def _check_rps_and_rfs_not_configured(cfg):
|
||||
"""Verify that RPS is not already configured."""
|
||||
|
||||
for rps_file in glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*/rps_cpus"):
|
||||
with open(rps_file, "r", encoding="utf-8") as fp:
|
||||
val = fp.read().strip()
|
||||
if set(val) - {"0", ","}:
|
||||
raise KsftSkipEx(f"RPS already configured on {rps_file}: {val}")
|
||||
|
||||
rfs_file = "/proc/sys/net/core/rps_sock_flow_entries"
|
||||
with open(rfs_file, "r", encoding="utf-8") as fp:
|
||||
val = fp.read().strip()
|
||||
if val != "0":
|
||||
raise KsftSkipEx(f"RFS already configured {rfs_file}: {val}")
|
||||
|
||||
|
||||
def _get_rss_key(cfg):
|
||||
"""
|
||||
Read the RSS key from the device.
|
||||
Return a string in the traditional %02x:%02x:%02x:.. format.
|
||||
"""
|
||||
|
||||
rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
|
||||
return ':'.join(f'{b:02x}' for b in rss["hkey"])
|
||||
|
||||
|
||||
def _get_cpu_for_irq(irq):
|
||||
with open(f"/proc/irq/{irq}/smp_affinity_list", "r",
|
||||
encoding="utf-8") as fp:
|
||||
data = fp.read().strip()
|
||||
if "," in data or "-" in data:
|
||||
raise KsftFailEx(f"IRQ{irq} is not mapped to a single core: {data}")
|
||||
return int(data)
|
||||
|
||||
|
||||
def _get_irq_cpus(cfg):
|
||||
"""
|
||||
Read the list of IRQs for the device Rx queues.
|
||||
"""
|
||||
queues = cfg.netnl.queue_get({"ifindex": cfg.ifindex}, dump=True)
|
||||
napis = cfg.netnl.napi_get({"ifindex": cfg.ifindex}, dump=True)
|
||||
|
||||
# Remap into ID-based dicts
|
||||
napis = {n["id"]: n for n in napis}
|
||||
queues = {f"{q['type']}{q['id']}": q for q in queues}
|
||||
|
||||
cpus = []
|
||||
for rx in range(9999):
|
||||
name = f"rx{rx}"
|
||||
if name not in queues:
|
||||
break
|
||||
cpus.append(_get_cpu_for_irq(napis[queues[name]["napi-id"]]["irq"]))
|
||||
|
||||
return cpus
|
||||
|
||||
|
||||
def _get_unused_cpus(cfg, count=2):
|
||||
"""
|
||||
Get CPUs that are not used by Rx queues.
|
||||
Returns a list of at least 'count' CPU numbers.
|
||||
"""
|
||||
|
||||
# Get CPUs used by Rx queues
|
||||
rx_cpus = set(_get_irq_cpus(cfg))
|
||||
|
||||
# Get total number of CPUs
|
||||
num_cpus = os.cpu_count()
|
||||
|
||||
# Find unused CPUs
|
||||
unused_cpus = [cpu for cpu in range(num_cpus) if cpu not in rx_cpus]
|
||||
|
||||
if len(unused_cpus) < count:
|
||||
raise KsftSkipEx(f"Need at {count} CPUs not used by Rx queues, found {len(unused_cpus)}")
|
||||
|
||||
return unused_cpus[:count]
|
||||
|
||||
|
||||
def _configure_rps(cfg, rps_cpus):
|
||||
"""Configure RPS for all Rx queues."""
|
||||
|
||||
mask = 0
|
||||
for cpu in rps_cpus:
|
||||
mask |= (1 << cpu)
|
||||
mask = hex(mask)[2:]
|
||||
|
||||
# Set RPS bitmap for all rx queues
|
||||
for rps_file in glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*/rps_cpus"):
|
||||
with open(rps_file, "w", encoding="utf-8") as fp:
|
||||
fp.write(mask)
|
||||
|
||||
return mask
|
||||
|
||||
|
||||
def _send_traffic(cfg, proto_flag, ipver, port):
|
||||
"""Send 20 packets of requested type."""
|
||||
|
||||
# Determine protocol and IP version for socat
|
||||
if proto_flag == "-u":
|
||||
proto = "UDP"
|
||||
else:
|
||||
proto = "TCP"
|
||||
|
||||
baddr = f"[{cfg.addr_v['6']}]" if ipver == "6" else cfg.addr_v["4"]
|
||||
|
||||
# Run socat in a loop to send traffic periodically
|
||||
# Use sh -c with a loop similar to toeplitz_client.sh
|
||||
socat_cmd = f"""
|
||||
for i in `seq 20`; do
|
||||
echo "msg $i" | socat -{ipver} -t 0.1 - {proto}:{baddr}:{port};
|
||||
sleep 0.001;
|
||||
done
|
||||
"""
|
||||
|
||||
cmd(socat_cmd, shell=True, host=cfg.remote)
|
||||
|
||||
|
||||
def _test_variants():
|
||||
for grp in ["", "rss", "rps"]:
|
||||
for l4 in ["tcp", "udp"]:
|
||||
for l3 in ["4", "6"]:
|
||||
name = f"{l4}_ipv{l3}"
|
||||
if grp:
|
||||
name = f"{grp}_{name}"
|
||||
yield KsftNamedVariant(name, "-" + l4[0], l3, grp)
|
||||
|
||||
|
||||
@ksft_variants(_test_variants())
|
||||
def test(cfg, proto_flag, ipver, grp):
|
||||
"""Run a single toeplitz test."""
|
||||
|
||||
cfg.require_ipver(ipver)
|
||||
|
||||
# Check that rxhash is enabled
|
||||
ksft_in("receive-hashing: on", cmd(f"ethtool -k {cfg.ifname}").stdout)
|
||||
|
||||
port = rand_port(socket.SOCK_DGRAM)
|
||||
key = _get_rss_key(cfg)
|
||||
|
||||
toeplitz_path = cfg.test_dir / "toeplitz"
|
||||
rx_cmd = [
|
||||
str(toeplitz_path),
|
||||
"-" + ipver,
|
||||
proto_flag,
|
||||
"-d", str(port),
|
||||
"-i", cfg.ifname,
|
||||
"-k", key,
|
||||
"-T", "1000",
|
||||
"-s",
|
||||
"-v"
|
||||
]
|
||||
|
||||
if grp:
|
||||
_check_rps_and_rfs_not_configured(cfg)
|
||||
if grp == "rss":
|
||||
irq_cpus = ",".join([str(x) for x in _get_irq_cpus(cfg)])
|
||||
rx_cmd += ["-C", irq_cpus]
|
||||
ksft_pr(f"RSS using CPUs: {irq_cpus}")
|
||||
elif grp == "rps":
|
||||
# Get CPUs not used by Rx queues and configure them for RPS
|
||||
rps_cpus = _get_unused_cpus(cfg, count=2)
|
||||
rps_mask = _configure_rps(cfg, rps_cpus)
|
||||
defer(_configure_rps, cfg, [])
|
||||
rx_cmd += ["-r", rps_mask]
|
||||
ksft_pr(f"RPS using CPUs: {rps_cpus}, mask: {rps_mask}")
|
||||
|
||||
# Run rx in background, it will exit once it has seen enough packets
|
||||
with bkg(" ".join(rx_cmd), ksft_ready=True, exit_wait=True) as rx_proc:
|
||||
while rx_proc.proc.poll() is None:
|
||||
_send_traffic(cfg, proto_flag, ipver, port)
|
||||
|
||||
# Check rx result
|
||||
ksft_pr("Receiver output:")
|
||||
ksft_pr(rx_proc.stdout.strip().replace('\n', '\n# '))
|
||||
if rx_proc.stderr:
|
||||
ksft_pr(rx_proc.stderr.strip().replace('\n', '\n# '))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Ksft boilerplate main."""
|
||||
|
||||
with NetDrvEpEnv(__file__) as cfg:
|
||||
cfg.ethnl = EthtoolFamily()
|
||||
cfg.netnl = NetdevFamily()
|
||||
ksft_run(cases=[test], args=(cfg,))
|
||||
ksft_exit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -25,7 +25,7 @@ try:
|
||||
fd_read_timeout, ip, rand_port, wait_port_listen, wait_file
|
||||
from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx
|
||||
from net.lib.py import ksft_disruptive, ksft_exit, ksft_pr, ksft_run, \
|
||||
ksft_setup
|
||||
ksft_setup, ksft_variants, KsftNamedVariant
|
||||
from net.lib.py import ksft_eq, ksft_ge, ksft_in, ksft_is, ksft_lt, \
|
||||
ksft_ne, ksft_not_in, ksft_raises, ksft_true, ksft_gt, ksft_not_none
|
||||
|
||||
@@ -38,7 +38,7 @@ try:
|
||||
"wait_port_listen", "wait_file",
|
||||
"KsftSkipEx", "KsftFailEx", "KsftXfailEx",
|
||||
"ksft_disruptive", "ksft_exit", "ksft_pr", "ksft_run",
|
||||
"ksft_setup",
|
||||
"ksft_setup", "ksft_variants", "KsftNamedVariant",
|
||||
"ksft_eq", "ksft_ge", "ksft_in", "ksft_is", "ksft_lt",
|
||||
"ksft_ne", "ksft_not_in", "ksft_raises", "ksft_true", "ksft_gt",
|
||||
"ksft_not_none", "ksft_not_none"]
|
||||
|
||||
@@ -168,6 +168,8 @@ class NetDrvEpEnv(NetDrvEnvBase):
|
||||
|
||||
# resolve remote interface name
|
||||
self.remote_ifname = self.resolve_remote_ifc()
|
||||
self.remote_dev = ip("-d link show dev " + self.remote_ifname,
|
||||
host=self.remote, json=True)[0]
|
||||
|
||||
self._required_cmd = {}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_ge, ksft_ne, ksft_pr
|
||||
from lib.py import KsftNamedVariant, ksft_variants
|
||||
from lib.py import KsftFailEx, NetDrvEpEnv
|
||||
from lib.py import EthtoolFamily, NetdevFamily, NlError
|
||||
from lib.py import bkg, cmd, rand_port, wait_port_listen
|
||||
@@ -672,7 +673,18 @@ def test_xdp_native_adjst_head_shrnk_data(cfg):
|
||||
_validate_res(res, offset_lst, pkt_sz_lst)
|
||||
|
||||
|
||||
def _test_xdp_native_ifc_stats(cfg, act):
|
||||
@ksft_variants([
|
||||
KsftNamedVariant("pass", XDPAction.PASS),
|
||||
KsftNamedVariant("drop", XDPAction.DROP),
|
||||
KsftNamedVariant("tx", XDPAction.TX),
|
||||
])
|
||||
def test_xdp_native_qstats(cfg, act):
|
||||
"""
|
||||
Send 1000 messages. Expect XDP action specified in @act.
|
||||
Make sure the packets were counted to interface level qstats
|
||||
(Rx, and Tx if act is TX).
|
||||
"""
|
||||
|
||||
cfg.require_cmd("socat")
|
||||
|
||||
bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500)
|
||||
@@ -733,30 +745,6 @@ def _test_xdp_native_ifc_stats(cfg, act):
|
||||
ksft_ge(after['tx-packets'], before['tx-packets'])
|
||||
|
||||
|
||||
def test_xdp_native_qstats_pass(cfg):
|
||||
"""
|
||||
Send 2000 messages, expect XDP_PASS, make sure the packets were counted
|
||||
to interface level qstats (Rx).
|
||||
"""
|
||||
_test_xdp_native_ifc_stats(cfg, XDPAction.PASS)
|
||||
|
||||
|
||||
def test_xdp_native_qstats_drop(cfg):
|
||||
"""
|
||||
Send 2000 messages, expect XDP_DROP, make sure the packets were counted
|
||||
to interface level qstats (Rx).
|
||||
"""
|
||||
_test_xdp_native_ifc_stats(cfg, XDPAction.DROP)
|
||||
|
||||
|
||||
def test_xdp_native_qstats_tx(cfg):
|
||||
"""
|
||||
Send 2000 messages, expect XDP_TX, make sure the packets were counted
|
||||
to interface level qstats (Rx and Tx)
|
||||
"""
|
||||
_test_xdp_native_ifc_stats(cfg, XDPAction.TX)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main function to execute the XDP tests.
|
||||
@@ -781,9 +769,7 @@ def main():
|
||||
test_xdp_native_adjst_tail_shrnk_data,
|
||||
test_xdp_native_adjst_head_grow_data,
|
||||
test_xdp_native_adjst_head_shrnk_data,
|
||||
test_xdp_native_qstats_pass,
|
||||
test_xdp_native_qstats_drop,
|
||||
test_xdp_native_qstats_tx,
|
||||
test_xdp_native_qstats,
|
||||
],
|
||||
args=(cfg,))
|
||||
ksft_exit()
|
||||
|
||||
2
tools/testing/selftests/net/.gitignore
vendored
2
tools/testing/selftests/net/.gitignore
vendored
@@ -7,7 +7,6 @@ cmsg_sender
|
||||
diag_uid
|
||||
epoll_busy_poll
|
||||
fin_ack_lat
|
||||
gro
|
||||
hwtstamp_config
|
||||
io_uring_zerocopy_tx
|
||||
ioam6_parser
|
||||
@@ -57,7 +56,6 @@ tcp_port_share
|
||||
tfo
|
||||
timestamping
|
||||
tls
|
||||
toeplitz
|
||||
tools
|
||||
tun
|
||||
txring_overwrite
|
||||
|
||||
@@ -38,7 +38,6 @@ TEST_PROGS := \
|
||||
fq_band_pktlimit.sh \
|
||||
gre_gso.sh \
|
||||
gre_ipv6_lladdr.sh \
|
||||
gro.sh \
|
||||
icmp.sh \
|
||||
icmp_redirect.sh \
|
||||
io_uring_zerocopy_tx.sh \
|
||||
@@ -121,8 +120,6 @@ TEST_PROGS := \
|
||||
# end of TEST_PROGS
|
||||
|
||||
TEST_PROGS_EXTENDED := \
|
||||
toeplitz.sh \
|
||||
toeplitz_client.sh \
|
||||
xfrm_policy_add_speed.sh \
|
||||
# end of TEST_PROGS_EXTENDED
|
||||
|
||||
@@ -130,7 +127,6 @@ TEST_GEN_FILES := \
|
||||
bind_bhash \
|
||||
cmsg_sender \
|
||||
fin_ack_lat \
|
||||
gro \
|
||||
hwtstamp_config \
|
||||
io_uring_zerocopy_tx \
|
||||
ioam6_parser \
|
||||
@@ -159,7 +155,6 @@ TEST_GEN_FILES := \
|
||||
tcp_mmap \
|
||||
tfo \
|
||||
timestamping \
|
||||
toeplitz \
|
||||
txring_overwrite \
|
||||
txtimestamp \
|
||||
udpgso \
|
||||
@@ -193,8 +188,6 @@ TEST_FILES := \
|
||||
in_netns.sh \
|
||||
lib.sh \
|
||||
settings \
|
||||
setup_loopback.sh \
|
||||
setup_veth.sh \
|
||||
# end of TEST_FILES
|
||||
|
||||
# YNL files, must be before "include ..lib.mk"
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
readonly SERVER_MAC="aa:00:00:00:00:02"
|
||||
readonly CLIENT_MAC="aa:00:00:00:00:01"
|
||||
readonly TESTS=("data" "ack" "flags" "tcp" "ip" "large")
|
||||
readonly PROTOS=("ipv4" "ipv6" "ipip")
|
||||
dev=""
|
||||
test="all"
|
||||
proto="ipv4"
|
||||
|
||||
run_test() {
|
||||
local server_pid=0
|
||||
local exit_code=0
|
||||
local protocol=$1
|
||||
local test=$2
|
||||
local ARGS=( "--${protocol}" "--dmac" "${SERVER_MAC}" \
|
||||
"--smac" "${CLIENT_MAC}" "--test" "${test}" "--verbose" )
|
||||
|
||||
setup_ns
|
||||
# Each test is run 6 times to deflake, because given the receive timing,
|
||||
# not all packets that should coalesce will be considered in the same flow
|
||||
# on every try.
|
||||
for tries in {1..6}; do
|
||||
# Actual test starts here
|
||||
ip netns exec $server_ns ./gro "${ARGS[@]}" "--rx" "--iface" "server" \
|
||||
1>>log.txt &
|
||||
server_pid=$!
|
||||
sleep 0.5 # to allow for socket init
|
||||
ip netns exec $client_ns ./gro "${ARGS[@]}" "--iface" "client" \
|
||||
1>>log.txt
|
||||
wait "${server_pid}"
|
||||
exit_code=$?
|
||||
if [[ ${test} == "large" && -n "${KSFT_MACHINE_SLOW}" && \
|
||||
${exit_code} -ne 0 ]]; then
|
||||
echo "Ignoring errors due to slow environment" 1>&2
|
||||
exit_code=0
|
||||
fi
|
||||
if [[ "${exit_code}" -eq 0 ]]; then
|
||||
break;
|
||||
fi
|
||||
done
|
||||
cleanup_ns
|
||||
echo ${exit_code}
|
||||
}
|
||||
|
||||
run_all_tests() {
|
||||
local failed_tests=()
|
||||
for proto in "${PROTOS[@]}"; do
|
||||
for test in "${TESTS[@]}"; do
|
||||
echo "running test ${proto} ${test}" >&2
|
||||
exit_code=$(run_test $proto $test)
|
||||
if [[ "${exit_code}" -ne 0 ]]; then
|
||||
failed_tests+=("${proto}_${test}")
|
||||
fi;
|
||||
done;
|
||||
done
|
||||
if [[ ${#failed_tests[@]} -ne 0 ]]; then
|
||||
echo "failed tests: ${failed_tests[*]}. \
|
||||
Please see log.txt for more logs"
|
||||
exit 1
|
||||
else
|
||||
echo "All Tests Succeeded!"
|
||||
fi;
|
||||
}
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 \
|
||||
[-i <DEV>] \
|
||||
[-t data|ack|flags|tcp|ip|large] \
|
||||
[-p <ipv4|ipv6>]" 1>&2;
|
||||
exit 1;
|
||||
}
|
||||
|
||||
while getopts "i:t:p:" opt; do
|
||||
case "${opt}" in
|
||||
i)
|
||||
dev="${OPTARG}"
|
||||
;;
|
||||
t)
|
||||
test="${OPTARG}"
|
||||
;;
|
||||
p)
|
||||
proto="${OPTARG}"
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -n "$dev" ]; then
|
||||
source setup_loopback.sh
|
||||
else
|
||||
source setup_veth.sh
|
||||
fi
|
||||
|
||||
setup
|
||||
trap cleanup EXIT
|
||||
if [[ "${test}" == "all" ]]; then
|
||||
run_all_tests
|
||||
else
|
||||
exit_code=$(run_test "${proto}" "${test}")
|
||||
exit $exit_code
|
||||
fi;
|
||||
@@ -8,6 +8,7 @@ CFLAGS += -I../../
|
||||
TEST_FILES := \
|
||||
../../../../net/ynl \
|
||||
../../../../../Documentation/netlink/specs \
|
||||
ksft_setup_loopback.sh \
|
||||
# end of TEST_FILES
|
||||
|
||||
TEST_GEN_FILES := \
|
||||
|
||||
111
tools/testing/selftests/net/lib/ksft_setup_loopback.sh
Executable file
111
tools/testing/selftests/net/lib/ksft_setup_loopback.sh
Executable file
@@ -0,0 +1,111 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
# Setup script for running ksft tests over a real interface in loopback mode.
|
||||
# This scripts replaces the historical setup_loopback.sh. It puts
|
||||
# a (presumably) real hardware interface into loopback mode, creates macvlan
|
||||
# interfaces on top and places them in a network namespace for isolation.
|
||||
#
|
||||
# NETIF env variable must be exported to indicate the real target device.
|
||||
# Note that the test will override NETIF with one of the macvlans, the
|
||||
# actual ksft test will only see the macvlans.
|
||||
#
|
||||
# Example use:
|
||||
# export NETIF=eth0
|
||||
# ./net/lib/ksft_setup_loopback.sh ./drivers/net/gro.py
|
||||
|
||||
if [ -z "$NETIF" ]; then
|
||||
echo "Error: NETIF variable not set"
|
||||
exit 1
|
||||
fi
|
||||
if ! [ -d "/sys/class/net/$NETIF" ]; then
|
||||
echo "Error: Can't find $NETIF, invalid netdevice"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Save original settings for cleanup
|
||||
readonly FLUSH_PATH="/sys/class/net/${NETIF}/gro_flush_timeout"
|
||||
readonly IRQ_PATH="/sys/class/net/${NETIF}/napi_defer_hard_irqs"
|
||||
FLUSH_TIMEOUT="$(< "${FLUSH_PATH}")"
|
||||
readonly FLUSH_TIMEOUT
|
||||
HARD_IRQS="$(< "${IRQ_PATH}")"
|
||||
readonly HARD_IRQS
|
||||
|
||||
SERVER_NS=$(mktemp -u server-XXXXXXXX)
|
||||
readonly SERVER_NS
|
||||
CLIENT_NS=$(mktemp -u client-XXXXXXXX)
|
||||
readonly CLIENT_NS
|
||||
readonly SERVER_MAC="aa:00:00:00:00:02"
|
||||
readonly CLIENT_MAC="aa:00:00:00:00:01"
|
||||
|
||||
# ksft expects addresses to communicate with remote
|
||||
export LOCAL_V6=2001:db8:1::1
|
||||
export REMOTE_V6=2001:db8:1::2
|
||||
|
||||
cleanup() {
|
||||
local exit_code=$?
|
||||
|
||||
echo "Cleaning up..."
|
||||
|
||||
# Remove macvlan interfaces and namespaces
|
||||
ip -netns "${SERVER_NS}" link del dev server 2>/dev/null || true
|
||||
ip netns del "${SERVER_NS}" 2>/dev/null || true
|
||||
ip -netns "${CLIENT_NS}" link del dev client 2>/dev/null || true
|
||||
ip netns del "${CLIENT_NS}" 2>/dev/null || true
|
||||
|
||||
# Disable loopback
|
||||
ethtool -K "${NETIF}" loopback off 2>/dev/null || true
|
||||
sleep 1
|
||||
|
||||
echo "${FLUSH_TIMEOUT}" >"${FLUSH_PATH}"
|
||||
echo "${HARD_IRQS}" >"${IRQ_PATH}"
|
||||
|
||||
exit $exit_code
|
||||
}
|
||||
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
# Enable loopback mode
|
||||
echo "Enabling loopback on ${NETIF}..."
|
||||
ethtool -K "${NETIF}" loopback on || {
|
||||
echo "Failed to enable loopback mode"
|
||||
exit 1
|
||||
}
|
||||
# The interface may need time to get carrier back, but selftests
|
||||
# will wait for carrier, so no need to wait / sleep here.
|
||||
|
||||
# Use timer on host to trigger the network stack
|
||||
# Also disable device interrupt to not depend on NIC interrupt
|
||||
# Reduce test flakiness caused by unexpected interrupts
|
||||
echo 100000 >"${FLUSH_PATH}"
|
||||
echo 50 >"${IRQ_PATH}"
|
||||
|
||||
# Create server namespace with macvlan
|
||||
ip netns add "${SERVER_NS}"
|
||||
ip link add link "${NETIF}" dev server address "${SERVER_MAC}" type macvlan
|
||||
ip link set dev server netns "${SERVER_NS}"
|
||||
ip -netns "${SERVER_NS}" link set dev server up
|
||||
ip -netns "${SERVER_NS}" addr add $LOCAL_V6/64 dev server
|
||||
ip -netns "${SERVER_NS}" link set dev lo up
|
||||
|
||||
# Create client namespace with macvlan
|
||||
ip netns add "${CLIENT_NS}"
|
||||
ip link add link "${NETIF}" dev client address "${CLIENT_MAC}" type macvlan
|
||||
ip link set dev client netns "${CLIENT_NS}"
|
||||
ip -netns "${CLIENT_NS}" link set dev client up
|
||||
ip -netns "${CLIENT_NS}" addr add $REMOTE_V6/64 dev client
|
||||
ip -netns "${CLIENT_NS}" link set dev lo up
|
||||
|
||||
echo "Setup complete!"
|
||||
echo " Device: ${NETIF}"
|
||||
echo " Server NS: ${SERVER_NS}"
|
||||
echo " Client NS: ${CLIENT_NS}"
|
||||
echo ""
|
||||
|
||||
# Setup environment variables for tests
|
||||
export NETIF=server
|
||||
export REMOTE_TYPE=netns
|
||||
export REMOTE_ARGS="${CLIENT_NS}"
|
||||
|
||||
# Run the command
|
||||
ip netns exec "${SERVER_NS}" "$@"
|
||||
@@ -8,7 +8,8 @@ from .consts import KSRC
|
||||
from .ksft import KsftFailEx, KsftSkipEx, KsftXfailEx, ksft_pr, ksft_eq, \
|
||||
ksft_ne, ksft_true, ksft_not_none, ksft_in, ksft_not_in, ksft_is, \
|
||||
ksft_ge, ksft_gt, ksft_lt, ksft_raises, ksft_busy_wait, \
|
||||
ktap_result, ksft_disruptive, ksft_setup, ksft_run, ksft_exit
|
||||
ktap_result, ksft_disruptive, ksft_setup, ksft_run, ksft_exit, \
|
||||
ksft_variants, KsftNamedVariant
|
||||
from .netns import NetNS, NetNSEnter
|
||||
from .nsim import NetdevSim, NetdevSimDev
|
||||
from .utils import CmdExitFailure, fd_read_timeout, cmd, bkg, defer, \
|
||||
@@ -21,7 +22,7 @@ __all__ = ["KSRC",
|
||||
"ksft_ne", "ksft_true", "ksft_not_none", "ksft_in", "ksft_not_in",
|
||||
"ksft_is", "ksft_ge", "ksft_gt", "ksft_lt", "ksft_raises",
|
||||
"ksft_busy_wait", "ktap_result", "ksft_disruptive", "ksft_setup",
|
||||
"ksft_run", "ksft_exit",
|
||||
"ksft_run", "ksft_exit", "ksft_variants", "KsftNamedVariant",
|
||||
"NetNS", "NetNSEnter",
|
||||
"CmdExitFailure", "fd_read_timeout", "cmd", "bkg", "defer",
|
||||
"bpftool", "ip", "ethtool", "bpftrace", "rand_port",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
import builtins
|
||||
import functools
|
||||
import inspect
|
||||
import signal
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
from collections import namedtuple
|
||||
from .consts import KSFT_MAIN_NAME
|
||||
from .utils import global_defer_queue
|
||||
|
||||
@@ -136,7 +136,7 @@ def ksft_busy_wait(cond, sleep=0.005, deadline=1, comment=""):
|
||||
time.sleep(sleep)
|
||||
|
||||
|
||||
def ktap_result(ok, cnt=1, case="", comment=""):
|
||||
def ktap_result(ok, cnt=1, case_name="", comment=""):
|
||||
global KSFT_RESULT_ALL
|
||||
KSFT_RESULT_ALL = KSFT_RESULT_ALL and ok
|
||||
|
||||
@@ -146,8 +146,8 @@ def ktap_result(ok, cnt=1, case="", comment=""):
|
||||
res += "ok "
|
||||
res += str(cnt) + " "
|
||||
res += KSFT_MAIN_NAME
|
||||
if case:
|
||||
res += "." + str(case.__name__)
|
||||
if case_name:
|
||||
res += "." + case_name
|
||||
if comment:
|
||||
res += " # " + comment
|
||||
print(res, flush=True)
|
||||
@@ -163,7 +163,7 @@ def ksft_flush_defer():
|
||||
entry = global_defer_queue.pop()
|
||||
try:
|
||||
entry.exec_only()
|
||||
except:
|
||||
except BaseException:
|
||||
ksft_pr(f"Exception while handling defer / cleanup (callback {i} of {qlen_start})!")
|
||||
tb = traceback.format_exc()
|
||||
for line in tb.strip().split('\n'):
|
||||
@@ -171,6 +171,10 @@ def ksft_flush_defer():
|
||||
KSFT_RESULT = False
|
||||
|
||||
|
||||
KsftCaseFunction = namedtuple("KsftCaseFunction",
|
||||
['name', 'original_func', 'variants'])
|
||||
|
||||
|
||||
def ksft_disruptive(func):
|
||||
"""
|
||||
Decorator that marks the test as disruptive (e.g. the test
|
||||
@@ -181,11 +185,47 @@ def ksft_disruptive(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
if not KSFT_DISRUPTIVE:
|
||||
raise KsftSkipEx(f"marked as disruptive")
|
||||
raise KsftSkipEx("marked as disruptive")
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
class KsftNamedVariant:
|
||||
""" Named string name + argument list tuple for @ksft_variants """
|
||||
|
||||
def __init__(self, name, *params):
|
||||
self.params = params
|
||||
self.name = name or "_".join([str(x) for x in self.params])
|
||||
|
||||
|
||||
def ksft_variants(params):
|
||||
"""
|
||||
Decorator defining the sets of inputs for a test.
|
||||
The parameters will be included in the name of the resulting sub-case.
|
||||
Parameters can be either single object, tuple or a KsftNamedVariant.
|
||||
The argument can be a list or a generator.
|
||||
|
||||
Example:
|
||||
|
||||
@ksft_variants([
|
||||
(1, "a"),
|
||||
(2, "b"),
|
||||
KsftNamedVariant("three", 3, "c"),
|
||||
])
|
||||
def my_case(cfg, a, b):
|
||||
pass # ...
|
||||
|
||||
ksft_run(cases=[my_case], args=(cfg, ))
|
||||
|
||||
Will generate cases:
|
||||
my_case.1_a
|
||||
my_case.2_b
|
||||
my_case.three
|
||||
"""
|
||||
|
||||
return lambda func: KsftCaseFunction(func.__name__, func, params)
|
||||
|
||||
|
||||
def ksft_setup(env):
|
||||
"""
|
||||
Setup test framework global state from the environment.
|
||||
@@ -199,7 +239,7 @@ def ksft_setup(env):
|
||||
return False
|
||||
try:
|
||||
return bool(int(value))
|
||||
except:
|
||||
except Exception:
|
||||
raise Exception(f"failed to parse {name}")
|
||||
|
||||
if "DISRUPTIVE" in env:
|
||||
@@ -220,9 +260,13 @@ def _ksft_intr(signum, frame):
|
||||
ksft_pr(f"Ignoring SIGTERM (cnt: {term_cnt}), already exiting...")
|
||||
|
||||
|
||||
def ksft_run(cases=None, globs=None, case_pfx=None, args=()):
|
||||
cases = cases or []
|
||||
def _ksft_generate_test_cases(cases, globs, case_pfx, args):
|
||||
"""Generate a flat list of (func, args, name) tuples"""
|
||||
|
||||
cases = cases or []
|
||||
test_cases = []
|
||||
|
||||
# If using the globs method find all relevant functions
|
||||
if globs and case_pfx:
|
||||
for key, value in globs.items():
|
||||
if not callable(value):
|
||||
@@ -232,6 +276,27 @@ def ksft_run(cases=None, globs=None, case_pfx=None, args=()):
|
||||
cases.append(value)
|
||||
break
|
||||
|
||||
for func in cases:
|
||||
if isinstance(func, KsftCaseFunction):
|
||||
# Parametrized test - create case for each param
|
||||
for param in func.variants:
|
||||
if not isinstance(param, KsftNamedVariant):
|
||||
if not isinstance(param, tuple):
|
||||
param = (param, )
|
||||
param = KsftNamedVariant(None, *param)
|
||||
|
||||
test_cases.append((func.original_func,
|
||||
(*args, *param.params),
|
||||
func.name + "." + param.name))
|
||||
else:
|
||||
test_cases.append((func, args, func.__name__))
|
||||
|
||||
return test_cases
|
||||
|
||||
|
||||
def ksft_run(cases=None, globs=None, case_pfx=None, args=()):
|
||||
test_cases = _ksft_generate_test_cases(cases, globs, case_pfx, args)
|
||||
|
||||
global term_cnt
|
||||
term_cnt = 0
|
||||
prev_sigterm = signal.signal(signal.SIGTERM, _ksft_intr)
|
||||
@@ -239,19 +304,19 @@ def ksft_run(cases=None, globs=None, case_pfx=None, args=()):
|
||||
totals = {"pass": 0, "fail": 0, "skip": 0, "xfail": 0}
|
||||
|
||||
print("TAP version 13", flush=True)
|
||||
print("1.." + str(len(cases)), flush=True)
|
||||
print("1.." + str(len(test_cases)), flush=True)
|
||||
|
||||
global KSFT_RESULT
|
||||
cnt = 0
|
||||
stop = False
|
||||
for case in cases:
|
||||
for func, args, name in test_cases:
|
||||
KSFT_RESULT = True
|
||||
cnt += 1
|
||||
comment = ""
|
||||
cnt_key = ""
|
||||
|
||||
try:
|
||||
case(*args)
|
||||
func(*args)
|
||||
except KsftSkipEx as e:
|
||||
comment = "SKIP " + str(e)
|
||||
cnt_key = 'skip'
|
||||
@@ -273,7 +338,7 @@ def ksft_run(cases=None, globs=None, case_pfx=None, args=()):
|
||||
if not cnt_key:
|
||||
cnt_key = 'pass' if KSFT_RESULT else 'fail'
|
||||
|
||||
ktap_result(KSFT_RESULT, cnt, case, comment=comment)
|
||||
ktap_result(KSFT_RESULT, cnt, name, comment=comment)
|
||||
totals[cnt_key] += 1
|
||||
|
||||
if stop:
|
||||
|
||||
@@ -27,7 +27,7 @@ class NetdevSim:
|
||||
self.port_index = port_index
|
||||
self.ns = ns
|
||||
self.dfs_dir = "%s/ports/%u/" % (nsimdev.dfs_dir, port_index)
|
||||
ret = ip("-j link show dev %s" % ifname, ns=ns)
|
||||
ret = ip("-d -j link show dev %s" % ifname, ns=ns)
|
||||
self.dev = json.loads(ret.stdout)[0]
|
||||
self.ifindex = self.dev["ifindex"]
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ class cmd:
|
||||
Use bkg() instead to run a command in the background.
|
||||
"""
|
||||
def __init__(self, comm, shell=None, fail=True, ns=None, background=False,
|
||||
host=None, timeout=5, ksft_wait=None):
|
||||
host=None, timeout=5, ksft_ready=None, ksft_wait=None):
|
||||
if ns:
|
||||
comm = f'ip netns exec {ns} ' + comm
|
||||
|
||||
@@ -52,21 +52,25 @@ class cmd:
|
||||
# ksft_wait lets us wait for the background process to fully start,
|
||||
# we pass an FD to the child process, and wait for it to write back.
|
||||
# Similarly term_fd tells child it's time to exit.
|
||||
pass_fds = ()
|
||||
pass_fds = []
|
||||
env = os.environ.copy()
|
||||
if ksft_wait is not None:
|
||||
rfd, ready_fd = os.pipe()
|
||||
wait_fd, self.ksft_term_fd = os.pipe()
|
||||
pass_fds = (ready_fd, wait_fd, )
|
||||
env["KSFT_READY_FD"] = str(ready_fd)
|
||||
pass_fds.append(wait_fd)
|
||||
env["KSFT_WAIT_FD"] = str(wait_fd)
|
||||
ksft_ready = True # ksft_wait implies ready
|
||||
if ksft_ready is not None:
|
||||
rfd, ready_fd = os.pipe()
|
||||
pass_fds.append(ready_fd)
|
||||
env["KSFT_READY_FD"] = str(ready_fd)
|
||||
|
||||
self.proc = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE, pass_fds=pass_fds,
|
||||
env=env)
|
||||
if ksft_wait is not None:
|
||||
os.close(ready_fd)
|
||||
os.close(wait_fd)
|
||||
if ksft_ready is not None:
|
||||
os.close(ready_fd)
|
||||
msg = fd_read_timeout(rfd, ksft_wait)
|
||||
os.close(rfd)
|
||||
if not msg:
|
||||
@@ -116,10 +120,10 @@ class bkg(cmd):
|
||||
with bkg("my_binary", ksft_wait=5):
|
||||
"""
|
||||
def __init__(self, comm, shell=None, fail=None, ns=None, host=None,
|
||||
exit_wait=False, ksft_wait=None):
|
||||
exit_wait=False, ksft_ready=None, ksft_wait=None):
|
||||
super().__init__(comm, background=True,
|
||||
shell=shell, fail=fail, ns=ns, host=host,
|
||||
ksft_wait=ksft_wait)
|
||||
ksft_ready=ksft_ready, ksft_wait=ksft_wait)
|
||||
self.terminate = not exit_wait and not ksft_wait
|
||||
self._exit_wait = exit_wait
|
||||
self.check_fail = fail
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
readonly FLUSH_PATH="/sys/class/net/${dev}/gro_flush_timeout"
|
||||
readonly IRQ_PATH="/sys/class/net/${dev}/napi_defer_hard_irqs"
|
||||
readonly FLUSH_TIMEOUT="$(< ${FLUSH_PATH})"
|
||||
readonly HARD_IRQS="$(< ${IRQ_PATH})"
|
||||
readonly server_ns=$(mktemp -u server-XXXXXXXX)
|
||||
readonly client_ns=$(mktemp -u client-XXXXXXXX)
|
||||
|
||||
netdev_check_for_carrier() {
|
||||
local -r dev="$1"
|
||||
|
||||
for i in {1..5}; do
|
||||
carrier="$(cat /sys/class/net/${dev}/carrier)"
|
||||
if [[ "${carrier}" -ne 1 ]] ; then
|
||||
echo "carrier not ready yet..." >&2
|
||||
sleep 1
|
||||
else
|
||||
echo "carrier ready" >&2
|
||||
break
|
||||
fi
|
||||
done
|
||||
echo "${carrier}"
|
||||
}
|
||||
|
||||
# Assumes that there is no existing ipvlan device on the physical device
|
||||
setup_loopback_environment() {
|
||||
local dev="$1"
|
||||
|
||||
# Fail hard if cannot turn on loopback mode for current NIC
|
||||
ethtool -K "${dev}" loopback on || exit 1
|
||||
sleep 1
|
||||
|
||||
# Check for the carrier
|
||||
carrier=$(netdev_check_for_carrier ${dev})
|
||||
if [[ "${carrier}" -ne 1 ]] ; then
|
||||
echo "setup_loopback_environment failed"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
setup_macvlan_ns(){
|
||||
local -r link_dev="$1"
|
||||
local -r ns_name="$2"
|
||||
local -r ns_dev="$3"
|
||||
local -r ns_mac="$4"
|
||||
local -r addr="$5"
|
||||
|
||||
ip link add link "${link_dev}" dev "${ns_dev}" \
|
||||
address "${ns_mac}" type macvlan
|
||||
exit_code=$?
|
||||
if [[ "${exit_code}" -ne 0 ]]; then
|
||||
echo "setup_macvlan_ns failed"
|
||||
exit $exit_code
|
||||
fi
|
||||
|
||||
[[ -e /var/run/netns/"${ns_name}" ]] || ip netns add "${ns_name}"
|
||||
ip link set dev "${ns_dev}" netns "${ns_name}"
|
||||
ip -netns "${ns_name}" link set dev "${ns_dev}" up
|
||||
if [[ -n "${addr}" ]]; then
|
||||
ip -netns "${ns_name}" addr add dev "${ns_dev}" "${addr}"
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
}
|
||||
|
||||
cleanup_macvlan_ns(){
|
||||
while (( $# >= 2 )); do
|
||||
ns_name="$1"
|
||||
ns_dev="$2"
|
||||
ip -netns "${ns_name}" link del dev "${ns_dev}"
|
||||
ip netns del "${ns_name}"
|
||||
shift 2
|
||||
done
|
||||
}
|
||||
|
||||
cleanup_loopback(){
|
||||
local -r dev="$1"
|
||||
|
||||
ethtool -K "${dev}" loopback off
|
||||
sleep 1
|
||||
|
||||
# Check for the carrier
|
||||
carrier=$(netdev_check_for_carrier ${dev})
|
||||
if [[ "${carrier}" -ne 1 ]] ; then
|
||||
echo "setup_loopback_environment failed"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
setup_interrupt() {
|
||||
# Use timer on host to trigger the network stack
|
||||
# Also disable device interrupt to not depend on NIC interrupt
|
||||
# Reduce test flakiness caused by unexpected interrupts
|
||||
echo 100000 >"${FLUSH_PATH}"
|
||||
echo 50 >"${IRQ_PATH}"
|
||||
}
|
||||
|
||||
setup_ns() {
|
||||
# Set up server_ns namespace and client_ns namespace
|
||||
setup_macvlan_ns "${dev}" ${server_ns} server "${SERVER_MAC}"
|
||||
setup_macvlan_ns "${dev}" ${client_ns} client "${CLIENT_MAC}"
|
||||
}
|
||||
|
||||
cleanup_ns() {
|
||||
cleanup_macvlan_ns ${server_ns} server ${client_ns} client
|
||||
}
|
||||
|
||||
setup() {
|
||||
setup_loopback_environment "${dev}"
|
||||
setup_interrupt
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
cleanup_loopback "${dev}"
|
||||
|
||||
echo "${FLUSH_TIMEOUT}" >"${FLUSH_PATH}"
|
||||
echo "${HARD_IRQS}" >"${IRQ_PATH}"
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
readonly server_ns=$(mktemp -u server-XXXXXXXX)
|
||||
readonly client_ns=$(mktemp -u client-XXXXXXXX)
|
||||
|
||||
setup_veth_ns() {
|
||||
local -r link_dev="$1"
|
||||
local -r ns_name="$2"
|
||||
local -r ns_dev="$3"
|
||||
local -r ns_mac="$4"
|
||||
|
||||
[[ -e /var/run/netns/"${ns_name}" ]] || ip netns add "${ns_name}"
|
||||
echo 200000 > "/sys/class/net/${ns_dev}/gro_flush_timeout"
|
||||
echo 1 > "/sys/class/net/${ns_dev}/napi_defer_hard_irqs"
|
||||
ip link set dev "${ns_dev}" netns "${ns_name}" mtu 65535
|
||||
ip -netns "${ns_name}" link set dev "${ns_dev}" up
|
||||
|
||||
ip netns exec "${ns_name}" ethtool -K "${ns_dev}" gro on tso off
|
||||
}
|
||||
|
||||
setup_ns() {
|
||||
# Set up server_ns namespace and client_ns namespace
|
||||
ip link add name server type veth peer name client
|
||||
|
||||
setup_veth_ns "${dev}" ${server_ns} server "${SERVER_MAC}"
|
||||
setup_veth_ns "${dev}" ${client_ns} client "${CLIENT_MAC}"
|
||||
}
|
||||
|
||||
cleanup_ns() {
|
||||
local ns_name
|
||||
|
||||
for ns_name in ${client_ns} ${server_ns}; do
|
||||
[[ -e /var/run/netns/"${ns_name}" ]] && ip netns del "${ns_name}"
|
||||
done
|
||||
}
|
||||
|
||||
setup() {
|
||||
# no global init setup step needed
|
||||
:
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
cleanup_ns
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# extended toeplitz test: test rxhash plus, optionally, either (1) rss mapping
|
||||
# from rxhash to rx queue ('-rss') or (2) rps mapping from rxhash to cpu
|
||||
# ('-rps <rps_map>')
|
||||
#
|
||||
# irq-pattern-prefix can be derived from /sys/kernel/irq/*/action,
|
||||
# which is a driver-specific encoding.
|
||||
#
|
||||
# invoke as ./toeplitz.sh (-i <iface>) -u|-t -4|-6 \
|
||||
# [(-rss -irq_prefix <irq-pattern-prefix>)|(-rps <rps_map>)]
|
||||
|
||||
source setup_loopback.sh
|
||||
readonly SERVER_IP4="192.168.1.200/24"
|
||||
readonly SERVER_IP6="fda8::1/64"
|
||||
readonly SERVER_MAC="aa:00:00:00:00:02"
|
||||
|
||||
readonly CLIENT_IP4="192.168.1.100/24"
|
||||
readonly CLIENT_IP6="fda8::2/64"
|
||||
readonly CLIENT_MAC="aa:00:00:00:00:01"
|
||||
|
||||
PORT=8000
|
||||
KEY="$(</proc/sys/net/core/netdev_rss_key)"
|
||||
TEST_RSS=false
|
||||
RPS_MAP=""
|
||||
PROTO_FLAG=""
|
||||
IP_FLAG=""
|
||||
DEV="eth0"
|
||||
|
||||
# Return the number of rxqs among which RSS is configured to spread packets.
|
||||
# This is determined by reading the RSS indirection table using ethtool.
|
||||
get_rss_cfg_num_rxqs() {
|
||||
echo $(ethtool -x "${DEV}" |
|
||||
grep -E [[:space:]]+[0-9]+:[[:space:]]+ |
|
||||
cut -d: -f2- |
|
||||
awk '{$1=$1};1' |
|
||||
tr ' ' '\n' |
|
||||
sort -u |
|
||||
wc -l)
|
||||
}
|
||||
|
||||
# Return a list of the receive irq handler cpus.
|
||||
# The list is ordered by the irqs, so first rxq-0 cpu, then rxq-1 cpu, etc.
|
||||
# Reads /sys/kernel/irq/ in order, so algorithm depends on
|
||||
# irq_{rxq-0} < irq_{rxq-1}, etc.
|
||||
get_rx_irq_cpus() {
|
||||
CPUS=""
|
||||
# sort so that irq 2 is read before irq 10
|
||||
SORTED_IRQS=$(for i in /sys/kernel/irq/*; do echo $i; done | sort -V)
|
||||
# Consider only as many queues as RSS actually uses. We assume that
|
||||
# if RSS_CFG_NUM_RXQS=N, then RSS uses rxqs 0-(N-1).
|
||||
RSS_CFG_NUM_RXQS=$(get_rss_cfg_num_rxqs)
|
||||
RXQ_COUNT=0
|
||||
|
||||
for i in ${SORTED_IRQS}
|
||||
do
|
||||
[[ "${RXQ_COUNT}" -lt "${RSS_CFG_NUM_RXQS}" ]] || break
|
||||
# lookup relevant IRQs by action name
|
||||
[[ -e "$i/actions" ]] || continue
|
||||
cat "$i/actions" | grep -q "${IRQ_PATTERN}" || continue
|
||||
irqname=$(<"$i/actions")
|
||||
|
||||
# does the IRQ get called
|
||||
irqcount=$(cat "$i/per_cpu_count" | tr -d '0,')
|
||||
[[ -n "${irqcount}" ]] || continue
|
||||
|
||||
# lookup CPU
|
||||
irq=$(basename "$i")
|
||||
cpu=$(cat "/proc/irq/$irq/smp_affinity_list")
|
||||
|
||||
if [[ -z "${CPUS}" ]]; then
|
||||
CPUS="${cpu}"
|
||||
else
|
||||
CPUS="${CPUS},${cpu}"
|
||||
fi
|
||||
RXQ_COUNT=$((RXQ_COUNT+1))
|
||||
done
|
||||
|
||||
echo "${CPUS}"
|
||||
}
|
||||
|
||||
get_disable_rfs_cmd() {
|
||||
echo "echo 0 > /proc/sys/net/core/rps_sock_flow_entries;"
|
||||
}
|
||||
|
||||
get_set_rps_bitmaps_cmd() {
|
||||
CMD=""
|
||||
for i in /sys/class/net/${DEV}/queues/rx-*/rps_cpus
|
||||
do
|
||||
CMD="${CMD} echo $1 > ${i};"
|
||||
done
|
||||
|
||||
echo "${CMD}"
|
||||
}
|
||||
|
||||
get_disable_rps_cmd() {
|
||||
echo "$(get_set_rps_bitmaps_cmd 0)"
|
||||
}
|
||||
|
||||
die() {
|
||||
echo "$1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
check_nic_rxhash_enabled() {
|
||||
local -r pattern="receive-hashing:\ on"
|
||||
|
||||
ethtool -k "${DEV}" | grep -q "${pattern}" || die "rxhash must be enabled"
|
||||
}
|
||||
|
||||
parse_opts() {
|
||||
local prog=$0
|
||||
shift 1
|
||||
|
||||
while [[ "$1" =~ "-" ]]; do
|
||||
if [[ "$1" = "-irq_prefix" ]]; then
|
||||
shift
|
||||
IRQ_PATTERN="^$1-[0-9]*$"
|
||||
elif [[ "$1" = "-u" || "$1" = "-t" ]]; then
|
||||
PROTO_FLAG="$1"
|
||||
elif [[ "$1" = "-4" ]]; then
|
||||
IP_FLAG="$1"
|
||||
SERVER_IP="${SERVER_IP4}"
|
||||
CLIENT_IP="${CLIENT_IP4}"
|
||||
elif [[ "$1" = "-6" ]]; then
|
||||
IP_FLAG="$1"
|
||||
SERVER_IP="${SERVER_IP6}"
|
||||
CLIENT_IP="${CLIENT_IP6}"
|
||||
elif [[ "$1" = "-rss" ]]; then
|
||||
TEST_RSS=true
|
||||
elif [[ "$1" = "-rps" ]]; then
|
||||
shift
|
||||
RPS_MAP="$1"
|
||||
elif [[ "$1" = "-i" ]]; then
|
||||
shift
|
||||
DEV="$1"
|
||||
else
|
||||
die "Usage: ${prog} (-i <iface>) -u|-t -4|-6 \
|
||||
[(-rss -irq_prefix <irq-pattern-prefix>)|(-rps <rps_map>)]"
|
||||
fi
|
||||
shift
|
||||
done
|
||||
}
|
||||
|
||||
setup() {
|
||||
setup_loopback_environment "${DEV}"
|
||||
|
||||
# Set up server_ns namespace and client_ns namespace
|
||||
setup_macvlan_ns "${DEV}" $server_ns server \
|
||||
"${SERVER_MAC}" "${SERVER_IP}"
|
||||
setup_macvlan_ns "${DEV}" $client_ns client \
|
||||
"${CLIENT_MAC}" "${CLIENT_IP}"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
cleanup_macvlan_ns $server_ns server $client_ns client
|
||||
cleanup_loopback "${DEV}"
|
||||
}
|
||||
|
||||
parse_opts $0 $@
|
||||
|
||||
setup
|
||||
trap cleanup EXIT
|
||||
|
||||
check_nic_rxhash_enabled
|
||||
|
||||
# Actual test starts here
|
||||
if [[ "${TEST_RSS}" = true ]]; then
|
||||
# RPS/RFS must be disabled because they move packets between cpus,
|
||||
# which breaks the PACKET_FANOUT_CPU identification of RSS decisions.
|
||||
eval "$(get_disable_rfs_cmd) $(get_disable_rps_cmd)" \
|
||||
ip netns exec $server_ns ./toeplitz "${IP_FLAG}" "${PROTO_FLAG}" \
|
||||
-d "${PORT}" -i "${DEV}" -k "${KEY}" -T 1000 \
|
||||
-C "$(get_rx_irq_cpus)" -s -v &
|
||||
elif [[ ! -z "${RPS_MAP}" ]]; then
|
||||
eval "$(get_disable_rfs_cmd) $(get_set_rps_bitmaps_cmd ${RPS_MAP})" \
|
||||
ip netns exec $server_ns ./toeplitz "${IP_FLAG}" "${PROTO_FLAG}" \
|
||||
-d "${PORT}" -i "${DEV}" -k "${KEY}" -T 1000 \
|
||||
-r "0x${RPS_MAP}" -s -v &
|
||||
else
|
||||
ip netns exec $server_ns ./toeplitz "${IP_FLAG}" "${PROTO_FLAG}" \
|
||||
-d "${PORT}" -i "${DEV}" -k "${KEY}" -T 1000 -s -v &
|
||||
fi
|
||||
|
||||
server_pid=$!
|
||||
|
||||
ip netns exec $client_ns ./toeplitz_client.sh "${PROTO_FLAG}" \
|
||||
"${IP_FLAG}" "${SERVER_IP%%/*}" "${PORT}" &
|
||||
|
||||
client_pid=$!
|
||||
|
||||
wait "${server_pid}"
|
||||
exit_code=$?
|
||||
kill -9 "${client_pid}"
|
||||
if [[ "${exit_code}" -eq 0 ]]; then
|
||||
echo "Test Succeeded!"
|
||||
fi
|
||||
exit "${exit_code}"
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# A simple program for generating traffic for the toeplitz test.
|
||||
#
|
||||
# This program sends packets periodically for, conservatively, 20 seconds. The
|
||||
# intent is for the calling program to kill this program once it is no longer
|
||||
# needed, rather than waiting for the 20 second expiration.
|
||||
|
||||
send_traffic() {
|
||||
expiration=$((SECONDS+20))
|
||||
while [[ "${SECONDS}" -lt "${expiration}" ]]
|
||||
do
|
||||
if [[ "${PROTO}" == "-u" ]]; then
|
||||
echo "msg $i" | nc "${IPVER}" -u -w 0 "${ADDR}" "${PORT}"
|
||||
else
|
||||
echo "msg $i" | nc "${IPVER}" -w 0 "${ADDR}" "${PORT}"
|
||||
fi
|
||||
sleep 0.001
|
||||
done
|
||||
}
|
||||
|
||||
PROTO=$1
|
||||
IPVER=$2
|
||||
ADDR=$3
|
||||
PORT=$4
|
||||
|
||||
send_traffic
|
||||
Reference in New Issue
Block a user