In test cases, it is common that we want to verify that a certain type
of traffic reaches a namespace. This has, up to this point, been
solved by the generic sniffer.Sniffer.
Try to improve the two most common use-cases...
1. Test if a flow reaches a namespace
2. Ensure that a flow does not reach a namespace
...by always exiting as soon as the first packet is received.
This saves time on positive tests, and the synchronous API allows it
to compose well with infamy.parallel().
QEMU systems seem work fine with the 5s timeout, whereas some physical
devices can't quite keep up with that.
Let the slower systems indicate that they need a bit more time without
punishing faster ones with a longer default.
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.
Add a helper that lets you call a Python function (or lambda) in the
context of a network samespace.
Example:
with infamy.IsolatedMacVlan("eth0") as ns:
res = ns.call(lambda: subprocess.run(["ip", "link"],
capture_output=True))
print(res.stdout, f"\n[exitcode:{res.returncode}]")
The call to subprocess.run will be executed in the context of the
network namespace, and thus only list "lo" and the "iface"
macvlan.
The return value of the function passed to ns.call() is passed back
over a multiprocessing.Pipe, which requires that the object be
"picklable". This is true for most objects (even more complex ones
like the CompletedProcess object seen in the example above). Some
notable exceptions are things like file objects, sockets, etc.
The setns(2) syscall is unfortunately not available in current
versions of Python shipped with neither Ubuntu nor Alpine at the time
of this writing. Therefore, shoot from the hip, assume that we're
running on an x86_64 CPU, and just hotwire the syscall directly with
some constants we found in a dumpster.
* sniffer did not work correctly
* links did not come upp as quick as in qemu
* must_reach in netns was to narrow, increase timeout
* add wait function to handle poll