From ae5a6bc1f6baacb3bd1695615e922ec28bdf9c62 Mon Sep 17 00:00:00 2001 From: Philippe Guibert Date: Tue, 10 Jan 2023 14:53:54 +0100 Subject: [PATCH] topotests: add bgp mpls allocation per next-hop test A new test suite checks for the mpls label allocation per nexthop mode. This test checks that: - The labels are correctly allocated per connected next-hop. - The default label is used for non connected prefixes - The withdraw operation frees the mpls entry. - If a recursive route is redistributed by BGP, then the nexthop tracking will find the appropriate nexthop entry, and the associated label will be found out. - When a prefix moves from one peer to one another behind the vrf, then the MPLS switching operation for return traffic is changing the outgoing interface to use. - When the 'label vpn export ' MPLS label value is changed, then the modification is propagated to prefixes which use that value. - When unconfiguring the per-nexthop allocation mode, check that the MPLS entries and the VPNv4 entries of r1 are changed accordingly. - Reversely, when re-configuring the per-nexthop allocation mode, check that the allocation mode reuses the other label values. Signed-off-by: Philippe Guibert --- .../bgp_vpnv4_per_nexthop_label/__init__.py | 0 .../r1/bgp_ipv4_routes_vrf1.json | 143 ++++ .../bgp_vpnv4_per_nexthop_label/r1/bgpd.conf | 30 + .../r1/ipv4_routes.json | 50 ++ .../bgp_vpnv4_per_nexthop_label/r1/zebra.conf | 18 + .../bgp_vpnv4_per_nexthop_label/r11/bgpd.conf | 11 + .../r11/zebra.conf | 4 + .../bgp_vpnv4_per_nexthop_label/r12/bgpd.conf | 9 + .../r12/zebra.conf | 4 + .../bgp_vpnv4_per_nexthop_label/r13/bgpd.conf | 9 + .../r13/zebra.conf | 4 + .../r2/bgp_ipv4_routes.json | 38 + .../r2/bgp_vpnv4_routes.json | 187 ++++ .../bgp_vpnv4_per_nexthop_label/r2/bgpd.conf | 25 + .../bgp_vpnv4_per_nexthop_label/r2/zebra.conf | 7 + .../bgp_vpnv4_per_nexthop_label/rr/bgpd.conf | 13 + .../bgp_vpnv4_per_nexthop_label/rr/zebra.conf | 4 + .../test_bgp_vpnv4_per_nexthop_label.py | 795 ++++++++++++++++++ 18 files changed, 1351 insertions(+) create mode 100644 tests/topotests/bgp_vpnv4_per_nexthop_label/__init__.py create mode 100644 tests/topotests/bgp_vpnv4_per_nexthop_label/r1/bgp_ipv4_routes_vrf1.json create mode 100644 tests/topotests/bgp_vpnv4_per_nexthop_label/r1/bgpd.conf create mode 100644 tests/topotests/bgp_vpnv4_per_nexthop_label/r1/ipv4_routes.json create mode 100644 tests/topotests/bgp_vpnv4_per_nexthop_label/r1/zebra.conf create mode 100644 tests/topotests/bgp_vpnv4_per_nexthop_label/r11/bgpd.conf create mode 100644 tests/topotests/bgp_vpnv4_per_nexthop_label/r11/zebra.conf create mode 100644 tests/topotests/bgp_vpnv4_per_nexthop_label/r12/bgpd.conf create mode 100644 tests/topotests/bgp_vpnv4_per_nexthop_label/r12/zebra.conf create mode 100644 tests/topotests/bgp_vpnv4_per_nexthop_label/r13/bgpd.conf create mode 100644 tests/topotests/bgp_vpnv4_per_nexthop_label/r13/zebra.conf create mode 100644 tests/topotests/bgp_vpnv4_per_nexthop_label/r2/bgp_ipv4_routes.json create mode 100644 tests/topotests/bgp_vpnv4_per_nexthop_label/r2/bgp_vpnv4_routes.json create mode 100644 tests/topotests/bgp_vpnv4_per_nexthop_label/r2/bgpd.conf create mode 100644 tests/topotests/bgp_vpnv4_per_nexthop_label/r2/zebra.conf create mode 100644 tests/topotests/bgp_vpnv4_per_nexthop_label/rr/bgpd.conf create mode 100644 tests/topotests/bgp_vpnv4_per_nexthop_label/rr/zebra.conf create mode 100644 tests/topotests/bgp_vpnv4_per_nexthop_label/test_bgp_vpnv4_per_nexthop_label.py diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/__init__.py b/tests/topotests/bgp_vpnv4_per_nexthop_label/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r1/bgp_ipv4_routes_vrf1.json b/tests/topotests/bgp_vpnv4_per_nexthop_label/r1/bgp_ipv4_routes_vrf1.json new file mode 100644 index 0000000000..31a1f3d6ed --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r1/bgp_ipv4_routes_vrf1.json @@ -0,0 +1,143 @@ +{ + "vrfName": "vrf1", + "localAS": 65500, + "routes": + { + "10.200.0.0/24": [ + { + "valid": true, + "bestpath": true, + "prefix": "10.200.0.0", + "prefixLen": 24, + "network": "10.200.0.0\/24", + "nexthops": [ + { + "ip": "192.168.0.2", + "afi": "ipv4", + "used": true + } + ] + } + + ], + "172.31.0.11/32": [ + { + "valid":true, + "bestpath":true, + "prefix":"172.31.0.11", + "prefixLen":32, + "network":"172.31.0.11/32", + "peerId":"192.0.2.100", + "nexthops":[ + { + "ip":"192.0.2.11", + "afi":"ipv4", + "used":true + } + ] + } + ], + "172.31.0.12/32": [ + { + "valid":true, + "bestpath":true, + "prefix":"172.31.0.12", + "prefixLen":32, + "network":"172.31.0.12/32", + "peerId":"192.0.2.100", + "nexthops":[ + { + "ip":"192.0.2.12", + "afi":"ipv4", + "used":true + } + ] + } + ], + "172.31.0.13/32": [ + { + "valid":true, + "bestpath":true, + "prefix":"172.31.0.13", + "prefixLen":32, + "network":"172.31.0.13/32", + "peerId":"192.168.255.13", + "nexthops":[ + { + "ip":"192.168.255.13", + "afi":"ipv4", + "used":true + } + ] + } + ], + "172.31.0.14/32": [ + { + "valid":true, + "bestpath":true, + "prefix":"172.31.0.14", + "prefixLen":32, + "network":"172.31.0.14/32", + "peerId":"(unspec)", + "nexthops":[ + { + "ip":"192.0.2.14", + "afi":"ipv4", + "used":true + } + ] + } + ], + "172.31.0.15/32": [ + { + "valid":true, + "bestpath":true, + "prefix":"172.31.0.15", + "prefixLen":32, + "network":"172.31.0.15/32", + "peerId":"(unspec)", + "nexthops":[ + { + "ip":"192.0.2.12", + "afi":"ipv4", + "used":true + } + ] + } + ], + "172.31.0.20/32": [ + { + "valid":true, + "bestpath":true, + "prefix":"172.31.0.20", + "prefixLen":32, + "network":"172.31.0.20/32", + "peerId":"192.0.2.100", + "nexthops":[ + { + "ip":"192.0.2.11", + "afi":"ipv4", + "used":true + } + ] + } + ], + "172.31.0.111/32": [ + { + "valid":true, + "bestpath":true, + "prefix":"172.31.0.111", + "prefixLen":32, + "network":"172.31.0.111/32", + "peerId":"192.0.2.100", + "nexthops":[ + { + "ip":"192.0.2.11", + "afi":"ipv4", + "used":true + } + ] + } + ] + } +} diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r1/bgpd.conf b/tests/topotests/bgp_vpnv4_per_nexthop_label/r1/bgpd.conf new file mode 100644 index 0000000000..35fb2ec23d --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r1/bgpd.conf @@ -0,0 +1,30 @@ +router bgp 65500 + bgp router-id 192.168.0.1 + no bgp ebgp-requires-policy + neighbor 192.168.0.2 remote-as 65501 + address-family ipv4 unicast + no neighbor 192.168.0.2 activate + exit-address-family + address-family ipv4 vpn + neighbor 192.168.0.2 activate + neighbor 192.168.0.2 soft-reconfiguration inbound + exit-address-family +! +router bgp 65500 vrf vrf1 + bgp router-id 192.168.0.1 + neighbor 192.0.2.100 remote-as 65500 + neighbor 192.168.255.13 remote-as 65500 + address-family ipv4 unicast + redistribute connected + redistribute static + label vpn export allocation-mode per-nexthop + label vpn export auto + rd vpn export 444:1 + rt vpn both 52:100 + export vpn + import vpn + exit-address-family +! +interface r1-eth0 + mpls bgp forwarding +! diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r1/ipv4_routes.json b/tests/topotests/bgp_vpnv4_per_nexthop_label/r1/ipv4_routes.json new file mode 100644 index 0000000000..da7d281833 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r1/ipv4_routes.json @@ -0,0 +1,50 @@ +{ + "10.200.0.0/24": [ + { + "prefix": "10.200.0.0/24", + "prefixLen": 24, + "protocol": "bgp", + "vrfName": "vrf1", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "10.125.0.2", + "afi": "ipv4", + "interfaceName": "r1-eth0", + "vrf": "default", + "active": true, + "labels":[ + 102 + ] + } + ] + } + ], + "10.201.0.0/24": [ + { + "prefix": "10.201.0.0/24", + "prefixLen": 24, + "protocol": "connected", + "vrfName": "vrf1", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "nexthops":[ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "r1-eth1", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r1/zebra.conf b/tests/topotests/bgp_vpnv4_per_nexthop_label/r1/zebra.conf new file mode 100644 index 0000000000..2618595014 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r1/zebra.conf @@ -0,0 +1,18 @@ +log stdout +debug zebra nht +!debug zebra kernel msgdump recv +!debug zebra dplane detailed +!debug zebra packet recv +interface r1-eth1 vrf vrf1 + ip address 192.0.2.1/24 +! +interface r1-eth2 vrf vrf1 + ip address 192.168.255.1/24 +! +interface r1-eth0 + ip address 192.168.0.1/24 +! +vrf vrf1 + ip route 172.31.0.14/32 192.0.2.14 + ip route 172.31.0.15/32 192.0.2.12 +exit-vrf diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r11/bgpd.conf b/tests/topotests/bgp_vpnv4_per_nexthop_label/r11/bgpd.conf new file mode 100644 index 0000000000..5da91518b4 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r11/bgpd.conf @@ -0,0 +1,11 @@ +router bgp 65500 + bgp router-id 192.0.2.11 + no bgp network import-check + neighbor 192.0.2.100 remote-as 65500 + address-family ipv4 unicast + network 172.31.0.11/32 + network 172.31.0.111/32 + network 172.31.0.20/32 + exit-address-family +! + diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r11/zebra.conf b/tests/topotests/bgp_vpnv4_per_nexthop_label/r11/zebra.conf new file mode 100644 index 0000000000..a080757561 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r11/zebra.conf @@ -0,0 +1,4 @@ +log stdout +interface r11-eth0 + ip address 192.0.2.11/24 +! diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r12/bgpd.conf b/tests/topotests/bgp_vpnv4_per_nexthop_label/r12/bgpd.conf new file mode 100644 index 0000000000..d3889f5040 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r12/bgpd.conf @@ -0,0 +1,9 @@ +router bgp 65500 + bgp router-id 192.0.2.12 + no bgp network import-check + neighbor 192.0.2.100 remote-as 65500 + address-family ipv4 unicast + network 172.31.0.12/32 + exit-address-family +! + diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r12/zebra.conf b/tests/topotests/bgp_vpnv4_per_nexthop_label/r12/zebra.conf new file mode 100644 index 0000000000..9ce3aba247 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r12/zebra.conf @@ -0,0 +1,4 @@ +log stdout +interface r12-eth0 + ip address 192.0.2.12/24 +! diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r13/bgpd.conf b/tests/topotests/bgp_vpnv4_per_nexthop_label/r13/bgpd.conf new file mode 100644 index 0000000000..21dbb588d5 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r13/bgpd.conf @@ -0,0 +1,9 @@ +router bgp 65500 + bgp router-id 192.168.255.13 + no bgp network import-check + address-family ipv4 unicast + neighbor 192.168.255.1 remote-as 65500 + network 172.31.0.13/32 + exit-address-family +! + diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r13/zebra.conf b/tests/topotests/bgp_vpnv4_per_nexthop_label/r13/zebra.conf new file mode 100644 index 0000000000..4d78b5f048 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r13/zebra.conf @@ -0,0 +1,4 @@ +log stdout +interface r13-eth0 + ip address 192.168.255.13/24 +! diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r2/bgp_ipv4_routes.json b/tests/topotests/bgp_vpnv4_per_nexthop_label/r2/bgp_ipv4_routes.json new file mode 100644 index 0000000000..3407925d5c --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r2/bgp_ipv4_routes.json @@ -0,0 +1,38 @@ +{ + "vrfName": "vrf1", + "localAS": 65501, + "routes": + { + "10.201.0.0/24": [ + { + "prefix": "10.201.0.0", + "prefixLen": 24, + "network": "10.201.0.0\/24", + "nhVrfName": "default", + "nexthops": [ + { + "ip": "192.168.0.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.200.0.0/24": [ + { + "valid": true, + "bestpath": true, + "prefix": "10.200.0.0", + "prefixLen": 24, + "network": "10.200.0.0\/24", + "nexthops": [ + { + "ip": "0.0.0.0", + "afi": "ipv4", + "used": true + } + ] + } + ] + } +} diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r2/bgp_vpnv4_routes.json b/tests/topotests/bgp_vpnv4_per_nexthop_label/r2/bgp_vpnv4_routes.json new file mode 100644 index 0000000000..46f4a18386 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r2/bgp_vpnv4_routes.json @@ -0,0 +1,187 @@ +{ + "vrfName": "default", + "localAS": 65501, + "routes": + { + "routeDistinguishers": + { + "444:1": + { + "172.31.0.11/32": [ + { + "valid": true, + "bestpath": true, + "prefix": "172.31.0.11", + "prefixLen": 32, + "network": "172.31.0.11\/32", + "peerId": "192.168.0.1", + "nexthops": [ + { + "ip": "192.168.0.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "172.31.0.12/32": [ + { + "valid": true, + "bestpath": true, + "prefix": "172.31.0.12", + "prefixLen": 32, + "network": "172.31.0.12\/32", + "peerId": "192.168.0.1", + "nexthops": [ + { + "ip": "192.168.0.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "172.31.0.13/32": [ + { + "valid": true, + "bestpath": true, + "prefix": "172.31.0.13", + "prefixLen": 32, + "network": "172.31.0.13\/32", + "peerId": "192.168.0.1", + "nexthops": [ + { + "ip": "192.168.0.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "172.31.0.14/32": [ + { + "valid": true, + "bestpath": true, + "prefix": "172.31.0.14", + "prefixLen": 32, + "network": "172.31.0.14\/32", + "peerId": "192.168.0.1", + "nexthops": [ + { + "ip": "192.168.0.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "172.31.0.15/32": [ + { + "valid": true, + "bestpath": true, + "prefix": "172.31.0.15", + "prefixLen": 32, + "network": "172.31.0.15\/32", + "peerId": "192.168.0.1", + "nexthops": [ + { + "ip": "192.168.0.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "172.31.0.20/32": [ + { + "valid": true, + "bestpath": true, + "prefix": "172.31.0.20", + "prefixLen": 32, + "network": "172.31.0.20\/32", + "peerId": "192.168.0.1", + "nexthops": [ + { + "ip": "192.168.0.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "172.31.0.111/32": [ + { + "valid": true, + "bestpath": true, + "prefix": "172.31.0.111", + "prefixLen": 32, + "network": "172.31.0.111\/32", + "peerId": "192.168.0.1", + "nexthops": [ + { + "ip": "192.168.0.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "192.0.2.0/24": [ + { + "valid": true, + "bestpath": true, + "prefix": "192.0.2.0", + "prefixLen": 24, + "network": "192.0.2.0\/24", + "peerId": "192.168.0.1", + "nexthops": [ + { + "ip": "192.168.0.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "192.168.255.0/24": [ + { + "valid": true, + "bestpath": true, + "prefix": "192.168.255.0", + "prefixLen": 24, + "network": "192.168.255.0\/24", + "peerId": "192.168.0.1", + "nexthops": [ + { + "ip": "192.168.0.1", + "afi": "ipv4", + "used": true + } + ] + } + ] + }, + "444:2": + { + "10.200.0.0/24": [ + { + "valid": true, + "bestpath": true, + "prefix": "10.200.0.0", + "prefixLen": 24, + "network": "10.200.0.0\/24", + "peerId": "(unspec)", + "nhVrfName": "vrf1", + "nexthops": [ + { + "ip": "0.0.0.0", + "afi": "ipv4", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r2/bgpd.conf b/tests/topotests/bgp_vpnv4_per_nexthop_label/r2/bgpd.conf new file mode 100644 index 0000000000..5fb79027a6 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r2/bgpd.conf @@ -0,0 +1,25 @@ +router bgp 65501 + bgp router-id 192.168.0.2 + no bgp ebgp-requires-policy + neighbor 192.168.0.1 remote-as 65500 + address-family ipv4 unicast + no neighbor 192.168.0.1 activate + exit-address-family + address-family ipv4 vpn + neighbor 192.168.0.1 activate + exit-address-family +! +router bgp 65501 vrf vrf1 + bgp router-id 192.168.0.2 + address-family ipv4 unicast + redistribute connected + label vpn export 102 + rd vpn export 444:2 + rt vpn both 52:100 + export vpn + import vpn + exit-address-family +! +interface r2-eth0 + mpls bgp forwarding +! diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r2/zebra.conf b/tests/topotests/bgp_vpnv4_per_nexthop_label/r2/zebra.conf new file mode 100644 index 0000000000..b7283a3592 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r2/zebra.conf @@ -0,0 +1,7 @@ +log stdout +interface r2-eth1 vrf vrf1 + ip address 10.200.0.2/24 +! +interface r2-eth0 + ip address 192.168.0.2/24 +! diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/rr/bgpd.conf b/tests/topotests/bgp_vpnv4_per_nexthop_label/rr/bgpd.conf new file mode 100644 index 0000000000..ff32314304 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/rr/bgpd.conf @@ -0,0 +1,13 @@ +router bgp 65500 + bgp router-id 100.100.100.100 + no bgp network import-check + neighbor 192.0.2.1 remote-as 65500 + neighbor 192.0.2.11 remote-as 65500 + neighbor 192.0.2.12 remote-as 65500 + address-family ipv4 unicast + neighbor 192.0.2.1 route-reflector-client + neighbor 192.0.2.11 route-reflector-client + neighbor 192.0.2.12 route-reflector-client + exit-address-family +! + diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/rr/zebra.conf b/tests/topotests/bgp_vpnv4_per_nexthop_label/rr/zebra.conf new file mode 100644 index 0000000000..315c22ab34 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/rr/zebra.conf @@ -0,0 +1,4 @@ +log stdout +interface rr-eth0 + ip address 192.0.2.100/24 +! diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/test_bgp_vpnv4_per_nexthop_label.py b/tests/topotests/bgp_vpnv4_per_nexthop_label/test_bgp_vpnv4_per_nexthop_label.py new file mode 100644 index 0000000000..966b717ab2 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/test_bgp_vpnv4_per_nexthop_label.py @@ -0,0 +1,795 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# test_bgp_vpnv4_per_nexthop_label.py +# +# Copyright 2023 6WIND S.A. +# + +""" + test_bgp_vpnv4_per_nexthop_label.py: Test the FRR BGP daemon using EBGP peering + Let us exchange VPNv4 updates between both devices + Updates from r1 will originate from the same RD, but will have separate + label values. + + +----------+ + | r11 | + |192.0.2.11+---+ + | | | +----+--------+ +----------+ + +----------+ | 192.0.2.1 |vrf | r1 |192.168.0.0/24| r2 | + +-------------------+ | 1+--------------+ | + +----------+ | |VRF1|AS65500 | | AS65501 | + | r12 | | +-------------+ | VPNV4| |VPNV4 | + |192.0.2.12+---+ |192.168.255.1+-+--+--------+ +----------+ + | | | + +----------+ | + | + +----------+ | + | r13 | | + |192.168. +---------+ + | 255.13 | + +----------+ +""" + +import os +import sys +import json +from functools import partial +import pytest +import functools + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# 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 + + +pytestmark = [pytest.mark.bgpd] + +PREFIXES_R11 = ["172.31.0.11/32", "172.31.0.20/32", "172.31.0.111/32"] +PREFIXES_R12 = ["172.31.0.12/32", "172.31.0.15/32"] +PREFIXES_R13 = ["172.31.0.13/32"] +PREFIXES_REDIST = ["172.31.0.14/32"] +PREFIXES_CONNECTED = ["192.168.255.0/24", "192.0.2.0/24"] + + +def build_topo(tgen): + "Build function" + + # Create 2 routers. + tgen.add_router("r1") + tgen.add_router("r2") + tgen.add_router("r11") + tgen.add_router("r12") + tgen.add_router("r13") + tgen.add_router("r14") + tgen.add_router("rr") + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r11"]) + switch.add_link(tgen.gears["r12"]) + switch.add_link(tgen.gears["rr"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r13"]) + + switch = tgen.add_switch("s5") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r14"]) + + +def _populate_iface(): + tgen = get_topogen() + cmds_list = [ + "ip link add vrf1 type vrf table 10", + "echo 100000 > /proc/sys/net/mpls/platform_labels", + "ip link set dev vrf1 up", + "ip link set dev {0}-eth1 master vrf1", + "echo 1 > /proc/sys/net/mpls/conf/{0}-eth0/input", + ] + cmds_list_plus = [ + "ip link set dev {0}-eth2 master vrf1", + ] + + for cmd in cmds_list: + input = cmd.format("r1") + logger.info("input: " + cmd) + output = tgen.net["r1"].cmd(cmd.format("r1")) + logger.info("output: " + output) + + for cmd in cmds_list_plus: + input = cmd.format("r1") + logger.info("input: " + cmd) + output = tgen.net["r1"].cmd(cmd.format("r1")) + logger.info("output: " + output) + + for cmd in cmds_list: + input = cmd.format("r2") + logger.info("input: " + cmd) + output = tgen.net["r2"].cmd(cmd.format("r2")) + logger.info("output: " + output) + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + 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)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + tgen.stop_topology() + + +def bgp_vpnv4_table_check(router, group, label_list=None, label_value_expected=None): + """ + Dump and check that vpnv4 entries have the same MPLS label value + * 'router': the router to check + * 'group': the list of prefixes to check. a single label value for the group has to be found + * 'label_list': check that the label values are not present in the vpnv4 entries + * that list is updated with the present label value + * 'label_value_expected': check that the mpls label read is the same as that value + """ + + stored_label_inited = False + for prefix in group: + dump = router.vtysh_cmd("show bgp ipv4 vpn {} json".format(prefix), isjson=True) + assert dump, "{0}, {1}, route distinguisher not present".format( + router.name, prefix + ) + for rd, pathes in dump.items(): + for path in pathes["paths"]: + assert ( + "remoteLabel" in path.keys() + ), "{0}, {1}, remoteLabel not present".format(router.name, prefix) + logger.info( + "{0}, {1}, label value is {2}".format( + router.name, prefix, path["remoteLabel"] + ) + ) + if stored_label_inited: + assert ( + path["remoteLabel"] == stored_label + ), "{0}, {1}, label value not expected one (expected {2}, observed {3}".format( + router.name, prefix, stored_label, path["remoteLabel"] + ) + else: + stored_label = path["remoteLabel"] + stored_label_inited = True + if label_list is not None: + assert ( + stored_label not in label_list + ), "{0}, {1}, label already detected in a previous prefix".format( + router.name, prefix + ) + label_list.add(stored_label) + + if label_value_expected: + assert ( + path["remoteLabel"] == label_value_expected + ), "{0}, {1}, label value not expected (expected {2}, observed {3}".format( + router.name, prefix, label_value_expected, path["remoteLabel"] + ) + + +def bgp_vpnv4_table_check_all(router, label_list=None, same=False): + """ + Dump and check that vpnv4 entries are correctly configured with specific label values + * 'router': the router to check + * 'label_list': check that the label values are not present in the vpnv4 entries + * that list is updated with the present label value found. + * 'same': by default, set to False. Addresses groups are classified by addresses. + * if set to True, all entries of all groups should have a unique label value + """ + if same: + bgp_vpnv4_table_check( + router, + group=PREFIXES_R11 + + PREFIXES_R12 + + PREFIXES_R13 + + PREFIXES_REDIST + + PREFIXES_CONNECTED, + label_list=label_list, + ) + else: + for group in ( + PREFIXES_R11, + PREFIXES_R12, + PREFIXES_R13, + PREFIXES_REDIST, + PREFIXES_CONNECTED, + ): + bgp_vpnv4_table_check(router, group=group, label_list=label_list) + + +def mpls_table_check(router, blacklist=None, label_list=None, whitelist=None): + """ + Dump and check 'show mpls table json' output. An assert is triggered in case test fails + * 'router': the router to check + * 'blacklist': the list of nexthops (IP or interface) that should not be on output + * 'label_list': the list of labels that should be in inLabel value + * 'whitelist': the list of nexthops (IP or interface) that should be on output + """ + nexthop_list = [] + if blacklist: + nexthop_list.append(blacklist) + logger.info("Checking MPLS labels on {}".format(router.name)) + dump = router.vtysh_cmd("show mpls table json", isjson=True) + for in_label, label_info in dump.items(): + if label_list is not None: + label_list.add(in_label) + for nh in label_info["nexthops"]: + assert ( + nh["installed"] == True and nh["type"] == "BGP" + ), "{}, show mpls table, nexthop is not installed".format(router.name) + if "nexthop" in nh.keys(): + assert ( + nh["nexthop"] not in nexthop_list + ), "{}, show mpls table, duplicated or blacklisted nexthop address".format( + router.name + ) + nexthop_list.append(nh["nexthop"]) + elif "interface" in nh.keys(): + assert ( + nh["interface"] not in nexthop_list + ), "{}, show mpls table, duplicated or blacklisted nexthop interface".format( + router.name + ) + nexthop_list.append(nh["interface"]) + else: + assert ( + 0 + ), "{}, show mpls table, entry with neither nexthop nor interface".format( + router.name + ) + + if whitelist: + for entry in whitelist: + assert ( + entry in nexthop_list + ), "{}, show mpls table, entry with nexthop {} not present in nexthop list".format( + router.name, entry + ) + + +def check_show_bgp_vpn_prefix_not_found(router, ipversion, prefix, rd, label=None): + output = json.loads( + router.vtysh_cmd("show bgp {} vpn {} json".format(ipversion, prefix)) + ) + if label: + expected = {rd: {"prefix": prefix, "paths": [{"remoteLabel": label}]}} + else: + expected = {rd: {"prefix": prefix}} + ret = topotest.json_cmp(output, expected) + if ret is None: + return "not good" + return None + + +def check_show_bgp_vpn_prefix_found(router, ipversion, prefix, rd): + output = json.loads( + router.vtysh_cmd("show bgp {} vpn {} json".format(ipversion, prefix)) + ) + expected = {rd: {"prefix": prefix}} + return topotest.json_cmp(output, expected) + + +def check_show_mpls_table_entry_label_found(router, inlabel, interface): + output = json.loads(router.vtysh_cmd("show mpls table {} json".format(inlabel))) + expected = { + "inLabel": inlabel, + "installed": True, + "nexthops": [{"interface": interface}], + } + return topotest.json_cmp(output, expected) + + +def check_show_mpls_table_entry_label_not_found(router, inlabel): + output = json.loads(router.vtysh_cmd("show mpls table {} json".format(inlabel))) + expected = {"inlabel": inlabel, "installed": True} + ret = topotest.json_cmp(output, expected) + if ret is None: + return "not good" + return None + + +def mpls_entry_get_interface(router, label): + """ + Assert that the label is in MPLS table + Assert an outgoing interface is programmed + return the outgoing interface + """ + outgoing_interface = None + + logger.info("Checking MPLS labels on {}".format(router.name)) + dump = router.vtysh_cmd("show mpls table {} json".format(label), isjson=True) + assert dump, "{0}, label {1} not present".format(router.name, label) + + for nh in dump["nexthops"]: + assert ( + "interface" in nh.keys() + ), "{}, show mpls table, nexthop interface not present for MPLS entry {}".format( + router.name, label + ) + + outgoing_interface = nh["interface"] + + return outgoing_interface + + +def test_protocols_convergence(): + """ + Assert that all protocols have converged + statuses as they depend on it. + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Check BGP IPv4 routing tables on VRF1 of r1 + logger.info("Checking BGP IPv4 routes for convergence on r1 VRF1") + router = tgen.gears["r1"] + json_file = "{}/{}/bgp_ipv4_routes_vrf1.json".format(CWD, router.name) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show bgp vrf vrf1 ipv4 json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + logger.info("Checking BGP VPNv4 routes for convergence on r2") + router = tgen.gears["r2"] + json_file = "{}/{}/bgp_vpnv4_routes.json".format(CWD, router.name) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show bgp ipv4 vpn json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + # Check BGP labels received on r2 + logger.info("Checking BGP VPNv4 labels on r2") + label_list = set() + bgp_vpnv4_table_check_all(tgen.gears["r2"], label_list) + + # Check MPLS labels received on r1 + mpls_table_check(tgen.gears["r1"], label_list) + + +def test_flapping_bgp_vrf_down(): + """ + Turn down a remote BGP session + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + logger.info("Unpeering BGP on r11") + tgen.gears["r11"].vtysh_cmd( + "configure terminal\nrouter bgp 65500\nno neighbor 192.0.2.100\n", + isjson=False, + ) + + def _bgp_prefix_not_found(router, vrf, ipversion, prefix): + output = json.loads( + router.vtysh_cmd( + "show bgp vrf {} {} {} json".format(vrf, ipversion, prefix) + ) + ) + expected = {"prefix": prefix} + ret = topotest.json_cmp(output, expected) + if ret is None: + return "not good" + return None + + # Check prefix from r11 is not present + test_func = functools.partial( + _bgp_prefix_not_found, tgen.gears["r1"], "vrf1", "ipv4", "172.31.0.11/32" + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert ( + success + ), "r1, prefix 172.31.0.11/32 from r11 did not disappear. r11 still connected to rr ?" + + # Check BGP updated received on r2 are not from r11 + logger.info("Checking BGP VPNv4 labels on r2") + for entry in PREFIXES_R11: + dump = tgen.gears["r2"].vtysh_cmd( + "show bgp ipv4 vpn {} json".format(entry), isjson=True + ) + for rd in dump: + assert False, "r2, {}, route distinguisher {} present".format(entry, rd) + + mpls_table_check(tgen.gears["r1"], blacklist=["192.0.2.11"]) + + +def test_flapping_bgp_vrf_up(): + """ + Turn up a remote BGP session + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + logger.info("Peering BGP on r11") + tgen.gears["r11"].vtysh_cmd( + "configure terminal\nrouter bgp 65500\nneighbor 192.0.2.100 remote-as 65500\n", + isjson=False, + ) + + # Check r2 gets prefix 172.31.0.11/128 + test_func = functools.partial( + check_show_bgp_vpn_prefix_found, + tgen.gears["r2"], + "ipv4", + "172.31.0.11/32", + "444:1", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert ( + success + ), "r2, prefix 172.31.0.11/32 from r11 not present. r11 still disconnected from rr ?" + bgp_vpnv4_table_check_all(tgen.gears["r2"]) + + +def test_recursive_route(): + """ + Test static recursive route redistributed over BGP + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Enabling recursive static route") + tgen.gears["r1"].vtysh_cmd( + "configure terminal\nvrf vrf1\nip route 172.31.0.30/32 172.31.0.20\n", + isjson=False, + ) + logger.info("Checking BGP VPNv4 labels on r2") + + # Check r2 received vpnv4 update with 172.31.0.30 + test_func = functools.partial( + check_show_bgp_vpn_prefix_found, + tgen.gears["r2"], + "ipv4", + "172.31.0.30/32", + "444:1", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r2, vpnv4 update 172.31.0.30 not found" + + bgp_vpnv4_table_check(tgen.gears["r2"], group=PREFIXES_R11 + ["172.31.0.30/32"]) + + # diagnostic + logger.info("Dumping label nexthop table") + tgen.gears["r1"].vtysh_cmd("show bgp vrf vrf1 label-nexthop detail", isjson=False) + logger.info("Dumping nexthop table") + tgen.gears["r1"].vtysh_cmd("show bgp vrf vrf1 nexthop detail", isjson=False) + + logger.info("Disabling recursive static route") + tgen.gears["r1"].vtysh_cmd( + "configure terminal\nvrf vrf1\nno ip route 172.31.0.30/32 172.31.0.20\n", + isjson=False, + ) + logger.info("Checking BGP VPNv4 labels on r2") + + # Check r2 removed 172.31.0.30 vpnv4 update + test_func = functools.partial( + check_show_bgp_vpn_prefix_not_found, + tgen.gears["r2"], + "ipv4", + "172.31.0.30/32", + "444:1", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r2, vpnv4 update 172.31.0.30 still present" + + +def test_prefix_changes_interface(): + """ + Test BGP update for a given prefix learnt on different interface + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Enabling a 172.31.0.50/32 prefix for r11") + tgen.gears["r11"].vtysh_cmd( + "configure terminal\nrouter bgp\naddress-family ipv4 unicast\nnetwork 172.31.0.50/32", + isjson=False, + ) + + # Check r2 received vpnv4 update with 172.31.0.50 + test_func = functools.partial( + check_show_bgp_vpn_prefix_found, + tgen.gears["r2"], + "ipv4", + "172.31.0.50/32", + "444:1", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r2, vpnv4 update 172.31.0.50 not found" + + # diagnostic + logger.info("Dumping label nexthop table") + tgen.gears["r1"].vtysh_cmd("show bgp vrf vrf1 label-nexthop detail", isjson=False) + + label_list = set() + bgp_vpnv4_table_check( + tgen.gears["r2"], + group=["172.31.0.11/32", "172.31.0.111/32", "172.31.0.50/32"], + label_list=label_list, + ) + + assert ( + len(label_list) == 1 + ), "Multiple Label values found for updates from r11 found" + + oldlabel = label_list.pop() + logger.info("r1, getting the outgoing interface used by label {}".format(oldlabel)) + old_outgoing_interface = mpls_entry_get_interface(tgen.gears["r1"], oldlabel) + logger.info( + "r1, outgoing interface used by label {} is {}".format( + oldlabel, old_outgoing_interface + ) + ) + + logger.info("Moving the 172.31.0.50/32 prefix from r11 to r13") + tgen.gears["r11"].vtysh_cmd( + "configure terminal\nrouter bgp\naddress-family ipv4 unicast\nno network 172.31.0.50/32", + isjson=False, + ) + tgen.gears["r13"].vtysh_cmd( + "configure terminal\nrouter bgp\naddress-family ipv4 unicast\nnetwork 172.31.0.50/32", + isjson=False, + ) + + # Check r2 removed 172.31.0.50 vpnv4 update with old label + test_func = functools.partial( + check_show_bgp_vpn_prefix_not_found, + tgen.gears["r2"], + "ipv4", + "172.31.0.50/32", + "444:1", + label=oldlabel, + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert ( + success + ), "r2, vpnv4 update 172.31.0.50 with old label {0} still present".format(oldlabel) + + # diagnostic + logger.info("Dumping label nexthop table") + tgen.gears["r1"].vtysh_cmd("show bgp vrf vrf1 label-nexthop detail", isjson=False) + + # Check r2 received new 172.31.0.50 vpnv4 update + test_func = functools.partial( + check_show_bgp_vpn_prefix_found, + tgen.gears["r2"], + "ipv4", + "172.31.0.50/32", + "444:1", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r2, vpnv4 update 172.31.0.50 not found" + + label_list = set() + bgp_vpnv4_table_check( + tgen.gears["r2"], + group=PREFIXES_R13 + ["172.31.0.50/32"], + label_list=label_list, + ) + assert ( + len(label_list) == 1 + ), "Multiple Label values found for updates from r13 found" + + newlabel = label_list.pop() + logger.info("r1, getting the outgoing interface used by label {}".format(newlabel)) + new_outgoing_interface = mpls_entry_get_interface(tgen.gears["r1"], newlabel) + logger.info( + "r1, outgoing interface used by label {} is {}".format( + newlabel, new_outgoing_interface + ) + ) + if old_outgoing_interface == new_outgoing_interface: + assert 0, "r1, outgoing interface did not change whereas BGP update moved" + + logger.info("Restoring state by removing the 172.31.0.50/32 prefix from r13") + tgen.gears["r13"].vtysh_cmd( + "configure terminal\nrouter bgp\naddress-family ipv4 unicast\nno network 172.31.0.50/32", + isjson=False, + ) + + +def test_changing_default_label_value(): + """ + Change the MPLS default value + Check that r1 VPNv4 entries have the 222 label value + Check that MPLS entry with old label value is no more present + Check that MPLS entry for local traffic has inLabel set to 222 + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r1"] + + # counting the number of labels used in the VPNv4 table + label_list = set() + logger.info("r1, vpnv4 table, check the number of labels used before modification") + bgp_vpnv4_table_check_all(router, label_list) + old_len = len(label_list) + assert ( + old_len != 1 + ), "r1, number of labels used should be greater than 1, oberved {} ".format(old_len) + + logger.info("r1, vrf1, changing the default MPLS label value to export to 222") + router.vtysh_cmd( + "configure terminal\nrouter bgp 65500 vrf vrf1\naddress-family ipv4 unicast\nlabel vpn export 222\n", + isjson=False, + ) + + # Check r1 updated the MPLS entry with the 222 label value + logger.info( + "r1, mpls table, check that MPLS entry with inLabel set to 222 has vrf1 interface" + ) + test_func = functools.partial( + check_show_mpls_table_entry_label_found, router, 222, "vrf1" + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r1, mpls entry with label 222 not found" + + # check label repartition is ok + logger.info("r1, vpnv4 table, check the number of labels used after modification") + label_list = set() + bgp_vpnv4_table_check_all(router, label_list) + new_len = len(label_list) + assert ( + old_len == new_len + ), "r1, number of labels after modification differ from previous, observed {}, expected {} ".format( + new_len, old_len + ) + + logger.info( + "r1, vpnv4 table, check that prefixes that were using the vrf label have refreshed the label value to 222" + ) + bgp_vpnv4_table_check( + router, group=["192.168.255.0/24", "192.0.2.0/24"], label_value_expected=222 + ) + + +def test_unconfigure_allocation_mode_nexthop(): + """ + Test unconfiguring allocation mode per nexthop + Check that show mpls table has no entry with label 17 (previously used) + Check that all VPN updates on r1 should have label value moved to 222 + Check that show mpls table will only have 222 label value + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Unconfiguring allocation mode per nexthop") + router = tgen.gears["r1"] + router.vtysh_cmd( + "configure terminal\nrouter bgp 65500 vrf vrf1\naddress-family ipv4 unicast\nno label vpn export allocation-mode per-nexthop\n", + isjson=False, + ) + + # Check r1 updated the MPLS entry with the 222 label value + logger.info( + "r1, mpls table, check that MPLS entry with inLabel set to 17 is not present" + ) + test_func = functools.partial( + check_show_mpls_table_entry_label_not_found, router, 17 + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r1, mpls entry with label 17 still present" + + # Check vpnv4 routes from r1 + logger.info("Checking vpnv4 routes on r1") + label_list = set() + bgp_vpnv4_table_check_all(router, label_list=label_list, same=True) + assert len(label_list) == 1, "r1, multiple Label values found for vpnv4 updates" + + new_label = label_list.pop() + assert ( + new_label == 222 + ), "r1, wrong label value in VPNv4 table, expected 222, observed {}".format( + new_label + ) + + # Check mpls table with 222 value + logger.info("Checking MPLS values on show mpls table of r1") + label_list = set() + label_list.add(222) + mpls_table_check(router, label_list=label_list) + + +def test_reconfigure_allocation_mode_nexthop(): + """ + Test re-configuring allocation mode per nexthop + Check that show mpls table has no entry with label 17 + Check that all VPN updates on r1 should have multiple label values and not only 222 + Check that show mpls table will have multiple label values and not only 222 + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Reconfiguring allocation mode per nexthop") + router = tgen.gears["r1"] + router.vtysh_cmd( + "configure terminal\nrouter bgp 65500 vrf vrf1\naddress-family ipv4 unicast\nlabel vpn export allocation-mode per-nexthop\n", + isjson=False, + ) + + # Check that show mpls table has no entry with label 17 + logger.info( + "r1, mpls table, check that MPLS entry with inLabel set to 17 is present" + ) + test_func = functools.partial( + check_show_mpls_table_entry_label_not_found, router, 17 + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r1, mpls entry with label 17 still present" + + # Check vpnv4 routes from r1 + logger.info("Checking vpnv4 routes on r1") + label_list = set() + bgp_vpnv4_table_check_all(router, label_list=label_list) + assert len(label_list) != 1, "r1, only 1 label values found for vpnv4 updates" + + # Check mpls table with all values + logger.info("Checking MPLS values on show mpls table of r1") + mpls_table_check(router, label_list=label_list) + + +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)) -- 2.39.5