From 39e19126a6eff6821ec16f3ad50820bd1fa5c292 Mon Sep 17 00:00:00 2001 From: Guy Shemesh Date: Wed, 26 Nov 2025 16:48:41 +0200 Subject: [PATCH] Manual cherry-pick of PR https://github.com/sonic-net/sonic-mgmt/pull/21377/files Adjust test_bgpmon.py to handle running over ipv6 only topologies --- tests/bgp/test_bgpmon.py | 235 +++++++++++++----- .../tests_mark_conditions.yaml | 4 +- 2 files changed, 170 insertions(+), 69 deletions(-) diff --git a/tests/bgp/test_bgpmon.py b/tests/bgp/test_bgpmon.py index 8954753d9b..49c5561625 100644 --- a/tests/bgp/test_bgpmon.py +++ b/tests/bgp/test_bgpmon.py @@ -8,10 +8,11 @@ import json from tests.common.fixtures.ptfhost_utils import change_mac_addresses # noqa F401 from tests.common.fixtures.ptfhost_utils import remove_ip_addresses # noqa F401 -from tests.common.helpers.generators import generate_ip_through_default_route +from tests.common.helpers.generators import generate_ip_through_default_route, generate_ip_through_default_v6_route from tests.common.helpers.assertions import pytest_assert from tests.common.utilities import wait_until from tests.common.utilities import wait_tcp_connection +from tests.common.utilities import is_ipv6_only_topology from bgp_helpers import BGPMON_TEMPLATE_FILE, BGPMON_CONFIG_FILE, BGP_MONITOR_NAME, BGP_MONITOR_PORT pytestmark = [ @@ -22,12 +23,14 @@ BGP_CONNECT_TIMEOUT = 121 MAX_TIME_FOR_BGPMON = 180 ZERO_ADDR = r'0.0.0.0/0' +ZERO_ADDR_V6 = r'::/0' logger = logging.getLogger(__name__) -def get_default_route_ports(host, tbinfo, default_addr=ZERO_ADDR): +def get_default_route_ports(host, tbinfo, default_addr=ZERO_ADDR, is_ipv6=False): mg_facts = host.get_extended_minigraph_facts(tbinfo) - route_info = json.loads(host.shell("show ip route {} json".format(default_addr))['stdout']) + ip_cmd = "ipv6" if is_ipv6 else "ip" + route_info = json.loads(host.shell("show {} route {} json".format(ip_cmd, default_addr))['stdout']) ports = [] for route in route_info[default_addr]: if route['protocol'] != 'bgp': @@ -47,14 +50,41 @@ def get_default_route_ports(host, tbinfo, default_addr=ZERO_ADDR): @pytest.fixture -def common_setup_teardown(dut_with_default_route, tbinfo): +def common_setup_teardown(dut_with_default_route, tbinfo): duthost = dut_with_default_route - peer_addr = generate_ip_through_default_route(duthost) - pytest_assert(peer_addr, "Failed to generate ip address for test") - peer_addr = str(IPNetwork(peer_addr).ip) - peer_ports = get_default_route_ports(duthost, tbinfo) + is_ipv6_only = is_ipv6_only_topology(tbinfo) + + # Generate a unique IPV4 address to be used as the BGP router identifier for the monitor connection + router_id = generate_ip_through_default_route(duthost) + pytest_assert(router_id, "Failed to generate router id") + router_id = str(IPNetwork(router_id).ip) + + if is_ipv6_only: + peer_addr = generate_ip_through_default_v6_route(duthost) + pytest_assert(peer_addr, "Failed to generate ipv6 address for test") + peer_addr = str(IPNetwork(peer_addr).ip) + else: + peer_addr = router_id + + peer_ports = get_default_route_ports( + duthost, + tbinfo, + default_addr=ZERO_ADDR_V6 if is_ipv6_only else ZERO_ADDR, + is_ipv6=is_ipv6_only + ) mg_facts = duthost.minigraph_facts(host=duthost.hostname)['ansible_facts'] - local_addr = mg_facts['minigraph_lo_interfaces'][0]['addr'] + + local_addr = None + for lo_intf in mg_facts['minigraph_lo_interfaces']: + if is_ipv6_only and ':' in lo_intf['addr']: + local_addr = lo_intf['addr'] + break + elif not is_ipv6_only and ':' not in lo_intf['addr']: + local_addr = lo_intf['addr'] + break + + pytest_assert(local_addr, "Failed to get appropriate loopback address") + # Assign peer addr to an interface on ptf logger.info("Generated peer address {}".format(peer_addr)) bgpmon_args = { @@ -67,35 +97,55 @@ def common_setup_teardown(dut_with_default_route, tbinfo): bgpmon_template = Template(open(BGPMON_TEMPLATE_FILE).read()) duthost.copy(content=bgpmon_template.render(**bgpmon_args), dest=BGPMON_CONFIG_FILE) - yield local_addr, peer_addr, peer_ports, mg_facts['minigraph_bgp_asn'] + yield local_addr, peer_addr, peer_ports, mg_facts['minigraph_bgp_asn'], is_ipv6_only, router_id # Cleanup bgp monitor duthost.run_sonic_db_cli_cmd("CONFIG_DB del 'BGP_MONITORS|{}'".format(peer_addr), asic_index='all') duthost.file(path=BGPMON_CONFIG_FILE, state='absent') -def build_syn_pkt(local_addr, peer_addr): - pkt = testutils.simple_tcp_packet( - pktlen=54, - ip_src=local_addr, - ip_dst=peer_addr, - tcp_dport=BGP_PORT, - tcp_flags="S" - ) - exp_packet = Mask(pkt) - exp_packet.set_do_not_care_scapy(scapy.Ether, "dst") - exp_packet.set_do_not_care_scapy(scapy.Ether, "src") - - exp_packet.set_do_not_care_scapy(scapy.IP, "version") - exp_packet.set_do_not_care_scapy(scapy.IP, "ihl") - exp_packet.set_do_not_care_scapy(scapy.IP, "tos") - exp_packet.set_do_not_care_scapy(scapy.IP, "len") - exp_packet.set_do_not_care_scapy(scapy.IP, "flags") - exp_packet.set_do_not_care_scapy(scapy.IP, "id") - exp_packet.set_do_not_care_scapy(scapy.IP, "frag") - exp_packet.set_do_not_care_scapy(scapy.IP, "ttl") - exp_packet.set_do_not_care_scapy(scapy.IP, "chksum") - exp_packet.set_do_not_care_scapy(scapy.IP, "options") +def build_syn_pkt(local_addr, peer_addr, is_ipv6=False): + if is_ipv6: + pkt = testutils.simple_tcpv6_packet( + pktlen=74, + ipv6_src=local_addr, + ipv6_dst=peer_addr, + tcp_dport=BGP_PORT, + tcp_flags="S" + ) + exp_packet = Mask(pkt) + exp_packet.set_do_not_care_scapy(scapy.Ether, "dst") + exp_packet.set_do_not_care_scapy(scapy.Ether, "src") + + exp_packet.set_do_not_care_scapy(scapy.IPv6, "version") + exp_packet.set_do_not_care_scapy(scapy.IPv6, "tc") + exp_packet.set_do_not_care_scapy(scapy.IPv6, "fl") + exp_packet.set_do_not_care_scapy(scapy.IPv6, "plen") + exp_packet.set_do_not_care_scapy(scapy.IPv6, "nh") + exp_packet.set_do_not_care_scapy(scapy.IPv6, "hlim") + else: + pkt = testutils.simple_tcp_packet( + pktlen=54, + ip_src=local_addr, + ip_dst=peer_addr, + tcp_dport=BGP_PORT, + tcp_flags="S" + ) + exp_packet = Mask(pkt) + exp_packet.set_do_not_care_scapy(scapy.Ether, "dst") + exp_packet.set_do_not_care_scapy(scapy.Ether, "src") + exp_packet.set_do_not_care_scapy(scapy.IP, "version") + exp_packet.set_do_not_care_scapy(scapy.IP, "ihl") + exp_packet.set_do_not_care_scapy(scapy.IP, "tos") + exp_packet.set_do_not_care_scapy(scapy.IP, "len") + exp_packet.set_do_not_care_scapy(scapy.IP, "id") + exp_packet.set_do_not_care_scapy(scapy.IP, "flags") + exp_packet.set_do_not_care_scapy(scapy.IP, "frag") + exp_packet.set_do_not_care_scapy(scapy.IP, "ttl") + exp_packet.set_do_not_care_scapy(scapy.IP, "chksum") + exp_packet.set_do_not_care_scapy(scapy.IP, "options") + + # TCP fields (common for both IPv4 and IPv6) exp_packet.set_do_not_care_scapy(scapy.TCP, "sport") exp_packet.set_do_not_care_scapy(scapy.TCP, "seq") exp_packet.set_do_not_care_scapy(scapy.TCP, "ack") @@ -120,6 +170,13 @@ def test_resolve_via_default_exist(duthost): "ipv6 nht resolve-via-default not present in global FRR config") +def configure_ipv6_bgpmon_update_source(duthost, asn, local_addr): + duthost.run_vtysh( + "-c 'configure terminal' -c 'router bgp {}' -c 'neighbor BGPMON update-source {}'".format(asn, local_addr), + asic_index='all' + ) + + def test_bgpmon(dut_with_default_route, localhost, enum_rand_one_frontend_asic_index, common_setup_teardown, set_timeout_for_bgpmon, ptfadapter, ptfhost): """ @@ -128,77 +185,123 @@ def test_bgpmon(dut_with_default_route, localhost, enum_rand_one_frontend_asic_i duthost = dut_with_default_route asichost = duthost.asic_instance(enum_rand_one_frontend_asic_index) - def bgpmon_peer_connected(duthost, bgpmon_peer): + def bgpmon_peer_connected(duthost, bgpmon_peer, is_ipv6): try: bgp_summary = json.loads(asichost.run_vtysh("-c 'show bgp summary json'")['stdout']) - return bgp_summary['ipv4Unicast']['peers'][bgpmon_peer]["state"] == "Established" + af_key = 'ipv6Unicast' if is_ipv6 else 'ipv4Unicast' + return bgp_summary[af_key]['peers'][bgpmon_peer]["state"] == "Established" except Exception: logger.info('Unable to get bgp status') return False - local_addr, peer_addr, peer_ports, asn = common_setup_teardown - exp_packet = build_syn_pkt(local_addr, peer_addr) + local_addr, peer_addr, peer_ports, asn, is_ipv6_only, router_id = common_setup_teardown + exp_packet = build_syn_pkt(local_addr, peer_addr, is_ipv6=is_ipv6_only) # Flush dataplane ptfadapter.dataplane.flush() # Load bgp monitor config logger.info("Configured bgpmon and verifying packet on {}".format(peer_ports)) asichost.write_to_config_db(BGPMON_CONFIG_FILE) + if is_ipv6_only: + configure_ipv6_bgpmon_update_source(duthost, asn, local_addr) # Verify syn packet on ptf - (rcvd_port_index, rcvd_pkt) = testutils.verify_packet_any_port(test=ptfadapter, pkt=exp_packet, - ports=peer_ports, timeout=BGP_CONNECT_TIMEOUT) + (rcvd_port_index, rcvd_pkt) = testutils.verify_packet_any_port( + test=ptfadapter, pkt=exp_packet, ports=peer_ports, timeout=BGP_CONNECT_TIMEOUT + ) # ip as BGMPMON IP , mac as the neighbor mac(mac for default nexthop that was used for sending syn packet) , # add the neighbor entry and the default route for dut loopback ptf_interface = "eth" + str(peer_ports[rcvd_port_index]) res = ptfhost.shell('cat /sys/class/net/{}/address'.format(ptf_interface)) original_mac = res['stdout'] ptfhost.shell("ifconfig %s hw ether %s" % (ptf_interface, scapy.Ether(rcvd_pkt).dst)) - ptfhost.shell("ip add add %s dev %s" % (peer_addr + "/24", ptf_interface)) - ptfhost.exabgp(name=BGP_MONITOR_NAME, - state="started", - local_ip=peer_addr, - router_id=peer_addr, - peer_ip=local_addr, - local_asn=asn, - peer_asn=asn, - port=BGP_MONITOR_PORT, passive=True) - ptfhost.shell("ip neigh add %s lladdr %s dev %s" % (local_addr, duthost.facts["router_mac"], ptf_interface)) - ptfhost.shell("ip route replace %s dev %s" % (local_addr + "/32", ptf_interface)) + + ip_cmd = "-6" if is_ipv6_only else "" + prefix_len = "/64" if is_ipv6_only else "/24" + route_prefix_len = "/128" if is_ipv6_only else "/32" + + ptfhost.shell("ip %s addr add %s dev %s" % (ip_cmd, peer_addr + prefix_len, ptf_interface)) + ptfhost.exabgp( + name=BGP_MONITOR_NAME, + state="started", + local_ip=peer_addr, + router_id=router_id, + peer_ip=local_addr, + local_asn=asn, + peer_asn=asn, + port=BGP_MONITOR_PORT, + passive=True + ) + ptfhost.shell( + "ip %s neigh add %s lladdr %s dev %s" + % (ip_cmd, local_addr, duthost.facts["router_mac"], ptf_interface) + ) + ptfhost.shell( + "ip %s route replace %s dev %s" + % (ip_cmd, local_addr + route_prefix_len, ptf_interface) + ) try: - pytest_assert(wait_tcp_connection(localhost, ptfhost.mgmt_ip, BGP_MONITOR_PORT, timeout_s=60), - "Failed to start bgp monitor session on PTF") - pytest_assert(wait_until(MAX_TIME_FOR_BGPMON, 5, 0, bgpmon_peer_connected, duthost, peer_addr), - "BGPMon Peer connection not established") + pytest_assert( + wait_tcp_connection(localhost, ptfhost.mgmt_ip, BGP_MONITOR_PORT, timeout_s=60), + "Failed to start bgp monitor session on PTF" + ) + pytest_assert( + wait_until( + MAX_TIME_FOR_BGPMON, 5, 0, bgpmon_peer_connected, duthost, peer_addr, is_ipv6_only + ), + "BGPMon Peer connection not established" + ) finally: ptfhost.exabgp(name=BGP_MONITOR_NAME, state="absent") - ptfhost.shell("ip route del %s dev %s" % (local_addr + "/32", ptf_interface)) - ptfhost.shell("ip neigh del %s lladdr %s dev %s" % (local_addr, duthost.facts["router_mac"], ptf_interface)) - ptfhost.shell("ip add del %s dev %s" % (peer_addr + "/24", ptf_interface)) + ptfhost.shell( + "ip %s route del %s dev %s" + % (ip_cmd, local_addr + route_prefix_len, ptf_interface) + ) + ptfhost.shell( + "ip %s neigh del %s lladdr %s dev %s" + % (ip_cmd, local_addr, duthost.facts["router_mac"], ptf_interface) + ) + ptfhost.shell( + "ip %s addr del %s dev %s" + % (ip_cmd, peer_addr + prefix_len, ptf_interface) + ) ptfhost.shell("ifconfig %s hw ether %s" % (ptf_interface, original_mac)) def test_bgpmon_no_resolve_via_default(dut_with_default_route, enum_rand_one_frontend_asic_index, common_setup_teardown, ptfadapter): """ - Verify no syn for BGP is sent when 'ip nht resolve-via-default' is disabled. + Verify no syn for BGP is sent when 'ip nht resolve-via-default' or 'ipv6 nht resolve-via-default' is disabled. """ duthost = dut_with_default_route asichost = duthost.asic_instance(enum_rand_one_frontend_asic_index) - local_addr, peer_addr, peer_ports, asn = common_setup_teardown - exp_packet = build_syn_pkt(local_addr, peer_addr) + local_addr, peer_addr, peer_ports, asn, is_ipv6_only, router_id = common_setup_teardown + exp_packet = build_syn_pkt(local_addr, peer_addr, is_ipv6=is_ipv6_only) + + ip_cmd = "ipv6" if is_ipv6_only else "ip" + # Load bgp monitor config - logger.info("Configured bgpmon and verifying no packet on {} when resolve-via-default is disabled" - .format(peer_ports)) + logger.info( + "Configured bgpmon and verifying no packet on {} when resolve-via-default is disabled".format(peer_ports) + ) try: # Disable resolve-via-default - duthost.run_vtysh(" -c \"configure terminal\" -c \"no ip nht resolve-via-default\"", asic_index='all') + duthost.run_vtysh( + " -c \"configure terminal\" -c \"no {} nht resolve-via-default\"".format(ip_cmd), + asic_index='all' + ) # Flush dataplane ptfadapter.dataplane.flush() asichost.write_to_config_db(BGPMON_CONFIG_FILE) # Verify no syn packet is received - pytest_assert(0 == testutils.count_matched_packets_all_ports(test=ptfadapter, exp_packet=exp_packet, - ports=peer_ports, timeout=BGP_CONNECT_TIMEOUT), - "Syn packets is captured when resolve-via-default is disabled") + pytest_assert( + 0 == testutils.count_matched_packets_all_ports( + test=ptfadapter, exp_packet=exp_packet, ports=peer_ports, timeout=BGP_CONNECT_TIMEOUT + ), + "Syn packets is captured when resolve-via-default is disabled" + ) finally: # Re-enable resolve-via-default - duthost.run_vtysh("-c \"configure terminal\" -c \"ip nht resolve-via-default\"", asic_index='all') + duthost.run_vtysh( + "-c \"configure terminal\" -c \"{} nht resolve-via-default\"".format(ip_cmd), + asic_index='all' + ) diff --git a/tests/common/plugins/conditional_mark/tests_mark_conditions.yaml b/tests/common/plugins/conditional_mark/tests_mark_conditions.yaml index d2aad5ef85..5716da2297 100644 --- a/tests/common/plugins/conditional_mark/tests_mark_conditions.yaml +++ b/tests/common/plugins/conditional_mark/tests_mark_conditions.yaml @@ -459,11 +459,9 @@ bgp/test_bgp_update_timer.py::test_bgp_update_timer_single_route: bgp/test_bgpmon.py: skip: - reason: "Not supported on T2 topology or topology backend - or Skip for IPv6-only topologies, since there are v6 verison of the test" + reason: "Not supported on T2 topology or topology backend" conditions: - "'backend' in topo_name or 't2' in topo_name" - - "'-v6-' in topo_name" bgp/test_bgpmon_v6.py::test_bgpmon_no_ipv6_resolve_via_default: skip: