diff --git a/test/case/ietf_routing/Readme.adoc b/test/case/ietf_routing/Readme.adoc index 539fcdaf..99234e34 100644 --- a/test/case/ietf_routing/Readme.adoc +++ b/test/case/ietf_routing/Readme.adoc @@ -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[] diff --git a/test/case/ietf_routing/ietf_routing.yaml b/test/case/ietf_routing/ietf_routing.yaml index 633f3a11..45228205 100644 --- a/test/case/ietf_routing/ietf_routing.yaml +++ b/test/case/ietf_routing/ietf_routing.yaml @@ -10,3 +10,6 @@ - name: ospf_multiarea case: ospf_multiarea/test.py + +- name: ospf_bfd + case: ospf_bfd/test.py diff --git a/test/case/ietf_routing/ospf_bfd/Readme.adoc b/test/case/ietf_routing/ospf_bfd/Readme.adoc new file mode 100644 index 00000000..ae2b34d5 --- /dev/null +++ b/test/case/ietf_routing/ospf_bfd/Readme.adoc @@ -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 + + +<<< + diff --git a/test/case/ietf_routing/ospf_bfd/test.py b/test/case/ietf_routing/ospf_bfd/test.py new file mode 100755 index 00000000..31740f4c --- /dev/null +++ b/test/case/ietf_routing/ospf_bfd/test.py @@ -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() diff --git a/test/case/ietf_routing/ospf_bfd/topology.dot b/test/case/ietf_routing/ospf_bfd/topology.dot new file mode 100644 index 00000000..f6f59985 --- /dev/null +++ b/test/case/ietf_routing/ospf_bfd/topology.dot @@ -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 } | { mgmt |

h1 | fast } }", + pos="0,6!", + + kind="infix", + ]; + R2 [ + label="{ { slow | R2 } | { fast |

h2 | mgmt } }", + pos="18,6!", + + kind="infix", + ]; + + PC [ + label="{ { R1mgmt |

h1 | R1fast | R2fast |

h2 | 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"] +} diff --git a/test/case/ietf_routing/ospf_bfd/topology.svg b/test/case/ietf_routing/ospf_bfd/topology.svg new file mode 100644 index 00000000..ca1ec0a6 --- /dev/null +++ b/test/case/ietf_routing/ospf_bfd/topology.svg @@ -0,0 +1,95 @@ + + + + + + +ospf-bfd + + + +R1 + +R1 + +slow + +mgmt + +h1 + +fast + + + +R2 + +slow + +R2 + +fast + +h2 + +mgmt + + + +R1:slow--R2:slow + +Cost: 200 + + + +PC + +R1mgmt + +h1 + +R1fast + +R2fast + +h2 + +R2mgmt + +PC + + + +R1:fast--PC:R1fast + +Cost: 100 + + + +R2:fast--PC:R2fast + + + + +PC:R1mgmt--R1:mgmt + + + + +PC:h1--R1:h1 + + + + +PC:R2mgmt--R2:mgmt + + + + +PC:h2--R2:h2 + + + +