]> git.proxmox.com Git - mirror_frr.git/blob - tests/topotests/bgp_remove_private_as/test_bgp_remove_private_as.py
Merge pull request #12798 from donaldsharp/rib_match_multicast
[mirror_frr.git] / tests / topotests / bgp_remove_private_as / test_bgp_remove_private_as.py
1 #!/usr/bin/env python
2 # SPDX-License-Identifier: ISC
3
4 #
5 # test_bgp_remove_private_as.py
6 #
7 # Copyright (C) 2022 NVIDIA Corporation
8 # Trey Aspelund
9 #
10
11 """
12 test_bgp_remove_private_as.py tests the following conditions:
13 1. "remove-private-AS" strips all private ASNs from the AS-path unless:
14 a. the ASN belongs to the peer
15 b. the ASN is both local + private
16 c. the AS-path is not completely comprised of public ASNs
17 2. "remove-private-AS all" strips all private ASNs from the AS-path unless:
18 a. the ASN belongs to the peer
19 b. the ASN is both local + private
20 3. "remove-private-AS replace-AS" swaps private ASNs with local ASN unless:
21 a. the ASN belongs to the peer
22 b. the AS-path is not completely comprised of public ASNs
23 4. "remove-private-AS all replace-AS" swaps private ASNs with local ASN unless:
24 a. the ASN belongs to the peer
25
26 All conditions are tested while the local ASN is private.
27 All conditions are tested while the local ASN is public.
28 All conditions are tested against an eBGP peer in a private ASN.
29 All conditions are tested against an eBGP peer in a public ASN.
30 """
31
32 import os
33 import sys
34 import json
35 import time
36 import pytest
37
38 CWD = os.path.dirname(os.path.realpath(__file__))
39 sys.path.append(os.path.join(CWD, "../"))
40
41 # pylint: disable=C0413
42 from lib import topotest
43 from lib.topogen import Topogen, TopoRouter, get_topogen
44 from functools import partial
45
46 pytestmark = [pytest.mark.bgpd]
47
48
49 def build_topo(tgen):
50 """
51 We are effectively creating two hub/spoke topologies with r2 and r5 acting
52 as hubs. "remove-private-AS" will be configured on r2/r5 towards r3/r4, and
53 r1 will act as the originator of the test prefixes. AS-Path validation will
54 be done on r3/r4.
55
56 Topology:
57 +-----+ +-----+ +-----+
58 | r1 |----->|r2/r5|---->| r3 |
59 +-----+ +-----+ +-----+
60 |
61 v
62 +-----+
63 | r4 |
64 +-----+
65 ASNs:
66 - r1: 65001
67 - r2: 65002
68 - r3: 65003
69 - r4: 4444
70 - r5: 5555
71 """
72 for routern in range(1, 6):
73 tgen.add_router(f"r{routern}")
74
75 #######################
76 # Connections to r2
77 #######################
78 switch = tgen.add_switch("s1")
79 switch.add_link(tgen.gears["r1"])
80 switch.add_link(tgen.gears["r2"])
81
82 switch = tgen.add_switch("s2")
83 switch.add_link(tgen.gears["r2"])
84 switch.add_link(tgen.gears["r3"])
85
86 switch = tgen.add_switch("s3")
87 switch.add_link(tgen.gears["r2"])
88 switch.add_link(tgen.gears["r4"])
89
90 #######################
91 # Connections to r5
92 #######################
93 switch = tgen.add_switch("s4")
94 switch.add_link(tgen.gears["r1"])
95 switch.add_link(tgen.gears["r5"])
96
97 switch = tgen.add_switch("s5")
98 switch.add_link(tgen.gears["r3"])
99 switch.add_link(tgen.gears["r5"])
100
101 switch = tgen.add_switch("s6")
102 switch.add_link(tgen.gears["r4"])
103 switch.add_link(tgen.gears["r5"])
104
105
106 def setup_module(mod):
107 tgen = Topogen(build_topo, mod.__name__)
108 tgen.start_topology()
109
110 router_list = tgen.routers()
111
112 for i, (rname, router) in enumerate(router_list.items(), 1):
113 router.load_config(
114 TopoRouter.RD_ZEBRA, os.path.join(CWD, f"{rname}/zebra.conf")
115 )
116 router.load_config(TopoRouter.RD_BGP, os.path.join(CWD, f"{rname}/bgpd.conf"))
117
118 tgen.start_router()
119
120
121 def teardown_module(mod):
122 tgen = get_topogen()
123 tgen.stop_topology()
124
125
126 def test_bgp_remove_private_as():
127 tgen = get_topogen()
128
129 if tgen.routers_have_failure():
130 pytest.skip(tgen.errors)
131
132 # Test routes
133 prefixes = [
134 "100.64.0.0/32",
135 "100.64.0.1/32",
136 "100.64.0.2/32",
137 "100.64.0.3/32",
138 "100.64.0.4/32",
139 ]
140
141 # r2/r5 are setup with remove-private-AS configs.
142 tx_routers = ["r2", "r5"]
143
144 # We will validate the paths received by r3/r4.
145 rx_routers = ["r3", "r4"]
146
147 # Config options for remove-private-AS
148 remove_types = [
149 "remove-private-AS",
150 "remove-private-AS all",
151 "remove-private-AS replace-AS",
152 "remove-private-AS all replace-AS",
153 ]
154
155 # Expected as-paths for each test route from the perspective of each
156 # rx_router, accounting for each variation of remove-private-AS.
157 #
158 # Structure:
159 # expected_paths = {
160 # rx_router: {
161 # remove_type: {
162 # tx_router: {
163 # prefix: "path"
164 # }
165 # }
166 # }
167 # }
168 expected_paths = {
169 "r3": {
170 "remove-private-AS": {
171 "r2": {
172 "100.64.0.0/32": "65002",
173 "100.64.0.1/32": "65002 65003 65003",
174 "100.64.0.2/32": "65002 65001 4200000000 1000 4200000001 2000 4200000002",
175 "100.64.0.3/32": "65002 65001 65003 4200000000 1000 4200000001 2000 4200000002 65003",
176 "100.64.0.4/32": "65002 65001 1000 2000 2000 3000",
177 },
178 "r5": {
179 "100.64.0.0/32": "5555",
180 "100.64.0.1/32": "5555 65003 65003",
181 "100.64.0.2/32": "5555 65001 4200000000 1000 4200000001 2000 4200000002",
182 "100.64.0.3/32": "5555 65001 65003 4200000000 1000 4200000001 2000 4200000002 65003",
183 "100.64.0.4/32": "5555 65001 1000 2000 2000 3000",
184 },
185 },
186 "remove-private-AS all": {
187 "r2": {
188 "100.64.0.0/32": "65002",
189 "100.64.0.1/32": "65002 65003 65003",
190 "100.64.0.2/32": "65002 1000 2000",
191 "100.64.0.3/32": "65002 65003 1000 2000 65003",
192 "100.64.0.4/32": "65002 1000 2000 2000 3000",
193 },
194 "r5": {
195 "100.64.0.0/32": "5555",
196 "100.64.0.1/32": "5555 65003 65003",
197 "100.64.0.2/32": "5555 1000 2000",
198 "100.64.0.3/32": "5555 65003 1000 2000 65003",
199 "100.64.0.4/32": "5555 1000 2000 2000 3000",
200 },
201 },
202 "remove-private-AS replace-AS": {
203 "r2": {
204 "100.64.0.0/32": "65002 65002 65002 65002 65002",
205 "100.64.0.1/32": "65002 65002 65003 65002 65002 65002 65003",
206 "100.64.0.2/32": "65002 65001 4200000000 1000 4200000001 2000 4200000002",
207 "100.64.0.3/32": "65002 65001 65003 4200000000 1000 4200000001 2000 4200000002 65003",
208 "100.64.0.4/32": "65002 65001 1000 2000 2000 3000",
209 },
210 "r5": {
211 "100.64.0.0/32": "5555 5555 5555 5555 5555",
212 "100.64.0.1/32": "5555 5555 65003 5555 5555 5555 65003",
213 "100.64.0.2/32": "5555 65001 4200000000 1000 4200000001 2000 4200000002",
214 "100.64.0.3/32": "5555 65001 65003 4200000000 1000 4200000001 2000 4200000002 65003",
215 "100.64.0.4/32": "5555 65001 1000 2000 2000 3000",
216 },
217 },
218 "remove-private-AS all replace-AS": {
219 "r2": {
220 "100.64.0.0/32": "65002 65002 65002 65002 65002",
221 "100.64.0.1/32": "65002 65002 65003 65002 65002 65002 65003",
222 "100.64.0.2/32": "65002 65002 65002 1000 65002 2000 65002",
223 "100.64.0.3/32": "65002 65002 65003 65002 1000 65002 2000 65002 65003",
224 "100.64.0.4/32": "65002 65002 1000 2000 2000 3000",
225 },
226 "r5": {
227 "100.64.0.0/32": "5555 5555 5555 5555 5555",
228 "100.64.0.1/32": "5555 5555 65003 5555 5555 5555 65003",
229 "100.64.0.2/32": "5555 5555 5555 1000 5555 2000 5555",
230 "100.64.0.3/32": "5555 5555 65003 5555 1000 5555 2000 5555 65003",
231 "100.64.0.4/32": "5555 5555 1000 2000 2000 3000",
232 },
233 },
234 },
235 "r4": {
236 "remove-private-AS": {
237 "r2": {
238 "100.64.0.0/32": "65002",
239 "100.64.0.1/32": "65002",
240 "100.64.0.2/32": "65002 65001 4200000000 1000 4200000001 2000 4200000002",
241 "100.64.0.3/32": "65002 65001 65003 4200000000 1000 4200000001 2000 4200000002 65003",
242 "100.64.0.4/32": "65002 65001 1000 2000 2000 3000",
243 },
244 "r5": {
245 "100.64.0.0/32": "5555",
246 "100.64.0.1/32": "5555",
247 "100.64.0.2/32": "5555 65001 4200000000 1000 4200000001 2000 4200000002",
248 "100.64.0.3/32": "5555 65001 65003 4200000000 1000 4200000001 2000 4200000002 65003",
249 "100.64.0.4/32": "5555 65001 1000 2000 2000 3000",
250 },
251 },
252 "remove-private-AS all": {
253 "r2": {
254 "100.64.0.0/32": "65002",
255 "100.64.0.1/32": "65002",
256 "100.64.0.2/32": "65002 1000 2000",
257 "100.64.0.3/32": "65002 1000 2000",
258 "100.64.0.4/32": "65002 1000 2000 2000 3000",
259 },
260 "r5": {
261 "100.64.0.0/32": "5555",
262 "100.64.0.1/32": "5555",
263 "100.64.0.2/32": "5555 1000 2000",
264 "100.64.0.3/32": "5555 1000 2000",
265 "100.64.0.4/32": "5555 1000 2000 2000 3000",
266 },
267 },
268 "remove-private-AS replace-AS": {
269 "r2": {
270 "100.64.0.0/32": "65002 65002 65002 65002 65002",
271 "100.64.0.1/32": "65002 65002 65002 65002 65002 65002 65002",
272 "100.64.0.2/32": "65002 65001 4200000000 1000 4200000001 2000 4200000002",
273 "100.64.0.3/32": "65002 65001 65003 4200000000 1000 4200000001 2000 4200000002 65003",
274 "100.64.0.4/32": "65002 65001 1000 2000 2000 3000",
275 },
276 "r5": {
277 "100.64.0.0/32": "5555 5555 5555 5555 5555",
278 "100.64.0.1/32": "5555 5555 5555 5555 5555 5555 5555",
279 "100.64.0.2/32": "5555 65001 4200000000 1000 4200000001 2000 4200000002",
280 "100.64.0.3/32": "5555 65001 65003 4200000000 1000 4200000001 2000 4200000002 65003",
281 "100.64.0.4/32": "5555 65001 1000 2000 2000 3000",
282 },
283 },
284 "remove-private-AS all replace-AS": {
285 "r2": {
286 "100.64.0.0/32": "65002 65002 65002 65002 65002",
287 "100.64.0.1/32": "65002 65002 65002 65002 65002 65002 65002",
288 "100.64.0.2/32": "65002 65002 65002 1000 65002 2000 65002",
289 "100.64.0.3/32": "65002 65002 65002 65002 1000 65002 2000 65002 65002",
290 "100.64.0.4/32": "65002 65002 1000 2000 2000 3000",
291 },
292 "r5": {
293 "100.64.0.0/32": "5555 5555 5555 5555 5555",
294 "100.64.0.1/32": "5555 5555 5555 5555 5555 5555 5555",
295 "100.64.0.2/32": "5555 5555 5555 1000 5555 2000 5555",
296 "100.64.0.3/32": "5555 5555 5555 5555 1000 5555 2000 5555 5555",
297 "100.64.0.4/32": "5555 5555 1000 2000 2000 3000",
298 },
299 },
300 },
301 }
302
303 # Simple lookup of remote peer ip by routers in session (local --> remote).
304 #
305 # Structure:
306 # peer_to_ip = {
307 # local_rtr: {
308 # peer_rtr: peer_ip
309 # }
310 # }
311 peer_to_ip = {
312 "r1": {"r2": "203.0.113.1", "r5": "203.0.113.3"},
313 "r2": {"r1": "203.0.113.0", "r3": "203.0.113.4", "r4": "203.0.113.8"},
314 "r3": {"r2": "203.0.113.5", "r5": "203.0.113.7"},
315 "r4": {"r2": "203.0.113.9", "r5": "203.0.113.11"},
316 "r5": {"r1": "203.0.113.2", "r3": "203.0.113.6", "r4": "203.0.113.10"},
317 }
318
319 def __bgp_up():
320 """Return True if all configured peers are Established."""
321 for router in tx_routers:
322 output = json.loads(
323 tgen.gears[router].vtysh_cmd("show ip bgp summary json")
324 )
325 numPeers = output["ipv4Unicast"]["totalPeers"]
326 numConverged = 0
327 for peer_data in output["ipv4Unicast"]["peers"].values():
328 if peer_data["state"] == "Established":
329 numConverged += 1
330 if numConverged == numPeers:
331 return True
332 return False
333
334 def __bgp_converged():
335 """Return True if all prefixes have been received from tx_routers."""
336 for router in rx_routers:
337 output = json.loads(
338 tgen.gears[router].vtysh_cmd("show ip bgp summary json")
339 )
340 numPeers = output["ipv4Unicast"]["totalPeers"]
341 numConverged = 0
342 for peer in tx_routers:
343 peer_ip = peer_to_ip[router][peer]
344 numPrefixes = output["ipv4Unicast"]["peers"][peer_ip]["pfxRcd"]
345 if numPrefixes == len(prefixes):
346 numConverged += 1
347 if numConverged == numPeers:
348 return True
349 return False
350
351 def _routers_up(tx_rtrs, rx_rtrs):
352 """Ensure all BGP sessions are up and all routes are installed."""
353 # all sessions go through tx_routers, so ensure all their peers are up
354 test_func = partial(__bgp_up)
355 _, result = topotest.run_and_expect(test_func, True, count=60, wait=0.5)
356 assert result == True, "Not all peers in Established state!"
357
358 # ensure correct number of routes are installed
359 test_func = partial(__bgp_converged)
360 _, result = topotest.run_and_expect(test_func, True, count=60, wait=0.5)
361 assert result == True, "Not all routes installed in time!"
362
363 def _change_remove_type(new_type, op):
364 """Update config with next remove-private-AS config variant."""
365 no = "no" if op == "del" else ""
366 for tr in tx_routers:
367 for rr in rx_routers:
368 p_ip = peer_to_ip[tr][rr]
369 tgen.gears[tr].vtysh_multicmd(
370 f"""
371 configure terminal
372 router bgp
373 address-family ipv4 unicast
374 {no} neighbor {p_ip} {new_type}
375 """
376 )
377
378 def _validate_paths(remove_type):
379 """Compare actual AS-Path against expected AS-Path."""
380 for rtr in rx_routers:
381 for peer in tx_routers:
382 p_ip = peer_to_ip[rtr][peer]
383 adj_rib_in = json.loads(
384 tgen.gears[rtr].vtysh_cmd(
385 f"show ip bgp neighbor {p_ip} received-routes json"
386 )
387 )
388 for pfx in prefixes:
389 good_path = expected_paths[rtr][remove_type][peer][pfx]
390 real_path = adj_rib_in["receivedRoutes"][pfx]["path"]
391 return real_path == good_path
392
393 #######################
394 # Begin Test
395 #######################
396
397 # make sure all peers come up and exchange routes
398 _routers_up(tx_routers, rx_routers)
399
400 # test each variation of remove-private-AS
401 for rmv_type in remove_types:
402 _change_remove_type(rmv_type, "add")
403
404 test_func = partial(_validate_paths, rmv_type)
405 _, result = topotest.run_and_expect(test_func, True, count=60, wait=0.5)
406 assert result == True, "Not all routes have correct AS-Path values!"
407
408 # each variation sets a separate peer flag in bgpd. we need to clear
409 # the old flag after each iteration so we only test the flags we expect.
410 _change_remove_type(rmv_type, "del")
411
412 return True
413
414
415 if __name__ == "__main__":
416 args = ["-s"] + sys.argv[1:]
417 sys.exit(pytest.main(args))