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