diff --git a/tests/TestSuite_mev_cpfl_hairpin_queue.py b/tests/TestSuite_mev_cpfl_hairpin_queue.py
new file mode 100644
index 00000000..d9cc71f7
--- /dev/null
+++ b/tests/TestSuite_mev_cpfl_hairpin_queue.py
@@ -0,0 +1,281 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2023 Intel Corporation
+#
+import re
+import time
+
+from framework.packet import Packet
+from framework.pmd_output import PmdOutput
+from framework.test_case import TestCase
+
+match_pkts = {
+    "rule_1_match_pkt": 'Ether(src="00:11:22:33:44:55", dst="de:ad:be:ef:20:24")/IP(src="10.2.29.100", dst="10.2.29.2")/UDP()',
+    "rule_2_match_pkt": 'Ether(src="00:11:22:33:44:55", dst="00:06:00:00:02:18")/IP(src="133.133.10.20", dst="133.133.10.10")/UDP()',
+    "rule_3_match_pkt": 'Ether(src="00:11:22:33:44:55", dst="de:ad:be:ef:05:34")/IP(src="10.2.28.10", dst="10.2.28.20")/UDP()',
+    "rule_4_match_pkt": 'Ether(src="00:11:22:33:44:55", dst="de:ad:be:ef:10:14")/IP(src="10.2.28.10", dst="10.2.28.20")/TCP()',
+    "rule_5_match_pkt": 'Ether(src="00:11:22:33:44:55", dst="00:05:00:00:03:17")/IP(src="133.133.10.60", dst="133.133.10.20")/UDP()',
+    "rule_6_match_pkt": 'Ether(src="00:11:22:33:44:55", dst="de:ad:be:ef:08:84")/IP(src="10.2.28.19", dst="10.2.28.29")/UDP()',
+    "rule_7_match_pkt": 'Ether(src="00:11:22:33:44:55", dst="00:05:00:00:03:18")/IP(src="133.133.10.60", dst="133.133.10.10")/TCP()',
+    "rule_8_match_pkt": 'Ether(src="00:11:22:33:44:55", dst="dc:ad:be:ef:04:29")/IP(src="10.2.28.39", dst="10.2.28.49")/UDP()',
+    "rule_9_match_pkt": 'Ether(src="00:11:22:33:44:55", dst="de:ad:be:ef:10:14")/IP(src="10.2.28.10", dst="10.2.28.20")/TCP()',
+    "rule_10_match_pkt": 'Ether(src="00:11:22:33:44:55", dst="00:08:00:00:03:19")/IP(src="133.133.10.60", dst="133.133.10.30")/TCP()',
+    "rule_11_match_pkt": 'Ether(src="00:11:22:33:44:55", dst="00:06:00:00:02:18")/IP(src="10.2.29.100", dst="10.2.29.2")/TCP()',
+    "rule_12_match_pkt": 'Ether(src="00:11:22:33:44:55", dst="00:07:00:00:05:19")/IP(src="133.133.10.20", dst="133.133.10.10")/TCP()',
+    "rule_13_match_pkt": 'Ether(src="00:11:22:33:44:55", dst="00:05:00:00:03:17")/IP(src="133.133.10.60", dst="133.133.10.20")/TCP()',
+    "rule_14_match_pkt": 'Ether(src="00:11:22:33:44:55", dst="dc:ad:be:ef:04:29")/IP(src="10.2.28.39", dst="10.2.28.49")/TCP()',
+    "rule_15_match_pkt": 'Ether(src="00:11:22:33:44:55", dst="de:ad:be:ef:08:84")/IP(src="10.2.28.19", dst="10.2.28.29")/TCP()',
+    "rule_16_match_pkt": 'Ether(src="00:11:22:33:44:55", dst="da:ad:be:ef:06:19")/IP(src="10.2.28.29", dst="10.2.28.39")/TCP()',
+}
+
+hairpin_rules = {
+    "rule_1": "opcode=0x1303 prof_id=0x34 sub_prof_id=0x0 cookie=0xa2b87 key=0x18,0x0,00,00,00,00,0xde,0xad,0xbe,0xef,0x20,0x24,0x0,0x0,0x0,0x0,00,00,00,00,00,00,0xa,0x2,0x1d,0x64,00,00,00,00,00,00,00,00,00,00,00,00,\
+0xa,0x2,0x1d,0x2,00,00,00,00,00,00,00,00,00,00,00,00 act=set_vsi{act_val=0 val_type=2 dst_pe=0 slot=0x0}",
+    "rule_2": "opcode=0x1303 prof_id=0x34 sub_prof_id=0x0 cookie=0xa2b87 key=0x18,0x0,00,00,00,00,00,0x06,0x00,0x00,0x02,0x18,0x0,0x0,0x0,0x0,00,00,00,00,00,00,0x85,0x85,0xa,0x14,00,00,00,00,00,00,00,00,00,00,00,00,\
+0x85,0x85,0xa,0xa,00,00,00,00,00,00,00,00,00,00,00,00 act=set_vsi{act_val=0 val_type=2 dst_pe=0 slot=0x0}",
+    "rule_3": "opcode=0x1303 prof_id=0x34 sub_prof_id=0x0 cookie=0xa2b87 key=0x18,0x0,00,00,00,00,0xde,0xad,0xbe,0xef,0x05,0x34,0x0,0x0,0x0,0x0,00,00,00,00,00,00,0xa,0x2,0x1c,0xa,00,00,00,00,00,00,00,00,00,00,00,00,\
+0xa,0x2,0x1c,0x14,00,00,00,00,00,00,00,00,00,00,00,00 act=set_vsi{act_val=0 val_type=2 dst_pe=0 slot=0x0}",
+    "rule_4": "opcode=0x1303 prof_id=0x32 sub_prof_id=0x0 cookie=0xa2b87 key=0x1a,0x0,00,00,00,00,0xde,0xad,0xbe,0xef,0x10,0x14,0x0,0x0,0x0,0x0,00,00,00,00,00,00,0xa,0x2,0x1c,0xa,00,00,00,00,00,00,00,00,00,00,00,00,\
+0xa,0x2,0x1c,0x14,00,00,00,00,00,00,00,00,00,00,00,00 act=set_vsi{act_val=0 val_type=2 dst_pe=0 slot=0x0}",
+    "rule_5": "opcode=0x1303 prof_id=0x34 sub_prof_id=0x0 cookie=0xa2b87 key=0x18,0x0,00,00,00,00,00,0x05,0x00,0x00,0x03,0x17,0x0,0x0,0x0,0x0,00,00,00,00,00,00,0x85,0x85,0xa,0x3c,00,00,00,00,00,00,00,00,00,00,00,00,\
+0x85,0x85,0xa,0x14,00,00,00,00,00,00,00,00,00,00,00,00 act=set_vsi{act_val=0 val_type=2 dst_pe=0 slot=0x0}",
+    "rule_6": "opcode=0x1303 prof_id=0x34 sub_prof_id=0x0 cookie=0xa2b87 key=0x18,0x0,00,00,00,00,0xde,0xad,0xbe,0xef,0x08,0x84,0x0,0x0,0x0,0x0,00,00,00,00,00,00,0xa,0x2,0x1c,0x13,00,00,00,00,00,00,00,00,00,00,00,00,\
+0xa,0x2,0x1c,0x1d,00,00,00,00,00,00,00,00,00,00,00,00 act=set_vsi{act_val=0 val_type=2 dst_pe=0 slot=0x0}",
+    "rule_7": "opcode=0x1303 prof_id=0x32 sub_prof_id=0x0 cookie=0xa2b87 key=0x1a,0x0,00,00,00,00,00,0x05,0x00,0x00,0x03,0x18,0x0,0x0,0x0,0x0,00,00,00,00,00,00,0x85,0x85,0xa,0x3c,00,00,00,00,00,00,00,00,00,00,00,00,\
+0x85,0x85,0xa,0xa,00,00,00,00,00,00,00,00,00,00,00,00 act=set_vsi{act_val=0 val_type=2 dst_pe=0 slot=0x0}",
+    "rule_8": "opcode=0x1303 prof_id=0x34 sub_prof_id=0x0 cookie=0xa2b87 key=0x18,0x0,00,00,00,00,0xdc,0xad,0xbe,0xef,0x04,0x29,0x0,0x0,0x0,0x0,00,00,00,00,00,00,0xa,0x2,0x1c,0x27,00,00,00,00,00,00,00,00,00,00,00,00,\
+0xa,0x2,0x1c,0x31,00,00,00,00,00,00,00,00,00,00,00,00 act=set_vsi{act_val=0 val_type=2 dst_pe=0 slot=0x0}",
+    "rule_9": "opcode=0x1303 prof_id=0x32 sub_prof_id=0x0 cookie=0xa2b87 key=0x1a,0x0,00,00,00,00,0xde,0xad,0xbe,0xef,0x10,0x14,0x0,0x0,0x0,0x0,00,00,00,00,00,00,0xa,0x2,0x1c,0xa,00,00,00,00,00,00,00,00,00,00,00,00,\
+0xa,0x2,0x1c,0x14,00,00,00,00,00,00,00,00,00,00,00,00 act=set_vsi{act_val=0 val_type=2 dst_pe=0 slot=0x0}",
+    "rule_10": "opcode=0x1303 prof_id=0x32 sub_prof_id=0x0 cookie=0xa2b87 key=0x1a,0x0,00,00,00,00,00,0x08,0x00,0x00,0x03,0x19,0x0,0x0,0x0,0x0,00,00,00,00,00,00,0x85,0x85,0xa,0x3c,00,00,00,00,00,00,00,00,00,00,00,00,\
+0x85,0x85,0xa,0x1e,00,00,00,00,00,00,00,00,00,00,00,00 act=set_vsi{act_val=0 val_type=2 dst_pe=0 slot=0x0}",
+    "rule_11": "opcode=0x1303 prof_id=0x32 sub_prof_id=0x0 cookie=0xa2b87 key=0x1a,0x0,00,00,00,00,00,0x06,0x00,0x00,0x02,0x18,0x0,0x0,0x0,0x0,00,00,00,00,00,00,0xa,0x2,0x1d,0x64,00,00,00,00,00,00,00,00,00,00,00,00,\
+0xa,0x2,0x1d,0x2,00,00,00,00,00,00,00,00,00,00,00,00 act=set_vsi{act_val=0 val_type=2 dst_pe=0 slot=0x0}",
+    "rule_12": "opcode=0x1303 prof_id=0x32 sub_prof_id=0x0 cookie=0xa2b87 key=0x1a,0x0,00,00,00,00,00,0x07,0x00,0x00,0x05,0x19,0x0,0x0,0x0,0x0,00,00,00,00,00,00,0x85,0x85,0xa,0x14,00,00,00,00,00,00,00,00,00,00,00,00,\
+0x85,0x85,0xa,0xa,00,00,00,00,00,00,00,00,00,00,00,00 act=set_vsi{act_val=0 val_type=2 dst_pe=0 slot=0x0}",
+    "rule_13": "opcode=0x1303 prof_id=0x32 sub_prof_id=0x0 cookie=0xa2b87 key=0x1a,0x0,00,00,00,00,00,0x05,0x00,0x00,0x03,0x17,0x0,0x0,0x0,0x0,00,00,00,00,00,00,0x85,0x85,0xa,0x3c,00,00,00,00,00,00,00,00,00,00,00,00,\
+0x85,0x85,0xa,0x14,00,00,00,00,00,00,00,00,00,00,00,00 act=set_vsi{act_val=0 val_type=2 dst_pe=0 slot=0x0}",
+    "rule_14": "opcode=0x1303 prof_id=0x32 sub_prof_id=0x0 cookie=0xa2b87 key=0x1a,0x0,00,00,00,00,0xdc,0xad,0xbe,0xef,0x04,0x29,0x0,0x0,0x0,0x0,00,00,00,00,00,00,0xa,0x2,0x1c,0x27,00,00,00,00,00,00,00,00,00,00,00,00,\
+0xa,0x2,0x1c,0x31,00,00,00,00,00,00,00,00,00,00,00,00 act=set_vsi{act_val=0 val_type=2 dst_pe=0 slot=0x0}",
+    "rule_15": "opcode=0x1303 prof_id=0x32 sub_prof_id=0x0 cookie=0xa2b87 key=0x1a,0x0,00,00,00,00,0xde,0xad,0xbe,0xef,0x08,0x84,0x0,0x0,0x0,0x0,00,00,00,00,00,00,0xa,0x2,0x1c,0x13,00,00,00,00,00,00,00,00,00,00,00,00,\
+0xa,0x2,0x1c,0x1d,00,00,00,00,00,00,00,00,00,00,00,00 act=set_vsi{act_val=0 val_type=2 dst_pe=0 slot=0x0}",
+    "rule_16": "opcode=0x1303 prof_id=0x32 sub_prof_id=0x0 cookie=0xa2b87 key=0x1a,0x0,00,00,00,00,0xda,0xad,0xbe,0xef,0x06,0x19,0x0,0x0,0x0,0x0,00,00,00,00,00,00,0xa,0x2,0x1c,0x1d,00,00,00,00,00,00,00,00,00,00,00,00,\
+0xa,0x2,0x1c,0x27,00,00,00,00,00,00,00,00,00,00,00,00 act=set_vsi{act_val=0 val_type=2 dst_pe=0 slot=0x0}",
+}
+
+mismatch_pkts = {
+    "mac_ipv4": 'Ether(src="00:11:22:33:44:55", dst="02:70:80:C6:67:CE")/IP(src="192.168.0.2",dst="192.168.0.3")/("X"*480)',
+    "mac_ipv6": 'Ether(src="00:11:22:33:44:55", dst="02:70:80:C6:67:CE")/IPv6(src="2001::2",dst="2001::3")/("X"*480)',
+    "mac_ipv4_udp": 'Ether(src="00:11:22:33:44:55", dst="02:70:80:C6:67:CE")/IP(src="10.2.28.12", dst="10.2.28.22")/UDP(sport=1026, dport=1027)/("X"*480)',
+    "mac_ipv4_tcp": 'Ether(src="00:11:22:33:44:55", dst="02:70:80:C6:67:CE")/IP(src="10.2.28.12", dst="10.2.28.22")/TCP(sport=1026, dport=1027)/("X"*480)',
+    "mac_ipv4_sctp": 'Ether(src="00:11:22:33:44:55", dst="02:70:80:C6:67:CE")/IP(src="10.2.28.12", dst="10.2.28.22")/SCTP(sport=1026, dport=1027)/("X"*480)',
+    "mac_ipv6_udp": 'Ether(src="00:11:22:33:44:55", dst="02:70:80:C6:67:CE")/IPv6(src="2001::2",dst="2001::3")/UDP(sport=1026, dport=1027)/("X"*480)',
+    "mac_ipv6_tcp": 'Ether(src="00:11:22:33:44:55", dst="02:70:80:C6:67:CE")/IPv6(src="2001::2",dst="2001::3")/TCP(sport=1026, dport=1027)/("X"*480)',
+    "mac_ipv6_sctp": 'Ether(src="00:11:22:33:44:55", dst="02:70:80:C6:67:CE")/IPv6(src="2001::2",dst="2001::3")/SCTP(sport=1026, dport=1027)/("X"*480)',
+}
+
+
+class TestHairpinqueue(TestCase):
+    def set_up_all(self):
+        """
+        Run at the start of each test suite.
+        Generic filter Prerequistites
+        """
+        self.verify(
+            self.nic in ["mev-cpfl"],
+            "%s nic not support hairpin queue" % self.nic,
+        )
+        self.dut_ports = self.dut.get_ports(self.nic)
+        self.ports_socket = self.dut.get_numa_id(self.dut_ports[0])
+        # Verify that enough ports are available
+        self.verify(len(self.dut_ports) >= 1, "Insufficient ports")
+        self.tester_port0 = self.tester.get_local_port(self.dut_ports[0])
+        self.tester_iface0 = self.tester.get_interface(self.tester_port0)
+        self.pkt = Packet()
+        self.pmdout = PmdOutput(self.dut)
+        self.pf_pci = self.dut.ports_info[self.dut_ports[0]]["pci"]
+        self.imc_rule_dir = "/home/rule/"
+
+    def set_up(self):
+        """
+        Run before each test case.
+        """
+        pass
+
+    def launch_testpmd(self, rxq, txq, hairpinq):
+        """
+        start testpmd
+        """
+        # Prepare testpmd EAL and parameters
+        out = self.pmdout.start_testpmd(
+            "Default",
+            param="--rxq=%d --txq=%d --hairpinq=%d --hairpin-mode=0"
+            % (rxq, txq, hairpinq),
+        )
+        qnum = re.findall("hairpin: vport 0, Rxq id (\w+)", out)
+        print(qnum)
+        return qnum
+
+    def start_testpmd(self):
+        self.dut.send_expect("set verbose 1", "testpmd>")
+        self.dut.send_expect("start", "testpmd>", 10)
+        # test link status
+        res = self.pmdout.wait_link_status_up("all", timeout=15)
+        self.verify(res is True, "There have port link is down")
+
+    def create_imc_session(self):
+        self.imc_test_session = self.dut.new_session(suite="imc")
+        self.imc_test_session.send_expect("export TERM=linux-c-nc", "#")
+        out = self.imc_test_session.send_command(
+            "sudo minicom -D /dev/ttyUSB2 -b 460800 -w"
+        )
+        if "Device /dev/ttyUSB2 is locked" in out:
+            self.imc_test_session.send_command(
+                "ps -ef | grep 'minicom -D /dev/ttyUSB2' | grep -v grep | awk '{print $2}' | xargs kill -9"
+            )
+            self.imc_test_session.send_command(
+                "sudo minicom -D /dev/ttyUSB2 -b 460800 -w"
+            )
+        if "mev-imc login:" in self.imc_test_session.send_command("\n"):
+            self.imc_test_session.send_expect("root", "mev-imc")
+        else:
+            self.imc_test_session.send_expect("\n", "mev-imc")
+        self.logger.info("create imc test session successfully")
+        return self.imc_test_session
+
+    def create_imc_rule(self, count, rules):
+        self.create_imc_session()
+        self.imc_test_session.send_expect(
+            "mkdir -p %s" % (self.imc_rule_dir), "mev-imc"
+        )
+        self.imc_test_session.send_expect("cd %s" % (self.imc_rule_dir), "mev-imc")
+        self.imc_test_session.send_expect("rm *.txt", "mev-imc")
+        for i in range(count):
+            rule = rules["rule_%d" % (i + 1)]
+            print(rule)
+            time.sleep(2)
+            self.imc_test_session.send_command("cat > rule_%d.txt" % (i + 1))
+            self.imc_test_session.send_command("%s" % rule)
+            self.imc_test_session.send_command("^d")
+            print(self.imc_test_session.send_expect("ls", "#"))
+            output = self.imc_test_session.send_command(
+                "cli_client -x -f rule_%d.txt" % (i + 1), 10
+            )
+            print(output)
+            time.sleep(2)
+            self.verify("WARNING" not in output, "Found bad key in rule")
+            self.verify("File successfully processed" in output, "Create rule failed")
+
+    def delete_imc_rule(self, count):
+        self.imc_test_session.send_expect("cd %s" % (self.imc_rule_dir), "mev-imc")
+        print(self.imc_test_session.send_expect("ls", "#"))
+        for i in range(count):
+            self.imc_test_session.send_command(
+                "sed -i 's/opcode=0x1303/opcode=0x1305/g' rule_%d.txt" % (i + 1), 2
+            )
+            output = self.imc_test_session.send_command(
+                "cli_client -x -f rule_%d.txt" % (i + 1), 5
+            )
+            self.verify("WARNING" not in output, "Found bad key in rule")
+            self.verify("File successfully processed" in output, "Create rule failed")
+
+    def send_packets(self, packets, tx_port, count=1):
+        self.pkt.update_pkt(packets)
+        self.pkt.send_pkt(crb=self.tester, tx_port=tx_port, count=count)
+
+    def check_tcpdump_pkts(self, pkts, count):
+        filters = [{"layer": "ether", "config": {"src": "00:11:22:33:44:55"}}]
+        inst = self.tester.tcpdump_sniff_packets(self.tester_iface0, filters=filters)
+        self.dut.send_expect("clear port stats all", "testpmd>", 10)
+        for i in range(len(pkts)):
+            pkt = pkts[i]
+            self.pkt.update_pkt(pkt)
+            self.pkt.send_pkt(crb=self.tester, tx_port=self.tester_iface0, count=1)
+        self.dut.send_expect("show port stats all", "testpmd>", 10)
+        time.sleep(2)
+        p = self.tester.load_tcpdump_sniff_packets(inst)
+        print(len(p))
+        self.verify(
+            len(p) == count,
+            "Send %d packets but received %d packets, not match" % (count, len(p)),
+        )
+
+    def validate_hairpin_queue(self, count, rules, match_pkts):
+        mismatch_pkt = list(mismatch_pkts.values())
+        self.create_imc_rule(count, rules)
+        self.check_tcpdump_pkts(match_pkts, count)
+        self.start_testpmd()
+        self.check_tcpdump_pkts(mismatch_pkt, count=8)
+        self.delete_imc_rule(count)
+
+    def test_single_hairpin_queue(self):
+        """
+        Test Case 1: Launch 1 hairpin queue
+        """
+        self.logger.info(
+            "===================Test subcase 1: single data queue and single hairpin queue ================"
+        )
+        qnum = self.launch_testpmd(rxq=1, txq=1, hairpinq=1)
+        rules = {}
+        match_pkt = [match_pkts["rule_4_match_pkt"]]
+        rules["rule_1"] = (
+            hairpin_rules["rule_4"]
+            + " act=set_q{qnum=%s no_implicit_vsi=1 prec=5}" % qnum[0]
+        )
+        self.validate_hairpin_queue(count=1, rules=rules, match_pkts=match_pkt)
+        self.dut.send_expect("stop", "testpmd> ", 10)
+        self.dut.send_expect("quit", "# ", 10)
+
+        self.logger.info(
+            "===================Test subcase 2: multi data queues and single hairpin queue ================"
+        )
+        qnum = self.launch_testpmd(rxq=16, txq=16, hairpinq=1)
+        match_pkt = [match_pkts["rule_1_match_pkt"]]
+        rules["rule_1"] = (
+            hairpin_rules["rule_1"]
+            + " act=set_q{qnum=%s no_implicit_vsi=1 prec=5}" % qnum[0]
+        )
+        self.validate_hairpin_queue(count=1, rules=rules, match_pkts=match_pkt)
+
+    def test_multi_hairpin_queues(self):
+        """
+        Test Case 2: Launch 16 hairpin queues
+        """
+        self.logger.info(
+            "===================Test subcase 1: single data queue and multi hairpin queues ================"
+        )
+        qnum = self.launch_testpmd(rxq=1, txq=1, hairpinq=16)
+        match_pkt = list(match_pkts.values())
+        rules = {}
+        for i in range(16):
+            rules["rule_%d" % (i + 1)] = (
+                hairpin_rules["rule_%d" % (i + 1)]
+                + " act=set_q{qnum=%s no_implicit_vsi=1 prec=5}" % qnum[i]
+            )
+        self.validate_hairpin_queue(count=16, rules=rules, match_pkts=match_pkt)
+        self.dut.send_expect("stop", "testpmd> ", 10)
+        self.dut.send_expect("quit", "# ", 10)
+
+        self.logger.info(
+            "===================Test subcase 2: multi data queues and multi hairpin queues ================"
+        )
+        qnum = self.launch_testpmd(rxq=16, txq=16, hairpinq=16)
+        for i in range(16):
+            rules["rule_%d" % (i + 1)] = (
+                hairpin_rules["rule_%d" % (i + 1)]
+                + " act=set_q{qnum=%s no_implicit_vsi=1 prec=5}" % qnum[i]
+            )
+        self.validate_hairpin_queue(count=16, rules=rules, match_pkts=match_pkt)
+
+    def tear_down(self):
+        """
+        Run after each test case.
+        """
+        self.dut.send_expect("stop", "testpmd> ", 10)
+        self.dut.send_expect("quit", "# ", 10)
+        self.dut.kill_all()
+
+    def tear_down_all(self):
+        """
+        Run after each test suite.
+        """
+        self.dut.kill_all()
