From b5679aa603bef28c68ecaa219ce0b863eaa3e7c1 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Fri, 10 May 2024 12:54:26 +0000 Subject: [PATCH] test: Improve robustness of pinging from within namespaces It turns out that the semantics of ping's -c option does not match our expectations. The manual says: > Stop after sending count ECHO_REQUEST packets. With deadline option, > ping waits for count ECHO_REPLY packets, until the timeout expires. But in fact, something like an ICMP_DEST_UNREACH will also count as a response, meaning that the process will terminate with non-zero exitcode even though the deadline has not yet been crossed, rather than keep waiting for an ECHO_REPLY. To add some extra flavor: this is only the case for iputils' ping, Busybox's implementation will hold out for a response until the deadline expires. Therefore, wrap the ping in an ugly retry loop. Also, return the full available context from must{,_not}_reach on failures, instead of just the output. This allows us to show the exit code in the top-level exception handler. --- test/infamy/netns.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/test/infamy/netns.py b/test/infamy/netns.py index 7a7b61e1..3c077276 100644 --- a/test/infamy/netns.py +++ b/test/infamy/netns.py @@ -136,15 +136,18 @@ class IsolatedMacVlan: result.append(l) return result - def ping(self, daddr, count=1, timeout=5, interval=2, check=False): - return self.runsh(f"""set -ex; ping -c {count} -w {timeout} -i {interval} {daddr}""", check=check) + def ping(self, daddr, timeout=5): + return self.run(["timeout", str(timeout), "/bin/sh"], text=True, check=True, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + input=f"while :; do ping -c1 -w1 {daddr} && break; done") def must_reach(self, *args, **kwargs): - res = self.ping(*args, **kwargs) - if res.returncode != 0: - raise Exception(res.stdout) + self.ping(*args, **kwargs) def must_not_reach(self, *args, **kwargs): - res = self.ping(*args, **kwargs) - if res.returncode == 0: - raise Exception(res.stdout) + try: + res = self.ping(*args, **kwargs) + except subprocess.CalledProcessError as e: + return + + raise Exception(res)