summaryrefslogtreecommitdiffstats
path: root/tests/topotests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-08-05 09:55:47 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-08-05 09:55:47 +0000
commit514c1c8597cf7f24871e15a6eaeaedd2b5aedf4d (patch)
treebc56d8649885e6012dc37130c9f5b2a7129ed79f /tests/topotests
parentReleasing progress-linux version 10.0-2~progress7.99u1. (diff)
downloadfrr-514c1c8597cf7f24871e15a6eaeaedd2b5aedf4d.tar.xz
frr-514c1c8597cf7f24871e15a6eaeaedd2b5aedf4d.zip
Merging upstream version 10.0.1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--tests/topotests/bgp_asdot_regex/r1/show_bgp_ipv4.json1
-rw-r--r--tests/topotests/bgp_asdot_regex/r2/show_bgp_ipv4.json1
-rw-r--r--tests/topotests/bgp_path_attribute_discard/peer1/exabgp.cfg29
-rw-r--r--tests/topotests/bgp_path_attribute_discard/r1/bgpd.conf6
-rw-r--r--tests/topotests/bgp_path_attribute_discard/r1/frr.conf9
-rw-r--r--tests/topotests/bgp_path_attribute_discard/r1/zebra.conf4
-rw-r--r--tests/topotests/bgp_path_attribute_discard/r2/frr.conf10
-rw-r--r--tests/topotests/bgp_path_attribute_discard/test_bgp_path_attribute_discard.py37
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r1/vpnv6_rib.json1
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r2/vpnv6_rib.json1
-rw-r--r--tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/vpnv6_rib.json1
-rw-r--r--tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/vpnv6_rib.json1
-rw-r--r--tests/topotests/bgp_vrf_route_leak_basic/test_bgp-vrf-route-leak-basic.py145
-rw-r--r--tests/topotests/evpn_pim_1/spine/bgp.summ.json1
-rw-r--r--tests/topotests/nhrp_redundancy/r1/nhrp_cache.json40
-rw-r--r--tests/topotests/nhrp_redundancy/r1/nhrp_route.json48
-rw-r--r--tests/topotests/nhrp_redundancy/r1/nhrpd.conf9
-rw-r--r--tests/topotests/nhrp_redundancy/r1/zebra.conf12
-rw-r--r--tests/topotests/nhrp_redundancy/r2/nhrp_cache.json40
-rw-r--r--tests/topotests/nhrp_redundancy/r2/nhrp_route.json48
-rw-r--r--tests/topotests/nhrp_redundancy/r2/nhrpd.conf9
-rw-r--r--tests/topotests/nhrp_redundancy/r2/zebra.conf12
-rw-r--r--tests/topotests/nhrp_redundancy/r3/nhrp_cache.json40
-rw-r--r--tests/topotests/nhrp_redundancy/r3/nhrp_route.json48
-rw-r--r--tests/topotests/nhrp_redundancy/r3/nhrpd.conf9
-rw-r--r--tests/topotests/nhrp_redundancy/r3/zebra.conf12
-rw-r--r--tests/topotests/nhrp_redundancy/r4/nhrp_cache.json51
-rw-r--r--tests/topotests/nhrp_redundancy/r4/nhrp_route.json71
-rw-r--r--tests/topotests/nhrp_redundancy/r4/nhrp_route_shortcut.json118
-rw-r--r--tests/topotests/nhrp_redundancy/r4/nhrpd.conf11
-rw-r--r--tests/topotests/nhrp_redundancy/r4/zebra.conf16
-rw-r--r--tests/topotests/nhrp_redundancy/r5/nhrp_cache.json51
-rw-r--r--tests/topotests/nhrp_redundancy/r5/nhrp_route.json71
-rw-r--r--tests/topotests/nhrp_redundancy/r5/nhrpd.conf11
-rw-r--r--tests/topotests/nhrp_redundancy/r5/zebra.conf16
-rw-r--r--tests/topotests/nhrp_redundancy/r6/zebra.conf7
-rw-r--r--tests/topotests/nhrp_redundancy/r7/zebra.conf4
-rw-r--r--tests/topotests/nhrp_redundancy/test_nhrp_redundancy.dot103
-rw-r--r--tests/topotests/nhrp_redundancy/test_nhrp_redundancy.py423
-rw-r--r--tests/topotests/ospf_multi_vrf_bgp_route_leak/r2/zebra-vrf-ray.txt4
40 files changed, 1355 insertions, 176 deletions
diff --git a/tests/topotests/bgp_asdot_regex/r1/show_bgp_ipv4.json b/tests/topotests/bgp_asdot_regex/r1/show_bgp_ipv4.json
index e3703bf..39ed61f 100644
--- a/tests/topotests/bgp_asdot_regex/r1/show_bgp_ipv4.json
+++ b/tests/topotests/bgp_asdot_regex/r1/show_bgp_ipv4.json
@@ -1,7 +1,6 @@
{
"vrfId": 0,
"vrfName": "default",
- "tableVersion": 3,
"routerId": "192.168.255.1",
"defaultLocPrf": 100,
"localAS": "1.1",
diff --git a/tests/topotests/bgp_asdot_regex/r2/show_bgp_ipv4.json b/tests/topotests/bgp_asdot_regex/r2/show_bgp_ipv4.json
index 1af4ff7..3013317 100644
--- a/tests/topotests/bgp_asdot_regex/r2/show_bgp_ipv4.json
+++ b/tests/topotests/bgp_asdot_regex/r2/show_bgp_ipv4.json
@@ -1,7 +1,6 @@
{
"vrfId": 0,
"vrfName": "default",
- "tableVersion": 3,
"routerId": "192.168.255.2",
"defaultLocPrf": 100,
"localAS": 65538,
diff --git a/tests/topotests/bgp_path_attribute_discard/peer1/exabgp.cfg b/tests/topotests/bgp_path_attribute_discard/peer1/exabgp.cfg
index 7fb9210..dccec7d 100644
--- a/tests/topotests/bgp_path_attribute_discard/peer1/exabgp.cfg
+++ b/tests/topotests/bgp_path_attribute_discard/peer1/exabgp.cfg
@@ -1,8 +1,8 @@
neighbor 10.0.0.1 {
- router-id 10.0.0.2;
- local-address 10.0.0.2;
- local-as 65001;
- peer-as 65002;
+ router-id 10.0.0.254;
+ local-address 10.0.0.254;
+ local-as 65254;
+ peer-as 65001;
capability {
route-refresh;
@@ -12,13 +12,28 @@ neighbor 10.0.0.1 {
route 192.168.100.101/32 {
atomic-aggregate;
community 65001:101;
- next-hop 10.0.0.2;
+ next-hop 10.0.0.254;
}
route 192.168.100.102/32 {
- originator-id 10.0.0.2;
+ originator-id 10.0.0.254;
community 65001:102;
- next-hop 10.0.0.2;
+ next-hop 10.0.0.254;
+ }
+ }
+}
+
+neighbor 10.0.0.2 {
+ router-id 10.0.0.254;
+ local-address 10.0.0.254;
+ local-as 65254;
+ peer-as 65254;
+
+ static {
+ route 192.168.100.101/32 {
+ # AIGP invalid attribute: flagged as transitive + optional.
+ attribute [0x1a 0xc0 0x00000064];
+ next-hop 10.0.0.254;
}
}
}
diff --git a/tests/topotests/bgp_path_attribute_discard/r1/bgpd.conf b/tests/topotests/bgp_path_attribute_discard/r1/bgpd.conf
deleted file mode 100644
index c96f354..0000000
--- a/tests/topotests/bgp_path_attribute_discard/r1/bgpd.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-!
-router bgp 65002
- no bgp ebgp-requires-policy
- neighbor 10.0.0.2 remote-as external
- neighbor 10.0.0.2 timers 3 10
-!
diff --git a/tests/topotests/bgp_path_attribute_discard/r1/frr.conf b/tests/topotests/bgp_path_attribute_discard/r1/frr.conf
new file mode 100644
index 0000000..ae7fbdd
--- /dev/null
+++ b/tests/topotests/bgp_path_attribute_discard/r1/frr.conf
@@ -0,0 +1,9 @@
+!
+interface r1-eth0
+ ip address 10.0.0.1/24
+!
+router bgp 65001
+ no bgp ebgp-requires-policy
+ neighbor 10.0.0.254 remote-as external
+ neighbor 10.0.0.254 timers 3 10
+!
diff --git a/tests/topotests/bgp_path_attribute_discard/r1/zebra.conf b/tests/topotests/bgp_path_attribute_discard/r1/zebra.conf
deleted file mode 100644
index 51a1b26..0000000
--- a/tests/topotests/bgp_path_attribute_discard/r1/zebra.conf
+++ /dev/null
@@ -1,4 +0,0 @@
-!
-interface r1-eth0
- ip address 10.0.0.1/24
-!
diff --git a/tests/topotests/bgp_path_attribute_discard/r2/frr.conf b/tests/topotests/bgp_path_attribute_discard/r2/frr.conf
new file mode 100644
index 0000000..1dafbdd
--- /dev/null
+++ b/tests/topotests/bgp_path_attribute_discard/r2/frr.conf
@@ -0,0 +1,10 @@
+!
+interface r2-eth0
+ ip address 10.0.0.2/24
+!
+router bgp 65254
+ no bgp ebgp-requires-policy
+ neighbor 10.0.0.254 remote-as internal
+ neighbor 10.0.0.254 timers 3 10
+ neighbor 10.0.0.254 path-attribute discard 26
+!
diff --git a/tests/topotests/bgp_path_attribute_discard/test_bgp_path_attribute_discard.py b/tests/topotests/bgp_path_attribute_discard/test_bgp_path_attribute_discard.py
index c97cd0b..bd8cd8e 100644
--- a/tests/topotests/bgp_path_attribute_discard/test_bgp_path_attribute_discard.py
+++ b/tests/topotests/bgp_path_attribute_discard/test_bgp_path_attribute_discard.py
@@ -31,10 +31,12 @@ pytestmark = [pytest.mark.bgpd]
def build_topo(tgen):
r1 = tgen.add_router("r1")
- peer1 = tgen.add_exabgp_peer("peer1", ip="10.0.0.2", defaultRoute="via 10.0.0.1")
+ r2 = tgen.add_router("r2")
+ peer1 = tgen.add_exabgp_peer("peer1", ip="10.0.0.254", defaultRoute="via 10.0.0.1")
switch = tgen.add_switch("s1")
switch.add_link(r1)
+ switch.add_link(r2)
switch.add_link(peer1)
@@ -42,10 +44,10 @@ def setup_module(mod):
tgen = Topogen(build_topo, mod.__name__)
tgen.start_topology()
- router = tgen.gears["r1"]
- router.load_config(TopoRouter.RD_ZEBRA, os.path.join(CWD, "r1/zebra.conf"))
- router.load_config(TopoRouter.RD_BGP, os.path.join(CWD, "r1/bgpd.conf"))
- router.start()
+ for _, (rname, router) in enumerate(tgen.routers().items(), 1):
+ router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname)))
+
+ tgen.start_router()
peer = tgen.gears["peer1"]
peer.start(os.path.join(CWD, "peer1"), os.path.join(CWD, "exabgp.env"))
@@ -63,6 +65,7 @@ def test_bgp_path_attribute_discard():
pytest.skip(tgen.errors)
r1 = tgen.gears["r1"]
+ r2 = tgen.gears["r2"]
def _bgp_converge():
output = json.loads(r1.vtysh_cmd("show bgp ipv4 unicast json detail"))
@@ -103,7 +106,7 @@ def test_bgp_path_attribute_discard():
"""
configure terminal
router bgp
- neighbor 10.0.0.2 path-attribute discard 6 8
+ neighbor 10.0.0.254 path-attribute discard 6 8
"""
)
@@ -139,6 +142,28 @@ def test_bgp_path_attribute_discard():
result is None
), "Failed to discard path attributes (atomic-aggregate, community)"
+ def _bgp_check_if_aigp_invalid_attribute_discarded():
+ output = json.loads(r2.vtysh_cmd("show bgp ipv4 unicast json detail"))
+ expected = {
+ "routes": {
+ "192.168.100.101/32": {
+ "paths": [
+ {
+ "valid": True,
+ "aigpMetric": None,
+ }
+ ],
+ },
+ }
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(_bgp_check_if_aigp_invalid_attribute_discarded)
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5)
+ assert (
+ result is None
+ ), "Failed to discard AIGP invalid path attribute (iBGP session)"
+
def test_memory_leak():
"Run the memory leak test and report results."
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vpnv6_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vpnv6_rib.json
index 6fc43e1..f054fab 100644
--- a/tests/topotests/bgp_srv6l3vpn_sid/r1/vpnv6_rib.json
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vpnv6_rib.json
@@ -1,6 +1,5 @@
{
"vrfName": "default",
- "tableVersion": 2,
"routerId": "192.0.2.1",
"defaultLocPrf": 100,
"localAS": 1,
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r2/vpnv6_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r2/vpnv6_rib.json
index 538e895..60bcb75 100644
--- a/tests/topotests/bgp_srv6l3vpn_sid/r2/vpnv6_rib.json
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r2/vpnv6_rib.json
@@ -1,6 +1,5 @@
{
"vrfName": "default",
- "tableVersion": 2,
"routerId": "192.0.2.2",
"defaultLocPrf": 100,
"localAS": 2,
diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/vpnv6_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/vpnv6_rib.json
index 25b7a86..0fdd3d6 100644
--- a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/vpnv6_rib.json
+++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/vpnv6_rib.json
@@ -1,7 +1,6 @@
{
"vrfId": 0,
"vrfName": "default",
- "tableVersion": 2,
"routerId": "1.1.1.1",
"defaultLocPrf": 100,
"localAS": 1,
diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/vpnv6_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/vpnv6_rib.json
index 2cd47b9..03bbcc0 100644
--- a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/vpnv6_rib.json
+++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/vpnv6_rib.json
@@ -1,7 +1,6 @@
{
"vrfId": 0,
"vrfName": "default",
- "tableVersion": 2,
"routerId": "2.2.2.2",
"defaultLocPrf": 100,
"localAS": 2,
diff --git a/tests/topotests/bgp_vrf_route_leak_basic/test_bgp-vrf-route-leak-basic.py b/tests/topotests/bgp_vrf_route_leak_basic/test_bgp-vrf-route-leak-basic.py
index ef813e9..13c38a9 100644
--- a/tests/topotests/bgp_vrf_route_leak_basic/test_bgp-vrf-route-leak-basic.py
+++ b/tests/topotests/bgp_vrf_route_leak_basic/test_bgp-vrf-route-leak-basic.py
@@ -116,7 +116,7 @@ def test_vrf_route_leak_donna():
"nexthops": [
{
"fib": True,
- "interfaceName": "lo",
+ "interfaceName": "dummy0",
"vrf": "default",
"active": True,
},
@@ -214,149 +214,6 @@ def test_vrf_route_leak_eva():
assert result, "BGP VRF EVA check failed:\n{}".format(diff)
-def test_vrf_route_leak_donna():
- logger.info("Ensure that routes are leaked back and forth")
- tgen = get_topogen()
- # Don't run this test if we have any failure.
- if tgen.routers_have_failure():
- pytest.skip(tgen.errors)
-
- r1 = tgen.gears["r1"]
-
- # Test DONNA VRF.
- expect = {
- "10.0.0.0/24": [
- {
- "protocol": "connected",
- }
- ],
- "10.0.1.0/24": [
- {
- "protocol": "bgp",
- "selected": True,
- "nexthops": [
- {
- "fib": True,
- "interfaceName": "EVA",
- "vrf": "EVA",
- "active": True,
- },
- ],
- },
- ],
- "10.0.2.0/24": [{"protocol": "connected"}],
- "10.0.3.0/24": [
- {
- "protocol": "bgp",
- "selected": True,
- "nexthops": [
- {
- "fib": True,
- "interfaceName": "EVA",
- "vrf": "EVA",
- "active": True,
- },
- ],
- },
- ],
- "10.0.4.0/24": [
- {
- "protocol": "bgp",
- "selected": True,
- "nexthops": [
- {
- "fib": True,
- "interfaceName": "lo",
- "vrf": "default",
- "active": True,
- },
- ],
- },
- ],
- "172.16.101.0/24": [
- {
- "protocol": "bgp",
- "nexthops": [
- {
- "interfaceIndex": 0,
- "interfaceName": "unknown",
- "vrf": "Unknown",
- },
- ],
- },
- ],
- }
-
- test_func = partial(
- topotest.router_json_cmp, r1, "show ip route vrf DONNA json", expect
- )
- result, diff = topotest.run_and_expect(test_func, None, count=10, wait=0.5)
- assert result, "BGP VRF DONNA check failed:\n{}".format(diff)
-
-
-def test_vrf_route_leak_eva():
- logger.info("Ensure that routes are leaked back and forth")
- tgen = get_topogen()
- # Don't run this test if we have any failure.
- if tgen.routers_have_failure():
- pytest.skip(tgen.errors)
-
- r1 = tgen.gears["r1"]
-
- # Test EVA VRF.
- expect = {
- "10.0.0.0/24": [
- {
- "protocol": "bgp",
- "selected": True,
- "nexthops": [
- {
- "fib": True,
- "interfaceName": "DONNA",
- "vrf": "DONNA",
- "active": True,
- },
- ],
- },
- ],
- "10.0.1.0/24": [
- {
- "protocol": "connected",
- }
- ],
- "10.0.2.0/24": [
- {
- "protocol": "bgp",
- "selected": True,
- "nexthops": [
- {
- "fib": True,
- "interfaceName": "DONNA",
- "vrf": "DONNA",
- "active": True,
- },
- ],
- },
- ],
- "10.0.3.0/24": [
- {
- "protocol": "connected",
- }
- ],
- "172.16.101.0/24": [
- {
- "protocol": "bgp",
- "nexthops": [
- {
- "interfaceIndex": 0,
- "interfaceName": "unknown",
- "vrf": "Unknown",
- },
- ],
- },
- ],
- }
-
def test_vrf_route_leak_default():
logger.info("Ensure that routes are leaked back and forth")
diff --git a/tests/topotests/evpn_pim_1/spine/bgp.summ.json b/tests/topotests/evpn_pim_1/spine/bgp.summ.json
index 7f37ced..b5b03e8 100644
--- a/tests/topotests/evpn_pim_1/spine/bgp.summ.json
+++ b/tests/topotests/evpn_pim_1/spine/bgp.summ.json
@@ -2,7 +2,6 @@
"routerId":"192.168.100.1",
"as":65001,
"vrfName":"default",
- "tableVersion":7,
"peerCount":2,
"peers":{
"192.168.1.2":{
diff --git a/tests/topotests/nhrp_redundancy/r1/nhrp_cache.json b/tests/topotests/nhrp_redundancy/r1/nhrp_cache.json
new file mode 100644
index 0000000..a94dd9f
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/r1/nhrp_cache.json
@@ -0,0 +1,40 @@
+{
+ "attr": {
+ "entriesCount": 3
+ },
+ "table": [
+ {
+ "interface": "r1-gre0",
+ "type": "dynamic",
+ "protocol": "176.16.1.4",
+ "nbma": "192.168.2.4",
+ "claimed_nbma": "192.168.2.4",
+ "used": false,
+ "timeout": true,
+ "auth": false,
+ "identity": ""
+ },
+ {
+ "interface": "r1-gre0",
+ "type": "local",
+ "protocol": "176.16.1.1",
+ "nbma": "192.168.1.1",
+ "claimed_nbma": "192.168.1.1",
+ "used": false,
+ "timeout": false,
+ "auth": false,
+ "identity": "-"
+ },
+ {
+ "interface": "r1-gre0",
+ "type": "dynamic",
+ "protocol": "176.16.1.5",
+ "nbma": "192.168.2.5",
+ "claimed_nbma": "192.168.2.5",
+ "used": false,
+ "timeout": true,
+ "auth": false,
+ "identity": ""
+ }
+ ]
+} \ No newline at end of file
diff --git a/tests/topotests/nhrp_redundancy/r1/nhrp_route.json b/tests/topotests/nhrp_redundancy/r1/nhrp_route.json
new file mode 100644
index 0000000..b5f3e29
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/r1/nhrp_route.json
@@ -0,0 +1,48 @@
+{
+ "176.16.1.4\/32": [
+ {
+ "prefix": "176.16.1.4\/32",
+ "protocol": "nhrp",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 10,
+ "metric": 0,
+ "installed": true,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "r1-gre0",
+ "active": true
+ }
+ ]
+ }
+ ],
+ "176.16.1.5\/32": [
+ {
+ "prefix": "176.16.1.5\/32",
+ "protocol": "nhrp",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 10,
+ "metric": 0,
+ "installed": true,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "r1-gre0",
+ "active": true
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/tests/topotests/nhrp_redundancy/r1/nhrpd.conf b/tests/topotests/nhrp_redundancy/r1/nhrpd.conf
new file mode 100644
index 0000000..ad48ce3
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/r1/nhrpd.conf
@@ -0,0 +1,9 @@
+!debug nhrp all
+nhrp nflog-group 1
+interface r1-gre0
+ ip nhrp holdtime 10
+ ip nhrp network-id 42
+ ip nhrp registration no-unique
+ ip nhrp redirect
+ tunnel source r1-eth0
+exit
diff --git a/tests/topotests/nhrp_redundancy/r1/zebra.conf b/tests/topotests/nhrp_redundancy/r1/zebra.conf
new file mode 100644
index 0000000..0f11563
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/r1/zebra.conf
@@ -0,0 +1,12 @@
+ip forwarding
+interface r1-eth0
+ ip address 192.168.1.1/24
+!
+ip route 192.168.2.0/24 192.168.1.6
+interface r1-gre0
+ ip address 176.16.1.1/32
+ no link-detect
+ ipv6 nd suppress-ra
+!
+ip route 4.4.4.0/24 176.16.1.4
+ip route 5.5.5.0/24 176.16.1.5
diff --git a/tests/topotests/nhrp_redundancy/r2/nhrp_cache.json b/tests/topotests/nhrp_redundancy/r2/nhrp_cache.json
new file mode 100644
index 0000000..91557a1
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/r2/nhrp_cache.json
@@ -0,0 +1,40 @@
+{
+ "attr": {
+ "entriesCount": 3
+ },
+ "table": [
+ {
+ "interface": "r2-gre0",
+ "type": "local",
+ "protocol": "176.16.1.2",
+ "nbma": "192.168.1.2",
+ "claimed_nbma": "192.168.1.2",
+ "used": false,
+ "timeout": false,
+ "auth": false,
+ "identity": "-"
+ },
+ {
+ "interface": "r2-gre0",
+ "type": "dynamic",
+ "protocol": "176.16.1.4",
+ "nbma": "192.168.2.4",
+ "claimed_nbma": "192.168.2.4",
+ "used": false,
+ "timeout": true,
+ "auth": false,
+ "identity": ""
+ },
+ {
+ "interface": "r2-gre0",
+ "type": "dynamic",
+ "protocol": "176.16.1.5",
+ "nbma": "192.168.2.5",
+ "claimed_nbma": "192.168.2.5",
+ "used": false,
+ "timeout": true,
+ "auth": false,
+ "identity": ""
+ }
+ ]
+} \ No newline at end of file
diff --git a/tests/topotests/nhrp_redundancy/r2/nhrp_route.json b/tests/topotests/nhrp_redundancy/r2/nhrp_route.json
new file mode 100644
index 0000000..f1fa6e5
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/r2/nhrp_route.json
@@ -0,0 +1,48 @@
+{
+ "176.16.1.4\/32": [
+ {
+ "prefix": "176.16.1.4\/32",
+ "protocol": "nhrp",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 10,
+ "metric": 0,
+ "installed": true,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "r2-gre0",
+ "active": true
+ }
+ ]
+ }
+ ],
+ "176.16.1.5\/32": [
+ {
+ "prefix": "176.16.1.5\/32",
+ "protocol": "nhrp",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 10,
+ "metric": 0,
+ "installed": true,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "r2-gre0",
+ "active": true
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/tests/topotests/nhrp_redundancy/r2/nhrpd.conf b/tests/topotests/nhrp_redundancy/r2/nhrpd.conf
new file mode 100644
index 0000000..4d63f07
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/r2/nhrpd.conf
@@ -0,0 +1,9 @@
+!debug nhrp all
+nhrp nflog-group 1
+interface r2-gre0
+ ip nhrp holdtime 10
+ ip nhrp network-id 42
+ ip nhrp registration no-unique
+ ip nhrp redirect
+ tunnel source r2-eth0
+exit
diff --git a/tests/topotests/nhrp_redundancy/r2/zebra.conf b/tests/topotests/nhrp_redundancy/r2/zebra.conf
new file mode 100644
index 0000000..1a9c4ff
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/r2/zebra.conf
@@ -0,0 +1,12 @@
+ip forwarding
+interface r2-eth0
+ ip address 192.168.1.2/24
+!
+ip route 192.168.2.0/24 192.168.1.6
+interface r2-gre0
+ ip address 176.16.1.2/32
+ no link-detect
+ ipv6 nd suppress-ra
+!
+ip route 4.4.4.0/24 176.16.1.4
+ip route 5.5.5.0/24 176.16.1.5
diff --git a/tests/topotests/nhrp_redundancy/r3/nhrp_cache.json b/tests/topotests/nhrp_redundancy/r3/nhrp_cache.json
new file mode 100644
index 0000000..ef3ab69
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/r3/nhrp_cache.json
@@ -0,0 +1,40 @@
+{
+ "attr": {
+ "entriesCount": 3
+ },
+ "table": [
+ {
+ "interface": "r3-gre0",
+ "type": "dynamic",
+ "protocol": "176.16.1.4",
+ "nbma": "192.168.2.4",
+ "claimed_nbma": "192.168.2.4",
+ "used": false,
+ "timeout": true,
+ "auth": false,
+ "identity": ""
+ },
+ {
+ "interface": "r3-gre0",
+ "type": "local",
+ "protocol": "176.16.1.3",
+ "nbma": "192.168.1.3",
+ "claimed_nbma": "192.168.1.3",
+ "used": false,
+ "timeout": false,
+ "auth": false,
+ "identity": "-"
+ },
+ {
+ "interface": "r3-gre0",
+ "type": "dynamic",
+ "protocol": "176.16.1.5",
+ "nbma": "192.168.2.5",
+ "claimed_nbma": "192.168.2.5",
+ "used": false,
+ "timeout": true,
+ "auth": false,
+ "identity": ""
+ }
+ ]
+} \ No newline at end of file
diff --git a/tests/topotests/nhrp_redundancy/r3/nhrp_route.json b/tests/topotests/nhrp_redundancy/r3/nhrp_route.json
new file mode 100644
index 0000000..3d548c0
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/r3/nhrp_route.json
@@ -0,0 +1,48 @@
+{
+ "176.16.1.4\/32": [
+ {
+ "prefix": "176.16.1.4\/32",
+ "protocol": "nhrp",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 10,
+ "metric": 0,
+ "installed": true,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "r3-gre0",
+ "active": true
+ }
+ ]
+ }
+ ],
+ "176.16.1.5\/32": [
+ {
+ "prefix": "176.16.1.5\/32",
+ "protocol": "nhrp",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 10,
+ "metric": 0,
+ "installed": true,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "r3-gre0",
+ "active": true
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/tests/topotests/nhrp_redundancy/r3/nhrpd.conf b/tests/topotests/nhrp_redundancy/r3/nhrpd.conf
new file mode 100644
index 0000000..87cc216
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/r3/nhrpd.conf
@@ -0,0 +1,9 @@
+!debug nhrp all
+nhrp nflog-group 1
+interface r3-gre0
+ ip nhrp holdtime 10
+ ip nhrp network-id 42
+ ip nhrp registration no-unique
+ ip nhrp redirect
+ tunnel source r3-eth0
+exit
diff --git a/tests/topotests/nhrp_redundancy/r3/zebra.conf b/tests/topotests/nhrp_redundancy/r3/zebra.conf
new file mode 100644
index 0000000..980cfbc
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/r3/zebra.conf
@@ -0,0 +1,12 @@
+ip forwarding
+interface r3-eth0
+ ip address 192.168.1.3/24
+!
+ip route 192.168.2.0/24 192.168.1.6
+interface r3-gre0
+ ip address 176.16.1.3/32
+ no link-detect
+ ipv6 nd suppress-ra
+!
+ip route 4.4.4.0/24 176.16.1.4
+ip route 5.5.5.0/24 176.16.1.5 \ No newline at end of file
diff --git a/tests/topotests/nhrp_redundancy/r4/nhrp_cache.json b/tests/topotests/nhrp_redundancy/r4/nhrp_cache.json
new file mode 100644
index 0000000..f87ebcf
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/r4/nhrp_cache.json
@@ -0,0 +1,51 @@
+{
+ "attr": {
+ "entriesCount": 4
+ },
+ "table": [
+ {
+ "interface": "r4-gre0",
+ "type": "nhs",
+ "protocol": "176.16.1.2",
+ "nbma": "192.168.1.2",
+ "claimed_nbma": "192.168.1.2",
+ "used": false,
+ "timeout": true,
+ "auth": false,
+ "identity": ""
+ },
+ {
+ "interface": "r4-gre0",
+ "type": "local",
+ "protocol": "176.16.1.4",
+ "nbma": "192.168.2.4",
+ "claimed_nbma": "192.168.2.4",
+ "used": false,
+ "timeout": false,
+ "auth": false,
+ "identity": "-"
+ },
+ {
+ "interface": "r4-gre0",
+ "type": "nhs",
+ "protocol": "176.16.1.3",
+ "nbma": "192.168.1.3",
+ "claimed_nbma": "192.168.1.3",
+ "used": false,
+ "timeout": true,
+ "auth": false,
+ "identity": ""
+ },
+ {
+ "interface": "r4-gre0",
+ "type": "nhs",
+ "protocol": "176.16.1.1",
+ "nbma": "192.168.1.1",
+ "claimed_nbma": "192.168.1.1",
+ "used": false,
+ "timeout": true,
+ "auth": false,
+ "identity": ""
+ }
+ ]
+} \ No newline at end of file
diff --git a/tests/topotests/nhrp_redundancy/r4/nhrp_route.json b/tests/topotests/nhrp_redundancy/r4/nhrp_route.json
new file mode 100644
index 0000000..4f1faee
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/r4/nhrp_route.json
@@ -0,0 +1,71 @@
+{
+ "176.16.1.1\/32": [
+ {
+ "prefix": "176.16.1.1\/32",
+ "protocol": "nhrp",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 10,
+ "metric": 0,
+ "installed": true,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "r4-gre0",
+ "active": true
+ }
+ ]
+ }
+ ],
+ "176.16.1.2\/32": [
+ {
+ "prefix": "176.16.1.2\/32",
+ "protocol": "nhrp",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 10,
+ "metric": 0,
+ "installed": true,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "r4-gre0",
+ "active": true
+ }
+ ]
+ }
+ ],
+ "176.16.1.3\/32": [
+ {
+ "prefix": "176.16.1.3\/32",
+ "protocol": "nhrp",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 10,
+ "metric": 0,
+ "installed": true,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "r4-gre0",
+ "active": true
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/tests/topotests/nhrp_redundancy/r4/nhrp_route_shortcut.json b/tests/topotests/nhrp_redundancy/r4/nhrp_route_shortcut.json
new file mode 100644
index 0000000..f8efff2
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/r4/nhrp_route_shortcut.json
@@ -0,0 +1,118 @@
+{
+ "5.5.5.5\/32": [
+ {
+ "prefix": "5.5.5.5\/32",
+ "protocol": "nhrp",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 10,
+ "metric": 0,
+ "installed": true,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "fib": true,
+ "ip": "176.16.1.5",
+ "afi": "ipv4",
+ "interfaceName": "r4-gre0",
+ "active": true
+ }
+ ]
+ }
+ ],
+ "176.16.1.1\/32": [
+ {
+ "prefix": "176.16.1.1\/32",
+ "protocol": "nhrp",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 10,
+ "metric": 0,
+ "installed": true,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "r4-gre0",
+ "active": true
+ }
+ ]
+ }
+ ],
+ "176.16.1.2\/32": [
+ {
+ "prefix": "176.16.1.2\/32",
+ "protocol": "nhrp",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 10,
+ "metric": 0,
+ "installed": true,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "r4-gre0",
+ "active": true
+ }
+ ]
+ }
+ ],
+ "176.16.1.3\/32": [
+ {
+ "prefix": "176.16.1.3\/32",
+ "protocol": "nhrp",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 10,
+ "metric": 0,
+ "installed": true,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "r4-gre0",
+ "active": true
+ }
+ ]
+ }
+ ],
+ "176.16.1.5\/32": [
+ {
+ "prefix": "176.16.1.5\/32",
+ "protocol": "nhrp",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 10,
+ "metric": 0,
+ "installed": true,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "r4-gre0",
+ "active": true
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/tests/topotests/nhrp_redundancy/r4/nhrpd.conf b/tests/topotests/nhrp_redundancy/r4/nhrpd.conf
new file mode 100644
index 0000000..8a52f33
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/r4/nhrpd.conf
@@ -0,0 +1,11 @@
+!debug nhrp all
+interface r4-gre0
+ ip nhrp holdtime 10
+ ip nhrp network-id 42
+ ip nhrp registration no-unique
+ ip nhrp nhs dynamic nbma 192.168.1.1
+ ip nhrp nhs dynamic nbma 192.168.1.2
+ ip nhrp nhs dynamic nbma 192.168.1.3
+ ip nhrp shortcut
+ tunnel source r4-eth0
+exit
diff --git a/tests/topotests/nhrp_redundancy/r4/zebra.conf b/tests/topotests/nhrp_redundancy/r4/zebra.conf
new file mode 100644
index 0000000..e4a9a6f
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/r4/zebra.conf
@@ -0,0 +1,16 @@
+ip forwarding
+interface r4-eth0
+ ip address 192.168.2.4/24
+!
+ip route 192.168.1.0/24 192.168.2.6
+interface r4-gre0
+ ip address 176.16.1.4/32
+ no link-detect
+ ipv6 nd suppress-ra
+!
+interface r4-eth1
+ ip address 4.4.4.4/24
+!
+ip route 0.0.0.0/0 176.16.1.1 50
+ip route 0.0.0.0/0 176.16.1.2 60
+ip route 0.0.0.0/0 176.16.1.3 70 \ No newline at end of file
diff --git a/tests/topotests/nhrp_redundancy/r5/nhrp_cache.json b/tests/topotests/nhrp_redundancy/r5/nhrp_cache.json
new file mode 100644
index 0000000..bc041c6
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/r5/nhrp_cache.json
@@ -0,0 +1,51 @@
+{
+ "attr": {
+ "entriesCount": 4
+ },
+ "table": [
+ {
+ "interface": "r5-gre0",
+ "type": "nhs",
+ "protocol": "176.16.1.2",
+ "nbma": "192.168.1.2",
+ "claimed_nbma": "192.168.1.2",
+ "used": false,
+ "timeout": true,
+ "auth": false,
+ "identity": ""
+ },
+ {
+ "interface": "r5-gre0",
+ "type": "nhs",
+ "protocol": "176.16.1.3",
+ "nbma": "192.168.1.3",
+ "claimed_nbma": "192.168.1.3",
+ "used": false,
+ "timeout": true,
+ "auth": false,
+ "identity": ""
+ },
+ {
+ "interface": "r5-gre0",
+ "type": "nhs",
+ "protocol": "176.16.1.1",
+ "nbma": "192.168.1.1",
+ "claimed_nbma": "192.168.1.1",
+ "used": false,
+ "timeout": true,
+ "auth": false,
+ "identity": ""
+ },
+ {
+ "interface": "r5-gre0",
+ "type": "local",
+ "protocol": "176.16.1.5",
+ "nbma": "192.168.2.5",
+ "claimed_nbma": "192.168.2.5",
+ "used": false,
+ "timeout": false,
+ "auth": false,
+ "identity": "-"
+ }
+ ]
+} \ No newline at end of file
diff --git a/tests/topotests/nhrp_redundancy/r5/nhrp_route.json b/tests/topotests/nhrp_redundancy/r5/nhrp_route.json
new file mode 100644
index 0000000..1d1c16f
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/r5/nhrp_route.json
@@ -0,0 +1,71 @@
+{
+ "176.16.1.1\/32": [
+ {
+ "prefix": "176.16.1.1\/32",
+ "protocol": "nhrp",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 10,
+ "metric": 0,
+ "installed": true,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "r5-gre0",
+ "active": true
+ }
+ ]
+ }
+ ],
+ "176.16.1.2\/32": [
+ {
+ "prefix": "176.16.1.2\/32",
+ "protocol": "nhrp",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 10,
+ "metric": 0,
+ "installed": true,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "r5-gre0",
+ "active": true
+ }
+ ]
+ }
+ ],
+ "176.16.1.3\/32": [
+ {
+ "prefix": "176.16.1.3\/32",
+ "protocol": "nhrp",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 10,
+ "metric": 0,
+ "installed": true,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "r5-gre0",
+ "active": true
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/tests/topotests/nhrp_redundancy/r5/nhrpd.conf b/tests/topotests/nhrp_redundancy/r5/nhrpd.conf
new file mode 100644
index 0000000..7241ed5
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/r5/nhrpd.conf
@@ -0,0 +1,11 @@
+!debug nhrp all
+interface r5-gre0
+ ip nhrp holdtime 10
+ ip nhrp network-id 42
+ ip nhrp nhs dynamic nbma 192.168.1.1
+ ip nhrp nhs dynamic nbma 192.168.1.2
+ ip nhrp nhs dynamic nbma 192.168.1.3
+ ip nhrp registration no-unique
+ ip nhrp shortcut
+ tunnel source r5-eth0
+exit
diff --git a/tests/topotests/nhrp_redundancy/r5/zebra.conf b/tests/topotests/nhrp_redundancy/r5/zebra.conf
new file mode 100644
index 0000000..9b1e1c0
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/r5/zebra.conf
@@ -0,0 +1,16 @@
+ip forwarding
+interface r5-eth0
+ ip address 192.168.2.5/24
+!
+ip route 192.168.1.0/24 192.168.2.6
+interface r5-gre0
+ ip address 176.16.1.5/32
+ no link-detect
+ ipv6 nd suppress-ra
+!
+interface r5-eth1
+ ip address 5.5.5.5/24
+!
+ip route 0.0.0.0/0 176.16.1.1 50
+ip route 0.0.0.0/0 176.16.1.2 60
+ip route 0.0.0.0/0 176.16.1.3 70
diff --git a/tests/topotests/nhrp_redundancy/r6/zebra.conf b/tests/topotests/nhrp_redundancy/r6/zebra.conf
new file mode 100644
index 0000000..63a37cd
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/r6/zebra.conf
@@ -0,0 +1,7 @@
+ip forwarding
+interface r6-eth0
+ ip address 192.168.1.6/24
+!
+interface r6-eth1
+ ip address 192.168.2.6/24
+exit
diff --git a/tests/topotests/nhrp_redundancy/r7/zebra.conf b/tests/topotests/nhrp_redundancy/r7/zebra.conf
new file mode 100644
index 0000000..5747b40
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/r7/zebra.conf
@@ -0,0 +1,4 @@
+interface r7-eth0
+ ip address 4.4.4.7/24
+!
+ip route 0.0.0.0/0 4.4.4.4
diff --git a/tests/topotests/nhrp_redundancy/test_nhrp_redundancy.dot b/tests/topotests/nhrp_redundancy/test_nhrp_redundancy.dot
new file mode 100644
index 0000000..c169436
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/test_nhrp_redundancy.dot
@@ -0,0 +1,103 @@
+## Color coding:
+#########################
+## Main FRR: #f08080 red
+## Switches: #d0e0d0 gray
+## RIP: #19e3d9 Cyan
+## RIPng: #fcb314 dark yellow
+## OSPFv2: #32b835 Green
+## OSPFv3: #19e3d9 Cyan
+## ISIS IPv4 #fcb314 dark yellow
+## ISIS IPv6 #9a81ec purple
+## BGP IPv4 #eee3d3 beige
+## BGP IPv6 #fdff00 yellow
+##### Colors (see http://www.color-hex.com/)
+
+graph template {
+ label="nhrp-topo-redundant-nhs";
+
+ # Routers
+ r1 [
+ shape=doubleoctagon,
+ label="NHS 1",
+ fillcolor="#f08080",
+ style=filled,
+ ];
+ r2 [
+ shape=doubleoctagon
+ label="NHS 2",
+ fillcolor="#f08080",
+ style=filled,
+ ];
+ r3 [
+ shape=doubleoctagon
+ label="NHS 3",
+ fillcolor="#f08080",
+ style=filled,
+ ];
+ r4 [
+ shape=doubleoctagon
+ label="NHC 1",
+ fillcolor="#f08080",
+ style=filled,
+ ];
+ r5 [
+ shape=doubleoctagon
+ label="NHC 2",
+ fillcolor="#f08080",
+ style=filled,
+ ];
+ r6 [
+ shape=doubleoctagon
+ label="router",
+ fillcolor="#f08080",
+ style=filled,
+ ];
+ r7 [
+ shape=doubleoctagon
+ label="host",
+ fillcolor="#f08080",
+ style=filled,
+ ];
+
+ # Switches
+ sw1 [
+ shape=oval,
+ label="sw1\n192.168.1.0/24",
+ fillcolor="#d0e0d0",
+ style=filled,
+ ];
+ sw2 [
+ shape=oval,
+ label="sw2\n192.168.2.0/24",
+ fillcolor="#d0e0d0",
+ style=filled,
+ ];
+ sw3 [
+ shape=oval,
+ label="sw3\n4.4.4.0/24",
+ fillcolor="#d0e0d0",
+ style=filled,
+ ];
+ sw4 [
+ shape=oval,
+ label="sw4\n5.5.5.0/24",
+ fillcolor="#d0e0d0",
+ style=filled,
+ ];
+
+ # Connections
+ r1 -- sw1 [label="eth0"];
+ r2 -- sw1 [label="eth0"];
+ r3 -- sw1 [label="eth0"];
+ r6 -- sw1 [label="eth0"];
+
+ r4 -- sw2 [label="eth0"];
+ r5 -- sw2 [label="eth0"];
+ r6 -- sw2 [label="eth1"];
+
+ r4 -- sw3 [label="eth1"];
+ r7 -- sw3 [label="eth0"];
+
+ r5 -- sw4 [label="eth1"];
+
+}
diff --git a/tests/topotests/nhrp_redundancy/test_nhrp_redundancy.py b/tests/topotests/nhrp_redundancy/test_nhrp_redundancy.py
new file mode 100644
index 0000000..81a22eb
--- /dev/null
+++ b/tests/topotests/nhrp_redundancy/test_nhrp_redundancy.py
@@ -0,0 +1,423 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# test_nhrp_redundancy.py
+#
+# Copyright 2024, LabN Consulting, L.L.C.
+# Dave LeRoy
+#
+
+import os
+import sys
+import json
+from time import sleep
+from functools import partial
+import pytest
+
+# pylint: disable=C0413
+# Import topogen and topotest helpers
+from lib import topotest
+from lib.topogen import Topogen, TopoRouter, get_topogen
+from lib.topolog import logger
+from lib.common_config import (
+ required_linux_kernel_version,
+ shutdown_bringup_interface,
+ retry,
+)
+
+"""
+test_nhrp_redundancy.py: Test NHS redundancy for NHRP
+"""
+
+TOPOLOGY = """
++------------+ +------------+ +------------+
+| | | | | |
+| | | | | |
+| NHS 1 | | NHS 2 | | NHS 3 |
+| | | | | |
++-----+------+ +-----+------+ +-----+------+
+ |.1 |.2 |.3
+ | | |
+ | | 192.168.1.0/24 |
+------+-------------------------------+------------------+-------------+------
+ |
+ |.6
+ GRE P2MP between all NHS and NHC +-----+------+
+ 172.16.1.x/32 | |
+ | |
+ | Router |
+ | |
+ +-----+------+
+ |
+ |
+ ---------+----------------+-------------+------
+ | 192.168.2.0/24 |
+ | |
+ | |.4 |.5
++------------+ | +-------+----+ +------+-----+ |
+| | | | | | | |
+| | +--------+ | | | |
+| Host |.7 | | NHC 1 | | NHC 2 +-----+5.5.5.0/24
+| +---------+ | | | | |
++------------+ | +------------+ +------------+ |
+ | |
+ 4.4.4.0/24
+"""
+
+# Save the Current Working Directory to find configuration files.
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+
+# Required to instantiate the topology builder class.
+
+pytestmark = [pytest.mark.nhrpd]
+
+
+def build_topo(tgen):
+ "Build function"
+
+ # Create 7 routers
+ for routern in range(1, 8):
+ tgen.add_router("r{}".format(routern))
+
+ # Interconnect routers 1, 2, 3, 6
+ switch = tgen.add_switch("s1")
+ switch.add_link(tgen.gears["r1"])
+ switch.add_link(tgen.gears["r2"])
+ switch.add_link(tgen.gears["r3"])
+ switch.add_link(tgen.gears["r6"])
+
+ # Interconnect routers 4, 5, 6
+ switch = tgen.add_switch("s2")
+ switch.add_link(tgen.gears["r4"])
+ switch.add_link(tgen.gears["r5"])
+ switch.add_link(tgen.gears["r6"])
+
+ # Connect router 4, 7
+ switch = tgen.add_switch("s3")
+ switch.add_link(tgen.gears["r4"])
+ switch.add_link(tgen.gears["r7"])
+
+ # Connect router 5
+ switch = tgen.add_switch("s4")
+ switch.add_link(tgen.gears["r5"])
+
+
+def _populate_iface():
+ tgen = get_topogen()
+ cmds_tot_hub = [
+ "ip tunnel add {0}-gre0 mode gre ttl 64 key 42 dev {0}-eth0 local 192.168.1.{1} remote 0.0.0.0",
+ "ip link set dev {0}-gre0 up",
+ "echo 0 > /proc/sys/net/ipv4/ip_forward_use_pmtu",
+ "echo 1 > /proc/sys/net/ipv6/conf/{0}-eth0/disable_ipv6",
+ "echo 1 > /proc/sys/net/ipv6/conf/{0}-gre0/disable_ipv6",
+ "iptables -A FORWARD -i {0}-gre0 -o {0}-gre0 -m hashlimit --hashlimit-upto 4/minute --hashlimit-burst 1 --hashlimit-mode srcip,dstip --hashlimit-srcmask 24 --hashlimit-dstmask 24 --hashlimit-name loglimit-0 -j NFLOG --nflog-group 1 --nflog-range 128",
+ ]
+
+ cmds_tot = [
+ "ip tunnel add {0}-gre0 mode gre ttl 64 key 42 dev {0}-eth0 local 192.168.2.{1} remote 0.0.0.0",
+ "ip link set dev {0}-gre0 up",
+ "echo 0 > /proc/sys/net/ipv4/ip_forward_use_pmtu",
+ "echo 1 > /proc/sys/net/ipv6/conf/{0}-eth0/disable_ipv6",
+ "echo 1 > /proc/sys/net/ipv6/conf/{0}-gre0/disable_ipv6",
+ ]
+
+ for cmd in cmds_tot_hub:
+ # Router 1
+ input = cmd.format("r1", "1")
+ logger.info("input: " + input)
+ output = tgen.net["r1"].cmd(input)
+ logger.info("output: " + output)
+
+ # Router 2
+ input = cmd.format("r2", "2")
+ logger.info("input: " + input)
+ output = tgen.net["r2"].cmd(input)
+ logger.info("output: " + output)
+
+ # Router 3
+ input = cmd.format("r3", "3")
+ logger.info("input: " + input)
+ output = tgen.net["r3"].cmd(input)
+ logger.info("output: " + output)
+
+ for cmd in cmds_tot:
+ input = cmd.format("r4", "4")
+ logger.info("input: " + input)
+ output = tgen.net["r4"].cmd(input)
+ logger.info("output: " + output)
+
+ input = cmd.format("r5", "5")
+ logger.info("input: " + input)
+ output = tgen.net["r5"].cmd(input)
+ logger.info("output: " + output)
+
+
+def _verify_iptables():
+ tgen = get_topogen()
+ # Verify iptables is installed. Required for shortcuts
+ rc, _, _ = tgen.net["r1"].cmd_status("iptables")
+ return False if rc == 127 else True
+
+
+def setup_module(mod):
+ logger.info("NHRP Redundant NHS:\n {}".format(TOPOLOGY))
+
+ result = required_linux_kernel_version("5.0")
+ if result is not True:
+ pytest.skip("Kernel requirements are not met")
+
+ tgen = Topogen(build_topo, mod.__name__)
+ tgen.start_topology()
+
+ # Starting Routers
+ router_list = tgen.routers()
+ _populate_iface()
+
+ for rname, router in router_list.items():
+ router.load_config(
+ TopoRouter.RD_ZEBRA,
+ os.path.join(CWD, "{}/zebra.conf".format(rname)),
+ )
+ if rname in ("r1", "r2", "r3", "r4", "r5"):
+ router.load_config(
+ TopoRouter.RD_NHRP, os.path.join(CWD, "{}/nhrpd.conf".format(rname))
+ )
+
+ # Initialize all routers.
+ tgen.start_router()
+
+
+def teardown_module(_mod):
+ "Teardown the pytest environment"
+ tgen = get_topogen()
+ tgen.stop_topology()
+
+
+def test_protocols_convergence():
+ """
+ Assert that all protocols have converged before checking for the NHRP
+ statuses as they depend on it.
+ """
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ logger.info("Checking NHRP cache and IPv4 routes for convergence")
+ router_list = tgen.routers()
+
+ # Check NHRP cache on servers and clients
+ for rname, router in router_list.items():
+
+ json_file = "{}/{}/nhrp_cache.json".format(CWD, router.name)
+ if not os.path.isfile(json_file):
+ logger.info("skipping file {}".format(json_file))
+ continue
+
+ expected = json.loads(open(json_file).read())
+ test_func = partial(
+ topotest.router_json_cmp, router, "show ip nhrp cache json", expected
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=40, wait=0.5)
+
+ output = router.vtysh_cmd("show ip nhrp cache")
+ logger.info(output)
+
+ assertmsg = '"{}" JSON output mismatches'.format(router.name)
+ assert result is None, assertmsg
+
+ # Check NHRP IPV4 routes on servers and clients
+ for rname, router in router_list.items():
+
+ json_file = "{}/{}/nhrp_route.json".format(CWD, router.name)
+ if not os.path.isfile(json_file):
+ logger.info("skipping file {}".format(json_file))
+ continue
+
+ expected = json.loads(open(json_file).read())
+ test_func = partial(
+ topotest.router_json_cmp, router, "show ip route nhrp json", expected
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=40, wait=0.5)
+
+ output = router.vtysh_cmd("show ip route nhrp")
+ logger.info(output)
+
+ assertmsg = '"{}" JSON output mismatches'.format(router.name)
+ assert result is None, assertmsg
+
+ # Test connectivity from 1 NHRP server to all clients
+ pingrouter = tgen.gears["r1"]
+ logger.info("Check Ping IPv4 from R1 to R4 = 176.16.1.4)")
+ output = pingrouter.run("ping 176.16.1.4 -f -c 1000")
+ logger.info(output)
+ if "1000 packets transmitted, 1000 received" not in output:
+ assertmsg = "expected ping IPv4 from R1 to R4 should be ok"
+ assert 0, assertmsg
+ else:
+ logger.info("Check Ping IPv4 from R1 to R4 OK")
+
+ logger.info("Check Ping IPv4 from R1 to R5 = 176.16.1.5)")
+ output = pingrouter.run("ping 176.16.1.5 -f -c 1000")
+ logger.info(output)
+ if "1000 packets transmitted, 1000 received" not in output:
+ assertmsg = "expected ping IPv4 from R1 to R5 should be ok"
+ assert 0, assertmsg
+ else:
+ logger.info("Check Ping IPv4 from R1 to R5 OK")
+
+ # Test connectivity from 1 NHRP client to all servers
+ pingrouter = tgen.gears["r4"]
+ logger.info("Check Ping IPv4 from R4 to R1 = 176.16.1.1)")
+ output = pingrouter.run("ping 176.16.1.1 -f -c 1000")
+ logger.info(output)
+ if "1000 packets transmitted, 1000 received" not in output:
+ assertmsg = "expected ping IPv4 from R4 to R1 should be ok"
+ assert 0, assertmsg
+ else:
+ logger.info("Check Ping IPv4 from R4 to R1 OK")
+
+ logger.info("Check Ping IPv4 from R4 to R2 = 176.16.1.2)")
+ output = pingrouter.run("ping 176.16.1.2 -f -c 1000")
+ logger.info(output)
+ if "1000 packets transmitted, 1000 received" not in output:
+ assertmsg = "expected ping IPv4 from R4 to R2 should be ok"
+ assert 0, assertmsg
+ else:
+ logger.info("Check Ping IPv4 from R4 to R2 OK")
+
+ logger.info("Check Ping IPv4 from R4 to R3 = 176.16.1.3)")
+ output = pingrouter.run("ping 176.16.1.3 -f -c 1000")
+ logger.info(output)
+ if "1000 packets transmitted, 1000 received" not in output:
+ assertmsg = "expected ping IPv4 from R4 to R3 should be ok"
+ assert 0, assertmsg
+ else:
+ logger.info("Check Ping IPv4 from R4 to R3 OK")
+
+
+@retry(retry_timeout=30, initial_wait=5)
+def verify_shortcut_path():
+ """
+ Verifying that traffic flows through shortcut path
+ """
+ tgen = get_topogen()
+ pingrouter = tgen.gears["r7"]
+ logger.info("Check Ping IPv4 from R7 to R5 = 5.5.5.5")
+
+ output = pingrouter.run("ping 5.5.5.5 -f -c 1000")
+ logger.info(output)
+ if "1000 packets transmitted, 1000 received" not in output:
+ assertmsg = "expected ping IPv4 from R7 to R5 should be ok"
+ assert 0, assertmsg
+ else:
+ logger.info("Check Ping IPv4 from R7 to R5 OK")
+
+
+def test_redundancy_shortcut():
+ """
+ Assert that if shortcut created and then NHS goes down, there is no traffic disruption
+ Stop traffic and verify next time traffic started, shortcut is initiated by backup NHS
+ """
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ if not _verify_iptables():
+ pytest.skip("iptables not installed")
+
+ logger.info("Testing NHRP shortcuts with redundant servers")
+
+ # Verify R4 nhrp routes before shortcut creation
+ router = tgen.gears["r4"]
+ json_file = "{}/{}/nhrp_route.json".format(CWD, router.name)
+ assertmsg = "No nhrp_route file found"
+ assert os.path.isfile(json_file), assertmsg
+
+ expected = json.loads(open(json_file).read())
+ test_func = partial(
+ topotest.router_json_cmp, router, "show ip route nhrp json", expected
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=40, wait=0.5)
+
+ output = router.vtysh_cmd("show ip route nhrp")
+ logger.info(output)
+
+ assertmsg = '"{}" JSON output mismatches'.format(router.name)
+ assert result is None, assertmsg
+
+ # Initiate shortcut by pinging between clients
+ pingrouter = tgen.gears["r7"]
+ logger.info("Check Ping IPv4 from R7 to R5 via shortcut = 5.5.5.5")
+
+ output = pingrouter.run("ping 5.5.5.5 -f -c 1000")
+ logger.info(output)
+ if "1000 packets transmitted, 1000 received" not in output:
+ assertmsg = "expected ping IPv4 from R7 to R5 via shortcut should be ok"
+ assert 0, assertmsg
+ else:
+ logger.info("Check Ping IPv4 from R7 to R5 via shortcut OK")
+
+ # Now check that NHRP shortcut route installed
+ json_file = "{}/{}/nhrp_route_shortcut.json".format(CWD, router.name)
+ assertmsg = "No nhrp_route file found"
+ assert os.path.isfile(json_file), assertmsg
+
+ expected = json.loads(open(json_file).read())
+ test_func = partial(
+ topotest.router_json_cmp, router, "show ip route nhrp json", expected
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=40, wait=0.5)
+
+ output = router.vtysh_cmd("show ip route nhrp")
+ logger.info(output)
+
+ assertmsg = '"{}" JSON output mismatches'.format(router.name)
+ assert result is None, assertmsg
+
+ # Bring down primary GRE interface and verify shortcut is not disturbed
+ logger.info("Bringing down R1, primary NHRP server.")
+ shutdown_bringup_interface(tgen, "r1", "r1-gre0", False)
+
+ # Verify shortcut is still active
+ pingrouter = tgen.gears["r7"]
+ logger.info("Check Ping IPv4 from R7 to R5 via shortcut = 5.5.5.5")
+
+ output = pingrouter.run("ping 5.5.5.5 -f -c 1000")
+ logger.info(output)
+ if "1000 packets transmitted, 1000 received" not in output:
+ assertmsg = "expected ping IPv4 from R7 to R5 via shortcut should be ok"
+ assert 0, assertmsg
+ else:
+ logger.info("Check Ping IPv4 from R7 to R5 via shortcut OK")
+
+ # Now verify shortcut is purged with lack of traffic
+ json_file = "{}/{}/nhrp_route.json".format(CWD, router.name)
+ assertmsg = "No nhrp_route file found"
+ assert os.path.isfile(json_file), assertmsg
+
+ expected = json.loads(open(json_file).read())
+ test_func = partial(
+ topotest.router_json_cmp, router, "show ip route nhrp json", expected
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=40, wait=0.5)
+
+ output = router.vtysh_cmd("show ip route nhrp")
+ logger.info(output)
+
+ assertmsg = '"{}" JSON output mismatches'.format(router.name)
+ assert result is None, assertmsg
+
+
+def test_memory_leak():
+ "Run the memory leak test and report results."
+ tgen = get_topogen()
+ if not tgen.is_memleak_enabled():
+ pytest.skip("Memory leak test/report is disabled")
+
+ tgen.report_memory_leaks()
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))
diff --git a/tests/topotests/ospf_multi_vrf_bgp_route_leak/r2/zebra-vrf-ray.txt b/tests/topotests/ospf_multi_vrf_bgp_route_leak/r2/zebra-vrf-ray.txt
index 6ab1bb8..1495c88 100644
--- a/tests/topotests/ospf_multi_vrf_bgp_route_leak/r2/zebra-vrf-ray.txt
+++ b/tests/topotests/ospf_multi_vrf_bgp_route_leak/r2/zebra-vrf-ray.txt
@@ -1,9 +1,9 @@
VRF ray:
B 10.0.1.0/24 [20/20] via 10.0.20.1, r2-eth1 (vrf default) inactive, weight 1, XX:XX:XX
-B 10.0.2.0/24 [20/0] is directly connected, lo (vrf default) inactive, weight 1, XX:XX:XX
+B 10.0.2.0/24 [20/0] is directly connected, r2-eth0 (vrf default) inactive, weight 1, XX:XX:XX
B>* 10.0.3.0/24 [20/20] via 10.0.20.1, r2-eth1 (vrf default), weight 1, XX:XX:XX
O>* 10.0.4.0/24 [110/20] via 10.0.40.4, r2-eth2, weight 1, XX:XX:XX
-B 10.0.20.0/24 [20/0] is directly connected, lo (vrf default) inactive, weight 1, XX:XX:XX
+B 10.0.20.0/24 [20/0] is directly connected, r2-eth1 (vrf default) inactive, weight 1, XX:XX:XX
B>* 10.0.30.0/24 [20/20] via 10.0.20.1, r2-eth1 (vrf default), weight 1, XX:XX:XX
O 10.0.40.0/24 [110/10] is directly connected, r2-eth2, weight 1, XX:XX:XX
C>* 10.0.40.0/24 is directly connected, r2-eth2, XX:XX:XX