Merge pull request #799 from kernelkit/ospf-bfd

test: Multi-Port Network Namespaces
This commit is contained in:
Tobias Waldekranz
2024-11-05 15:46:30 +01:00
committed by GitHub
10 changed files with 546 additions and 54 deletions
@@ -16,10 +16,12 @@ endif::testgroup[]
endif::topdoc[]
==== Test sequence
. Set up topology and attach to target DUT
. Set up VLAN interface on host:data with IP 10.0.0.1
. Configure VLAN 10 interface on target:data with IP 10.0.0.2
. Waiting for links to come up
. Ping 10.0.0.2 from VLAN 10 on host:data with IP 10.0.0.1
. Remove VLAN interface from target:data, and test again (should not be able to ping)
. Wait for links to come up
. Verify that host:data can reach 10.0.0.2
. Remove VLAN interface from target:data
. Verify that host:data can no longer reach 10.0.0.2
<<<
+16 -24
View File
@@ -10,31 +10,21 @@ import infamy.iface as iface
from infamy import until
def test_ping(hport, should_pass):
with infamy.IsolatedMacVlan(hport) as ns:
try:
ns.runsh("""
set -ex
ip link set iface up
ip link add dev vlan10 link iface up type vlan id 10
ip addr add 10.0.0.1/24 dev vlan10
""")
if should_pass:
ns.must_reach("10.0.0.2")
else:
ns.must_not_reach("10.0.0.2")
except Exception as e:
print(f"An error occurred during the VLAN setup or ping test: {e}")
raise
with infamy.Test() as test:
with test.step("Set up topology and attach to target DUT"):
env = infamy.Env()
target = env.attach("target", "mgmt")
_, tport = env.ltop.xlate("target", "data")
with test.step("Set up VLAN interface on host:data with IP 10.0.0.1"):
_, hport = env.ltop.xlate("host", "data")
datanet = infamy.IsolatedMacVlan(hport).start()
datanet.runsh("""
set -ex
ip link set iface up
ip link add dev vlan10 link iface up type vlan id 10
ip addr add 10.0.0.1/24 dev vlan10
""")
with test.step("Configure VLAN 10 interface on target:data with IP 10.0.0.2"):
target.put_config_dict("ietf-interfaces", {
@@ -64,16 +54,18 @@ with infamy.Test() as test:
}
})
with test.step("Waiting for links to come up"):
with test.step("Wait for links to come up"):
until(lambda: iface.get_param(target, tport, "oper-status") == "up")
with test.step("Ping 10.0.0.2 from VLAN 10 on host:data with IP 10.0.0.1"):
with test.step("Verify that host:data can reach 10.0.0.2"):
_, hport = env.ltop.xlate("host", "data")
test_ping(hport,True)
datanet.must_reach("10.0.0.2")
with test.step("Remove VLAN interface from target:data, and test again (should not be able to ping)"):
with test.step("Remove VLAN interface from target:data"):
target.delete_xpath(f"/ietf-interfaces:interfaces/interface[name='{tport}.10']")
with test.step("Verify that host:data can no longer reach 10.0.0.2"):
_, hport = env.ltop.xlate("host", "data")
test_ping(hport,False)
datanet.must_not_reach("10.0.0.2")
test.succeed()
+2
View File
@@ -11,3 +11,5 @@ include::ospf_basic/Readme.adoc[]
include::ospf_unnumbered_interface/Readme.adoc[]
include::ospf_multiarea/Readme.adoc[]
include::ospf_bfd/Readme.adoc[]
+3
View File
@@ -10,3 +10,6 @@
- name: ospf_multiarea
case: ospf_multiarea/test.py
- name: ospf_bfd
case: ospf_bfd/test.py
@@ -0,0 +1,35 @@
=== OSPF BFD
==== Description
Verify that a router running OSPF, with Bidirectional Forwarding
Detection (BFD) enabled, will detect link faults even when the
physical layer is still operational.
This can typically happen when one logical link, from OSPF's
perspective, is made up of multiple physical links containing media
converters without link fault forwarding.
==== Topology
ifdef::topdoc[]
image::../../test/case/ietf_routing/ospf_bfd/topology.svg[OSPF BFD topology]
endif::topdoc[]
ifndef::topdoc[]
ifdef::testgroup[]
image::ospf_bfd/topology.svg[OSPF BFD topology]
endif::testgroup[]
ifndef::testgroup[]
image::topology.svg[OSPF BFD topology]
endif::testgroup[]
endif::topdoc[]
==== Test sequence
. Set up topology and attach to target DUTs
. Setup TPMR between R1fast and R2fast
. Configure R1 and R2
. Setup IP addresses and default routes on h1 and h2
. Wait for R1 and R2 to peer
. Verify connectivity from PC:src to PC:dst via fast link
. Disable forwarding between R1fast and R2fast to trigger fail-over
. Verify connectivity from PC:src to PC:dst via slow link
<<<
+183
View File
@@ -0,0 +1,183 @@
#!/usr/bin/env python3
"""
OSPF BFD
Verify that a router running OSPF, with Bidirectional Forwarding
Detection (BFD) enabled, will detect link faults even when the
physical layer is still operational.
This can typically happen when one logical link, from OSPF's
perspective, is made up of multiple physical links containing media
converters without link fault forwarding.
"""
import time
import infamy
import infamy.route as route
from infamy.netns import TPMR
from infamy.util import until, parallel
def config(target, params):
name = params["name"]
dif, fif, sif = \
params["link"]["data"], \
params["link"]["fast"], \
params["link"]["slow"]
rid, daddr, faddr, saddr = \
params["addr"]["rid"], \
params["addr"]["data"], \
params["addr"]["fast"], \
params["addr"]["slow"]
def ifconfig(name, addr, plen):
return {
"name": name,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": addr,
"prefix-length": plen,
}]}
}
target.put_config_dict("ietf-interfaces", {
"interfaces": {
"interface": [
ifconfig("lo", rid, 32),
ifconfig(dif, daddr, 24),
ifconfig(fif, faddr, 30),
ifconfig(sif, saddr, 30),
]
}
})
target.put_config_dict("ietf-system", {
"system": {
"hostname": name,
}
})
target.put_config_dict("ietf-routing", {
"routing": {
"control-plane-protocols": {
"control-plane-protocol": [{
"type": "infix-routing:ospfv2",
"name": "default",
"ospf": {
"areas": {
"area": [{
"area-id": "0.0.0.0",
"interfaces":
{
"interface": [{
"bfd": {
"enabled": True
},
"name": fif,
"hello-interval": 1,
"dead-interval": 10,
"cost": 100,
},
{
"bfd": {
"enabled": True
},
"name": sif,
"hello-interval": 1,
"dead-interval": 10,
"cost": 200,
}, {
"name": dif,
"passive": True,
}]
},
}]
}
}
}]
}
}
})
with infamy.Test() as test:
with test.step("Set up topology and attach to target DUTs"):
env = infamy.Env()
R1 = env.attach("R1", "mgmt")
R2 = env.attach("R2", "mgmt")
with test.step("Setup TPMR between R1fast and R2fast"):
breaker = TPMR(env.ltop.xlate("PC", "R1fast")[1],
env.ltop.xlate("PC", "R2fast")[1]).start()
with test.step("Configure R1 and R2"):
r1cfg = {
"name": "R1",
"addr": {
"rid": "192.168.1.1",
"data": "192.168.10.1",
"fast": "192.168.100.1",
"slow": "192.168.200.1",
},
"link": {
"data": env.ltop.xlate("R1", "h1")[1],
"fast": env.ltop.xlate("R1", "fast")[1],
"slow": env.ltop.xlate("R1", "slow")[1],
}
}
r2cfg = {
"name": "R2",
"addr": {
"rid": "192.168.1.2",
"data": "192.168.20.1",
"fast": "192.168.100.2",
"slow": "192.168.200.2",
},
"link": {
"data": env.ltop.xlate("R2", "h2")[1],
"fast": env.ltop.xlate("R2", "fast")[1],
"slow": env.ltop.xlate("R2", "slow")[1],
}
}
parallel(config(R1, r1cfg), config(R2, r2cfg))
with test.step("Setup IP addresses and default routes on h1 and h2"):
_, h1 = env.ltop.xlate("PC", "h1")
_, h2 = env.ltop.xlate("PC", "h2")
h1net = infamy.IsolatedMacVlan(h1).start()
h1net.addip("192.168.10.2")
h1net.addroute("default", "192.168.10.1")
h2net = infamy.IsolatedMacVlan(h2).start()
h2net.addip("192.168.20.2")
h2net.addroute("default", "192.168.20.1")
with test.step("Wait for R1 and R2 to peer"):
print("Waiting for R1 and R2 to peer")
until(lambda: route.ipv4_route_exist(R1, "192.168.20.0/24", proto="ietf-ospf:ospfv2"), attempts=200)
until(lambda: route.ipv4_route_exist(R2, "192.168.10.0/24", proto="ietf-ospf:ospfv2"), attempts=200)
with test.step("Verify connectivity from PC:src to PC:dst via fast link"):
h1net.must_reach("192.168.20.2")
hops = [row[1] for row in h1net.traceroute("192.168.20.2")]
assert "192.168.100.2" in hops, f"Path to h2 ({repr(hops)}), does not use fast link"
with test.step("Disable forwarding between R1fast and R2fast to trigger fail-over"):
breaker.block()
print("Give BFD some time to detect the bad link, " +
"but not enough for the OSPF dead interval expire")
time.sleep(1)
with test.step("Verify connectivity from PC:src to PC:dst via slow link"):
h1net.must_reach("192.168.20.2")
hops = [row[1] for row in h1net.traceroute("192.168.20.2")]
assert "192.168.200.2" in hops, f"Path to h2 ({repr(hops)}), does not use slow link"
test.succeed()
@@ -0,0 +1,39 @@
graph "ospf-bfd" {
layout="neato";
overlap="false";
esep="+20";
size=10
node [shape=record, fontname="DejaVu Sans Mono, Book"];
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
R1 [
label=" { { R1 | <slow> slow } | { <mgmt> mgmt | <h1> h1 | <fast> fast } }",
pos="0,6!",
kind="infix",
];
R2 [
label="{ { <slow> slow | R2 } | { <fast> fast | <h2> h2 | <mgmt> mgmt } }",
pos="18,6!",
kind="infix",
];
PC [
label="{ { <R1mgmt> R1mgmt | <h1> h1 | <R1fast> R1fast | <R2fast> R2fast | <h2> h2 | <R2mgmt> R2mgmt } | PC }",
pos="9,0!",
kind="controller",
];
PC:R1mgmt -- R1:mgmt [kind=mgmt, color="lightgray"]
PC:R2mgmt -- R2:mgmt [kind=mgmt, color="lightgray"]
PC:h1 -- R1:h1
PC:h2 -- R2:h2
R1:fast -- PC:R1fast [color="lightgreen", taillabel="Cost: 100"]
R2:fast -- PC:R2fast [color="lightgreen"]
R1:slow -- R2:slow [color="crimson", taillabel="Cost: 200"]
}
@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: ospf&#45;bfd Pages: 1 -->
<svg width="432pt" height="156pt"
viewBox="0.00 0.00 432.03 155.51" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 151.51)">
<title>ospf&#45;bfd</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-151.51 428.03,-151.51 428.03,4 -4,4"/>
<!-- R1 -->
<g id="node1" class="node">
<title>R1</title>
<polygon fill="none" stroke="black" points="0,-97.51 0,-143.51 133,-143.51 133,-97.51 0,-97.51"/>
<text text-anchor="middle" x="29" y="-128.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">R1</text>
<polyline fill="none" stroke="black" points="58,-120.51 58,-143.51 "/>
<text text-anchor="middle" x="95.5" y="-128.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">slow</text>
<polyline fill="none" stroke="black" points="0,-120.51 133,-120.51 "/>
<text text-anchor="middle" x="25" y="-105.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="50,-97.51 50,-120.51 "/>
<text text-anchor="middle" x="66.5" y="-105.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">h1</text>
<polyline fill="none" stroke="black" points="83,-97.51 83,-120.51 "/>
<text text-anchor="middle" x="108" y="-105.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">fast</text>
</g>
<!-- R2 -->
<g id="node2" class="node">
<title>R2</title>
<polygon fill="none" stroke="black" points="291.03,-97.51 291.03,-143.51 424.03,-143.51 424.03,-97.51 291.03,-97.51"/>
<text text-anchor="middle" x="328.53" y="-128.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">slow</text>
<polyline fill="none" stroke="black" points="366.03,-120.51 366.03,-143.51 "/>
<text text-anchor="middle" x="395.03" y="-128.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">R2</text>
<polyline fill="none" stroke="black" points="291.03,-120.51 424.03,-120.51 "/>
<text text-anchor="middle" x="316.03" y="-105.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">fast</text>
<polyline fill="none" stroke="black" points="341.03,-97.51 341.03,-120.51 "/>
<text text-anchor="middle" x="357.53" y="-105.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">h2</text>
<polyline fill="none" stroke="black" points="374.03,-97.51 374.03,-120.51 "/>
<text text-anchor="middle" x="399.03" y="-105.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
</g>
<!-- R1&#45;&#45;R2 -->
<g id="edge7" class="edge">
<title>R1:slow&#45;&#45;R2:slow</title>
<path fill="none" stroke="crimson" stroke-width="2" d="M133.5,-132.51C133.5,-132.51 290.53,-132.51 290.53,-132.51"/>
<text text-anchor="middle" x="168" y="-136.31" font-family="DejaVu Serif, Book" font-size="14.00">Cost: 200</text>
</g>
<!-- PC -->
<g id="node3" class="node">
<title>PC</title>
<polygon fill="none" stroke="black" points="47.01,-0.5 47.01,-46.5 377.01,-46.5 377.01,-0.5 47.01,-0.5"/>
<text text-anchor="middle" x="80.01" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">R1mgmt</text>
<polyline fill="none" stroke="black" points="113.01,-23.5 113.01,-46.5 "/>
<text text-anchor="middle" x="129.51" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">h1</text>
<polyline fill="none" stroke="black" points="146.01,-23.5 146.01,-46.5 "/>
<text text-anchor="middle" x="179.01" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">R1fast</text>
<polyline fill="none" stroke="black" points="212.01,-23.5 212.01,-46.5 "/>
<text text-anchor="middle" x="245.01" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">R2fast</text>
<polyline fill="none" stroke="black" points="278.01,-23.5 278.01,-46.5 "/>
<text text-anchor="middle" x="294.51" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">h2</text>
<polyline fill="none" stroke="black" points="311.01,-23.5 311.01,-46.5 "/>
<text text-anchor="middle" x="344.01" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">R2mgmt</text>
<polyline fill="none" stroke="black" points="47.01,-23.5 377.01,-23.5 "/>
<text text-anchor="middle" x="212.01" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">PC</text>
</g>
<!-- R1&#45;&#45;PC -->
<g id="edge5" class="edge">
<title>R1:fast&#45;&#45;PC:R1fast</title>
<path fill="none" stroke="lightgreen" stroke-width="2" d="M133.5,-108.51C133.5,-108.51 179.01,-46.5 179.01,-46.5"/>
<text text-anchor="middle" x="168" y="-112.31" font-family="DejaVu Serif, Book" font-size="14.00">Cost: 100</text>
</g>
<!-- R2&#45;&#45;PC -->
<g id="edge6" class="edge">
<title>R2:fast&#45;&#45;PC:R2fast</title>
<path fill="none" stroke="lightgreen" stroke-width="2" d="M290.53,-108.51C290.53,-108.51 245.01,-46.5 245.01,-46.5"/>
</g>
<!-- PC&#45;&#45;R1 -->
<g id="edge1" class="edge">
<title>PC:R1mgmt&#45;&#45;R1:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M80.01,-46.5C80.01,-46.5 24.5,-97.51 24.5,-97.51"/>
</g>
<!-- PC&#45;&#45;R1 -->
<g id="edge3" class="edge">
<title>PC:h1&#45;&#45;R1:h1</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M129.01,-46.5C129.01,-46.5 66.5,-97.51 66.5,-97.51"/>
</g>
<!-- PC&#45;&#45;R2 -->
<g id="edge2" class="edge">
<title>PC:R2mgmt&#45;&#45;R2:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M344.01,-46.5C344.01,-46.5 399.53,-97.51 399.53,-97.51"/>
</g>
<!-- PC&#45;&#45;R2 -->
<g id="edge4" class="edge">
<title>PC:h2&#45;&#45;R2:h2</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M295.01,-46.5C295.01,-46.5 357.53,-97.51 357.53,-97.51"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

+164 -27
View File
@@ -1,4 +1,5 @@
import ctypes
import json
import multiprocessing
import os
import random
@@ -18,31 +19,58 @@ 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
"""
Instances = []
def Cleanup():
for ns in list(IsolatedMacVlans.Instances):
ns.stop()
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", "mode", "passthru"], 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
@@ -54,14 +82,43 @@ class IsolatedMacVlan:
self.__exit__(None, None, None)
raise e
self.Instances.append(self)
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):
for n in range(100):
promisc = False
for parent in self.ifmap.keys():
iplink = subprocess.run(f"ip -d -j link show dev {parent}".split(),
stdout=subprocess.PIPE, check=True)
link = json.loads(iplink.stdout)[0]
if link["promiscuity"]:
# Use promisc as a substitute for an indicator
# of whether the kernel has actually removed
# the passthru MACVLAN yet or not
promisc = True
break
if not promisc:
break
time.sleep(.1)
else:
raise TimeoutError("Lingering MACVLAN")
if self in self.Instances:
self.Instances.remove(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 +132,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 +203,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 +245,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 +263,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
@@ -253,7 +342,6 @@ class Pcap:
# terminating the capture.
time.sleep(sleep)
self.proc.send_signal(signal.SIGUSR2)
self.proc.terminate()
try:
_, stderr = self.proc.communicate(5)
@@ -272,3 +360,52 @@ class Pcap:
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
text=True, check=True)
return tcpdump.stdout
class TPMR(IsolatedMacVlans):
"""Two-Port MAC Relay
Creates a network namespace containing two controller interfaces
(`a` and `b`). By default, tc rules are setup to copy all frames
ingressing on `a` to egress on `b`, and vice versa.
These rules can be removed and reinserted dynamically using the
`block()` and `forward()` methods, respectively.
This is useful to verify the correctness of fail-over behavior in
various protocols. See ospf_bfd for a usage example.
"""
def __init__(self, a, b):
super().__init__(ifmap={ a: "a", b: "b" }, lo=False)
def start(self, forward=True):
ret = super().start()
for dev in ("a", "b"):
self.run(f"ip link set dev {dev} promisc on up".split())
self.run(f"tc qdisc add dev {dev} clsact".split())
if forward:
self.forward()
return ret
def _clear_ingress(self, iface):
return self.run(f"tc filter del dev {iface} ingress".split())
def _add_redir(self, frm, to):
cmd = \
"tc filter add dev".split() \
+ [frm] \
+ "ingress matchall action mirred egress redirect dev".split() \
+ [to]
return self.run(cmd)
def forward(self):
for iface in ("a", "b"):
self._clear_ingress(iface)
self._add_redir(iface, "a" if iface == "b" else "b")
def block(self):
for iface in ("a", "b"):
self._clear_ingress(iface)
+4
View File
@@ -4,6 +4,8 @@ import subprocess
import sys
import traceback
import infamy.netns
class Test:
def __init__(self, output=sys.stdout):
self.out = output
@@ -24,6 +26,8 @@ class Test:
self.out.write(f"# Exiting ({now})\n")
self.out.flush()
infamy.netns.IsolatedMacVlans.Cleanup()
if not e:
self._not_ok("Missing explicit test result\n")
else: