diff --git a/bgpd/bgp_attr.c b/bgpd/bgp_attr.c index a3f0ca0facfc..eba9318d8797 100644 --- a/bgpd/bgp_attr.c +++ b/bgpd/bgp_attr.c @@ -1049,6 +1049,9 @@ unsigned int attrhash_key_make(const void *p) MIX3(attr->bh_type, attr->otc, bgp_attr_get_aigp_metric(attr)); MIX3(attr->mm_seqnum, attr->df_alg, attr->df_pref); MIX(attr->encap_tunneltype); + MIX(bgp_attr_get_pmsi_tnl_type(attr)); + if (bgp_attr_get_pmsi_tnl_type(attr) == PMSI_TNLTYPE_INGR_REPL) + key = jhash(attr->tunn_id.s6_addr, IPV6_MAX_BYTELEN, key); key = jhash(&attr->rmac, sizeof(attr->rmac), key); if (bgp_attr_get_nhc(attr)) MIX(bgp_nhc_hash_key_make(bgp_attr_get_nhc(attr))); @@ -1104,7 +1107,9 @@ bool attrhash_cmp(const void *p1, const void *p2) attr1->bh_type == attr2->bh_type && attr1->otc == attr2->otc && !memcmp(&attr1->rmac, &attr2->rmac, sizeof(struct ethaddr)) && bgp_nhc_same(bgp_attr_get_nhc(attr1), bgp_attr_get_nhc(attr2)) && - bgp_ls_attr_same(attr1->ls_attr, attr2->ls_attr)) + bgp_ls_attr_same(attr1->ls_attr, attr2->ls_attr) && + (attr1->pmsi_tnl_type == attr2->pmsi_tnl_type) && + IPV6_ADDR_SAME(&attr1->tunn_id, &attr2->tunn_id)) return true; } @@ -3751,6 +3756,7 @@ bgp_attr_pmsi_tunnel(struct bgp_attr_parser_args *args) const bgp_size_t length = args->length; uint8_t tnl_type; int attr_parse_len = 2 + BGP_LABEL_BYTES; + struct in_addr tunn_id; if (peer->discard_attrs[args->type] || peer->withdraw_attrs[args->type]) goto pmsi_tunnel_ignore; @@ -3773,9 +3779,14 @@ bgp_attr_pmsi_tunnel(struct bgp_attr_parser_args *args) args->total); } if (tnl_type == PMSI_TNLTYPE_INGR_REPL) { - if (length != 9) { + /* The length should be one of these values: + * 1 (Flags) + 1 (Tunnel Type) + 3 (Label) + 4 (IPv4 address) = 9 octets + * 1 (Flags) + 1 (Tunnel Type) + 3 (Label) + 16 (IPv6 address) = 21 octets + */ + if (length != BGP_ATTR_PMSI_TUNNEL_V4_LENGTH && + length != BGP_ATTR_PMSI_TUNNEL_V6_LENGTH) { flog_err(EC_BGP_ATTR_PMSI_LEN, - "Bad PMSI tunnel attribute length %d for IR", + "Bad PMSI tunnel attribute length %d for Ingress Replication", length); return bgp_attr_malformed( args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, @@ -3787,7 +3798,19 @@ bgp_attr_pmsi_tunnel(struct bgp_attr_parser_args *args) bgp_attr_set_pmsi_tnl_type(attr, tnl_type); stream_get(&attr->label, connection->curr, BGP_LABEL_BYTES); - /* Forward read pointer of input stream. */ + /* Decode ingress-replication tunnel id */ + if (tnl_type == PMSI_TNLTYPE_INGR_REPL) { + if (length == BGP_ATTR_PMSI_TUNNEL_V4_LENGTH) { + tunn_id.s_addr = stream_get_ipv4(connection->curr); + ipv4_to_ipv4_mapped_ipv6(&attr->tunn_id, tunn_id); + attr_parse_len += IPV4_MAX_BYTELEN; + } else { + stream_get(&attr->tunn_id, connection->curr, IPV6_MAX_BYTELEN); + attr_parse_len += IPV6_MAX_BYTELEN; + } + } + + /* Forward read pointer of input stream to skip anything we didn't parse */ stream_forward_getp(connection->curr, length - attr_parse_len); return BGP_ATTR_PARSE_PROCEED; @@ -5652,15 +5675,37 @@ bgp_size_t bgp_packet_attribute(struct bgp *bgp, struct peer *peer, struct strea /* PMSI Tunnel */ if (bgp_attr_exists(attr, BGP_ATTR_PMSI_TUNNEL)) { + uint8_t tunn_id_len = 0; + uint8_t *nh; + struct in_addr tunn_id; + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS); stream_putc(s, BGP_ATTR_PMSI_TUNNEL); - stream_putc(s, 9); // Length - stream_putc(s, 0); // Flags + + /* Encode tunnel id for known tunnel type */ + if (bgp_attr_get_pmsi_tnl_type(attr) == PMSI_TNLTYPE_INGR_REPL) { + if (IS_MAPPED_IPV6(&attr->tunn_id)) { + stream_putc(s, BGP_ATTR_PMSI_TUNNEL_V4_LENGTH); + tunn_id_len = IPV4_MAX_BYTELEN; + ipv4_mapped_ipv6_to_ipv4(&attr->tunn_id, &tunn_id); + nh = (uint8_t *)&tunn_id; + } else { + stream_putc(s, BGP_ATTR_PMSI_TUNNEL_V6_LENGTH); + tunn_id_len = IPV6_MAX_BYTELEN; + nh = (uint8_t *)&attr->tunn_id; + } + } else { + /* Encode label part only */ + stream_putc(s, BGP_ATTR_PMSI_TUNNEL_LBL_ONLY_LEN); + } + + stream_putc(s, 0); /* Flags */ stream_putc(s, bgp_attr_get_pmsi_tnl_type(attr)); - stream_put(s, &(attr->label), - BGP_LABEL_BYTES); // MPLS Label / VXLAN VNI - stream_put_ipv4(s, attr->nexthop.s_addr); - // Unicast tunnel endpoint IP address + stream_put(s, &(attr->label), BGP_LABEL_BYTES); /* MPLS Label/VXLAN VNI */ + + /* Unicast tunnel endpoint IP address */ + if (tunn_id_len > 0) + stream_put(s, nh, tunn_id_len); } /* OTC */ diff --git a/bgpd/bgp_attr.h b/bgpd/bgp_attr.h index 2849063e29cc..d5fa02009f9d 100644 --- a/bgpd/bgp_attr.h +++ b/bgpd/bgp_attr.h @@ -108,6 +108,11 @@ enum pta_type { PMSI_TNLTYPE_MAX = PMSI_TNLTYPE_MLDP_MP2MP }; +/* PMSI lengths for label-only, ipv4, and ipv6 "identifier" */ +#define BGP_ATTR_PMSI_TUNNEL_LBL_ONLY_LEN 5 +#define BGP_ATTR_PMSI_TUNNEL_V4_LENGTH 9 +#define BGP_ATTR_PMSI_TUNNEL_V6_LENGTH 21 + /* * Prefix-SID type-4 * SRv6-VPN-SID-TLV @@ -217,6 +222,7 @@ struct attr { /* PMSI tunnel type (RFC 6514). */ enum pta_type pmsi_tnl_type; + struct in6_addr tunn_id; /* PMSI Tunnel Id */ /* Multi-Protocol Nexthop, AFI IPv6 */ struct in6_addr mp_nexthop_global; @@ -509,7 +515,7 @@ static inline uint32_t mac_mobility_seqnum(struct attr *attr) return (attr) ? attr->mm_seqnum : 0; } -static inline enum pta_type bgp_attr_get_pmsi_tnl_type(struct attr *attr) +static inline enum pta_type bgp_attr_get_pmsi_tnl_type(const struct attr *attr) { return attr->pmsi_tnl_type; } diff --git a/bgpd/bgp_evpn.c b/bgpd/bgp_evpn.c index 14197791f808..7cf4c3003322 100644 --- a/bgpd/bgp_evpn.c +++ b/bgpd/bgp_evpn.c @@ -2318,6 +2318,10 @@ static int update_evpn_route(struct bgp *bgp, struct bgpevpn *vpn, if (p->prefix.route_type == BGP_EVPN_IMET_ROUTE) { SET_FLAG(attr.flag, ATTR_FLAG_BIT(BGP_ATTR_PMSI_TUNNEL)); bgp_attr_set_pmsi_tnl_type(&attr, PMSI_TNLTYPE_INGR_REPL); + if (attr.mp_nexthop_len == BGP_ATTR_NHLEN_IPV4) + ipv4_to_ipv4_mapped_ipv6(&attr.tunn_id, attr.mp_nexthop_global_in); + else + IPV6_ADDR_COPY(&attr.tunn_id, &attr.mp_nexthop_global); } /* router mac is only needed for type-2 routes here. */ diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c index 2ae62cf7d235..ee2092c81c91 100644 --- a/bgpd/bgp_route.c +++ b/bgpd/bgp_route.c @@ -12276,6 +12276,7 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, bool nexthop_self = CHECK_FLAG(path->flags, BGP_PATH_ANNC_NH_SELF) ? true : false; int i; + const char *msgstr; char *nexthop_hostname = bgp_nexthop_hostname(path->peer, path->nexthop); char time_buf[64]; struct bgp_path_info *bpi_ultimate = @@ -13318,19 +13319,41 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, /* Line 10 display PMSI tunnel attribute, if present */ if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_PMSI_TUNNEL))) { - const char *str = lookup_msg(bgp_pmsi_tnltype_str, - bgp_attr_get_pmsi_tnl_type(attr), - PMSI_TNLTYPE_STR_DEFAULT); + msgstr = lookup_msg(bgp_pmsi_tnltype_str, bgp_attr_get_pmsi_tnl_type(attr), + PMSI_TNLTYPE_STR_DEFAULT); if (json_paths) { json_pmsi = json_object_new_object(); - json_object_string_add(json_pmsi, "tunnelType", str); + json_object_string_add(json_pmsi, "tunnelType", msgstr); json_object_int_add(json_pmsi, "label", label2vni(&attr->label)); + + if (bgp_attr_get_pmsi_tnl_type(attr) == PMSI_TNLTYPE_INGR_REPL) { + if (IS_MAPPED_IPV6(&attr->tunn_id)) { + json_object_string_addf( + json_pmsi, "id", "%pI4", + (in_addr_t *)&attr->tunn_id.s6_addr32[3]); + } else { + json_object_string_addf(json_pmsi, "id", "%pI6", + &attr->tunn_id); + } + } json_object_object_add(json_path, "pmsi", json_pmsi); - } else - vty_out(vty, " PMSI Tunnel Type: %s, label: %d\n", - str, label2vni(&attr->label)); + } else if (bgp_attr_get_pmsi_tnl_type(attr) == PMSI_TNLTYPE_INGR_REPL) { + /* Include tunnel ID for known types */ + if (IS_MAPPED_IPV6(&attr->tunn_id)) { + vty_out(vty, " PMSI Tunnel Type: %s, label: %d ID:%pI4\n", + msgstr, label2vni(&attr->label), + (in_addr_t *)&attr->tunn_id.s6_addr32[3]); + } else { + vty_out(vty, " PMSI Tunnel Type: %s, label: %d ID:%pI6\n", + msgstr, label2vni(&attr->label), &attr->tunn_id); + } + } else { + /* Label only */ + vty_out(vty, " PMSI Tunnel Type: %s, label: %d\n", msgstr, + label2vni(&attr->label)); + } } if (path->peer->connection->t_gr_restart && diff --git a/tests/topotests/bgp_evpn_v6_pmsi_tunnel/r1/evpn_type3.json b/tests/topotests/bgp_evpn_v6_pmsi_tunnel/r1/evpn_type3.json new file mode 100644 index 000000000000..899efe96da95 --- /dev/null +++ b/tests/topotests/bgp_evpn_v6_pmsi_tunnel/r1/evpn_type3.json @@ -0,0 +1,9 @@ +{ + "101": { + "type": "L2", + "numRemoteVteps": 1, + "remoteVteps": [ + "fd00:100::2" + ] + } +} diff --git a/tests/topotests/bgp_evpn_v6_pmsi_tunnel/r1/frr.conf b/tests/topotests/bgp_evpn_v6_pmsi_tunnel/r1/frr.conf new file mode 100644 index 000000000000..0a8accb09926 --- /dev/null +++ b/tests/topotests/bgp_evpn_v6_pmsi_tunnel/r1/frr.conf @@ -0,0 +1,15 @@ +interface r1-eth0 + ipv6 address fd00:100::1/64 +! +router bgp 65000 + bgp router-id 10.10.10.1 + bgp log-neighbor-changes + no bgp default ipv4-unicast + neighbor fd00:100::2 remote-as 65000 + neighbor fd00:100::2 capability extended-nexthop + ! + address-family l2vpn evpn + neighbor fd00:100::2 activate + advertise-all-vni + exit-address-family +! diff --git a/tests/topotests/bgp_evpn_v6_pmsi_tunnel/r2/evpn_type3.json b/tests/topotests/bgp_evpn_v6_pmsi_tunnel/r2/evpn_type3.json new file mode 100644 index 000000000000..8e496d3ec212 --- /dev/null +++ b/tests/topotests/bgp_evpn_v6_pmsi_tunnel/r2/evpn_type3.json @@ -0,0 +1,9 @@ +{ + "101": { + "type": "L2", + "numRemoteVteps": 1, + "remoteVteps": [ + "fd00:100::1" + ] + } +} diff --git a/tests/topotests/bgp_evpn_v6_pmsi_tunnel/r2/frr.conf b/tests/topotests/bgp_evpn_v6_pmsi_tunnel/r2/frr.conf new file mode 100644 index 000000000000..a5c8d585afb1 --- /dev/null +++ b/tests/topotests/bgp_evpn_v6_pmsi_tunnel/r2/frr.conf @@ -0,0 +1,15 @@ +interface r2-eth0 + ipv6 address fd00:100::2/64 +! +router bgp 65000 + bgp router-id 10.10.10.2 + bgp log-neighbor-changes + no bgp default ipv4-unicast + neighbor fd00:100::1 remote-as 65000 + neighbor fd00:100::1 capability extended-nexthop + ! + address-family l2vpn evpn + neighbor fd00:100::1 activate + advertise-all-vni + exit-address-family +! diff --git a/tests/topotests/bgp_evpn_v6_pmsi_tunnel/test_bgp_evpn_v6_pmsi_tunnel.py b/tests/topotests/bgp_evpn_v6_pmsi_tunnel/test_bgp_evpn_v6_pmsi_tunnel.py new file mode 100644 index 000000000000..a1ec12acfd2a --- /dev/null +++ b/tests/topotests/bgp_evpn_v6_pmsi_tunnel/test_bgp_evpn_v6_pmsi_tunnel.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_evpn_v6_pmsi_tunnel.py +# +# Copyright (c) 2025 Nvidia Inc. +# Donald Sharp + +""" +Test that EVPN with an IPv6-only underlay correctly encodes and parses +the PMSI Tunnel attribute with an IPv6 tunnel identifier (length 21). + +Type-3 IMET routes carry the PMSI Tunnel attribute, so this test verifies +that two FRR routers with IPv6-only EVPN peering and IPv6 VTEPs can +exchange type-3 routes and install remote VTEPs. +""" + +import json +from functools import partial +import os +import sys +import pytest +import platform + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +from lib import topotest +from lib.topogen import Topogen, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + tgen.add_router("r1") + tgen.add_router("r2") + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + krel = platform.release() + if topotest.version_cmp(krel, "4.18") < 0: + logger.info( + 'BGP EVPN v6 PMSI tests require kernel >= 4.18 (have "{}")'.format(krel) + ) + return pytest.skip("Kernel too old for EVPN NETNS test") + + cmds_vxlan = [ + "ip link add name bridge-101 up type bridge stp_state 0", + "ip link set dev bridge-101 up", + "ip link add name vxlan-101 type vxlan id 101 dstport 4789 dev {0}-eth0 local {1}", + "ip link set dev vxlan-101 master bridge-101", + "ip link set vxlan-101 up type bridge_slave learning off flood off mcast_flood off", + ] + + for rname, vtep_ip in [("r1", "fd00:100::1"), ("r2", "fd00:100::2")]: + router = tgen.gears[rname] + for cmd in cmds_vxlan: + formatted = cmd.format(rname, vtep_ip) + logger.info("cmd to {}: {}".format(rname, formatted)) + output = router.cmd_raises(formatted) + logger.info("result: " + output) + + for rname, router in tgen.routers().items(): + logger.info("Loading router %s" % rname) + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + +def teardown_module(_mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_convergence(): + """ + Assert that BGP EVPN sessions come up between r1 and r2 over IPv6. + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for rname in ("r1", "r2"): + router = tgen.gears[rname] + + expected = {"peers": {}} + if rname == "r1": + expected["peers"]["fd00:100::2"] = {"state": "Established"} + else: + expected["peers"]["fd00:100::1"] = {"state": "Established"} + + test_func = partial( + topotest.router_json_cmp, + router, + "show bgp l2vpn evpn summary json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, '"{}" BGP session did not establish'.format(rname) + + +def test_evpn_type3_routes(): + """ + Verify EVPN type-3 IMET routes are received. These carry the PMSI Tunnel + attribute. With IPv6-only peering, the PMSI tunnel identifier must be + encoded as 21 bytes (IPv6). Without the fix, these routes would be + rejected as malformed. + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for rname in ("r1", "r2"): + router = tgen.gears[rname] + json_file = "{}/{}/evpn_type3.json".format(CWD, rname) + expected = json.loads(open(json_file).read()) + + test_func = partial( + topotest.router_json_cmp, + router, + "show evpn vni json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, '"{}" EVPN type-3 route check failed'.format(rname) + + +def test_memory_leak(): + 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)) diff --git a/tests/topotests/bgp_evpn_vxlan_topo1/test_bgp_evpn_vxlan.py b/tests/topotests/bgp_evpn_vxlan_topo1/test_bgp_evpn_vxlan.py index 1e8b967dbacf..f4ed0903f102 100755 --- a/tests/topotests/bgp_evpn_vxlan_topo1/test_bgp_evpn_vxlan.py +++ b/tests/topotests/bgp_evpn_vxlan_topo1/test_bgp_evpn_vxlan.py @@ -30,6 +30,7 @@ evpn_mac_learn_test, evpn_mac_test_local_remote, evpn_show_vni_json_elide_ifindex, + evpn_check_bgp_imet, ) from lib.topogen import Topogen, TopoRouter, get_topogen from lib.topolog import logger @@ -632,6 +633,39 @@ def test_evpn_l3vni_vlan_bridge(): assert "Type: L3" in output, assertmsg +def test_imet(): + """ + Verify PMSI tunnel attribute info + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + dut_name = "PE1" + dut = tgen.gears[dut_name] + rd = "10.30.30.30:2" + prefix = "[3]:[0]:[32]:[10.30.30.30]" + pmsi_label = 101 + pmsi_id = "10.30.30.30" + # Check Imet from PE2 to PE1 + test_fn = partial(evpn_check_bgp_imet, dut, rd, prefix, pmsi_label, pmsi_id) + _, result = topotest.run_and_expect(test_fn, None, count=10, wait=3) + assertmsg = f"{dut_name} IMET not present/incorrect, result:{result}" + assert result is None, assertmsg + + # Check Imet from PE1 to PE2 + dut_name = "PE2" + dut = tgen.gears[dut_name] + rd = "10.10.10.10:2" + prefix = "[3]:[0]:[32]:[10.10.10.10]" + pmsi_label = 101 + pmsi_id = "10.10.10.10" + test_fn = partial(evpn_check_bgp_imet, dut, rd, prefix, pmsi_label, pmsi_id) + _, result = topotest.run_and_expect(test_fn, None, count=10, wait=3) + assertmsg = f"{dut_name} IMET not present/incorrect, result:{result}" + assert result is None, assertmsg + + def test_remote_neigh_uninstall_on_vxlan_down(): "Ensure remote neighs are removed when VxLAN if is down" tgen = get_topogen() diff --git a/tests/topotests/bgp_evpn_vxlan_topo1/test_bgp_evpn_vxlan_v6_vtep.py b/tests/topotests/bgp_evpn_vxlan_topo1/test_bgp_evpn_vxlan_v6_vtep.py index d4828d435c3b..4cd6cba93bef 100755 --- a/tests/topotests/bgp_evpn_vxlan_topo1/test_bgp_evpn_vxlan_v6_vtep.py +++ b/tests/topotests/bgp_evpn_vxlan_topo1/test_bgp_evpn_vxlan_v6_vtep.py @@ -34,6 +34,7 @@ evpn_mac_learn_test, evpn_mac_test_local_remote, evpn_show_vni_json_elide_ifindex, + evpn_check_bgp_imet, ) from lib.topogen import Topogen, TopoRouter, get_topogen from lib.topolog import logger @@ -117,10 +118,12 @@ def setup_module(mod): # For all registered routers, load the zebra configuration file for rname, router in router_list.items(): router.load_config( - TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra_v6_vtep.conf".format(rname)) + TopoRouter.RD_ZEBRA, + os.path.join(CWD, "{}/zebra_v6_vtep.conf".format(rname)), ) router.load_config( - TopoRouter.RD_OSPF6, os.path.join(CWD, "{}/ospfd_v6_vtep.conf".format(rname)) + TopoRouter.RD_OSPF6, + os.path.join(CWD, "{}/ospfd_v6_vtep.conf".format(rname)), ) router.load_config( TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd_v6_vtep.conf".format(rname)) @@ -299,6 +302,34 @@ def test_ip_pe2_learn(): # tgen.mininet_cli() +def test_imet(): + tgen = get_topogen() + + dut_name = "PE1" + dut = tgen.gears[dut_name] + rd = "10.30.30.30:2" + prefix = "[3]:[0]:[128]:[10:30:30::30]" + pmsi_label = 101 + pmsi_id = "10:30:30::30" + # Check Imet from PE2 to PE1 + test_fn = partial(evpn_check_bgp_imet, dut, rd, prefix, pmsi_label, pmsi_id) + _, result = topotest.run_and_expect(test_fn, None, count=10, wait=3) + assertmsg = f"{dut_name} IMET not present/incorrect, result:{result}" + assert result is None, assertmsg + + # Check Imet from PE1 to PE2 + dut_name = "PE2" + dut = tgen.gears[dut_name] + rd = "10.10.10.10:2" + prefix = "[3]:[0]:[128]:[10:10:10::10]" + pmsi_label = 101 + pmsi_id = "10:10:10::10" + test_fn = partial(evpn_check_bgp_imet, dut, rd, prefix, pmsi_label, pmsi_id) + _, result = topotest.run_and_expect(test_fn, None, count=10, wait=3) + assertmsg = f"{dut_name} IMET not present/incorrect, result:{result}" + assert result is None, assertmsg + + def test_memory_leak(): "Run the memory leak test and report results." tgen = get_topogen() diff --git a/tests/topotests/lib/evpn.py b/tests/topotests/lib/evpn.py index 490435782668..f42fbdc2c0e4 100644 --- a/tests/topotests/lib/evpn.py +++ b/tests/topotests/lib/evpn.py @@ -1558,6 +1558,34 @@ def evpn_trigger_host_arp(tgen, host_gateways, interface="swp1", count=3, interv sleep(interval) +# +# +# +def evpn_check_bgp_imet(dut, rd, prefix, pmsi_label, pmsi_id): + """ + Return error if the type-3 PMSI attr label or ID don't match the inputs + """ + rd_routes_json = dut.vtysh_cmd(f"show bgp l2vpn evpn route rd {rd} type 3 json") + rd_routes = json.loads(rd_routes_json) + + if not rd_routes: + return "Imet routes not found" + + if rd not in rd_routes or prefix not in rd_routes[rd]: + return f"Imet routes not found for rd {rd} and {prefix}" + paths = rd_routes[rd][prefix]["paths"] + if not len(paths): + return f"Imet route paths routes not found for rd {rd} and {prefix}" + out_label = paths[0][0]["pmsi"].get("label", 0) + if out_label != pmsi_label: + return f"Imet PMSI Label mismatch Expected {pmsi_label} Got {out_label}" + + out_id = paths[0][0]["pmsi"].get("id", "") + if out_id != pmsi_id: + return f"Imet PMSI Id mismatch Expected {pmsi_id} Got {out_id}" + return None + + def evpn_trigger_arp_scapy(tgen, host_gateways, interface="swp1"): """ Trigger ARP using Scapy to populate MAC address tables in the EVPN fabric.