From a0d102f4a926aed9cba8a2cd1018d190d925220e Mon Sep 17 00:00:00 2001 From: Philippe Guibert Date: Tue, 6 May 2025 16:56:11 +0200 Subject: [PATCH 1/8] topotests: add bgp_evpn_rt5_vrf_underlay with backbone in non default vrf Add a second test in bgp_evpn_rt5 folder that runs the same testing, but by using a non default vrf as evpn backbone for R2. Signed-off-by: Philippe Guibert --- .../bgp_evpn_rt5/r2/frr_vrf_underlay.conf | 98 +++++++++++++++++++ tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py | 67 ++++++++++--- .../test_bgp_evpn_vrf_underlay.py | 26 +++++ 3 files changed, 180 insertions(+), 11 deletions(-) create mode 100644 tests/topotests/bgp_evpn_rt5/r2/frr_vrf_underlay.conf create mode 100644 tests/topotests/bgp_evpn_rt5/test_bgp_evpn_vrf_underlay.py diff --git a/tests/topotests/bgp_evpn_rt5/r2/frr_vrf_underlay.conf b/tests/topotests/bgp_evpn_rt5/r2/frr_vrf_underlay.conf new file mode 100644 index 000000000000..e6993b3f621d --- /dev/null +++ b/tests/topotests/bgp_evpn_rt5/r2/frr_vrf_underlay.conf @@ -0,0 +1,98 @@ +! debug zebra vxlan +! debug bgp neighbor-events +! debug bgp updates +! debug bgp zebra + +vrf vrf-101 + vni 101 + exit-vrf +! +vrf vrf-102 + vni 102 + exit-vrf +! +vrf vrf-evpn + ip route 0.0.0.0/0 192.168.2.101 + exit-vrf +! +interface loop101 vrf vrf-101 + ip address 10.0.101.2/32 + ipv6 address fd01::2/128 +! +interface loop102 vrf vrf-102 + ip address 10.0.102.2/32 + ipv6 address fd02::2/128 +! +int lo + ip address 192.168.0.2/32 +! +interface eth-rr vrf vrf-evpn + ip address 192.168.2.2/24 +! +! +router bgp 65000 vrf vrf-evpn + bgp router-id 192.168.0.2 + bgp log-neighbor-changes + no bgp default ipv4-unicast + neighbor 192.168.2.101 peer-group + neighbor 192.168.2.101 remote-as 65000 + neighbor 192.168.2.101 capability extended-nexthop + ! + address-family l2vpn evpn + neighbor 192.168.2.101 activate + advertise-all-vni + exit-address-family +! +router bgp 65000 vrf vrf-101 + bgp router-id 10.0.101.2 + bgp log-neighbor-changes + no bgp network import-check + address-family ipv4 unicast + network 10.0.101.2/32 + network 10.0.101.12/32 + exit-address-family + address-family ipv6 unicast + network fd01::2/128 + network fd01::12/128 + exit-address-family + address-family l2vpn evpn + rd 65000:2 + route-target both 65000:101 + advertise ipv4 unicast route-map rmap4 + advertise ipv6 unicast route-map rmap6 + exit-address-family + ! +router bgp 65000 vrf vrf-102 + bgp router-id 10.0.102.2 + bgp log-neighbor-changes + no bgp network import-check + address-family ipv4 unicast + network 10.0.102.2/32 + exit-address-family + address-family ipv6 unicast + network fd02::2/128 + exit-address-family + address-family l2vpn evpn + rd 65000:4 + route-target both 65000:102 + advertise ipv4 unicast + advertise ipv6 unicast + exit-address-family + ! +access-list acl4_1 seq 10 permit 10.0.101.2/32 +access-list acl4_2 seq 10 permit 10.0.101.12/32 +ipv6 access-list acl6_1 seq 10 permit fd01::2/128 +ipv6 access-list acl6_2 seq 10 permit fd01::12/128 +route-map rmap4 permit 1 + match ip address acl4_1 +exit +route-map rmap4 deny 2 + match ip address acl4_2 +exit +route-map rmap6 permit 1 + match ipv6 address acl6_1 +exit +route-map rmap6 deny 2 + match ipv6 address acl6_2 +exit + diff --git a/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py b/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py index 18a4bc6ecadd..3162cb354482 100644 --- a/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py +++ b/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py @@ -37,6 +37,8 @@ pytestmark = [pytest.mark.bgpd] +R2_VRF_UNDERLAY = None + def build_topo(tgen): "Build function" @@ -56,10 +58,16 @@ def connect_routers(tgen, left, right): def setup_module(mod): "Sets up the pytest environment" + global R2_VRF_UNDERLAY tgen = Topogen(build_topo, mod.__name__) tgen.start_topology() + if "vrf_underlay" in mod.__name__: + R2_VRF_UNDERLAY = "vrf-evpn" + else: + R2_VRF_UNDERLAY = None + router_list = tgen.routers() krel = platform.release() @@ -71,6 +79,19 @@ def setup_module(mod): ) return pytest.skip("Skipping BGP EVPN RT5 NETNS Test. Kernel not supported") + if R2_VRF_UNDERLAY: + tgen.gears["r2"].cmd( + """ + ip link add vrf-evpn type vrf table 150 + ip link set dev vrf-evpn up + ip link add loopevpn type dummy + ip link set dev loopevpn master vrf-evpn + ip link set dev loopevpn up + ip link set dev eth-rr master vrf-evpn + ip link set dev eth-rr up + """ + ) + r1 = tgen.net["r1"] for vrf in (101, 102): ns = "vrf-{}".format(vrf) @@ -119,7 +140,11 @@ def setup_module(mod): logger.info("Loading router %s" % rname) if rname == "r1": router.use_netns_vrf() - router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + if rname == "r2" and R2_VRF_UNDERLAY: + frr_config = f"{rname}/frr_vrf_underlay.conf" + else: + frr_config = f"{rname}/frr.conf" + router.load_frr_config(os.path.join(CWD, frr_config)) # Initialize all routers. tgen.start_router() @@ -574,11 +599,14 @@ def test_evpn_restore_ipv4(): _test_router_check_evpn_contexts(tgen.gears["r1"]) -def _get_established_epoch(router, peer): +def _get_established_epoch(router, peer, vrf=None): """ Get the established epoch for a peer """ - output = router.vtysh_cmd(f"show bgp neighbor {peer} json", isjson=True) + vrf_arg = f" vrf {vrf}" if vrf else "" + output = router.vtysh_cmd( + f"show bgp{vrf_arg} neighbor {peer} json", isjson=True + ) assert peer in output, "peer not found" peer_info = output[peer] assert "bgpState" in peer_info, "peer state not found" @@ -587,11 +615,14 @@ def _get_established_epoch(router, peer): return peer_info["bgpTimerUpEstablishedEpoch"] -def _check_established_epoch_differ(router, peer, last_established_epoch): +def _check_established_epoch_differ(router, peer, last_established_epoch, vrf=None): """ Check that the established epoch has changed """ - output = router.vtysh_cmd(f"show bgp neighbor {peer} json", isjson=True) + vrf_arg = f" vrf {vrf}" if vrf else "" + output = router.vtysh_cmd( + f"show bgp{vrf_arg} neighbor {peer} json", isjson=True + ) assert peer in output, "peer not found" peer_info = output[peer] assert "bgpState" in peer_info, "peer state not found" @@ -606,7 +637,7 @@ def _check_established_epoch_differ(router, peer, last_established_epoch): return None -def _test_epoch_after_clear(router, peer, last_established_epoch): +def _test_epoch_after_clear(router, peer, last_established_epoch, vrf=None): """ Checking that the established epoch has changed and the peer is in Established state again after clear Without this, the second session is cleared as well on slower systems (like CI) @@ -616,6 +647,7 @@ def _test_epoch_after_clear(router, peer, last_established_epoch): router, peer, last_established_epoch, + vrf=vrf ) _, result = topotest.run_and_expect(test_func, None, count=20, wait=1) assert ( @@ -688,9 +720,13 @@ def test_evpn_multipath(): }, "r2": { "raw_config": [ - "interface eth-rr", + "interface eth-rr {0}".format( + f"vrf {R2_VRF_UNDERLAY}" if R2_VRF_UNDERLAY else "" + ), "ip address 192.168.102.2/24", - "router bgp 65000", + "router bgp 65000 {0}".format( + f"vrf {R2_VRF_UNDERLAY}" if R2_VRF_UNDERLAY else "" + ), "neighbor 192.168.102.101 remote-as 65000", "neighbor 192.168.102.101 capability extended-nexthop", "neighbor 192.168.102.101 update-source 192.168.102.2", @@ -744,7 +780,9 @@ def test_evpn_multipath(): r2_addr = "192.168.2.2" if i % 2 == 0 else "192.168.102.2" # Retrieving the last established epoch from the r2 to check against - last_established_epoch = _get_established_epoch(r2, rr_addr) + last_established_epoch = _get_established_epoch( + r2, rr_addr, vrf=R2_VRF_UNDERLAY if R2_VRF_UNDERLAY else None + ) if last_established_epoch is None: assert False, "Failed to retrieve established epoch for peer {}".format( rr_addr @@ -752,7 +790,12 @@ def test_evpn_multipath(): rr.vtysh_cmd("clear bgp {0}".format(r2_addr)) - _test_epoch_after_clear(r2, rr_addr, last_established_epoch) + _test_epoch_after_clear( + r2, + rr_addr, + last_established_epoch, + vrf=R2_VRF_UNDERLAY if R2_VRF_UNDERLAY else None, + ) _test_wait_for_multipath_convergence(r2, expected_paths=2) _test_rmac_present(r2) _test_router_check_evpn_next_hop(expected_paths=2) @@ -787,7 +830,9 @@ def test_shutdown_multipath_check_next_hops(): }, "r2": { "raw_config": [ - "router bgp 65000", + "router bgp 65000 {0}".format( + f"vrf {R2_VRF_UNDERLAY}" if R2_VRF_UNDERLAY else "" + ), "neighbor 192.168.102.101 shutdown", ] }, diff --git a/tests/topotests/bgp_evpn_rt5/test_bgp_evpn_vrf_underlay.py b/tests/topotests/bgp_evpn_rt5/test_bgp_evpn_vrf_underlay.py new file mode 100644 index 000000000000..0b30fffc14c2 --- /dev/null +++ b/tests/topotests/bgp_evpn_rt5/test_bgp_evpn_vrf_underlay.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_evpn_vrf_underlay.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2025 by 6WIND +# + +""" +bgp_evpn_vrf_underlay.py: run test_bgp_evpn tests by using r2 evpn backbone in a L3VRF. +""" +import os +import sys + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +from bgp_evpn_rt5.test_bgp_evpn import * + +if __name__ == "__main__": + # run test_bgp_evpn_rt5.py test but with different parameters + # the name of the file controls the name of the global variable R2_VRF_UNDERLAY + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) From ef00a5189c9a69d43d680b1accca6d84e19fcce4 Mon Sep 17 00:00:00 2001 From: Philippe Guibert Date: Fri, 9 May 2025 09:30:33 +0200 Subject: [PATCH 2/8] topotests: fix pylint code style in test_bgp_evpn.py file The pylint code style needs to be executed. Signed-off-by: Philippe Guibert --- tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py b/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py index 3162cb354482..975e04b64dea 100644 --- a/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py +++ b/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py @@ -604,9 +604,7 @@ def _get_established_epoch(router, peer, vrf=None): Get the established epoch for a peer """ vrf_arg = f" vrf {vrf}" if vrf else "" - output = router.vtysh_cmd( - f"show bgp{vrf_arg} neighbor {peer} json", isjson=True - ) + output = router.vtysh_cmd(f"show bgp{vrf_arg} neighbor {peer} json", isjson=True) assert peer in output, "peer not found" peer_info = output[peer] assert "bgpState" in peer_info, "peer state not found" @@ -620,9 +618,7 @@ def _check_established_epoch_differ(router, peer, last_established_epoch, vrf=No Check that the established epoch has changed """ vrf_arg = f" vrf {vrf}" if vrf else "" - output = router.vtysh_cmd( - f"show bgp{vrf_arg} neighbor {peer} json", isjson=True - ) + output = router.vtysh_cmd(f"show bgp{vrf_arg} neighbor {peer} json", isjson=True) assert peer in output, "peer not found" peer_info = output[peer] assert "bgpState" in peer_info, "peer state not found" @@ -643,11 +639,7 @@ def _test_epoch_after_clear(router, peer, last_established_epoch, vrf=None): Without this, the second session is cleared as well on slower systems (like CI) """ test_func = partial( - _check_established_epoch_differ, - router, - peer, - last_established_epoch, - vrf=vrf + _check_established_epoch_differ, router, peer, last_established_epoch, vrf=vrf ) _, result = topotest.run_and_expect(test_func, None, count=20, wait=1) assert ( From 39f654966c73efdc39ca6a69200974eb21e70213 Mon Sep 17 00:00:00 2001 From: Louis Scalbert Date: Fri, 11 Apr 2025 14:07:30 +0200 Subject: [PATCH 3/8] zebra: fix heap-after-free on shutdown in netns mode Seen with bfd_vrf_topo1, and bgp_evpn_rt5 on Ubuntu 22.04 hwe. Do not call ns_delete() from zebra_vrf_delete(), which calls zebra_ns_delete(). - If a netns is removed from the system, vrf_delete()->zebra_vrf_delete() is called before calling ns_delete() (see zebra_ns_notify.c). - If zebra is terminating, zebra_ns_final_shutdown() will call zebra_vrf_delete(). > ==616172==ERROR: AddressSanitizer: heap-use-after-free on address 0x6160000ae3a4 at pc 0x556cdc178d8f bp 0x7ffe4f41ace0 sp 0x7ffe4f41acd0 > READ of size 4 at 0x6160000ae3a4 thread T0 > #0 0x556cdc178d8e in ctx_info_from_zns zebra/zebra_dplane.c:3394 > #1 0x556cdc178f55 in dplane_ctx_ns_init zebra/zebra_dplane.c:3410 > #2 0x556cdc17b829 in dplane_ctx_nexthop_init zebra/zebra_dplane.c:3759 > #3 0x556cdc18095f in dplane_nexthop_update_internal zebra/zebra_dplane.c:4566 > #4 0x556cdc1813f1 in dplane_nexthop_delete zebra/zebra_dplane.c:4793 > #5 0x556cdc229234 in zebra_nhg_uninstall_kernel zebra/zebra_nhg.c:3484 > #6 0x556cdc21f8fe in zebra_nhg_decrement_ref zebra/zebra_nhg.c:1804 > #7 0x556cdc24b05a in route_entry_update_nhe zebra/zebra_rib.c:456 > #8 0x556cdc255083 in rib_re_nhg_free zebra/zebra_rib.c:2633 > #9 0x556cdc25e3bb in rib_unlink zebra/zebra_rib.c:4049 > #10 0x556cdc24c9b0 in zebra_rtable_node_cleanup zebra/zebra_rib.c:903 > #11 0x7fb25c173144 in route_node_free lib/table.c:75 > #12 0x7fb25c17337f in route_table_free lib/table.c:111 > #13 0x7fb25c172fe4 in route_table_finish lib/table.c:46 > #14 0x556cdc266f62 in zebra_router_free_table zebra/zebra_router.c:191 > #15 0x556cdc2673ef in zebra_router_terminate zebra/zebra_router.c:243 > #16 0x556cdc10638b in zebra_finalize zebra/main.c:240 > #17 0x7fb25c18e012 in event_call lib/event.c:2019 > #18 0x7fb25c04afc6 in frr_run lib/libfrr.c:1247 > #19 0x556cdc106deb in main zebra/main.c:543 > #20 0x7fb25ba29d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58 > #21 0x7fb25ba29e3f in __libc_start_main_impl ../csu/libc-start.c:392 > #22 0x556cdc0c7ed4 in _start (/usr/lib/frr/zebra+0x192ed4) > > 0x6160000ae3a4 is located 36 bytes inside of 592-byte region [0x6160000ae380,0x6160000ae5d0) > freed by thread T0 here: > #0 0x7fb25c6b4537 in __interceptor_free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:127 > #1 0x7fb25c0790e3 in qfree lib/memory.c:131 > #2 0x556cdc22d9c9 in zebra_ns_delete zebra/zebra_ns.c:261 > #3 0x7fb25c0ac400 in ns_delete lib/netns_linux.c:319 > #4 0x556cdc28026a in zebra_vrf_delete zebra/zebra_vrf.c:343 > #5 0x7fb25c197443 in vrf_delete lib/vrf.c:282 > #6 0x7fb25c1987e8 in vrf_terminate_single lib/vrf.c:601 > #7 0x7fb25c197a7a in vrf_iterate lib/vrf.c:394 > #8 0x7fb25c198834 in vrf_terminate lib/vrf.c:609 > #9 0x556cdc106345 in zebra_finalize zebra/main.c:223 > #10 0x7fb25c18e012 in event_call lib/event.c:2019 > #11 0x7fb25c04afc6 in frr_run lib/libfrr.c:1247 > #12 0x556cdc106deb in main zebra/main.c:543 > #13 0x7fb25ba29d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58 > > previously allocated by thread T0 here: > #0 0x7fb25c6b4a57 in __interceptor_calloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:154 > #1 0x7fb25c078f91 in qcalloc lib/memory.c:106 > #2 0x556cdc22d6a1 in zebra_ns_new zebra/zebra_ns.c:231 > #3 0x556cdc22e30b in zebra_ns_init zebra/zebra_ns.c:429 > #4 0x556cdc106cec in main zebra/main.c:480 > #5 0x7fb25ba29d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58 > > SUMMARY: AddressSanitizer: heap-use-after-free zebra/zebra_dplane.c:3394 in ctx_info_from_zns Signed-off-by: Louis Scalbert Signed-off-by: Philippe Guibert --- zebra/zebra_vrf.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/zebra/zebra_vrf.c b/zebra/zebra_vrf.c index 3231b03b8152..4d52368c979a 100644 --- a/zebra/zebra_vrf.c +++ b/zebra/zebra_vrf.c @@ -339,11 +339,6 @@ static int zebra_vrf_delete(struct vrf *vrf) otable_fini(&zvrf->other_tables); XFREE(MTYPE_ZEBRA_VRF, zvrf); - if (vrf->ns_ctxt) { - ns_delete(vrf->ns_ctxt); - vrf->ns_ctxt = NULL; - } - vrf->info = NULL; return 0; From 4656368f62f6b9ecc06d21596cd8399127e401ab Mon Sep 17 00:00:00 2001 From: Philippe Guibert Date: Mon, 10 Mar 2025 22:28:05 +0100 Subject: [PATCH 4/8] bgpd: do not set default bgp instance at bgp evpn at creation It is not possible to configure an EVPN underlay in a VRF: > ubuntu2204hwe(config)# router bgp 65000 > ubuntu2204hwe(config-router)# bgp router-id 192.168.105.41 > ubuntu2204hwe(config-router)# exit > ubuntu2204hwe(config)# router bgp 65000 vrf r2-vrf-evpn > ubuntu2204hwe(config-router)# neighbor 192.168.100.21 remote-as 65000 > ubuntu2204hwe(config-router)# address-family l2vpn evpn > ubuntu2204hwe(config-router-af)# neighbor 192.168.100.21 activate > ubuntu2204hwe(config-router-af)# advertise-all-vni > % Please unconfigure EVPN in VRF default > ubuntu2204hwe(config-router-af)# advertise-all-vni cannot set bm->bgp_evpn because it was already set when the default instance was configured. Remove setting bm->bgp_evpn at default instance creation. It is done anyway when configuring advertise-all-vni by invoking evpn_set_advertise_all_vni() -> bgp_set_evpn(). From now, bm->bgp_evpn is only modified by bgp_set_evpn(); inside, the bgp_lock() and bgp_unlock() calls are done only when bgp_evpn pointer are modified, and avoid multiple references. Fixes: ("e2f3a930c54c") bgpd: Allow non-default instance to be EVPN one Signed-off-by: Philippe Guibert --- bgpd/bgpd.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c index ba82a0d58536..658a9e69220b 100644 --- a/bgpd/bgpd.c +++ b/bgpd/bgpd.c @@ -3515,12 +3515,6 @@ static struct bgp *bgp_create(as_t *as, const char *name, name, bgp->as_pretty); } - /* Default the EVPN VRF to the default one */ - if (inst_type == BGP_INSTANCE_TYPE_DEFAULT && !bgp_master.bgp_evpn) { - bgp_lock(bgp); - bm->bgp_evpn = bgp; - } - bgp_lock(bgp); bgp->allow_martian = false; From c27a4aa0ea501bf024034cc06f8ec5538816733f Mon Sep 17 00:00:00 2001 From: Philippe Guibert Date: Tue, 11 Mar 2025 14:41:31 +0100 Subject: [PATCH 5/8] bgpd: fix remove too verbose error message When setting up a RR EVPN setup, the below error message is displayed for each received BGP update. It becomes flooding when many BGP updates are received. > 2025/03/11 14:38:24.959177 BGP: [J51AF-GQ2HJ][EC 33554468] vrf import rt lookup - evpn instance not created yet > 2025/03/11 14:38:24.959188 BGP: [J51AF-GQ2HJ][EC 33554468] vrf import rt lookup - evpn instance not created yet The message is just here to inform that there is no BGP backbone defined, which is normal under a RR configuration. Setting up a RR EVPN configuration should not lead to such message. Fix this by removing the log messages. Signed-off-by: Philippe Guibert --- bgpd/bgp_evpn.c | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/bgpd/bgp_evpn.c b/bgpd/bgp_evpn.c index 972ee75a4479..d5edaa2680f5 100644 --- a/bgpd/bgp_evpn.c +++ b/bgpd/bgp_evpn.c @@ -144,11 +144,8 @@ static struct vrf_irt_node *vrf_import_rt_new(struct ecommunity_val *rt) struct vrf_irt_node *irt; bgp_evpn = bgp_get_evpn(); - if (!bgp_evpn) { - flog_err(EC_BGP_NO_DFLT, - "vrf import rt new - evpn instance not created yet"); + if (!bgp_evpn) return NULL; - } irt = XCALLOC(MTYPE_BGP_EVPN_VRF_IMPORT_RT, sizeof(struct vrf_irt_node)); @@ -170,11 +167,8 @@ static void vrf_import_rt_free(struct vrf_irt_node *irt) struct bgp *bgp_evpn = NULL; bgp_evpn = bgp_get_evpn(); - if (!bgp_evpn) { - flog_err(EC_BGP_NO_DFLT, - "vrf import rt free - evpn instance not created yet"); + if (!bgp_evpn) return; - } hash_release(bgp_evpn->vrf_import_rt_hash, irt); list_delete(&irt->vrfs); @@ -197,12 +191,8 @@ static struct vrf_irt_node *lookup_vrf_import_rt(struct ecommunity_val *rt) struct vrf_irt_node tmp; bgp_evpn = bgp_get_evpn(); - if (!bgp_evpn) { - flog_err( - EC_BGP_NO_DFLT, - "vrf import rt lookup - evpn instance not created yet"); + if (!bgp_evpn) return NULL; - } memset(&tmp, 0, sizeof(tmp)); memcpy(&tmp.rt, rt, ECOMMUNITY_SIZE); From 19707c83ed86c27e1ca35e5e968c37ccc9a3ffe0 Mon Sep 17 00:00:00 2001 From: Philippe Guibert Date: Mon, 10 Mar 2025 21:54:34 +0100 Subject: [PATCH 6/8] topotests: add bgp route importation from ce to l3vpn evpn network Add a CE that advertises IP networksk to R2, which will import those prefixes in the L3VPN EVPN network. The import command and the match source-vrf commands are tested. Signed-off-by: Philippe Guibert --- tests/topotests/bgp_evpn_rt5/ce1/frr.conf | 42 ++++++ .../bgp_l2vpn_evpn_routes_source_vrf_all.json | 127 ++++++++++++++++ ...2vpn_evpn_routes_source_vrf_filtering.json | 67 +++++++++ tests/topotests/bgp_evpn_rt5/r2/frr.conf | 33 ++++ .../bgp_evpn_rt5/r2/frr_vrf_underlay.conf | 33 ++++ tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py | 142 ++++++++++++++++++ 6 files changed, 444 insertions(+) create mode 100644 tests/topotests/bgp_evpn_rt5/ce1/frr.conf create mode 100644 tests/topotests/bgp_evpn_rt5/r1/bgp_l2vpn_evpn_routes_source_vrf_all.json create mode 100644 tests/topotests/bgp_evpn_rt5/r1/bgp_l2vpn_evpn_routes_source_vrf_filtering.json diff --git a/tests/topotests/bgp_evpn_rt5/ce1/frr.conf b/tests/topotests/bgp_evpn_rt5/ce1/frr.conf new file mode 100644 index 000000000000..84c345e0ff5a --- /dev/null +++ b/tests/topotests/bgp_evpn_rt5/ce1/frr.conf @@ -0,0 +1,42 @@ +interface loop141 vrf vrf-141 + ip address 192.168.116.61/32 + ipv6 address fd00::116/128 +! +interface loop142 vrf vrf-142 + ip address 192.168.126.61/32 + ipv6 address fd00::126/128 +! +interface eth-r2.141 vrf vrf-141 + ip address 192.168.106.61/24 +! +interface eth-r2.142 vrf vrf-142 + ip address 192.168.105.61/24 +! +router bgp 65001 vrf vrf-142 + bgp router-id 192.168.105.61 + bgp log-neighbor-changes + no bgp ebgp-requires-policy + neighbor 192.168.105.41 remote-as 65000 + neighbor 192.168.105.41 capability extended-nexthop + address-family ipv4 unicast + network 192.168.126.61/32 + exit-address-family + address-family ipv6 unicast + neighbor 192.168.105.41 activate + network fd00::126/128 + exit-address-family +! +router bgp 65001 vrf vrf-141 + bgp router-id 192.168.106.61 + bgp log-neighbor-changes + no bgp ebgp-requires-policy + neighbor 192.168.106.41 remote-as 65000 + neighbor 192.168.106.41 capability extended-nexthop + address-family ipv4 unicast + network 192.168.116.61/32 + exit-address-family + address-family ipv6 unicast + neighbor 192.168.106.41 activate + network fd00::116/128 + exit-address-family + ! diff --git a/tests/topotests/bgp_evpn_rt5/r1/bgp_l2vpn_evpn_routes_source_vrf_all.json b/tests/topotests/bgp_evpn_rt5/r1/bgp_l2vpn_evpn_routes_source_vrf_all.json new file mode 100644 index 000000000000..0878a0c48b74 --- /dev/null +++ b/tests/topotests/bgp_evpn_rt5/r1/bgp_l2vpn_evpn_routes_source_vrf_all.json @@ -0,0 +1,127 @@ +{ + "65000:2":{ + "rd":"65000:2", + "[5]:[0]:[32]:[192.168.116.61]":{ + "prefix":"[5]:[0]:[32]:[192.168.116.61]", + "prefixLen":352, + "paths":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"internal", + "routeType":5, + "ethTag":0, + "ipLen":32, + "ip":"192.168.116.61", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"192.168.1.101", + "path":"65001", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.2.2", + "hostname":"rr", + "afi":"ipv4", + "used":true + } + ] + } + ] + }, + "[5]:[0]:[32]:[192.168.126.61]":{ + "prefix":"[5]:[0]:[32]:[192.168.126.61]", + "prefixLen":352, + "paths":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"internal", + "routeType":5, + "ethTag":0, + "ipLen":32, + "ip":"192.168.126.61", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"192.168.1.101", + "path":"65001", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.2.2", + "hostname":"rr", + "afi":"ipv4", + "used":true + } + ] + } + ] + }, + "[5]:[0]:[128]:[fd00::116]":{ + "prefix":"[5]:[0]:[128]:[fd00::116]", + "prefixLen":352, + "paths":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"internal", + "routeType":5, + "ethTag":0, + "ipLen":128, + "ip":"fd00::116", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"192.168.1.101", + "path":"65001", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.2.2", + "hostname":"rr", + "afi":"ipv4", + "used":true + } + ] + } + ] + }, + "[5]:[0]:[128]:[fd00::126]":{ + "prefix":"[5]:[0]:[128]:[fd00::126]", + "prefixLen":352, + "paths":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"internal", + "routeType":5, + "ethTag":0, + "ipLen":128, + "ip":"fd00::126", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"192.168.1.101", + "path":"65001", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.2.2", + "hostname":"rr", + "afi":"ipv4", + "used":true + } + ] + } + ] + } + }, + "numPrefix":14, + "totalPrefix":14 +} diff --git a/tests/topotests/bgp_evpn_rt5/r1/bgp_l2vpn_evpn_routes_source_vrf_filtering.json b/tests/topotests/bgp_evpn_rt5/r1/bgp_l2vpn_evpn_routes_source_vrf_filtering.json new file mode 100644 index 000000000000..2aa23394626b --- /dev/null +++ b/tests/topotests/bgp_evpn_rt5/r1/bgp_l2vpn_evpn_routes_source_vrf_filtering.json @@ -0,0 +1,67 @@ +{ + "65000:2":{ + "rd":"65000:2", + "[5]:[0]:[32]:[192.168.116.61]":{ + "prefix":"[5]:[0]:[32]:[192.168.116.61]", + "prefixLen":352, + "paths":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"internal", + "routeType":5, + "ethTag":0, + "ipLen":32, + "ip":"192.168.116.61", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"192.168.1.101", + "path":"65001", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.2.2", + "hostname":"rr", + "afi":"ipv4", + "used":true + } + ] + } + ] + }, + "[5]:[0]:[128]:[fd00::126]":{ + "prefix":"[5]:[0]:[128]:[fd00::126]", + "prefixLen":352, + "paths":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"internal", + "routeType":5, + "ethTag":0, + "ipLen":128, + "ip":"fd00::126", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"192.168.1.101", + "path":"65001", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.2.2", + "hostname":"rr", + "afi":"ipv4", + "used":true + } + ] + } + ] + } + }, + "numPrefix":10, + "totalPrefix":10 +} diff --git a/tests/topotests/bgp_evpn_rt5/r2/frr.conf b/tests/topotests/bgp_evpn_rt5/r2/frr.conf index 4118710a37f9..e2fb5fe1cd74 100644 --- a/tests/topotests/bgp_evpn_rt5/r2/frr.conf +++ b/tests/topotests/bgp_evpn_rt5/r2/frr.conf @@ -27,6 +27,35 @@ interface eth-rr ! ip route 0.0.0.0/0 192.168.2.101 ! +interface eth-ce1.141 vrf vrf-141 + ip address 192.168.106.41/24 +! +interface eth-ce1.142 vrf vrf-142 + ip address 192.168.105.41/24 +! +router bgp 65000 vrf vrf-142 + bgp router-id 192.168.105.41 + bgp log-neighbor-changes + no bgp ebgp-requires-policy + neighbor 192.168.105.61 remote-as 65001 + neighbor 192.168.105.61 shutdown + neighbor 192.168.105.61 capability extended-nexthop + address-family ipv6 unicast + neighbor 192.168.105.61 activate + exit-address-family +! +router bgp 65000 vrf vrf-141 + bgp router-id 192.168.106.41 + bgp log-neighbor-changes + ! + no bgp ebgp-requires-policy + neighbor 192.168.106.61 remote-as 65001 + neighbor 192.168.106.61 shutdown + neighbor 192.168.106.61 capability extended-nexthop + address-family ipv6 unicast + neighbor 192.168.106.61 activate + exit-address-family +! router bgp 65000 bgp router-id 192.168.0.2 bgp log-neighbor-changes @@ -45,10 +74,14 @@ router bgp 65000 vrf vrf-101 bgp log-neighbor-changes no bgp network import-check address-family ipv4 unicast + import vrf vrf-141 + import vrf vrf-142 network 10.0.101.2/32 network 10.0.101.12/32 exit-address-family address-family ipv6 unicast + import vrf vrf-141 + import vrf vrf-142 network fd01::2/128 network fd01::12/128 exit-address-family diff --git a/tests/topotests/bgp_evpn_rt5/r2/frr_vrf_underlay.conf b/tests/topotests/bgp_evpn_rt5/r2/frr_vrf_underlay.conf index e6993b3f621d..daf5749ef727 100644 --- a/tests/topotests/bgp_evpn_rt5/r2/frr_vrf_underlay.conf +++ b/tests/topotests/bgp_evpn_rt5/r2/frr_vrf_underlay.conf @@ -30,6 +30,35 @@ interface eth-rr vrf vrf-evpn ip address 192.168.2.2/24 ! ! +interface eth-ce1.141 vrf vrf-141 + ip address 192.168.106.41/24 +! +interface eth-ce1.142 vrf vrf-142 + ip address 192.168.105.41/24 +! +router bgp 65000 vrf vrf-142 + bgp router-id 192.168.105.41 + bgp log-neighbor-changes + no bgp ebgp-requires-policy + neighbor 192.168.105.61 remote-as 65001 + neighbor 192.168.105.61 shutdown + neighbor 192.168.105.61 capability extended-nexthop + address-family ipv6 unicast + neighbor 192.168.105.61 activate + exit-address-family +! +router bgp 65000 vrf vrf-141 + bgp router-id 192.168.106.41 + bgp log-neighbor-changes + ! + no bgp ebgp-requires-policy + neighbor 192.168.106.61 remote-as 65001 + neighbor 192.168.106.61 shutdown + neighbor 192.168.106.61 capability extended-nexthop + address-family ipv6 unicast + neighbor 192.168.106.61 activate + exit-address-family +! router bgp 65000 vrf vrf-evpn bgp router-id 192.168.0.2 bgp log-neighbor-changes @@ -48,10 +77,14 @@ router bgp 65000 vrf vrf-101 bgp log-neighbor-changes no bgp network import-check address-family ipv4 unicast + import vrf vrf-141 + import vrf vrf-142 network 10.0.101.2/32 network 10.0.101.12/32 exit-address-family address-family ipv6 unicast + import vrf vrf-141 + import vrf vrf-142 network fd01::2/128 network fd01::12/128 exit-address-family diff --git a/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py b/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py index 975e04b64dea..5d2313d8c26b 100644 --- a/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py +++ b/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py @@ -11,6 +11,8 @@ """ test_bgp_evpn.py: Test the FRR BGP daemon with BGP IPv6 interface with route advertisements on a separate netns. +- R1 and R2 are EVPN vteps. +- CE1 stands for two IP endpoints which distribute addresses imported via R2 in EVPN L3. """ import json @@ -54,6 +56,7 @@ def connect_routers(tgen, left, right): connect_routers(tgen, "rr", "r1") connect_routers(tgen, "rr", "r2") + connect_routers(tgen, "r2", "ce1") def setup_module(mod): @@ -136,6 +139,32 @@ def setup_module(mod): ) ) + for vrf_id in (141, 142): + tgen.gears["r2"].cmd( + f""" + ip link add vrf-{vrf_id} type vrf table {vrf_id} + ip link set dev vrf-{vrf_id} up + ip link add loop{vrf_id} type dummy + ip link set dev loop{vrf_id} master vrf-{vrf_id} + ip link set dev loop{vrf_id} up + ip link add link eth-ce1 name eth-ce1.{vrf_id} type vlan id {vrf_id} + ip link set dev eth-ce1.{vrf_id} master vrf-{vrf_id} + ip link set dev eth-ce1.{vrf_id} up + """ + ) + tgen.gears["ce1"].cmd( + f""" + ip link add vrf-{vrf_id} type vrf table {vrf_id} + ip link set dev vrf-{vrf_id} up + ip link add loop{vrf_id} type dummy + ip link set dev loop{vrf_id} master vrf-{vrf_id} + ip link set dev loop{vrf_id} up + ip link add link eth-r2 name eth-r2.{vrf_id} type vlan id {vrf_id} + ip link set dev eth-r2.{vrf_id} master vrf-{vrf_id} + ip link set dev eth-r2.{vrf_id} up + """ + ) + for rname, router in tgen.routers().items(): logger.info("Loading router %s" % rname) if rname == "r1": @@ -599,6 +628,119 @@ def test_evpn_restore_ipv4(): _test_router_check_evpn_contexts(tgen.gears["r1"]) +def test_evpn_enable_ce_importation(): + """ + Reconfigure IPv6 networks + Unshutdown BGP CE neighbors + Ensure R1 receives 10 EVPN prefixes + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + tgen.gears["r2"].vtysh_cmd( + """ + configure terminal + router bgp 65000 vrf vrf-142 + no neighbor 192.168.105.61 shutdown + exit + router bgp 65000 vrf vrf-141 + no neighbor 192.168.106.61 shutdown + exit + """ + ) + router = tgen.gears["r1"] + json_file = "{}/{}/bgp_l2vpn_evpn_routes_source_vrf_all.json".format( + CWD, router.name + ) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show bgp l2vpn evpn json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + +def test_evpn_enable_routemap_with_source_vrf_filtering(): + """ + Reapply the route-map with source-vrf filtering applied: + - filtering vrf-141 only for ipv4 network + - filtering vrf-142 only for ipv6 network + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + tgen.gears["r2"].vtysh_cmd( + """ + configure terminal + route-map rmap4 permit 3 + match source-vrf vrf-141 + exit + route-map rmap4 deny 4 + match source-vrf vrf-142 + exit + route-map rmap6 permit 3 + match source-vrf vrf-142 + exit + route-map rmap6 deny 4 + match source-vrf vrf-141 + exit + router bgp 65000 vrf vrf-101 + address-family l2vpn evpn + advertise ipv4 unicast route-map rmap4 + advertise ipv6 unicast route-map rmap6 + """ + ) + router = tgen.gears["r1"] + json_file = "{}/{}/bgp_l2vpn_evpn_routes_source_vrf_filtering.json".format( + CWD, router.name + ) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show bgp l2vpn evpn json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + +def test_evpn_disable_ce_importation(): + """ + Reconfigure IPv6 networks + Unshutdown BGP CE neighbors + Ensure R1 receives 10 EVPN prefixes + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + tgen.gears["r2"].vtysh_cmd( + """ + configure terminal + router bgp 65000 vrf vrf-142 + neighbor 192.168.105.61 shutdown + exit + router bgp 65000 vrf vrf-141 + neighbor 192.168.106.61 shutdown + exit + router bgp 65000 vrf vrf-101 + address-family l2vpn evpn + no advertise ipv4 unicast route-map rmap4 + no advertise ipv6 unicast route-map rmap6 + advertise ipv4 unicast + advertise ipv6 unicast + """ + ) + + def _get_established_epoch(router, peer, vrf=None): """ Get the established epoch for a peer From fb4bae192a4dd0c6ffb4d98e27923f1a708fe4b9 Mon Sep 17 00:00:00 2001 From: Philippe Guibert Date: Thu, 24 Apr 2025 11:00:09 +0200 Subject: [PATCH 7/8] bgpd: fix allow to reconfigure advertise-all-vni on non default vrf When running 'no advertise-all-vni' command on the evpn bgp instance, then reconfiguring it is not possible: > r2(config)# router bgp 65000 vrf vrf-evpn > r2(config-router)# address-family l2vpn evpn > r2(config-router-af)# > r2(config-router-af)# no advertise-all-vni > r2(config-router-af)# advertise-all-vni > % Please unconfigure EVPN in VRF default > r2(config-router-af)# Actually, the evpn bgp instance is not the default one. Fix this by not setting the bgp evpn instance to the default BGP instance, when removing the 'advertise-all-vni' command. Fixes: e2f3a930c54c ("bgpd: Allow non-default instance to be EVPN one") Signed-off-by: Philippe Guibert --- bgpd/bgp_evpn_vty.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bgpd/bgp_evpn_vty.c b/bgpd/bgp_evpn_vty.c index 9baccd872727..9da0889778ed 100644 --- a/bgpd/bgp_evpn_vty.c +++ b/bgpd/bgp_evpn_vty.c @@ -3516,7 +3516,7 @@ static void evpn_set_advertise_all_vni(struct bgp *bgp) static void evpn_unset_advertise_all_vni(struct bgp *bgp) { bgp->advertise_all_vni = 0; - bgp_set_evpn(bgp_get_default()); + bgp_set_evpn(NULL); bgp_zebra_advertise_all_vni(bgp, bgp->advertise_all_vni); bgp_evpn_cleanup_on_disable(bgp); } @@ -3739,9 +3739,14 @@ DEFUN (no_bgp_evpn_advertise_all_vni, "Advertise All local VNIs\n") { struct bgp *bgp = VTY_GET_CONTEXT(bgp); + struct bgp *bgp_evpn = NULL; if (!bgp) return CMD_WARNING; + bgp_evpn = bgp_get_evpn(); + if (!bgp_evpn || bgp_evpn != bgp) + return CMD_SUCCESS; + evpn_unset_advertise_all_vni(bgp); return CMD_SUCCESS; } From f58a727a300da9e9ec84f5606a8407d234ddce4a Mon Sep 17 00:00:00 2001 From: Philippe Guibert Date: Thu, 24 Apr 2025 11:32:16 +0200 Subject: [PATCH 8/8] topotests: bgp_evpn_rt5, add check on reconfigure evpn instance Add a test that controls that it is possible to reconfigure a BGP EVPN instance by using the advertise-all-vni command. Negating this command will remove all the EVPN rib entries. Signed-off-by: Philippe Guibert --- tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py b/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py index 5d2313d8c26b..bdf99297e566 100644 --- a/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py +++ b/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py @@ -344,6 +344,74 @@ def test_bgp_vrf_routes(): _test_bgp_vrf_routes(router, vrf) +def test_evpn_unconfigure_evpn(): + """ + Unconfigure R2 with advertise-all-vni on vrf-evpn instance + Ensure that local L2VPN EVPN entries are removed + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + logger.info("==== r2, unconfigure advertise-all-vni from BGP EVPN instance") + router = tgen.gears["r2"] + vrf_to_write = f" vrf {R2_VRF_UNDERLAY}" if R2_VRF_UNDERLAY else "" + router.vtysh_cmd( + f""" + configure terminal + router bgp 65000{vrf_to_write} + address-family l2vpn evpn + no advertise-all-vni + exit-address-family + exit + """ + ) + expected = {} + test_func = partial( + topotest.router_json_cmp, + router, + "show bgp l2vpn evpn json", + expected, + exact=True, + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + +def test_evpn_reconfigure_evpn(): + """ + reconfigure R2 with advertise-all-vni on vrf-evpn instance + Ensure that L2VPN EVPN entries are correctly learned + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + logger.info("==== r2, reconfigure advertise-all-vni from BGP EVPN instance") + router = tgen.gears["r2"] + vrf_to_write = f" vrf {R2_VRF_UNDERLAY}" if R2_VRF_UNDERLAY else "" + router.vtysh_cmd( + f""" + configure terminal + router bgp 65000{vrf_to_write} + address-family l2vpn evpn + advertise-all-vni + exit-address-family + exit + """ + ) + json_file = "{}/r2/bgp_l2vpn_evpn_routes.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show bgp l2vpn evpn json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + def test_router_check_ip(): """ Check routes are correctly installed