diff --git a/test/infamy/netns.py b/test/infamy/netns.py index caf327f0..c5827428 100644 --- a/test/infamy/netns.py +++ b/test/infamy/netns.py @@ -18,31 +18,53 @@ def setns(fd, nstype): __NR_setns = 308 __libc.syscall(__NR_setns, fd, nstype) -class IsolatedMacVlan: - """Create an isolated interface on top of a PC interface.""" - def __init__(self, parent, ifname="iface", lo=True): +class IsolatedMacVlans: + """A network namespace containing a multiple MACVLANs + + Stacks a MACVLAN on top of each specificed controller interface, + and moves those interfaces to a separate namespace, isolating it + from all others. + + NOTE: For the simple case when only one interface needs to be + mapped, see IsolatedMacVlan below. + + Example: + + netns = IsolatedMacVlans({ "eth2": "a", "eth3": "b" }) + + netns: + .--------. + | a b | (MACVLANs) + '-+----+-' + | | + eth0 eth1 eth2 eth3 + + """ + + def __init__(self, ifmap, lo=True): self.sleeper = None - self.parent, self.ifname, self.lo = parent, ifname, lo + self.ifmap, self.lo = ifmap, lo self.ping_timeout = env.ENV.attr("ping_timeout", 5) - def __enter__(self): + def start(self): self.sleeper = subprocess.Popen(["unshare", "-r", "-n", "sh", "-c", "echo && exec sleep infinity"], stdout=subprocess.PIPE) self.sleeper.stdout.readline() try: - subprocess.run(["ip", "link", "add", - "dev", self.ifname, - "link", self.parent, - "address", self._stable_mac(), - "netns", str(self.sleeper.pid), - "type", "macvlan"], check=True) - self.runsh(f""" - while ! ip link show dev {self.ifname}; do - sleep 0.1 - done - """) + for parent, ifname in self.ifmap.items(): + subprocess.run(["ip", "link", "add", + "dev", ifname, + "link", parent, + "address", self._stable_mac(parent), + "netns", str(self.sleeper.pid), + "type", "macvlan"], check=True) + self.runsh(f""" + while ! ip link show dev {ifname}; do + sleep 0.1 + done + """) except Exception as e: self.__exit__(None, None, None) raise e @@ -56,12 +78,18 @@ class IsolatedMacVlan: return self - def __exit__(self, val, typ, tb): + def stop(self): self.sleeper.kill() self.sleeper.wait() time.sleep(0.5) - def _stable_mac(self): + def __enter__(self): + return self.start() + + def __exit__(self, val, typ, tb): + return self.stop() + + def _stable_mac(self, parent): """Generate address for MACVLAN By default, the kernel will assign a random address. This @@ -75,7 +103,7 @@ class IsolatedMacVlan: the resource exhaustion issue. """ - random.seed(self.parent) + random.seed(parent) a = list(random.randbytes(6)) a[0] |= 0x02 a[0] &= ~0x01 @@ -146,13 +174,13 @@ class IsolatedMacVlan: ip -{p} route add {subnet}{prefix_length} via {nexthop} """, check=True) - def addip(self, addr, prefix_length=24, proto="ipv4"): + def addip(self, ifname, addr, prefix_length=24, proto="ipv4"): p=proto[3] self.runsh(f""" set -ex ip link set iface up - ip -{p} addr add {addr}/{prefix_length} dev iface + ip -{p} addr add {addr}/{prefix_length} dev {ifname} """, check=True) @@ -188,8 +216,7 @@ class IsolatedMacVlan: raise Exception(res) - def must_receive(self, expr, timeout=None, ifname=None, must=True): - ifname = ifname if ifname else self.ifname + def must_receive(self, expr, ifname, timeout=None, must=True): timeout = timeout if timeout else self.ping_timeout tshark = self.run(["tshark", "-nl", f"-i{ifname}", @@ -207,10 +234,43 @@ class IsolatedMacVlan: def must_not_receive(self, *args, **kwargs): self.must_receive(*args, **kwargs, must=False) - def pcap(self, expr, ifname=None): - ifname = ifname if ifname else self.ifname + def pcap(self, expr, ifname): return Pcap(self, ifname, expr) +class IsolatedMacVlan(IsolatedMacVlans): + """A network namespace containing a single MACVLAN + + Stacks a MACVLAN on top of an interface on the controller, and + moves that interface to a separate namespace, isolating it from + all other interfaces. + + Example: + + netns = IsolatedMacVlan("eth3") + + netns: + .-------. + | iface | (MACVLAN) + '---+---' + | + eth0 eth1 eth2 eth3 + + """ + def __init__(self, parent, ifname="iface", lo=True): + self._ifname = ifname + return super().__init__(ifmap={ parent: ifname }, lo=lo) + + def addip(self, addr, prefix_length=24, proto="ipv4"): + return super().addip(ifname=self._ifname, addr=addr, prefix_length=prefix_length, proto=proto) + + def must_receive(self, expr, timeout=None, ifname=None, must=True): + ifname = ifname if ifname else self._ifname + return super().must_receive(expr=expr, ifname=ifname, timeout=timeout, must=must) + + def pcap(self, expr, ifname=None): + ifname = ifname if ifname else self._ifname + return super().pcap(expr=expr, ifname=ifname) + class Pcap: def __init__(self, netns, ifname, expr): self.netns, self.ifname, self.expr = netns, ifname, expr