#!/usr/bin/env python3 # # # 10.0.0.1/32 (lo) # | # +------+---------+ .1 10.0.12.0/30 .2+--------------------+ # | R1 +-----------------------------+ R2 | # | | AREA0 | +-10.0.0.2/32 (lo) # +-------+--------+--.1 .1 -+--------+-----------+ # .2 | \--- ---/ |.1 # | \--- ----/ | # 10.0.41.0 /30 | AREA2 \--- ---/ | 10.0.23.0/30 # | 10.0.24.0/30 ---/\--- 10.0.13.0/30 | # | ----/ \--- AREA1 | # .1 | .2 ---/ \--- .2 |.2 # +-------+--------+-/ \-+--------+----------+ # | R4 .2 | | R3 | # | +---------+ .1| +-10.0.0.3/32 (lo) # +------+---------+.1 | 192.168.4.0/24 +-------------------+ # | |.2 # 10.0.0.4/32 (lo) +-------+ # | | # | PC | # | | # +-------+ # # """OSPF with multiple areas This test evaluates various OSPF features across three areas (one NSSA area, with no summary) to ensure that route distribution is deterministic (based on cost). It also tests link failures using BFD, though BFD is not yet implemented in test framework (Infamy). This test also verifies broadcast and point-to-point interface types on /30 network, and explicit router-id. .... +-------------+ +---------------+ +-------------+ +---------------+ | R1 | | R2 | | R3 | | R4 | | 10.0.0.1/32 | | 10.0.0.2/32 | | 10.0.0.3/32 | | 10.0.0.4/32 | | (lo) | | 11.0.9.1/24 | | (lo) | | (lo) | +-------------+ | 11.0.10.1/24 | +-------------+ +---------------+ | 11.0.11.1/24 | | 11.0.12.1/24 | | 11.0.13.1/24 | | 11.0.14.1/24 | | 11.0.15.1/24 | | (lo) | +---------------+ .... """ import infamy import infamy.route as route from infamy.util import until, parallel def config_target1(target, ring1, ring2, cross): target.put_config_dicts({ "ietf-interfaces": { "interfaces": { "interface": [ { "name": ring1, "enabled": True, "ipv4": { "forwarding": True, "address": [{ "ip": "10.0.12.1", "prefix-length": 30 }] } }, { "name": ring2, "enabled": True, "ipv4": { "forwarding": True, "address": [{ "ip": "10.0.41.2", "prefix-length": 30 }] } }, { "name": cross, "enabled": True, "ipv4": { "forwarding": True, "address": [{ "ip": "10.0.13.1", "prefix-length": 30 }] } }, { "name": "lo", "enabled": True, "ipv4": { "address": [ { "ip": "11.0.8.1", "prefix-length": 24 }, { "ip": "10.0.0.1", "prefix-length": 32 } ] } } ] } }, "ietf-routing": { "routing": { "control-plane-protocols": { "control-plane-protocol": [ { "type": "infix-routing:ospfv2", "name": "default", "ospf": { "explicit-router-id": "10.0.0.1", "areas": { "area": [ { "area-id": "0.0.0.0", "interfaces": { "interface": [ { "bfd": { "enabled": True }, "name": ring1, "hello-interval": 1, "enabled": True } ] } }, { "area-id": "0.0.0.1", "area-type": "nssa-area", "summary": False, "interfaces": { "interface": [ { "bfd": { "enabled": True }, "name": cross, "hello-interval": 1, "enabled": True, "cost": 2000 }, { "name": "lo", "enabled": True } ] } }, { "area-id": "0.0.0.2", "interfaces": { "interface": [ { "bfd": { "enabled": True }, "name": ring2, "hello-interval": 1, "enabled": True, "interface-type": "point-to-point" } ] } } ] } } } ] } } }, "ietf-system": { "system": { "hostname": "R1" } } }) def config_target2(target, ring1, ring2, cross): target.put_config_dicts({ "ietf-interfaces": { "interfaces": { "interface": [ { "name": ring1, "enabled": True, "ipv4": { "forwarding": True, "address": [{ "ip": "10.0.23.1", "prefix-length": 30 }] } }, { "name": ring2, "enabled": True, "ipv4": { "forwarding": True, "address": [{ "ip": "10.0.12.2", "prefix-length": 30 }] } }, { "name": cross, "enabled": True, "ipv4": { "forwarding": True, "address": [{ "ip": "10.0.24.1", "prefix-length": 30 }] } }, { "name": "lo", "enabled": True, "ipv4": { "address": [ { "ip": "10.0.0.2", "prefix-length": 32 }, { "ip": "11.0.9.1", "prefix-length": 24 }, { "ip": "11.0.10.1", "prefix-length": 24 }, { "ip": "11.0.11.1", "prefix-length": 24 }, { "ip": "11.0.12.1", "prefix-length": 24 }, { "ip": "11.0.13.1", "prefix-length": 24 }, { "ip": "11.0.14.1", "prefix-length": 24 }, { "ip": "11.0.15.1", "prefix-length": 24 } ] } } ] } }, "ietf-system": { "system": { "hostname": "R2" } }, "ietf-routing": { "routing": { "control-plane-protocols": { "control-plane-protocol": [ { "type": "infix-routing:ospfv2", "name": "default", "ospf": { "explicit-router-id": "1.1.1.1", "areas": { "area": [ { "area-id": "0.0.0.0", "interfaces": { "interface": [ { "bfd": { "enabled": True }, "name": ring2, "hello-interval": 1, "enabled": True }, { "name": "lo", "enabled": True } ] } }, { "area-id": "0.0.0.1", "area-type": "nssa-area", "summary": False, "interfaces": { "interface": [ { "bfd": { "enabled": True }, "name": ring1, "hello-interval": 1, "enabled": True } ] } }, { "area-id": "0.0.0.2", "interfaces": { "interface": [ { "bfd": { "enabled": True }, "name": cross, "hello-interval": 1, "cost": 2000, "enabled": True } ] } } ] } } } ] } } } }) def config_target3(target, ring2, cross, link): target.put_config_dicts({ "ietf-interfaces": { "interfaces": { "interface": [ { "name": ring2, "enabled": True, "ipv4": { "forwarding": True, "address": [{ "ip": "10.0.23.2", "prefix-length": 30 }] } }, { "name": link, "enabled": True, "ipv4": { "forwarding": True, "address": [{ "ip": "192.168.3.1", "prefix-length": 24 }] } }, { "name": cross, "enabled": True, "ipv4": { "forwarding": True, "address": [{ "ip": "10.0.13.2", "prefix-length": 30 }] } }, { "name": "lo", "enabled": True, "ipv4": { "address": [{ "ip": "10.0.0.3", "prefix-length": 32 }] } } ] } }, "ietf-system": { "system": { "hostname": "R3" } }, "ietf-routing": { "routing": { "control-plane-protocols": { "control-plane-protocol": [{ "type": "infix-routing:ospfv2", "name": "default", "ospf": { "explicit-router-id": "10.0.0.3", "areas": { "area": [{ "area-id": "0.0.0.1", "area-type": "nssa-area", "summary": False, "interfaces": { "interface": [{ "bfd": { "enabled": True }, "name": cross, "hello-interval": 1, "enabled": True, "cost": 2000 }, { "bfd": { "enabled": True }, "name": ring2, "hello-interval": 1, "enabled": True }, { "name": link, "enabled": True, "passive": True }, { "name": "lo", "enabled": True }] } }] } } }] } } } }) def config_target4(target, ring1, cross, link): target.put_config_dicts({ "ietf-interfaces": { "interfaces": { "interface": [ { "name": ring1, "enabled": True, "ipv4": { "forwarding": True, "address": [{ "ip": "10.0.41.1", "prefix-length": 30 }]} }, { "name": cross, "enabled": True, "ipv4": { "forwarding": True, "address": [{ "ip": "10.0.24.2", "prefix-length": 30 }]} }, { "name": link, "enabled": True, "ipv4": { "forwarding": True, "address": [{ "ip": "192.168.4.1", "prefix-length": 24 }] } }, { "name": "lo", "enabled": True, "ipv4": { "address": [{ "ip": "10.0.0.4", "prefix-length": 32 }] } } ] } }, "ietf-system": { "system": { "hostname": "R4" } }, "ietf-routing": { "routing": { "control-plane-protocols": { "control-plane-protocol": [ { "type": "infix-routing:ospfv2", "name": "default", "ospf": { "explicit-router-id": "10.0.0.4", "redistribute": { "redistribute": [{ "protocol": "connected" }] }, "areas": { "area": [{ "area-id": "0.0.0.2", "interfaces": { "interface": [{ "bfd": { "enabled": True }, "name": ring1, "hello-interval": 1, "enabled": True, "interface-type": "point-to-point" }, { "bfd": { "enabled": True }, "name": cross, "hello-interval": 1, "enabled": True, "cost": 5000 }, { "name": "lo", "enabled": True }] } }] } } } ] } } } }) def disable_link(target, link): target.put_config_dict("ietf-interfaces", { "interfaces": { "interface": [ { "name": link, "enabled": False }] } }) 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") R3 = env.attach("R3", "mgmt") R4 = env.attach("R4", "mgmt") _, R1ring1 = env.ltop.xlate("R1", "ring1") _, R1ring2 = env.ltop.xlate("R1", "ring2") _, R2ring1 = env.ltop.xlate("R2", "ring1") _, R2ring2 = env.ltop.xlate("R2", "ring2") _, R3ring2 = env.ltop.xlate("R3", "ring2") _, R4ring1 = env.ltop.xlate("R4", "ring1") _, R3data = env.ltop.xlate("R3", "data") _, R4data = env.ltop.xlate("R4", "data") _, R1cross = env.ltop.xlate("R1", "cross") _, R2cross = env.ltop.xlate("R2", "cross") _, R3cross = env.ltop.xlate("R3", "cross") _, R4cross = env.ltop.xlate("R4", "cross") with test.step("Configure targets"): parallel(config_target1(R1, R1ring1, R1ring2, R1cross), config_target2(R2, R2ring1, R2ring2, R2cross), config_target3(R3, R3ring2, R3cross, R3data), config_target4(R4, R4ring1, R4cross, R4data)) with test.step("Wait for all neighbors to peer"): print("Waiting for neighbors to peer") until(lambda: route.ospf_get_neighbor(R1, "0.0.0.0", R1ring1, "1.1.1.1"), attempts=200) until(lambda: route.ospf_get_neighbor(R1, "0.0.0.1", R1cross, "10.0.0.3"), attempts=200) until(lambda: route.ospf_get_neighbor(R2, "0.0.0.1", R2ring1, "10.0.0.3"), attempts=200) until(lambda: route.ospf_get_neighbor(R2, "0.0.0.0", R2ring2, "10.0.0.1"), attempts=200) until(lambda: route.ospf_get_neighbor(R2, "0.0.0.2", R2cross, "10.0.0.4"), attempts=200) with test.step("Wait for routes from OSPF on all routers"): print("Waiting for routes from OSPF") until(lambda: route.ipv4_route_exist(R1, "10.0.0.2/32", nexthop="10.0.12.2", proto="ietf-ospf:ospfv2"), attempts=200) until(lambda: route.ipv4_route_exist(R1, "10.0.0.3/32", nexthop="10.0.13.2", proto="ietf-ospf:ospfv2"), attempts=200) until(lambda: route.ipv4_route_exist(R1, "10.0.0.4/32", nexthop="10.0.41.1", proto="ietf-ospf:ospfv2"), attempts=200) until(lambda: route.ipv4_route_exist(R1, "192.168.4.0/24", nexthop="10.0.41.1", proto="ietf-ospf:ospfv2"), attempts=200) until(lambda: route.ipv4_route_exist(R1, "10.0.24.0/30", nexthop="10.0.41.1", proto="ietf-ospf:ospfv2"), attempts=200) until(lambda: route.ipv4_route_exist(R2, "10.0.0.1/32", nexthop="10.0.23.2", proto="ietf-ospf:ospfv2"), attempts=200) until(lambda: route.ipv4_route_exist(R2, "10.0.0.3/32", nexthop="10.0.23.2", proto="ietf-ospf:ospfv2"), attempts=200) until(lambda: route.ipv4_route_exist(R2, "10.0.0.4/32", nexthop="10.0.24.2", proto="ietf-ospf:ospfv2"), attempts=200) until(lambda: route.ipv4_route_exist(R3, "0.0.0.0/0", nexthop="10.0.23.1", proto="ietf-ospf:ospfv2"), attempts=200) until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.41.2", proto="ietf-ospf:ospfv2"), attempts=200) until(lambda: route.ipv4_route_exist(R2, "10.0.13.0/30", nexthop="10.0.23.2", proto="ietf-ospf:ospfv2"), attempts=200) with test.step("Verify Area 0.0.0.1 on R3 is NSSA area"): assert(route.ospf_is_area_nssa(R3, "0.0.0.1")) with test.step("Verify R1:ring2 is of type point-to-point"): assert(route.ospf_get_interface_type(R1, "0.0.0.2", R1ring2) == "point-to-point") with test.step("Verify R4:ring1 is of type point-to-point"): assert(route.ospf_get_interface_type(R4, "0.0.0.2", R4ring1) == "point-to-point") with test.step("Verify on R3, there are no routes beyond 10.0.23.1, just a default route"): # Should be only default route out of the area. parallel(until(lambda: route.ipv4_route_exist(R3, "0.0.0.0/0"), attempts=200), until(lambda: route.ipv4_route_exist(R3, "10.0.12.0/30") is False, attempts=5), until(lambda: route.ipv4_route_exist(R3, "10.0.12.0/30") is False, attempts=5), until(lambda: route.ipv4_route_exist(R3, "11.0.8.0/24") is False, attempts=5), until(lambda: route.ipv4_route_exist(R3, "11.0.9.0/24") is False, attempts=5), until(lambda: route.ipv4_route_exist(R3, "11.0.10.0/24") is False, attempts=5), until(lambda: route.ipv4_route_exist(R3, "11.0.11.0/24") is False, attempts=5), until(lambda: route.ipv4_route_exist(R3, "11.0.12.0/24") is False, attempts=5), until(lambda: route.ipv4_route_exist(R3, "11.0.13.0/24") is False, attempts=5), until(lambda: route.ipv4_route_exist(R3, "11.0.14.0/24") is False, attempts=5), until(lambda: route.ipv4_route_exist(R3, "11.0.15.0/24") is False, attempts=5)) _, hport0 = env.ltop.xlate("PC", "data3") with infamy.IsolatedMacVlan(hport0) as ns0: with test.step("Testing connectivity through NSSA area, from PC:data3 to 11.0.8.1"): ns0.addip("192.168.3.2") ns0.addroute("0.0.0.0/0", "192.168.3.1") ns0.must_reach("11.0.8.1") _, hport0 = env.ltop.xlate("PC", "data4") with infamy.IsolatedMacVlan(hport0) as ns0: ns0.addip("192.168.4.2") ns0.addroute("0.0.0.0/0", "192.168.4.1") with test.step("Verify that the route to 10.0.0.3 from PC:data4, go through 10.0.41.2"): trace = ns0.traceroute("10.0.0.3") assert len(trace) == 3 assert trace[1][1] == "10.0.41.2" assert trace[2][1] == "10.0.0.3" with test.step("Break link R1:ring2 --- R4:ring1"): # Here we should test with link breakers, to test BFD # recouppling, for now disable the link disable_link(R1, R1ring2) with test.step("Verify that the route to 10.0.0.3 from PC:data4, go through 10.0.24.1"): until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.24.1", proto="ietf-ospf:ospfv2"), attempts=100) until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.41.2") is False, attempts=10) trace = ns0.traceroute("10.0.0.3") assert len(trace) == 3 assert trace[1][1] == "10.0.24.1" assert trace[2][1] == "10.0.0.3" ns0.must_reach("10.0.0.3") test.succeed()