diff --git a/test/case/ietf_routing/Readme.adoc b/test/case/ietf_routing/Readme.adoc index 0a54ca87..d0287187 100644 --- a/test/case/ietf_routing/Readme.adoc +++ b/test/case/ietf_routing/Readme.adoc @@ -14,6 +14,8 @@ include::ospf_multiarea/Readme.adoc[] include::ospf_bfd/Readme.adoc[] +include::ospf_default_route_advertise/Readme.adoc[] + include::route_pref_ospf/Readme.adoc[] include::route_pref_dhcp/Readme.adoc[] diff --git a/test/case/ietf_routing/ietf_routing.yaml b/test/case/ietf_routing/ietf_routing.yaml index 4c70c1e7..7a2fc28b 100644 --- a/test/case/ietf_routing/ietf_routing.yaml +++ b/test/case/ietf_routing/ietf_routing.yaml @@ -13,7 +13,7 @@ - name: ospf_bfd case: ospf_bfd/test.py - + - name: route_pref_ospf case: route_pref_ospf/test.py @@ -22,3 +22,6 @@ - name: route_pref_255 case: route_pref_255/test.py + +- name: ospf_default_route_advertise + case: ospf_default_route_advertise/test.py diff --git a/test/case/ietf_routing/ospf_basic/test.py b/test/case/ietf_routing/ospf_basic/test.py index f3a9a1f0..b99d3ec1 100755 --- a/test/case/ietf_routing/ospf_basic/test.py +++ b/test/case/ietf_routing/ospf_basic/test.py @@ -56,7 +56,22 @@ def config_target1(target, data, link): target.put_config_dict("ietf-routing", { "routing": { "control-plane-protocols": { - "control-plane-protocol": [{ + "control-plane-protocol": [ + { + "type": "infix-routing:static", + "name": "default", + "static-routes": { + "ipv4": { + "route": [{ + "destination-prefix": "192.168.33.1/32", + "next-hop": { + "special-next-hop": "blackhole" + } + }] + } + } + }, + { "type": "infix-routing:ospfv2", "name": "default", "ospf": { @@ -74,6 +89,7 @@ def config_target1(target, data, link): "interfaces": { "interface": [{ + "enabled": True, "name": link, "hello-interval": 1, "dead-interval": 3 @@ -132,9 +148,6 @@ def config_target2(target, link): "ospf": { "redistribute": { "redistribute": [{ - "protocol": "static" - }, - { "protocol": "connected" }] }, @@ -143,6 +156,7 @@ def config_target2(target, link): "area-id": "0.0.0.0", "interfaces":{ "interface": [{ + "enabled": True, "name": link, "hello-interval": 1, "dead-interval": 3 @@ -174,6 +188,7 @@ with infamy.Test() as test: print("Waiting for OSPF routes..") until(lambda: route.ipv4_route_exist(R1, "192.168.200.1/32", proto="ietf-ospf:ospfv2"), attempts=200) until(lambda: route.ipv4_route_exist(R2, "192.168.100.1/32", proto="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R2, "192.168.33.1/32", proto="ietf-ospf:ospfv2"), attempts=200) with test.step("Test connectivity from PC:data to 192.168.200.1"): _, hport0 = env.ltop.xlate("PC", "data") diff --git a/test/case/ietf_routing/ospf_default_route_advertise/Readme.adoc b/test/case/ietf_routing/ospf_default_route_advertise/Readme.adoc new file mode 100644 index 00000000..006f23e1 --- /dev/null +++ b/test/case/ietf_routing/ospf_default_route_advertise/Readme.adoc @@ -0,0 +1,57 @@ +=== OSPF Default route advertise +==== Description +Verify _default-route-advertising_ in OSPF, sometimes called 'redistribute origin'. Verify both +always (regardless if a local default route exist or not) and only redistribute +when local default route exist. + +The test is performed by setting a default route on R1 to 192.168.10.2, and setting +_default-route-advertising_ in OSPF, this will result that R2 will see a default route. + +When set interface R1:data down, OSPF will no longer redistribute default route to R2, +unless _always_ is set for _default-route-advertising_. +.... + +-------------------+ Area 0 +------------------+ + | R1 |.1 192.168.50.0/24 .2| R2 | + | 192.169.100.1/32 +------------------------+ 192.168.200.1/32| + | 10.10.10.10/32 |R1:link R2:link | | + +--------------+----+ +---+--------------+ + R1:data |.1 R2:data |.1 + | | + | 192.168.10.0/24 | 192.168.20.0/24 + | | + host:data1 |.2 host:data2 |.2 + +-----+---------------------------------+-------+ + | | + | host | + | | + +-----------------------------------------------+ +.... + +==== Topology +ifdef::topdoc[] +image::../../test/case/ietf_routing/ospf_default_route_advertise/topology.svg[OSPF Default route advertise topology] +endif::topdoc[] +ifndef::topdoc[] +ifdef::testgroup[] +image::ospf_default_route_advertise/topology.svg[OSPF Default route advertise topology] +endif::testgroup[] +ifndef::testgroup[] +image::topology.svg[OSPF Default route advertise topology] +endif::testgroup[] +endif::topdoc[] +==== Test sequence +. Set up topology and attach to target DUTs +. Configure targets +. Verify R2 has a default route and 192.168.100.1/32 from OSPF +. Verify connectivity from PC:data2 to 10.10.10.10 +. Disable link PC:data1 <--> R1:data (take default gateway down) +. Verify R2 does not have a default route but a 192.168.100.1/32 from OSPF +. Verify no connectivity from PC:data2 to 10.10.10.10 +. Enable redistribute default route 'always' on R1 +. Wait for all neighbors to peer +. Verify R2 has a default route and 192.168.100.1/32 from OSPF +. Verify connectivity from PC:data2 to 10.10.10.10 + + +<<< + diff --git a/test/case/ietf_routing/ospf_default_route_advertise/test.py b/test/case/ietf_routing/ospf_default_route_advertise/test.py new file mode 100755 index 00000000..8cb055f9 --- /dev/null +++ b/test/case/ietf_routing/ospf_default_route_advertise/test.py @@ -0,0 +1,323 @@ +#!/usr/bin/env python3 +""" +OSPF Default route advertise + +Verify _default-route-advertising_ in OSPF, sometimes called 'redistribute origin'. Verify both +always (regardless if a local default route exist or not) and only redistribute +when local default route exist. + +The test is performed by setting a default route on R1 to 192.168.10.2, and setting +_default-route-advertising_ in OSPF, this will result that R2 will see a default route. + +When set interface R1:data down, OSPF will no longer redistribute default route to R2, +unless _always_ is set for _default-route-advertising_. +.... + +-------------------+ Area 0 +------------------+ + | R1 |.1 192.168.50.0/24 .2| R2 | + | 192.169.100.1/32 +------------------------+ 192.168.200.1/32| + | 10.10.10.10/32 |R1:link R2:link | | + +--------------+----+ +---+--------------+ + R1:data |.1 R2:data |.1 + | | + | 192.168.10.0/24 | 192.168.20.0/24 + | | + host:data1 |.2 host:data2 |.2 + +-----+---------------------------------+-------+ + | | + | host | + | | + +-----------------------------------------------+ +.... +""" + +import infamy +import infamy.route as route +from infamy.util import until, parallel + + +def config_target1(target, data, link): + target.put_config_dicts({ + "ietf-interfaces": { + "interfaces": { + "interface": [ + { + "name": "dummy0", + "enabled": True, + "type": "infix-if-type:dummy", + "ipv4": { + "address": [{ + "ip": "10.10.10.10", + "prefix-length": 32 + }] + } + }, + { + "name": data, + "enabled": True, + "ipv4": { + "forwarding": True, + "address": [{ + "ip": "192.168.10.1", + "prefix-length": 24 + }]} + }, + { + "name": link, + "enabled": True, + "ipv4": { + "forwarding": True, + "address": [{ + "ip": "192.168.50.1", + "prefix-length": 24 + }] + } + }, + { + "name": "lo", + "enabled": True, + "ipv4": { + "address": [{ + "ip": "192.168.100.1", + "prefix-length": 32 + }] + } + } + ] + } + }, + "ietf-routing": { + "routing": { + "control-plane-protocols": { + "control-plane-protocol": [ + { + "type": "infix-routing:static", + "name": "default", + "static-routes": { + "ipv4": { + "route": [ + { + "destination-prefix": "0.0.0.0/0", + "next-hop": { + "next-hop-address": "192.168.10.2" + } + }] + } + } + }, + { + "type": "infix-routing:ospfv2", + "name": "default", + "ospf": { + "explicit-router-id": "1.1.1.1", + "default-route-advertise": { + "enabled": True + }, + "areas": { + "area": [{ + "area-id": "0.0.0.0", + "interfaces": + { + "interface": [{ + "name": link, + "enabled": True, + "hello-interval": 1, + "dead-interval": 3 + }, + { + "name": "lo", + "enabled": True + }] + }, + }] + } + } + } + ] + } + } + } +}) + + +def config_target2(target, data, link): + target.put_config_dicts({ + "ietf-interfaces": { + "interfaces": { + "interface": [ + { + "name": link, + "enabled": True, + "ipv4": { + "forwarding": True, + "address": [{ + "ip": "192.168.50.2", + "prefix-length": 24 + }] + } + }, + { + "name": data, + "enabled": True, + "ipv4": { + "forwarding": True, + "address": [{ + "ip": "192.168.20.1", + "prefix-length": 24 + }] + } + }, + { + "name": "lo", + "enabled": True, + "forwarding": True, + "ipv4": { + "address": [{ + "ip": "192.168.200.1", + "prefix-length": 32 + }] + } + } + ] + } + }, + "ietf-routing": { + "routing": { + "control-plane-protocols": { + "control-plane-protocol": [ + { + "type": "infix-routing:ospfv2", + "name": "default", + "ospf": { + "explicit-router-id": "2.2.2.2", + "areas": { + "area": [{ + "area-id": "0.0.0.0", + "interfaces":{ + "interface": [{ + "enabled": True, + "name": link, + "hello-interval": 1, + "dead-interval": 3 + }, + { + "name": data, + "passive": True, + "enabled": True + }, + { + "enabled": True, + "name": "lo" + }] + } + }] + } + } + } + ] + } + } + } +}) + + +def disable_interface(target, iface): + target.put_config_dicts({ + "ietf-interfaces": { + "interfaces": { + "interface": [ + { + "name": iface, + "enabled": False, + } + ] + } + } + }) + + +def set_redistribute_default_always(target): + target.put_config_dicts({ + "ietf-routing": { + "routing": { + "control-plane-protocols": { + "control-plane-protocol": [ + { + "type": "infix-routing:ospfv2", + "name": "default", + "ospf": { + "default-route-advertise": { + "enabled": True, + "always": 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("Configure targets"): + _, R1data = env.ltop.xlate("R1", "data") + _, R2data = env.ltop.xlate("R2", "data") + _, R2link = env.ltop.xlate("R2", "link") + _, R1link= env.ltop.xlate("R1", "link") + + parallel(config_target1(R1, R1data, R1link), + config_target2(R2, R2data, R2link)) + with test.step("Verify R2 has a default route and 192.168.100.1/32 from OSPF"): + print("Waiting for OSPF routes...") + until(lambda: route.ipv4_route_exist(R2, "0.0.0.0/0", proto="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R2, "192.168.100.1/32", proto="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R1, "192.168.200.1/32", proto="ietf-ospf:ospfv2"), attempts=200) + + with test.step("Verify connectivity from PC:data2 to 10.10.10.10"): + _, hport0 = env.ltop.xlate("PC", "data2") + with infamy.IsolatedMacVlan(hport0) as ns0: + ns0.addip("192.168.20.2") + ns0.addroute("0.0.0.0/0", "192.168.20.1") + #breakpoint() + ns0.must_reach("10.10.10.10") + + with test.step("Disable link PC:data1 <--> R1:data (take default gateway down)"): + disable_interface(R1, R1data) + + with test.step("Verify R2 does not have a default route but a 192.168.100.1/32 from OSPF"): + until(lambda: route.ipv4_route_exist(R2, "192.168.100.1/32", proto="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R1, "192.168.200.1/32", proto="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R2, "0.0.0.0/0", proto="ietf-ospf:ospfv2") == False, attempts=200) + + with test.step("Verify no connectivity from PC:data2 to 10.10.10.10"): + _, hport0 = env.ltop.xlate("PC", "data2") + with infamy.IsolatedMacVlan(hport0) as ns0: + ns0.addip("192.168.20.2") + ns0.addroute("0.0.0.0/0", "192.168.20.1") + ns0.must_not_reach("10.10.10.10") + + with test.step("Enable redistribute default route 'always' on R1"): + set_redistribute_default_always(R1) + + with test.step("Wait for all neighbors to peer"): + until(lambda: route.ospf_get_neighbor(R1, "0.0.0.0", R1link, "2.2.2.2"), attempts=200) + until(lambda: route.ospf_get_neighbor(R2, "0.0.0.0", R2link, "1.1.1.1"), attempts=200) + + with test.step("Verify R2 has a default route and 192.168.100.1/32 from OSPF"): + print("Waiting for OSPF routes...") + until(lambda: route.ipv4_route_exist(R2, "192.168.100.1/32", proto="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R1, "192.168.200.1/32", proto="ietf-ospf:ospfv2"), attempts=200) + until(lambda: route.ipv4_route_exist(R2, "0.0.0.0/0", proto="ietf-ospf:ospfv2"), attempts=200) + + with test.step("Verify connectivity from PC:data2 to 10.10.10.10"): + _, hport0 = env.ltop.xlate("PC", "data2") + with infamy.IsolatedMacVlan(hport0) as ns0: + ns0.addip("192.168.20.2") + ns0.addroute("0.0.0.0/0", "192.168.20.1") + ns0.must_reach("10.10.10.10") + + test.succeed() diff --git a/test/case/ietf_routing/ospf_default_route_advertise/topology.dot b/test/case/ietf_routing/ospf_default_route_advertise/topology.dot new file mode 100644 index 00000000..4d044aa9 --- /dev/null +++ b/test/case/ietf_routing/ospf_default_route_advertise/topology.dot @@ -0,0 +1,33 @@ +graph "2x2" { + 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"]; + + PC [ + label="PC | { mgmt1 | data1 | data2 | mgmt2 }", + pos="20,80!", + kind="controller", + ]; + + R1 [ + label="{ mgmt | data | link } | R1 \n 192.168.100.1/32 \n 10.10.10.10/32\n(lo)", + pos="250,85!", + + kind="infix", + ]; + R2 [ + label="{ link | data | mgmt } | R2 \n 192.168.200.1/32 \n(lo)", + pos="250,30!", + + kind="infix", + ]; + PC:mgmt1 -- R1:mgmt [kind=mgmt, color="lightgray"] + PC:mgmt2 -- R2:mgmt [kind=mgmt, color="lightgray"] + PC:data1 -- R1:data [color="black", headlabel="192.168.10.1/24", taillabel="192.168.10.2/24", fontcolor="black"] + PC:data2 -- R2:data [color="black", headlabel="192.168.20.1/24", taillabel="192.168.20.2/24", fontcolor="black"] + R1:link -- R2:link [headlabel="192.168.50.2/24", taillabel="192.168.50.1/24", labeldistance=1, fontcolor="black", color="black"] +} diff --git a/test/case/ietf_routing/ospf_default_route_advertise/topology.svg b/test/case/ietf_routing/ospf_default_route_advertise/topology.svg new file mode 100644 index 00000000..1cd043b5 --- /dev/null +++ b/test/case/ietf_routing/ospf_default_route_advertise/topology.svg @@ -0,0 +1,86 @@ + + + + + + +2x2 + + + +PC + +PC + +mgmt1 + +data1 + +data2 + +mgmt2 + + + +R1 + +mgmt + +data + +link + +R1 + 192.168.100.1/32 + 10.10.10.10/32 +(lo) + + + +PC:mgmt1--R1:mgmt + + + + +PC:data1--R1:data + +192.168.10.1/24 +192.168.10.2/24 + + + +R2 + +link + +data + +mgmt + +R2 + 192.168.200.1/32 +(lo) + + + +PC:mgmt2--R2:mgmt + + + + +PC:data2--R2:data + +192.168.20.1/24 +192.168.20.2/24 + + + +R1:link--R2:link + +192.168.50.2/24 +192.168.50.1/24 + + +