diff --git a/tests/common/plugins/conditional_mark/tests_mark_conditions.yaml b/tests/common/plugins/conditional_mark/tests_mark_conditions.yaml index 9a969d5bd7..04dfecfdc2 100644 --- a/tests/common/plugins/conditional_mark/tests_mark_conditions.yaml +++ b/tests/common/plugins/conditional_mark/tests_mark_conditions.yaml @@ -1884,6 +1884,18 @@ srv6/test_srv6_basic_sanity.py: conditions: - topo_name not in ["ciscovs-7nodes", "ciscovs-5nodes"] +srv6/test_srv6_dataplane.py: + skip: + reason: "Only target mellanox platform with 202412 image at this time" + conditions: + - "asic_type not in ['mellanox', 'broadcom'] or release not in ['202412']" + +srv6/test_srv6_static_config.py: + skip: + reason: "Requires particular image support, skip in PR testing" + conditions: + - "release not in ['202412']" + ####################################### ##### ssh ##### ####################################### diff --git a/tests/common/plugins/ptfadapter/__init__.py b/tests/common/plugins/ptfadapter/__init__.py index 5c0f618e33..273c0438d8 100644 --- a/tests/common/plugins/ptfadapter/__init__.py +++ b/tests/common/plugins/ptfadapter/__init__.py @@ -168,7 +168,8 @@ def check_if_use_minigraph_from_tbinfo(tbinfo): return False return True - with PtfTestAdapter(tbinfo['ptf_ip'], ptf_nn_agent_port, 0, list(ifaces_map.keys()), ptfhost) as adapter: + with PtfTestAdapter(tbinfo['ptf_ip'], tbinfo['ptf_ipv6'], + ptf_nn_agent_port, 0, list(ifaces_map.keys()), ptfhost) as adapter: if not request.config.option.keep_payload: override_ptf_functions() node_id = request.module.__name__ diff --git a/tests/common/plugins/ptfadapter/ptfadapter.py b/tests/common/plugins/ptfadapter/ptfadapter.py index efedbbc30b..e1075fc0ca 100644 --- a/tests/common/plugins/ptfadapter/ptfadapter.py +++ b/tests/common/plugins/ptfadapter/ptfadapter.py @@ -30,9 +30,10 @@ class PtfTestAdapter(BaseTest): # the number of currently established connections NN_STAT_CURRENT_CONNECTIONS = 201 - def __init__(self, ptf_ip, ptf_nn_port, device_num, ptf_port_set, ptfhost): + def __init__(self, ptf_ip, ptf_ipv6, ptf_nn_port, device_num, ptf_port_set, ptfhost): """ initialize PtfTestAdapter :param ptf_ip: PTF host IP + :param ptf_ipv6: PTF host IPv6 address :param ptf_nn_port: PTF nanomessage agent port :param device_num: device number :param ptf_port_set: PTF ports @@ -43,7 +44,7 @@ def __init__(self, ptf_ip, ptf_nn_port, device_num, ptf_port_set, ptfhost): self.payload_pattern = "" self.connected = False self.ptfhost = ptfhost - self._init_ptf_dataplane(ptf_ip, ptf_nn_port, device_num, ptf_port_set) + self._init_ptf_dataplane(ptf_ip, ptf_ipv6, ptf_nn_port, device_num, ptf_port_set) def __enter__(self): """ enter in 'with' block """ @@ -64,17 +65,19 @@ def _check_ptf_nn_agent_availability(self, socket_addr): finally: sock.close() - def _init_ptf_dataplane(self, ptf_ip, ptf_nn_port, device_num, ptf_port_set, ptf_config=None): + def _init_ptf_dataplane(self, ptf_ip, ptf_ipv6, ptf_nn_port, device_num, ptf_port_set, ptf_config=None): """ initialize ptf framework and establish connection to ptf_nn_agent running on PTF host :param ptf_ip: PTF host IP + :param ptf_ipv6: PTF host IPv6 address :param ptf_nn_port: PTF nanomessage agent port :param device_num: device number :param ptf_port_set: PTF ports :return: """ self.ptf_ip = ptf_ip + self.ptf_ipv6 = ptf_ipv6 self.ptf_nn_port = ptf_nn_port self.device_num = device_num self.ptf_port_set = ptf_port_set @@ -137,7 +140,8 @@ def reinit(self, ptf_config=None): self.ptfhost.command('supervisorctl update') self.ptfhost.command('supervisorctl restart ptf_nn_agent') - self._init_ptf_dataplane(self.ptf_ip, self.ptf_nn_port, self.device_num, self.ptf_port_set, ptf_config) + self._init_ptf_dataplane(self.ptf_ip, self.ptf_ipv6, self.ptf_nn_port, + self.device_num, self.ptf_port_set, ptf_config) def update_payload(self, pkt): """Update the payload of packet to the default pattern when certain conditions are met. diff --git a/tests/srv6/srv6_utils.py b/tests/srv6/srv6_utils.py index a9c1c31917..0eb804f0b2 100755 --- a/tests/srv6/srv6_utils.py +++ b/tests/srv6/srv6_utils.py @@ -128,11 +128,13 @@ def runSendReceive(pkt, src_port, exp_pkt, dst_ports, pkt_expected, ptfadapter): # Send the packet and poll on destination ports testutils.send(ptfadapter, src_port, pkt, 1) logger.debug("Sent packet: " + pkt.summary()) + + time.sleep(1) (index, rcv_pkt) = testutils.verify_packet_any_port(ptfadapter, exp_pkt, dst_ports) received = False if rcv_pkt: received = True - pytest_assert(received is True) + pytest_assert(received == pkt_expected) logger.debug('index=%s, received=%s' % (str(index), str(received))) if received: logger.debug("Received packet: " + scapy.Ether(rcv_pkt).summary()) diff --git a/tests/srv6/test_srv6_dataplane.py b/tests/srv6/test_srv6_dataplane.py new file mode 100644 index 0000000000..47eb637185 --- /dev/null +++ b/tests/srv6/test_srv6_dataplane.py @@ -0,0 +1,179 @@ +import pytest +import time +import random +import logging +from scapy.layers.inet6 import IPv6, ICMPv6EchoRequest +from scapy.layers.l2 import Ether + +from srv6_utils import runSendReceive +from common.helpers.voq_helpers import get_neighbor_info +from ptf.testutils import simple_ipv6_sr_packet + +logger = logging.getLogger(__name__) + +pytestmark = [ + pytest.mark.asic("mellanox", "broadcom"), + pytest.mark.topology("t0", "t1") +] + + +def get_ptf_src_port_and_dut_port_and_neighbor(dut, tbinfo): + """Get the PTF port mapping for the duthost or an asic of the duthost""" + dut_mg_facts = dut.get_extended_minigraph_facts(tbinfo) + ports_map = dut_mg_facts["minigraph_ptf_indices"] + if len(ports_map) == 0: + pytest.skip("No PTF ports found for {}".format(dut)) + + lldp_table = dut.command("show lldp table")['stdout'].split("\n")[3:] + neighbor_table = [line.split() for line in lldp_table] + for entry in neighbor_table: + intf = entry[0] + if intf in ports_map: + return intf, ports_map[intf], entry[1] # local intf, ptf_src_port, neighbor hostname + + dut_port, ptf_src_port = random.choice(ports_map) + return dut_port, ptf_src_port, None + + +@pytest.mark.parametrize("with_srh", [True, False]) +def test_srv6_uN_forwarding(duthosts, enum_frontend_dut_hostname, enum_frontend_asic_index, + ptfadapter, tbinfo, nbrhosts, with_srh): + duthost = duthosts[enum_frontend_dut_hostname] + asic_index = enum_frontend_asic_index + + if duthost.is_multi_asic: + cli_options = " -n " + duthost.get_namespace_from_asic_id(asic_index) + dut_asic = duthost.asic_instance[asic_index] + dut_mac = dut_asic.get_router_mac() + dut_port, ptf_src_port, neighbor = get_ptf_src_port_and_dut_port_and_neighbor(dut_asic, tbinfo) + else: + cli_options = '' + dut_mac = duthost._get_router_mac() + dut_port, ptf_src_port, neighbor = get_ptf_src_port_and_dut_port_and_neighbor(duthost, tbinfo) + + logger.info("Doing test on DUT port {} | PTF port {}".format(dut_port, ptf_src_port)) + + # get neighbor IP + lines = duthost.command("show ipv6 bgp sum")['stdout'].split("\n") + for line in lines: + if neighbor in line: + neighbor_ip = line.split()[0] + assert neighbor_ip + + # use DUT portchannel if applicable + pc_info = duthost.command("show int portchannel")['stdout'] + if dut_port in pc_info: + lines = pc_info.split("\n") + for line in lines: + if dut_port in line: + dut_port = line.split()[1] + + sonic_db_cli = "sonic-db-cli" + cli_options + + # add a locator configuration entry + duthost.command(sonic_db_cli + " CONFIG_DB HSET SRV6_MY_LOCATORS\\|loc1 prefix fcbb:bbbb:1::") + # add a uN sid configuration entry + duthost.command(sonic_db_cli + + " CONFIG_DB HSET SRV6_MY_SIDS\\|loc1\\|fcbb:bbbb:1::/48 action uN decap_dscp_mode pipe") + # add the static route for IPv6 forwarding towards PTF's uSID + duthost.command(sonic_db_cli + " CONFIG_DB HSET STATIC_ROUTE\\|default\\|fcbb:bbbb:2::/48 nexthop {} ifname {}" + .format(neighbor_ip, dut_port)) + time.sleep(5) + + for i in range(0, 10): + if with_srh: + injected_pkt = simple_ipv6_sr_packet( + eth_dst=dut_mac, + eth_src=ptfadapter.dataplane.get_mac(0, ptf_src_port).decode(), + ipv6_src=ptfadapter.ptf_ipv6, + ipv6_dst="fcbb:bbbb:1:2::", + srh_seg_left=1, + srh_nh=41, + inner_frame=IPv6()/ICMPv6EchoRequest(seq=i) + ) + else: + injected_pkt = Ether(dst=dut_mac, src=ptfadapter.dataplane.get_mac(0, ptf_src_port).decode()) \ + / IPv6(src=ptfadapter.ptf_ipv6, dst="fcbb:bbbb:1:2::") \ + / IPv6() / ICMPv6EchoRequest(seq=i) + + expected_pkt = injected_pkt.copy() + expected_pkt['Ether'].dst = get_neighbor_info(neighbor_ip, nbrhosts)['mac'] + expected_pkt['Ether'].src = dut_mac + expected_pkt['IPv6'].dst = "fcbb:bbbb:2::" + expected_pkt['IPv6'].hlim -= 1 + logger.debug("Expected packet #{}: {}".format(i, expected_pkt.summary())) + runSendReceive(injected_pkt, ptf_src_port, expected_pkt, [ptf_src_port], True, ptfadapter) + + # delete the SRv6 configuration + duthost.command(sonic_db_cli + " CONFIG_DB DEL SRV6_MY_LOCATORS\\|loc1") + duthost.command(sonic_db_cli + " CONFIG_DB DEL SRV6_MY_SIDS\\|loc1\\|fcbb:bbbb:1::/48") + duthost.command(sonic_db_cli + " CONFIG_DB DEL STATIC_ROUTE\\|default\\|fcbb:bbbb:2::/48") + + +@pytest.mark.parametrize("with_srh", [True, False]) +def test_srv6_uN_decap_pipe_mode(duthosts, enum_frontend_dut_hostname, enum_frontend_asic_index, + ptfadapter, tbinfo, nbrhosts, with_srh): + duthost = duthosts[enum_frontend_dut_hostname] + asic_index = enum_frontend_asic_index + + if duthost.is_multi_asic: + cli_options = " -n " + duthost.get_namespace_from_asic_id(asic_index) + dut_asic = duthost.asic_instance[asic_index] + dut_mac = dut_asic.get_router_mac() + dut_port, ptf_src_port, neighbor = get_ptf_src_port_and_dut_port_and_neighbor(dut_asic, tbinfo) + else: + cli_options = '' + dut_mac = duthost._get_router_mac() + dut_port, ptf_src_port, neighbor = get_ptf_src_port_and_dut_port_and_neighbor(duthost, tbinfo) + + logger.info("Doing test on DUT port {} | PTF port {}".format(dut_port, ptf_src_port)) + + # get neighbor IP + lines = duthost.command("show ipv6 bgp sum")['stdout'].split("\n") + for line in lines: + if neighbor in line: + neighbor_ip = line.split()[0] + assert neighbor_ip + + # use DUT portchannel if applicable + pc_info = duthost.command("show int portchannel")['stdout'] + if dut_port in pc_info: + lines = pc_info.split("\n") + for line in lines: + if dut_port in line: + dut_port = line.split()[1] + + sonic_db_cli = "sonic-db-cli" + cli_options + + # add a locator configuration entry + duthost.command(sonic_db_cli + " CONFIG_DB HSET SRV6_MY_LOCATORS\\|loc1 prefix fcbb:bbbb:1::") + # add a uN sid configuration entry + duthost.command(sonic_db_cli + + " CONFIG_DB HSET SRV6_MY_SIDS\\|loc1\\|fcbb:bbbb:1::/48 action uN decap_dscp_mode pipe") + + time.sleep(5) + + for i in range(0, 10): + if with_srh: + injected_pkt = simple_ipv6_sr_packet( + eth_dst=dut_mac, + eth_src=ptfadapter.dataplane.get_mac(0, ptf_src_port).decode(), + ipv6_src=ptfadapter.ptf_ipv6, + ipv6_dst="fcbb:bbbb:1::", + srh_seg_left=1, + srh_nh=41, + inner_frame=IPv6(dst=neighbor_ip, src=ptfadapter.ptf_ipv6)/ICMPv6EchoRequest(seq=i) + ) + else: + injected_pkt = Ether(dst=dut_mac, src=ptfadapter.dataplane.get_mac(0, ptf_src_port).decode()) \ + / IPv6(src=ptfadapter.ptf_ipv6, dst="fcbb:bbbb:1::") \ + / IPv6(dst=neighbor_ip, src=ptfadapter.ptf_ipv6) / ICMPv6EchoRequest(seq=i) + + expected_pkt = Ether(dst=get_neighbor_info(neighbor_ip, nbrhosts)['mac'], src=dut_mac) / \ + IPv6(dst=neighbor_ip, src=ptfadapter.ptf_ipv6)/ICMPv6EchoRequest(seq=i) + logger.debug("Expected packet #{}: {}".format(i, expected_pkt.summary())) + runSendReceive(injected_pkt, ptf_src_port, expected_pkt, [ptf_src_port], True, ptfadapter) + + # delete the SRv6 configuration + duthost.command(sonic_db_cli + " CONFIG_DB DEL SRV6_MY_LOCATORS\\|loc1") + duthost.command(sonic_db_cli + " CONFIG_DB DEL SRV6_MY_SIDS\\|loc1\\|fcbb:bbbb:1::/48")