From 0c14dd3af605efd9fafbf224069806ed5ff03d02 Mon Sep 17 00:00:00 2001 From: Christopher Dziomba Date: Thu, 17 Apr 2025 10:41:06 +0200 Subject: [PATCH 1/2] zebra: add vtep_ip to rmac nh_list in all cases zebra rmac has a nh_list which tracks the assigned VTEP IPs to RMACs. It can also receive IPv6 encoded IPv4 addresses as VTEPs. Changing/ Installing the RMAC into the Kernel is only important when the IPv4 address changes. However because nh_list is a nodup list used to track usage or RMACs by VTEP IPs, both IP addresses (IPv4 and IPv6 encoded IPv4) should be written into it, as both could be removed in l3vni_rmac_nh_list_nh_delete independently. Signed-off-by: Christopher Dziomba --- zebra/zebra_vxlan.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/zebra/zebra_vxlan.c b/zebra/zebra_vxlan.c index 6a0cee6ced..1dd212cea1 100644 --- a/zebra/zebra_vxlan.c +++ b/zebra/zebra_vxlan.c @@ -1390,23 +1390,22 @@ static int zl3vni_remote_rmac_add(struct zebra_l3vni *zl3vni, /* install rmac in kernel */ zl3vni_rmac_install(zl3vni, zrmac); - } else if (!IPV4_ADDR_SAME(&zrmac->fwd_info.r_vtep_ip, - &(ipv4_vtep.ipaddr_v4))) { - if (IS_ZEBRA_DEBUG_VXLAN) - zlog_debug( - "L3VNI %u Remote VTEP change(%pI4 -> %pIA) for RMAC %pEA", - zl3vni->vni, &zrmac->fwd_info.r_vtep_ip, - vtep_ip, rmac); + } else { + if (!IPV4_ADDR_SAME(&zrmac->fwd_info.r_vtep_ip, &(ipv4_vtep.ipaddr_v4))) { + if (IS_ZEBRA_DEBUG_VXLAN) + zlog_debug("L3VNI %u Remote VTEP change(%pI4 -> %pIA) for RMAC %pEA", + zl3vni->vni, &zrmac->fwd_info.r_vtep_ip, vtep_ip, rmac); - zrmac->fwd_info.r_vtep_ip = ipv4_vtep.ipaddr_v4; + zrmac->fwd_info.r_vtep_ip = ipv4_vtep.ipaddr_v4; + + /* install rmac in kernel */ + zl3vni_rmac_install(zl3vni, zrmac); + } vtep = XCALLOC(MTYPE_EVPN_VTEP, sizeof(struct ipaddr)); memcpy(vtep, vtep_ip, sizeof(struct ipaddr)); if (!listnode_add_sort_nodup(zrmac->nh_list, (void *)vtep)) XFREE(MTYPE_EVPN_VTEP, vtep); - - /* install rmac in kernel */ - zl3vni_rmac_install(zl3vni, zrmac); } return 0; From ab68237e3e2ce894dba54256e2f2c21b868ba486 Mon Sep 17 00:00:00 2001 From: Christopher Dziomba Date: Tue, 22 Apr 2025 17:25:57 +0200 Subject: [PATCH 2/2] tests: remove ipv4 and ipv6 in bgp_evpn_rt5 Removal of IPv6 was already tested in bgp_evpn_rt5 topotest, however IPv4 was not tested afterwards. This now removes IPv6 routes first, then adds them back and removes IPv4 afterwards, waiting for convergence everytime. Signed-off-by: Christopher Dziomba --- tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py | 183 +++++++++++++----- 1 file changed, 135 insertions(+), 48 deletions(-) diff --git a/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py b/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py index a628584a00..f4af54358b 100644 --- a/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py +++ b/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py @@ -180,34 +180,35 @@ def teardown_module(_mod): tgen.stop_topology() -def _test_evpn_ping_router(pingrouter, ipv4_only=False): +def _test_evpn_ping_router(pingrouter, ipv4_only=False, ipv6_only=False): """ internal function to check ping between r1 and r2 """ # Check IPv4 and IPv6 connectivity between r1 and r2 ( routing vxlan evpn) - logger.info( - "Check Ping IPv4 from R1(r1-vrf-101) to R2(r2-vrf-101 = 192.168.101.41)" - ) - output = pingrouter.run("ip netns exec r1-vrf-101 ping 192.168.101.41 -f -c 1000") - logger.info(output) - if "1000 packets transmitted, 1000 received" not in output: - assertmsg = ( - "expected ping IPv4 from R1(r1-vrf-101) to R2(192.168.101.41) should be ok" + if not ipv6_only: + logger.info( + "Check Ping IPv4 from R1(r1-vrf-101) to R2(r2-vrf-101 = 192.168.101.41)" ) - assert 0, assertmsg - else: - logger.info("Check Ping IPv4 from R1(r1-vrf-101) to R2(192.168.101.41) OK") + output = pingrouter.run( + "ip netns exec r1-vrf-101 ping 192.168.101.41 -f -c 1000" + ) + logger.info(output) + if "1000 packets transmitted, 1000 received" not in output: + assertmsg = "expected ping IPv4 from R1(r1-vrf-101) to R2(192.168.101.41) should be ok" + assert 0, assertmsg + else: + logger.info("Check Ping IPv4 from R1(r1-vrf-101) to R2(192.168.101.41) OK") - if ipv4_only: - return - - logger.info("Check Ping IPv6 from R1(r1-vrf-101) to R2(r2-vrf-101 = fd00::2)") - output = pingrouter.run("ip netns exec r1-vrf-101 ping fd00::2 -f -c 1000") - logger.info(output) - if "1000 packets transmitted, 1000 received" not in output: - assert 0, "expected ping IPv6 from R1(r1-vrf-101) to R2(fd00::2) should be ok" - else: - logger.info("Check Ping IPv6 from R1(r1-vrf-101) to R2(fd00::2) OK") + if not ipv4_only: + logger.info("Check Ping IPv6 from R1(r1-vrf-101) to R2(r2-vrf-101 = fd00::2)") + output = pingrouter.run("ip netns exec r1-vrf-101 ping fd00::2 -f -c 1000") + logger.info(output) + if "1000 packets transmitted, 1000 received" not in output: + assert ( + 0 + ), "expected ping IPv6 from R1(r1-vrf-101) to R2(fd00::2) should be ok" + else: + logger.info("Check Ping IPv6 from R1(r1-vrf-101) to R2(fd00::2) OK") def test_protocols_convergence(): @@ -239,6 +240,16 @@ def test_protocols_convergence(): assert result is None, assertmsg +def _print_evpn_nexthop_rmac(router): + tgen = get_topogen() + output = tgen.gears[router].vtysh_cmd("show evpn next-hops vni all", isjson=False) + logger.info("==== result from {} show evpn next-hops vni all".format(router)) + logger.info(output) + output = tgen.gears[router].vtysh_cmd("show evpn rmac vni all", isjson=False) + logger.info("==== result from {}: show evpn rmac vni all".format(router)) + logger.info(output) + + def test_protocols_dump_info(): """ Dump EVPN information @@ -273,12 +284,7 @@ def test_protocols_dump_info(): output = tgen.gears["r1"].vtysh_cmd("show evpn vni detail", isjson=False) logger.info("==== result from show evpn vni detail") logger.info(output) - output = tgen.gears["r1"].vtysh_cmd("show evpn next-hops vni all", isjson=False) - logger.info("==== result from show evpn next-hops vni all") - logger.info(output) - output = tgen.gears["r1"].vtysh_cmd("show evpn rmac vni all", isjson=False) - logger.info("==== result from show evpn rmac vni all") - logger.info(output) + _print_evpn_nexthop_rmac("r1") def test_router_check_ip(): @@ -342,7 +348,7 @@ def _test_router_check_evpn_next_hop(expected_paths=1): assert result is None, "evpn ipv6 next-hops check failed" -def _test_router_check_evpn_contexts(router, ipv4_only=False): +def _test_router_check_evpn_contexts(router, ipv4_only=False, ipv6_only=False): """ Check EVPN nexthops and RMAC number are correctly configured """ @@ -355,6 +361,15 @@ def _test_router_check_evpn_contexts(router, ipv4_only=False): }, } } + elif ipv6_only: + expected = { + "101": { + "numNextHops": 1, + "::ffff:192.168.100.41": { + "nexthopIp": "::ffff:192.168.100.41", + }, + } + } else: expected = { "101": { @@ -434,6 +449,26 @@ def test_evpn_disable_routemap(): assert result is None, assertmsg +def _check_evpn_routes(router, family, vrf, routes, expected=True): + tgen = get_topogen() + rib_routes = { + "r1": { + "static_routes": [ + { + "vrf": vrf, + "network": routes, + } + ] + } + } + result = verify_bgp_rib(tgen, family, router, rib_routes, expected=expected) + + if expected: + assert result is True, "expect routes {} present".format(routes) + else: + assert result is not True, "expect routes {} not present".format(routes) + + def test_evpn_remove_ip(): """ Check the removal of an EVPN route is correctly handled @@ -458,25 +493,8 @@ def test_evpn_remove_ip(): assert result is True, "Failed to remove IPv6 network on R2, Error: {} ".format( result ) - ipv6_routes = { - "r1": { - "static_routes": [ - { - "vrf": "r1-vrf-101", - "network": ["fd00::2/128"], - } - ] - } - } - result = verify_bgp_rib(tgen, "ipv6", "r1", ipv6_routes, expected=False) - assert result is not True, "expect IPv6 route fd00::2/128 withdrawn" - - output = tgen.gears["r1"].vtysh_cmd("show evpn next-hops vni all", isjson=False) - logger.info("==== result from show evpn next-hops vni all") - logger.info(output) - output = tgen.gears["r1"].vtysh_cmd("show evpn rmac vni all", isjson=False) - logger.info("==== result from show evpn next-hops vni all") - logger.info(output) + _check_evpn_routes("r1", "ipv6", "r1-vrf-101", ["fd00::2/128"], expected=False) + _print_evpn_nexthop_rmac("r1") def test_router_check_evpn_contexts_again(): @@ -502,6 +520,75 @@ def test_evpn_ping_again(): _test_evpn_ping_router(tgen.gears["r1"], ipv4_only=True) +def test_evpn_other_address_family(): + """ + Check the removal of an EVPN route is correctly handled + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + config_add_ipv6 = { + "r2": { + "raw_config": [ + "router bgp 65000 vrf r2-vrf-101", + "address-family ipv6 unicast", + "network fd00::3/128", + "network fd00::2/128", + ] + } + } + + logger.info("==== Add IPv6 again network on R2") + result = apply_raw_config(tgen, config_add_ipv6) + assert result is True, "Failed to add IPv6 network on R2, Error: {} ".format(result) + _check_evpn_routes("r1", "ipv6", "r1-vrf-101", ["fd00::2/128"], expected=True) + + config_no_ipv4 = { + "r2": { + "raw_config": [ + "router bgp 65000 vrf r2-vrf-101", + "address-family ipv4 unicast", + "no network 192.168.101.41/32", + "no network 192.168.102.41/32", + ] + } + } + + logger.info("==== Remove IPv4 network on R2") + result = apply_raw_config(tgen, config_no_ipv4) + assert result is True, "Failed to remove IPv4 network on R2, Error: {} ".format( + result + ) + + _check_evpn_routes( + "r1", "ipv4", "r1-vrf-101", ["192.168.101.41/32"], expected=False + ) + _print_evpn_nexthop_rmac("r1") + + +def test_router_check_evpn_contexts_again_other_address_family(): + """ + Check EVPN nexthops and RMAC number are correctly configured + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + _test_router_check_evpn_contexts(tgen.gears["r1"], ipv6_only=True) + + +def test_evpn_ping_again_other_address_family(): + """ + Check ping between R1 and R2 is ok + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + _test_evpn_ping_router(tgen.gears["r1"], ipv6_only=True) + + def _get_established_epoch(router, peer): """ Get the established epoch for a peer