Repository: sonic-net/sonic-mgmt

Issue:
Missing coverage for IPv6 TC to queue mapping
<!--
If you are reporting a new issue, make sure that we do not have any duplicates
already open. You can ensure this by searching the issue list for this
repository. If there is a duplicate, please close your issue and add a comment
to the existing issue instead.

If you suspect your issue is a bug, please edit your issue description to
include the BUG REPORT INFORMATION shown below. If you fail to provide this
information within 7 days, we cannot debug your issue and will close it. We
will, however, reopen it if you later provide the information.

For more information about reporting issues, see
https://github.com/sonic-net/SONiC/wiki#report-issues

---------------------------------------------------
GENERAL SUPPORT INFORMATION
---------------------------------------------------

The GitHub issue tracker is for bug reports and feature requests.
General support can be found at the following locations:

- SONiC Support Forums - https://groups.google.com/forum/#!forum/sonicproject

---------------------------------------------------
BUG REPORT INFORMATION
---------------------------------------------------
Use the commands below to provide key information from your environment:
You do NOT have to include this information if this is a FEATURE REQUEST
-->

**Description**
There is currently a test gap for IPv6 traffic for TC to queue mapping.


Discussion:
@arlakshm 

Resolving patch:
diff --git a/ansible/library/show_ipv6_interface.py b/ansible/library/show_ipv6_interface.py
new file mode 100644
--- /dev/null
+++ b/ansible/library/show_ipv6_interface.py
@@ -0,0 +1,116 @@
+#!/usr/bin/python
+
+from ansible.module_utils.basic import AnsibleModule
+import socket
+
+DOCUMENTATION = '''
+module: show_ipv6_interface.py
+Short_description: Retrieve show ipv6 interface
+Description:
+    - Retrieve IPv6 address of interface and IPv4 address of its neighbor
+
+options:
+    - namespace::
+          Description: In multi ASIC env, namespace to run the command
+          Required: False
+
+'''
+
+EXAMPLES = '''
+  # Get show ipv6 interface
+  - show_ipv6_interface:
+
+  # Get show ipv6 interface in namespace asic0
+  - show_ipv6_interface: namespace='asic0'
+
+'''
+
+
+def split_dash(string_with_dash):
+    return string_with_dash.split("/")
+
+
+class ShowIpv6InterfaceModule(object):
+    def __init__(self):
+        self.module = AnsibleModule(
+            argument_spec=dict(
+                namespace=dict(required=False, type='str', default=None),
+            ),
+            supports_check_mode=False
+        )
+        self.m_args = self.module.params
+        self.out = None
+        self.facts = {}
+        self.ns = ""
+        ns = self.m_args["namespace"]
+        if ns is not None:
+            self.ns = " -n {} -d all  ".format(ns)
+
+    def run(self):
+        """
+            Main method of the class
+        """
+        self.ip_int = {}
+        try:
+            rc, self.out, err = self.module.run_command(
+                "show ipv6 interfaces{}".format(self.ns),
+                executable='/bin/bash',
+                use_unsafe_shell=True
+            )
+            for line in self.out.split("\n"):
+                line = line.split()
+
+                # only collect non-link addresses
+                if not len(line) or (not line[0].startswith("Ethernet") and not line[0].startswith("PortChannel")):
+                    continue
+
+                intf = line[0]
+
+                if len(line) == 6:
+                    address, prefix = split_dash(line[2])
+                    admin, oper = split_dash(line[3])
+                    bgp_neighbour = line[4]
+                    peer_ipv6 = line[5]
+                elif len(line) == 5:
+                    address, prefix = split_dash(line[1])
+                    admin, oper = split_dash(line[2])
+                    bgp_neighbour = line[3]
+                    peer_ipv6 = line[4]
+                else:
+                    raise Exception("Unexpected output")
+
+                if peer_ipv6 == "N/A":
+                    continue
+
+                # sanity check ipv6 address
+                try:
+                    socket.inet_pton(socket.AF_INET6, address)
+                except socket.error:
+                    continue
+
+                self.ip_int[intf] = {
+                      "ipv6": address,
+                      "prefix_len": prefix,
+                      "admin": admin,
+                      "oper_state": oper,
+                      "bgp_neighbour": bgp_neighbour,
+                      "peer_ipv6": peer_ipv6
+                }
+            self.facts['ipv6_interfaces'] = self.ip_int
+        except Exception as e:
+            self.module.fail_json(msg=str(e))
+        if rc != 0:
+            self.module.fail_json(
+                msg="Command failed rc = %d, out = %s, err = %s" % (rc, self.out, err))
+
+        self.module.exit_json(ansible_facts=self.facts)
+
+
+def main():
+    ShowIpInt = ShowIpv6InterfaceModule()
+    ShowIpInt.run()
+    return
+
+
+if __name__ == "__main__":
+    main()


Test patch:
diff --git a/tests/common/devices/sonic.py b/tests/common/devices/sonic.py
--- a/tests/common/devices/sonic.py
+++ b/tests/common/devices/sonic.py
@@ -2171,6 +2171,32 @@ def ping_v4(self, ipv4, count=1, ns_arg=""):
             return False
         return True
 
+    def ping_v6(self, ipv6, count=1, ns_arg=""):
+        """
+        Returns 'True' if ping to IP address works, else 'False'
+        Args:
+            IPv6 address
+
+        Returns:
+            True or False
+        """
+        try:
+            socket.inet_pton(socket.AF_INET6, ipv6)
+        except socket.error:
+            raise Exception("Invalid IPv6 address {}".format(ipv6))
+
+        netns_arg = ""
+        if ns_arg is not DEFAULT_NAMESPACE:
+            netns_arg = "sudo ip netns exec {} ".format(ns_arg)
+
+        try:
+            self.shell("{}ping -6 -q -c{} {} > /dev/null".format(
+                netns_arg, count, ipv6
+            ))
+        except RunAnsibleModuleFail:
+            return False
+        return True
+
     def is_backend_portchannel(self, port_channel, mg_facts):
         ports = mg_facts["minigraph_portchannels"].get(port_channel)
         # minigraph facts does not have backend portchannel IFs
@@ -2181,17 +2207,21 @@ def is_backend_portchannel(self, port_channel, mg_facts):
     def is_backend_port(self, port, mg_facts):
         return True if "Ethernet-BP" in port else False
 
-    def active_ip_interfaces(self, ip_ifs, tbinfo, ns_arg=DEFAULT_NAMESPACE, intf_num="all"):
+    def active_ip_interfaces(self, ip_ifs, tbinfo, ns_arg=DEFAULT_NAMESPACE, intf_num="all", ipv6_ifs=None):
         """
         Return a dict of active IP (Ethernet or PortChannel) interfaces, with
         interface and peer IPv4 address.
 
+        If ipv6_ifs exists, also returns the interfaces' IPv6 address and its peer
+        IPv6 addresses if found for each interface.
+
         Returns:
-            Dict of Interfaces and their IPv4 address
+            Dict of Interfaces and their IPv4 address (with IPv6 if ipv6_ifs exists)
         """
         active_ip_intf_cnt = 0
         mg_facts = self.get_extended_minigraph_facts(tbinfo, ns_arg)
         ip_ifaces = {}
+
         for k, v in list(ip_ifs.items()):
             if ((k.startswith("Ethernet") and not is_inband_port(k)) or
                (k.startswith("PortChannel") and not
@@ -2207,6 +2237,15 @@ def active_ip_interfaces(self, ip_ifs, tbinfo, ns_arg=DEFAULT_NAMESPACE, intf_nu
                     }
                     active_ip_intf_cnt += 1
 
+                    if ipv6_ifs:
+                        ipv6_intf = ipv6_ifs[k]
+                        if (ipv6_intf["peer_ipv6"] != "N/A" and self.ping_v6(ipv6_intf["peer_ipv6"],
+                                                                             count=3, ns_arg=ns_arg)):
+                            ip_ifaces[k].update({
+                                "ipv6": ipv6_intf["ipv6"],
+                                "peer_ipv6": ipv6_intf["peer_ipv6"]
+                            })
+
                 if isinstance(intf_num, int) and intf_num > 0 and active_ip_intf_cnt == intf_num:
                     break
 
diff --git a/tests/common/devices/sonic_asic.py b/tests/common/devices/sonic_asic.py
--- a/tests/common/devices/sonic_asic.py
+++ b/tests/common/devices/sonic_asic.py
@@ -154,6 +154,19 @@ def show_ip_interface(self, *module_args, **complex_args):
         complex_args['namespace'] = self.namespace
         return self.sonichost.show_ip_interface(*module_args, **complex_args)
 
+    def show_ipv6_interface(self, *module_args, **complex_args):
+        """Wrapper for the ansible module 'show_ipv6_interface'
+
+        Args:
+            module_args: other ansible module args passed from the caller
+            complex_args: other ansible keyword args
+
+        Returns:
+            [dict]: [the output of show ipv6 interface status command]
+        """
+        complex_args['namespace'] = self.namespace
+        return self.sonichost.show_ipv6_interface(*module_args, **complex_args)
+
     def run_sonic_db_cli_cmd(self, sonic_db_cmd):
         cmd = "{} {}".format(self.sonic_db_cli, sonic_db_cmd)
         return self.sonichost.command(cmd, verbose=False)
@@ -281,17 +294,23 @@ def is_backend_portchannel(self, port_channel):
                 return False
         return True
 
-    def get_active_ip_interfaces(self, tbinfo, intf_num="all"):
+    def get_active_ip_interfaces(self, tbinfo, intf_num="all", include_ipv6=False):
         """
         Return a dict of active IP (Ethernet or PortChannel) interfaces, with
         interface and peer IPv4 address.
 
+        If include_ipv6 is true, also returns IPv6 and its peer IPv6 addresses.
+
         Returns:
-            Dict of Interfaces and their IPv4 address
+            Dict of Interfaces and their IPv4 address (with IPv6 if include_ipv6 option is true)
         """
+        ipv6_ifs = None
         ip_ifs = self.show_ip_interface()["ansible_facts"]["ip_interfaces"]
+        if include_ipv6:
+            ipv6_ifs = self.show_ipv6_interface()["ansible_facts"]["ipv6_interfaces"]
+
         return self.sonichost.active_ip_interfaces(
-            ip_ifs, tbinfo, self.namespace, intf_num=intf_num
+            ip_ifs, tbinfo, self.namespace, intf_num=intf_num, ipv6_ifs=ipv6_ifs
         )
 
     def bgp_drop_rule(self, ip_version, state="present"):
diff --git a/tests/qos/qos_sai_base.py b/tests/qos/qos_sai_base.py
--- a/tests/qos/qos_sai_base.py
+++ b/tests/qos/qos_sai_base.py
@@ -120,7 +120,7 @@ def runPtfTest(self, ptfhost, testCase='', testParams={}, relax=False):
             Raises:
                 RunAnsibleModuleFail if ptf test fails
         """
-        custom_options = " --disable-ipv6 --disable-vxlan --disable-geneve" \
+        custom_options = " --disable-vxlan --disable-geneve" \
                          " --disable-erspan --disable-mpls --disable-nvgre"
         ptf_runner(
             ptfhost,
@@ -754,18 +754,24 @@ def __buildTestPorts(self, request, testPortIds, testPortIps, src_port_ids,
         dstVlan3 = dst_test_port_ips[dstPort3]['vlan_id'] if 'vlan_id' in dst_test_port_ips[dstPort3] else None
         srcPort = srcPorts[0] if src_port_ids else src_test_port_ids[srcPorts[0]]
         srcVlan = src_test_port_ips[srcPort]['vlan_id'] if 'vlan_id' in src_test_port_ips[srcPort] else None
+
+        src_port_ip = src_test_port_ips[srcPorts[0] if src_port_ids else src_test_port_ids[srcPorts[0]]]
         return {
          "dst_port_id": dstPort,
          "dst_port_ip": dst_test_port_ips[dstPort]['peer_addr'],
+         "dst_port_ipv6": dst_test_port_ips[dstPort]['peer_addr_ipv6'],
          "dst_port_vlan": dstVlan,
          "dst_port_2_id": dstPort2,
          "dst_port_2_ip": dst_test_port_ips[dstPort2]['peer_addr'],
+         "dst_port_2_ipv6": dst_test_port_ips[dstPort2]['peer_addr_ipv6'],
          "dst_port_2_vlan": dstVlan2,
          'dst_port_3_id': dstPort3,
          "dst_port_3_ip": dst_test_port_ips[dstPort3]['peer_addr'],
+         "dst_port_3_ipv6": dst_test_port_ips[dstPort3]['peer_addr_ipv6'],
          "dst_port_3_vlan": dstVlan3,
          "src_port_id": srcPort,
-         "src_port_ip": src_test_port_ips[srcPorts[0] if src_port_ids else src_test_port_ids[srcPorts[0]]]["peer_addr"],
+         "src_port_ip": src_port_ip["peer_addr"],
+         "src_port_ipv6": src_port_ip["peer_addr_ipv6"],
          "src_port_vlan": srcVlan
         }
 
@@ -866,6 +872,15 @@ def dutConfig(
                             uplinkPortIds.append(portIndex)
                             uplinkPortIps.append(portConfig["peer_addr"])
                             uplinkPortNames.append(intf)
+                elif ipaddress.ip_interface(portConfig['peer_addr']).ip.version == 6:
+                    portIndex = src_mgFacts["minigraph_ptf_indices"][intf]
+                    if portIndex in testPortIds[src_dut_index][src_asic_index]:
+                        if portIndex in dutPortIps[src_dut_index][src_asic_index]:
+                            dutPortIps[src_dut_index][src_asic_index][portIndex].update(
+                                {'peer_addr_ipv6': portConfig['peer_addr']})
+                        else:
+                            portIpMap = {'peer_addr_ipv6': portConfig['peer_addr']}
+                            dutPortIps[src_dut_index][src_asic_index].update({portIndex: portIpMap})
 
             testPortIps[src_dut_index] = {}
             testPortIps[src_dut_index][src_asic_index] = self.__assignTestPortIps(src_mgFacts, topo)
@@ -882,14 +897,14 @@ def dutConfig(
             testPortIds[src_dut_index] = {}
             for dut_asic in get_src_dst_asic_and_duts['all_asics']:
                 dutPortIps[src_dut_index][dut_asic.asic_index] = {}
-                for iface, addr in dut_asic.get_active_ip_interfaces(tbinfo).items():
+                for iface, addr in dut_asic.get_active_ip_interfaces(tbinfo, include_ipv6=True).items():
                     vlan_id = None
                     if iface.startswith("Ethernet"):
                         portName = iface
                         if "." in iface:
                             portName, vlan_id = iface.split(".")
                         portIndex = src_mgFacts["minigraph_ptf_indices"][portName]
-                        portIpMap = {'peer_addr': addr["peer_ipv4"]}
+                        portIpMap = {'peer_addr': addr["peer_ipv4"], 'peer_addr_ipv6': addr["peer_ipv6"]}
                         if vlan_id is not None:
                             portIpMap['vlan_id'] = vlan_id
                         dutPortIps[src_dut_index][dut_asic.asic_index].update({portIndex: portIpMap})
@@ -898,7 +913,7 @@ def dutConfig(
                             iter(src_mgFacts["minigraph_portchannels"][iface]["members"])
                         )
                         portIndex = src_mgFacts["minigraph_ptf_indices"][portName]
-                        portIpMap = {'peer_addr': addr["peer_ipv4"]}
+                        portIpMap = {'peer_addr': addr["peer_ipv4"], 'peer_addr_ipv6': addr["peer_ipv6"]}
                         dutPortIps[src_dut_index][dut_asic.asic_index].update({portIndex: portIpMap})
                     # If the leaf router is using separated DSCP_TO_TC_MAP on uplink/downlink ports.
                     # we also need to test them separately
@@ -939,18 +954,20 @@ def dutConfig(
             dutPortIps[src_dut_index] = {}
             testPortIds[src_dut_index] = {}
             dutPortIps[src_dut_index][src_asic_index] = {}
-            active_ips = src_asic.get_active_ip_interfaces(tbinfo)
+            active_ips = src_asic.get_active_ip_interfaces(tbinfo, include_ipv6=True)
             for iface, addr in active_ips.items():
                 if iface.startswith("Ethernet") and ("Ethernet-Rec" not in iface):
                     portIndex = src_mgFacts["minigraph_ptf_indices"][iface]
-                    portIpMap = {'peer_addr': addr["peer_ipv4"], 'port': iface}
+                    portIpMap = {'peer_addr': addr["peer_ipv4"], 'peer_addr_ipv6': addr['peer_ipv6'],
+                                 'port': iface}
                     dutPortIps[src_dut_index][src_asic_index].update({portIndex: portIpMap})
                 elif iface.startswith("PortChannel"):
                     portName = next(
                         iter(src_mgFacts["minigraph_portchannels"][iface]["members"])
                     )
                     portIndex = src_mgFacts["minigraph_ptf_indices"][portName]
-                    portIpMap = {'peer_addr': addr["peer_ipv4"], 'port': portName}
+                    portIpMap = {'peer_addr': addr["peer_ipv4"], 'peer_addr_ipv6': addr['peer_ipv6'],
+                                 'port': portName}
                     dutPortIps[src_dut_index][src_asic_index].update({portIndex: portIpMap})
 
             testPortIds[src_dut_index][src_asic_index] = sorted(dutPortIps[src_dut_index][src_asic_index].keys())
@@ -966,18 +983,20 @@ def dutConfig(
                 else:
                     dst_mgFacts = src_mgFacts
                 dutPortIps[dst_dut_index][dst_asic_index] = {}
-                active_ips = dst_asic.get_active_ip_interfaces(tbinfo)
+                active_ips = dst_asic.get_active_ip_interfaces(tbinfo, include_ipv6=True)
                 for iface, addr in active_ips.items():
                     if iface.startswith("Ethernet") and ("Ethernet-Rec" not in iface):
                         portIndex = dst_mgFacts["minigraph_ptf_indices"][iface]
-                        portIpMap = {'peer_addr': addr["peer_ipv4"], 'port': iface}
+                        portIpMap = {'peer_addr': addr["peer_ipv4"], 'peer_addr_ipv6': addr['peer_ipv6'],
+                                     'port': iface}
                         dutPortIps[dst_dut_index][dst_asic_index].update({portIndex: portIpMap})
                     elif iface.startswith("PortChannel"):
                         portName = next(
                             iter(dst_mgFacts["minigraph_portchannels"][iface]["members"])
                         )
                         portIndex = dst_mgFacts["minigraph_ptf_indices"][portName]
-                        portIpMap = {'peer_addr': addr["peer_ipv4"], 'port': portName}
+                        portIpMap = {'peer_addr': addr["peer_ipv4"], 'peer_addr_ipv6': addr['peer_ipv6'],
+                                     'port': portName}
                         dutPortIps[dst_dut_index][dst_asic_index].update({portIndex: portIpMap})
 
                 testPortIds[dst_dut_index][dst_asic_index] = sorted(dutPortIps[dst_dut_index][dst_asic_index].keys())
diff --git a/tests/qos/test_qos_sai.py b/tests/qos/test_qos_sai.py
--- a/tests/qos/test_qos_sai.py
+++ b/tests/qos/test_qos_sai.py
@@ -1282,8 +1282,9 @@ def testQosSaiLossyQueueVoq(
         except Exception:
             raise
 
+    @pytest.mark.parametrize("ip_version", ["ipv4", "ipv6"])
     def testQosSaiDscpQueueMapping(
-        self, ptfhost, get_src_dst_asic_and_duts, dutTestParams, dutConfig, dut_qos_maps # noqa F811
+        self, ip_version, ptfhost, get_src_dst_asic_and_duts, dutTestParams, dutConfig, dut_qos_maps # noqa F811
     ):
         """
             Test QoS SAI DSCP to queue mapping
@@ -1311,11 +1312,22 @@ def testQosSaiDscpQueueMapping(
 
         testParams = dict()
         testParams.update(dutTestParams["basicParams"])
+
+        if ip_version == "ipv6":
+            testParams.update({
+                "src_port_ip": dutConfig["testPorts"]["src_port_ipv6"],
+                "dst_port_ip": dutConfig["testPorts"]["dst_port_ipv6"],
+                "ipv6": True
+            })
+        else:
+            testParams.update({
+                "src_port_ip": dutConfig["testPorts"]["src_port_ip"],
+                "dst_port_ip": dutConfig["testPorts"]["dst_port_ip"],
+            })
+
         testParams.update({
             "dst_port_id": dutConfig["testPorts"]["dst_port_id"],
-            "dst_port_ip": dutConfig["testPorts"]["dst_port_ip"],
             "src_port_id": dutConfig["testPorts"]["src_port_id"],
-            "src_port_ip": dutConfig["testPorts"]["src_port_ip"],
             "hwsku": dutTestParams['hwsku'],
             "dual_tor": dutConfig['dualTor'],
             "dual_tor_scenario": dutConfig['dualTorScenario']
diff --git a/tests/saitests/py3/sai_qos_tests.py b/tests/saitests/py3/sai_qos_tests.py
--- a/tests/saitests/py3/sai_qos_tests.py
+++ b/tests/saitests/py3/sai_qos_tests.py
@@ -20,6 +20,7 @@
                            simple_qinq_tcp_packet,
                            simple_ip_packet,
                            simple_ipv4ip_packet,
+                           simple_ipv6ip_packet,
                            hex_dump_buffer,
                            verify_packet_any_port)
 from ptf.mask import Mask
@@ -277,37 +278,60 @@ def construct_ip_pkt(pkt_len, dst_mac, src_mac, src_ip, dst_ip, dscp, src_vlan,
     ip_id = kwargs.get('ip_id', None)
     ttl = kwargs.get('ttl', None)
     exp_pkt = kwargs.get('exp_pkt', False)
+    ipv6 = kwargs.get('ipv6', False)
 
     tos = (dscp << 2) | ecn
     pkt_args = {
         'pktlen': pkt_len,
         'eth_dst': dst_mac,
         'eth_src': src_mac,
-        'ip_src': src_ip,
-        'ip_dst': dst_ip,
-        'ip_tos': tos
     }
-    if ip_id is not None:
+
+    if ipv6:
+        pkt_args.update({
+            'ipv6_src': src_ip,
+            'ipv6_dst': dst_ip,
+            'ipv6_dscp': dscp,
+            'ipv6_ecn': ecn
+        })
+    else:
+        pkt_args.update({
+            'ip_src': src_ip,
+            'ip_dst': dst_ip,
+            'ip_tos': tos
+        })
+
+    if ip_id is not None and not ipv6:
         pkt_args['ip_id'] = ip_id
 
     if ttl is not None:
-        pkt_args['ip_ttl'] = ttl
+        if ipv6:
+            pkt_args['ipv6_hlim'] = ttl
+        else:
+            pkt_args['ip_ttl'] = ttl
 
     if src_vlan is not None:
         pkt_args['dl_vlan_enable'] = True
         pkt_args['vlan_vid'] = int(src_vlan)
         pkt_args['vlan_pcp'] = dscp
 
-    pkt = simple_ip_packet(**pkt_args)
+    if ipv6:
+        pkt = simple_ipv6ip_packet(**pkt_args)
+    else:
+        pkt = simple_ip_packet(**pkt_args)
 
     if exp_pkt:
         masked_exp_pkt = Mask(pkt, ignore_extra_bytes=True)
         masked_exp_pkt.set_do_not_care_scapy(scapy.Ether, "dst")
         masked_exp_pkt.set_do_not_care_scapy(scapy.Ether, "src")
-        masked_exp_pkt.set_do_not_care_scapy(scapy.IP, "chksum")
-        masked_exp_pkt.set_do_not_care_scapy(scapy.IP, "ttl")
-        masked_exp_pkt.set_do_not_care_scapy(scapy.IP, "len")
-        masked_exp_pkt.set_do_not_care_scapy(scapy.IP, "len")
+
+        if ipv6:
+            masked_exp_pkt.set_do_not_care_scapy(scapy.IPv6, "hlim")
+        else:
+            masked_exp_pkt.set_do_not_care_scapy(scapy.IP, "chksum")
+            masked_exp_pkt.set_do_not_care_scapy(scapy.IP, "ttl")
+            masked_exp_pkt.set_do_not_care_scapy(scapy.IP, "len")
+
         if src_vlan is not None:
             masked_exp_pkt.set_do_not_care_scapy(scapy.Dot1Q, "vlan")
         return masked_exp_pkt
@@ -334,11 +358,11 @@ def construct_arp_pkt(eth_dst, eth_src, arp_op, src_ip, dst_ip, hw_dst, src_vlan
     return pkt
 
 
-def get_rx_port(dp, device_number, src_port_id, dst_mac, dst_ip, src_ip, src_vlan=None):
+def get_rx_port(dp, device_number, src_port_id, dst_mac, dst_ip, src_ip, src_vlan=None, ipv6=False):
     ip_id = 0xBABE
     src_port_mac = dp.dataplane.get_mac(device_number, src_port_id)
-    pkt = construct_ip_pkt(64, dst_mac, src_port_mac,
-                           src_ip, dst_ip, 0, src_vlan, ip_id=ip_id)
+    pkt = construct_ip_pkt(64, dst_mac, src_port_mac, src_ip, dst_ip, 0, src_vlan, ip_id=ip_id, ipv6=ipv6)
+
     # Send initial packet for any potential ARP resolution, which may cause the LAG
     # destination to change. Can occur especially when running tests in isolation on a
     # first test attempt.
@@ -348,7 +372,7 @@ def get_rx_port(dp, device_number, src_port_id, dst_mac, dst_ip, src_ip, src_vla
     send_packet(dp, src_port_id, pkt, 1)
 
     masked_exp_pkt = construct_ip_pkt(
-        48, dst_mac, src_port_mac, src_ip, dst_ip, 0, src_vlan, ip_id=ip_id, exp_pkt=True)
+        48, dst_mac, src_port_mac, src_ip, dst_ip, 0, src_vlan, ip_id=ip_id, ipv6=ipv6, exp_pkt=True)
 
     pre_result = dp.dataplane.poll(
         device_number=0, exp_pkt=masked_exp_pkt, timeout=3)
@@ -635,6 +659,7 @@ def runTest(self):
         dual_tor = self.test_params.get('dual_tor', None)
         leaf_downstream = self.test_params.get('leaf_downstream', None)
         asic_type = self.test_params['sonic_asic_type']
+        ipv6 = self.test_params.get('ipv6', False)
         exp_ip_id = 101
         exp_ttl = 63
         pkt_dst_mac = router_mac if router_mac != '' else dst_port_mac
@@ -644,7 +669,7 @@ def runTest(self):
         # in case dst_port_id is part of LAG, find out the actual dst port
         # for given IP parameters
         dst_port_id = get_rx_port(
-            self, 0, src_port_id, pkt_dst_mac, dst_port_ip, src_port_ip
+            self, 0, src_port_id, pkt_dst_mac, dst_port_ip, src_port_ip, ipv6=ipv6
         )
         print("actual dst_port_id: %d" % (dst_port_id), file=sys.stderr)
         print("dst_port_mac: %s, src_port_mac: %s, src_port_ip: %s, dst_port_ip: %s" % (
@@ -677,14 +702,24 @@ def runTest(self):
             for dscp in range(0, 64):
                 tos = (dscp << 2)
                 tos |= 1
-                pkt = simple_ip_packet(pktlen=64,
-                                       eth_dst=pkt_dst_mac,
-                                       eth_src=src_port_mac,
-                                       ip_src=src_port_ip,
-                                       ip_dst=dst_port_ip,
-                                       ip_tos=tos,
-                                       ip_id=exp_ip_id,
-                                       ip_ttl=ip_ttl)
+
+                if ipv6:
+                    pkt = simple_ipv6ip_packet(pktlen=64,
+                                               eth_dst=pkt_dst_mac,
+                                               eth_src=src_port_mac,
+                                               ipv6_src=src_port_ip,
+                                               ipv6_dst=dst_port_ip,
+                                               ipv6_tc=tos,
+                                               ipv6_hlim=ip_ttl)
+                else:
+                    pkt = simple_ip_packet(pktlen=64,
+                                           eth_dst=pkt_dst_mac,
+                                           eth_src=src_port_mac,
+                                           ip_src=src_port_ip,
+                                           ip_dst=dst_port_ip,
+                                           ip_tos=tos,
+                                           ip_id=exp_ip_id,
+                                           ip_ttl=ip_ttl)
                 send_packet(self, src_port_id, pkt, 1)
                 print("dscp: %d, calling send_packet()" %
                       (tos >> 2), file=sys.stderr)
@@ -703,11 +738,16 @@ def runTest(self):
 
                     # Verify dscp flag
                     try:
-                        if (recv_pkt.payload.tos == tos and
-                                recv_pkt.payload.src == src_port_ip and
-                                recv_pkt.payload.dst == dst_port_ip and
-                                recv_pkt.payload.ttl == exp_ttl and
-                                recv_pkt.payload.id == exp_ip_id):
+                        if ((recv_pkt.payload.src == src_port_ip and
+                             recv_pkt.payload.dst == dst_port_ip)
+                            and
+                            ((ipv6 and
+                              recv_pkt.payload.hlim == exp_ttl and
+                              recv_pkt.payload.tc == tos)
+                             or
+                             (recv_pkt.payload.ttl == exp_ttl and
+                              recv_pkt.payload.id == exp_ip_id and
+                              recv_pkt.payload.tos == tos))):
                             dscp_received = True
                             print("dscp: %d, total received: %d" %
                                   (tos >> 2, cnt), file=sys.stderr)