Message ID | 20240417231146.2435572-9-kuba@kernel.org |
---|---|
State | New |
Headers | show |
Series | selftests: drv-net: support testing with a remote system | expand |
Jakub Kicinski wrote: > More complex tests often have to spawn a background process, > like a server which will respond to requests or tcpdump. > > Add support for creating such processes using the with keyword: > > with bkg("my-daemon", ..): > # my-daemon is alive in this block > > My initial thought was to add this support to cmd() directly > but it runs the command in the constructor, so by the time > we __enter__ it's too late to make sure we used "background=True". > > Second useful helper transplanted from net_helper.sh is > wait_port_listen(). > > Signed-off-by: Jakub Kicinski <kuba@kernel.org> > --- > tools/testing/selftests/drivers/net/ping.py | 24 +++++++++++++-- > tools/testing/selftests/net/lib/py/utils.py | 33 +++++++++++++++++++++ > 2 files changed, 55 insertions(+), 2 deletions(-) > > diff --git a/tools/testing/selftests/drivers/net/ping.py b/tools/testing/selftests/drivers/net/ping.py > index 58aefd3e740f..8532e3be72ba 100755 > --- a/tools/testing/selftests/drivers/net/ping.py > +++ b/tools/testing/selftests/drivers/net/ping.py > @@ -1,9 +1,12 @@ > #!/usr/bin/env python3 > # SPDX-License-Identifier: GPL-2.0 > > -from lib.py import ksft_run, ksft_exit, KsftXfailEx > +import random > + > +from lib.py import ksft_run, ksft_exit > +from lib.py import ksft_eq, KsftXfailEx > from lib.py import NetDrvEpEnv > -from lib.py import cmd > +from lib.py import bkg, cmd, wait_port_listen > > > def test_v4(cfg) -> None: > @@ -22,6 +25,23 @@ from lib.py import cmd > cmd(f"ping -c 1 -W0.5 {cfg.v6}", host=cfg.remote) > > > +def test_tcp(cfg) -> None: > + port = random.randrange(1024 + (1 << 15)) > + with bkg(f"nc -l {cfg.addr} {port}") as nc: > + wait_port_listen(port) > + > + cmd(f"echo ping | nc {cfg.addr} {port}", > + shell=True, host=cfg.remote) > + ksft_eq(nc.stdout.strip(), "ping") > + > + port = random.randrange(1024 + (1 << 15)) > + with bkg(f"nc -l {cfg.remote_addr} {port}", host=cfg.remote) as nc: > + wait_port_listen(port, host=cfg.remote) > + > + cmd(f"echo ping | nc {cfg.remote_addr} {port}", shell=True) > + ksft_eq(nc.stdout.strip(), "ping") > + There are different netcat implementations floating around. I notice that I have to pass -N on the client to terminate the connection after EOF. Else both peers keep the connection open, waiting for input. And explicitly pass -6 if passing an IPv6 address. I think this is the one that ships with Debian..
On Thu, 18 Apr 2024 10:44:39 -0400 Willem de Bruijn wrote: > > +def test_tcp(cfg) -> None: > > + port = random.randrange(1024 + (1 << 15)) > > + with bkg(f"nc -l {cfg.addr} {port}") as nc: > > + wait_port_listen(port) > > + > > + cmd(f"echo ping | nc {cfg.addr} {port}", > > + shell=True, host=cfg.remote) > > + ksft_eq(nc.stdout.strip(), "ping") > > + > > + port = random.randrange(1024 + (1 << 15)) > > + with bkg(f"nc -l {cfg.remote_addr} {port}", host=cfg.remote) as nc: > > + wait_port_listen(port, host=cfg.remote) > > + > > + cmd(f"echo ping | nc {cfg.remote_addr} {port}", shell=True) > > + ksft_eq(nc.stdout.strip(), "ping") > > + > > There are different netcat implementations floating around. > > I notice that I have to pass -N on the client to terminate the > connection after EOF. Else both peers keep the connection open, > waiting for input. And explicitly pass -6 if passing an IPv6 > address. I think this is the one that ships with Debian.. Right, 100% laziness on my part. Mostly because socat requires bracketed IPv6. But once I tried it I also run into the premature termination problem, so ended up with this diff on top: diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py index 579c5b34e6fd..2f62270d59fa 100644 --- a/tools/testing/selftests/drivers/net/lib/py/env.py +++ b/tools/testing/selftests/drivers/net/lib/py/env.py @@ -110,6 +110,10 @@ from .remote import Remote self.addr = self.v6 if self.v6 else self.v4 self.remote_addr = self.remote_v6 if self.remote_v6 else self.remote_v4 + # Bracketed addresses, some commands need IPv6 to be inside [] + self.baddr = f"[{self.v6}]" if self.v6 else self.v4 + self.remote_baddr = f"[{self.remote_v6}]" if self.remote_v6 else self.remote_v4 + self.ifname = self.dev['ifname'] self.ifindex = self.dev['ifindex'] diff --git a/tools/testing/selftests/drivers/net/ping.py b/tools/testing/selftests/drivers/net/ping.py index 8532e3be72ba..985b06ce2e81 100755 --- a/tools/testing/selftests/drivers/net/ping.py +++ b/tools/testing/selftests/drivers/net/ping.py @@ -27,18 +27,20 @@ from lib.py import bkg, cmd, wait_port_listen def test_tcp(cfg) -> None: port = random.randrange(1024 + (1 << 15)) - with bkg(f"nc -l {cfg.addr} {port}") as nc: + + with bkg(f"socat -t 2 -u TCP-LISTEN:{port} STDOUT", exit_wait=True) as nc: wait_port_listen(port) - cmd(f"echo ping | nc {cfg.addr} {port}", + cmd(f"echo ping | socat -t 2 -u STDIN TCP:{cfg.baddr}:{port}", shell=True, host=cfg.remote) ksft_eq(nc.stdout.strip(), "ping") port = random.randrange(1024 + (1 << 15)) - with bkg(f"nc -l {cfg.remote_addr} {port}", host=cfg.remote) as nc: + with bkg(f"socat -t 2 -u TCP-LISTEN:{port} STDOUT", host=cfg.remote, + exit_wait=True) as nc: wait_port_listen(port, host=cfg.remote) - cmd(f"echo ping | nc {cfg.remote_addr} {port}", shell=True) + cmd(f"echo ping | socat -t 2 -u STDIN TCP:{cfg.remote_baddr}:{port}", shell=True) ksft_eq(nc.stdout.strip(), "ping") diff --git a/tools/testing/selftests/net/lib/py/utils.py b/tools/testing/selftests/net/lib/py/utils.py index 6bacdc99d21b..85a6a9bb35fd 100644 --- a/tools/testing/selftests/net/lib/py/utils.py +++ b/tools/testing/selftests/net/lib/py/utils.py @@ -42,15 +42,17 @@ import time class bkg(cmd): - def __init__(self, comm, shell=True, fail=True, ns=None, host=None): + def __init__(self, comm, shell=True, fail=True, ns=None, host=None, + exit_wait=False): super().__init__(comm, background=True, shell=shell, fail=fail, ns=ns, host=host) + self.terminate = not exit_wait def __enter__(self): return self def __exit__(self, ex_type, ex_value, ex_tb): - return self.process() + return self.process(terminate=self.terminate) def ip(args, json=None, ns=None, host=None):
diff --git a/tools/testing/selftests/drivers/net/ping.py b/tools/testing/selftests/drivers/net/ping.py index 58aefd3e740f..8532e3be72ba 100755 --- a/tools/testing/selftests/drivers/net/ping.py +++ b/tools/testing/selftests/drivers/net/ping.py @@ -1,9 +1,12 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0 -from lib.py import ksft_run, ksft_exit, KsftXfailEx +import random + +from lib.py import ksft_run, ksft_exit +from lib.py import ksft_eq, KsftXfailEx from lib.py import NetDrvEpEnv -from lib.py import cmd +from lib.py import bkg, cmd, wait_port_listen def test_v4(cfg) -> None: @@ -22,6 +25,23 @@ from lib.py import cmd cmd(f"ping -c 1 -W0.5 {cfg.v6}", host=cfg.remote) +def test_tcp(cfg) -> None: + port = random.randrange(1024 + (1 << 15)) + with bkg(f"nc -l {cfg.addr} {port}") as nc: + wait_port_listen(port) + + cmd(f"echo ping | nc {cfg.addr} {port}", + shell=True, host=cfg.remote) + ksft_eq(nc.stdout.strip(), "ping") + + port = random.randrange(1024 + (1 << 15)) + with bkg(f"nc -l {cfg.remote_addr} {port}", host=cfg.remote) as nc: + wait_port_listen(port, host=cfg.remote) + + cmd(f"echo ping | nc {cfg.remote_addr} {port}", shell=True) + ksft_eq(nc.stdout.strip(), "ping") + + def main() -> None: with NetDrvEpEnv(__file__) as cfg: ksft_run(globs=globals(), case_pfx={"test_"}, args=(cfg, )) diff --git a/tools/testing/selftests/net/lib/py/utils.py b/tools/testing/selftests/net/lib/py/utils.py index e80fea9f6562..6bacdc99d21b 100644 --- a/tools/testing/selftests/net/lib/py/utils.py +++ b/tools/testing/selftests/net/lib/py/utils.py @@ -1,7 +1,10 @@ # SPDX-License-Identifier: GPL-2.0 import json as _json +import re import subprocess +import time + class cmd: def __init__(self, comm, shell=True, fail=True, ns=None, background=False, host=None): @@ -38,6 +41,18 @@ import subprocess (self.proc.args, stdout, stderr)) +class bkg(cmd): + def __init__(self, comm, shell=True, fail=True, ns=None, host=None): + super().__init__(comm, background=True, + shell=shell, fail=fail, ns=ns, host=host) + + def __enter__(self): + return self + + def __exit__(self, ex_type, ex_value, ex_tb): + return self.process() + + def ip(args, json=None, ns=None, host=None): cmd_str = "ip " if json: @@ -47,3 +62,21 @@ import subprocess if json: return _json.loads(cmd_obj.stdout) return cmd_obj + + +def wait_port_listen(port, proto="tcp", ns=None, host=None, sleep=0.005, deadline=1): + end = time.monotonic() + deadline + + pattern = f":{port:04X} .* " + if proto == "tcp": # for tcp protocol additionally check the socket state + pattern += "0A" + pattern = re.compile(pattern) + + while True: + data = cmd(f'cat /proc/net/{proto}*', ns=ns, host=host, shell=True).stdout + for row in data.split("\n"): + if pattern.search(row): + return + if time.monotonic() > end: + raise Exception("Waiting for port listen timed out") + time.sleep(sleep)
More complex tests often have to spawn a background process, like a server which will respond to requests or tcpdump. Add support for creating such processes using the with keyword: with bkg("my-daemon", ..): # my-daemon is alive in this block My initial thought was to add this support to cmd() directly but it runs the command in the constructor, so by the time we __enter__ it's too late to make sure we used "background=True". Second useful helper transplanted from net_helper.sh is wait_port_listen(). Signed-off-by: Jakub Kicinski <kuba@kernel.org> --- tools/testing/selftests/drivers/net/ping.py | 24 +++++++++++++-- tools/testing/selftests/net/lib/py/utils.py | 33 +++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-)