diff --git a/tests/offload_common.py b/tests/offload_common.py
new file mode 100644
index 00000000..6905aa1e
--- /dev/null
+++ b/tests/offload_common.py
@@ -0,0 +1,926 @@
+import re
+from math import ceil
+from typing import List
+
+from framework.crb import Crb
+from framework.dut import Dut
+from framework.packet import *
+from framework.pmd_output import PmdOutput
+from framework.test_case import TestCase
+from framework.tester import Tester
+
+TX_INTERVAL = 0.01
+TX_REPEAT = 10
+
+CSUM_PAYLOAD_SIZE = 1024
+
+TSO_MTU = 9000
+TSO_SEGMENT_LENGTH = 800
+TSO_PAYLOAD_SIZE_LIST = [128, 800, 801, 1700, 2500, 8500]
+
+CSUM_INNER_PACKET_PART = {
+    "IP/UDP": IP(src="10.0.0.1")
+    / UDP(sport=29999, dport=30000)
+    / Raw(randstring(CSUM_PAYLOAD_SIZE)),
+    "IP/TCP": IP(src="10.0.0.1")
+    / TCP(sport=29999, dport=30000)
+    / Raw(randstring(CSUM_PAYLOAD_SIZE)),
+    "IP/SCTP": IP(src="10.0.0.1")
+    / SCTP(sport=29999, dport=30000, chksum=0x0000)
+    / Raw(randstring(CSUM_PAYLOAD_SIZE)),
+    "IPv6/UDP": IPv6(src="::1")
+    / UDP(sport=29999, dport=30000)
+    / Raw(randstring(CSUM_PAYLOAD_SIZE)),
+    "IPv6/TCP": IPv6(src="::1")
+    / TCP(sport=29999, dport=30000)
+    / Raw(randstring(CSUM_PAYLOAD_SIZE)),
+    "IPv6/SCTP": IPv6(src="::1")
+    / SCTP(sport=29999, dport=30000, chksum=0x0000)
+    / Raw(randstring(CSUM_PAYLOAD_SIZE)),
+    "VLAN/IP/UDP": Dot1Q(vlan=100)
+    / IP(src="10.0.0.1")
+    / UDP(sport=29999, dport=30000)
+    / Raw(randstring(CSUM_PAYLOAD_SIZE)),
+    "VLAN/IP/TCP": Dot1Q(vlan=100)
+    / IP(src="10.0.0.1")
+    / TCP(sport=29999, dport=30000)
+    / Raw(randstring(CSUM_PAYLOAD_SIZE)),
+    "VLAN/IP/SCTP": Dot1Q(vlan=100)
+    / IP(src="10.0.0.1")
+    / SCTP(sport=29999, dport=30000, chksum=0x0000)
+    / Raw(randstring(CSUM_PAYLOAD_SIZE)),
+    "VLAN/IPv6/UDP": Dot1Q(vlan=100)
+    / IPv6(src="::1")
+    / UDP(sport=29999, dport=30000)
+    / Raw(randstring(CSUM_PAYLOAD_SIZE)),
+    "VLAN/IPv6/TCP": Dot1Q(vlan=100)
+    / IPv6(src="::1")
+    / TCP(sport=29999, dport=30000)
+    / Raw(randstring(CSUM_PAYLOAD_SIZE)),
+    "VLAN/IPv6/SCTP": Dot1Q(vlan=100)
+    / IPv6(src="::1")
+    / SCTP(sport=29999, dport=30000, chksum=0x0000)
+    / Raw(randstring(CSUM_PAYLOAD_SIZE)),
+}
+
+TSO_INNER_PACKET_PART = {
+    "IP/TCP": IP(src="10.0.0.1") / TCP(sport=29999, dport=30000),
+    "IP/UDP": IP(src="10.0.0.1") / UDP(sport=29999, dport=30000),
+    "IPv6/TCP": IPv6(src="::1") / TCP(sport=29999, dport=30000),
+    "IPv6/UDP": IPv6(src="::1") / UDP(sport=29999, dport=30000),
+    "VLAN/IP/TCP": Dot1Q(vlan=100) / IP(src="10.0.0.1") / TCP(sport=29999, dport=30000),
+    "VLAN/IP/UDP": Dot1Q(vlan=100) / IP(src="10.0.0.1") / UDP(sport=29999, dport=30000),
+    "VLAN/IPv6/TCP": Dot1Q(vlan=100) / IPv6(src="::1") / TCP(sport=29999, dport=30000),
+    "VLAN/IPv6/UDP": Dot1Q(vlan=100) / IPv6(src="::1") / UDP(sport=29999, dport=30000),
+}
+
+OUTER_PACKET_PART = {
+    "IP/VXLAN": IP(src="10.0.0.1") / UDP(dport=4789) / VXLAN() / Ether(),
+    "IPv6/VXLAN": IPv6() / UDP(dport=4789) / VXLAN() / Ether(),
+    "IP/VXLAN-GPE": IP(src="10.0.0.1") / UDP(sport=4790, dport=4790) / VXLAN(),
+    "IPv6/VXLAN-GPE": IPv6() / UDP(sport=4790, dport=4790) / VXLAN(),
+    "IP/VXLAN-GPE/Ether": IP(src="10.0.0.1")
+    / UDP(sport=4790, dport=4790)
+    / VXLAN()
+    / Ether(),
+    "IPv6/VXLAN-GPE/Ether": IPv6() / UDP(sport=4790, dport=4790) / VXLAN() / Ether(),
+    "IP/GRE": IP(src="10.0.0.1", proto=47) / GRE(),
+    "IPv6/GRE": IPv6(nh=47) / GRE(),
+    "IP/GRE/Ether": IP(src="10.0.0.1", proto=47) / GRE() / Ether(),
+    "IPv6/GRE/Ether": IPv6(nh=47) / GRE() / Ether(),
+    "IP/NVGRE": IP(src="10.0.0.1", proto=47)
+    / GRE(key_present=1, proto=0x6558, key=0x00000100)
+    / Ether(),
+    "IPv6/NVGRE": IPv6(nh=47)
+    / GRE(key_present=1, proto=0x6558, key=0x00000100)
+    / Ether(),
+    "IP/GTPU": IP(src="10.0.0.1")
+    / UDP(dport=2152)
+    / GTP_U_Header(gtp_type=255, teid=0x123456),
+    "IPv6/GTPU": IPv6() / UDP(dport=2152) / GTP_U_Header(gtp_type=255, teid=0x123456),
+    "IP/GENEVE": IP(src="10.0.0.1") / UDP(dport=6081, sport=29999) / GENEVE(),
+    "IPv6/GENEVE": IPv6() / UDP(dport=6081, sport=29999) / GENEVE(),
+    "IP": IP(src="10.0.0.1"),
+    "IPv6": IPv6(),
+    "VLAN/IP/VXLAN": Dot1Q(vlan=100)
+    / IP(src="10.0.0.1")
+    / UDP(dport=4789)
+    / VXLAN()
+    / Ether(),
+    "VLAN/IPv6/VXLAN": Dot1Q(vlan=100) / IPv6() / UDP(dport=4789) / VXLAN() / Ether(),
+    "VLAN/IP/VXLAN-GPE": Dot1Q(vlan=100)
+    / IP(src="10.0.0.1")
+    / UDP(sport=4790, dport=4790)
+    / VXLAN(),
+    "VLAN/IPv6/VXLAN-GPE": Dot1Q(vlan=100)
+    / IPv6()
+    / UDP(sport=4790, dport=4790)
+    / VXLAN(),
+    "VLAN/IP/VXLAN-GPE/Ether": Dot1Q(vlan=100)
+    / IP(src="10.0.0.1")
+    / UDP(sport=4790, dport=4790)
+    / VXLAN()
+    / Ether(),
+    "VLAN/IPv6/VXLAN-GPE/Ether": Dot1Q(vlan=100)
+    / IPv6()
+    / UDP(sport=4790, dport=4790)
+    / VXLAN()
+    / Ether(),
+    "VLAN/IP/GRE": Dot1Q(vlan=100) / IP(src="10.0.0.1", proto=47) / GRE(),
+    "VLAN/IPv6/GRE": Dot1Q(vlan=100) / IPv6(nh=47) / GRE(),
+    "VLAN/IP/GRE/Ether": Dot1Q(vlan=100)
+    / IP(src="10.0.0.1", proto=47)
+    / GRE()
+    / Ether(),
+    "VLAN/IPv6/GRE/Ether": Dot1Q(vlan=100) / IPv6(nh=47) / GRE() / Ether(),
+    "VLAN/IP/NVGRE": Dot1Q(vlan=100)
+    / IP(src="10.0.0.1", proto=47)
+    / GRE(key_present=1, proto=0x6558, key=0x00000100)
+    / Ether(),
+    "VLAN/IPv6/NVGRE": Dot1Q(vlan=100)
+    / IPv6(nh=47)
+    / GRE(key_present=1, proto=0x6558, key=0x00000100)
+    / Ether(),
+    "VLAN/IP/GTPU": Dot1Q(vlan=100)
+    / IP(src="10.0.0.1")
+    / UDP(dport=2152)
+    / GTP_U_Header(gtp_type=255, teid=0x123456),
+    "VLAN/IPv6/GTPU": Dot1Q(vlan=100)
+    / IPv6()
+    / UDP(dport=2152)
+    / GTP_U_Header(gtp_type=255, teid=0x123456),
+    "VLAN/IP/GENEVE": Dot1Q(vlan=100)
+    / IP(src="10.0.0.1")
+    / UDP(dport=6081, sport=29999)
+    / GENEVE(),
+    "VLAN/IPv6/GENEVE": Dot1Q(vlan=100)
+    / IPv6()
+    / UDP(dport=6081, sport=29999)
+    / GENEVE(),
+    "VLAN/IP": Dot1Q(vlan=100) / IP(src="10.0.0.1"),
+    "VLAN/IPv6": Dot1Q(vlan=100) / IPv6(),
+}
+
+
+def filter_packets(pkts: list):
+    return [
+        p
+        for p in pkts
+        if len(p.layers()) >= 3
+        and p.layers()[1] in {IP, IPv6, Dot1Q}
+        and p.layers()[2] in {IP, IPv6, Dot1Q, UDP, TCP, SCTP, GRE, MPLS}
+        and Raw in p
+    ]
+
+
+def get_packet_with_bad_no_checksum(pkt):
+    pkt = pkt.copy()
+    return pkt
+
+
+def get_packet_with_bad_all_checksum(pkt):
+    pkt = pkt.copy()
+    (
+        _index_oip,
+        _index_ol4,
+        _index_tunnel,
+        _index_ip,
+        _index_l4,
+    ) = get_packet_layer_index_oip_ol4_tun_ip_l4(pkt)
+    if not _index_oip is None and "chksum" in pkt[_index_oip].fieldtype:
+        pkt[_index_oip].chksum = 0xFF
+    if not _index_ol4 is None and "chksum" in pkt[_index_ol4].fieldtype:
+        if isinstance(pkt[_index_ol4], UDP):
+            pkt[_index_ol4].chksum = 0xFF
+        else:
+            pkt[_index_ol4].chksum = 0xFF
+    if not _index_ip is None and "chksum" in pkt[_index_ip].fieldtype:
+        pkt[_index_ip].chksum = 0xFF
+    if not _index_l4 is None and "chksum" in pkt[_index_l4].fieldtype:
+        if isinstance(pkt[_index_l4], UDP) or isinstance(pkt[_index_l4], TCP):
+            pkt[_index_l4].chksum = 0xFF
+        elif isinstance(pkt[_index_l4], SCTP):
+            pkt[_index_l4].chksum = 0x0000
+        else:
+            pkt[_index_l4].chksum = 0xFF
+    return pkt
+
+
+def get_packet_with_bad_ip_checksum(pkt):
+    pkt = pkt.copy()
+    (
+        _index_oip,
+        _index_ol4,
+        _index_tunnel,
+        _index_ip,
+        _index_l4,
+    ) = get_packet_layer_index_oip_ol4_tun_ip_l4(pkt)
+    if not _index_ip is None and "chksum" in pkt[_index_ip].fieldtype:
+        pkt[_index_ip].chksum = 0xFF
+    return pkt
+
+
+def get_packet_with_bad_l4_checksum(pkt):
+    pkt = pkt.copy()
+    (
+        _index_oip,
+        _index_ol4,
+        _index_tunnel,
+        _index_ip,
+        _index_l4,
+    ) = get_packet_layer_index_oip_ol4_tun_ip_l4(pkt)
+    if not _index_l4 is None and "chksum" in pkt[_index_l4].fieldtype:
+        if isinstance(pkt[_index_l4], UDP) or isinstance(pkt[_index_l4], TCP):
+            pkt[_index_l4].chksum = 0xFF
+        elif isinstance(pkt[_index_l4], SCTP):
+            pkt[_index_l4].chksum = 0x0000
+        else:
+            pkt[_index_l4].chksum = 0xFF
+    return pkt
+
+
+def get_packet_with_bad_outer_ip_checksum(pkt):
+    pkt = pkt.copy()
+    (
+        _index_oip,
+        _index_ol4,
+        _index_tunnel,
+        _index_ip,
+        _index_l4,
+    ) = get_packet_layer_index_oip_ol4_tun_ip_l4(pkt)
+    if not _index_oip is None and "chksum" in pkt[_index_oip].fieldtype:
+        pkt[_index_oip].chksum = 0xFF
+    return pkt
+
+
+def get_packet_with_bad_outer_l4_checksum(pkt):
+    pkt = pkt.copy()
+    (
+        _index_oip,
+        _index_ol4,
+        _index_tunnel,
+        _index_ip,
+        _index_l4,
+    ) = get_packet_layer_index_oip_ol4_tun_ip_l4(pkt)
+    if not _index_ol4 is None and "chksum" in pkt[_index_ol4].fieldtype:
+        if isinstance(pkt[_index_ol4], UDP):
+            pkt[_index_ol4].chksum = 0xFF
+        else:
+            pkt[_index_ol4].chksum = 0xFF
+    return pkt
+
+
+def get_verbose_line_key_value(line: str):
+    _re_key_value = re.compile(r"(.*)=(.*)")
+    _re_key_flag = re.compile(r"(.*):([A-Z0-9\_\s]+)")
+    line_key_value = {}
+    keys = line.split(" - ")
+    for _k in keys:
+        _f_kv = _re_key_value.findall(_k)
+        _f_kf = _re_key_flag.findall(_k)
+        if _f_kv:
+            _key = _f_kv[0][0]
+            _value = _f_kv[0][1]
+            try:
+                if _value.startswith("0x"):
+                    _value = int(_value[2:], 16)
+                else:
+                    _value = int(_value)
+            except:
+                pass
+            if _key:
+                line_key_value[_key] = _value
+        elif _f_kf:
+            _key = _f_kf[0][0]
+            _flags = {_f.strip() for _f in _f_kf[0][1].split(" ") if _f}
+            if _key:
+                line_key_value[_key] = _flags
+        else:
+            _key = _k.strip()
+            if _key:
+                line_key_value[_key] = ""
+    return line_key_value
+
+
+def get_verbose_list(verbose_str: str):
+    _re_verbose_head = re.compile(
+        r"port (\d+)\/queue (\d+): (received|sent) (\d+) packets"
+    )
+    _re_verbose_restore_info = re.compile(r"restore info:(.*)")
+    _re_verbose_basic_info = re.compile(r"  src=([A-Z0-9:]+) - dst=([A-Z0-9:]+)(.*)")
+    _re_verbose_ol_flags = re.compile(r"  ol_flags:(.*)")
+    _re_verbose_invalid_mbuf = re.compile(r"INVALID mbuf:(.*)")
+    _lines = verbose_str.splitlines(keepends=True)
+    _lines_index = 0
+    verbose_list = []
+    verbose_index = 0
+
+    while _lines_index < len(_lines):
+        _l = _lines[_lines_index]
+        _find_head = _re_verbose_head.findall(_l)
+        if _find_head:
+            _port = int(_find_head[0][0])
+            _queue = int(_find_head[0][1])
+            _rs = _find_head[0][2]
+            _count = int(_find_head[0][3])
+            _lines_index += 1
+            if _lines_index < len(_lines):
+                _l = _lines[_lines_index]
+            verbose_sub_burst_index = 0
+            verbose_sub_extra_index = 0
+            while _lines_index < len(_lines) and verbose_sub_burst_index < _count:
+                _verbose_sub_burst = {}
+                _verbose_sub_burst["port"] = _port
+                _verbose_sub_burst["queue"] = _queue
+                _verbose_sub_burst["rs"] = _rs
+                _find_restore_info = _re_verbose_restore_info.findall(_l)
+                if _find_restore_info:
+                    _verbose_sub_burst["restore info"] = get_verbose_line_key_value(
+                        _find_restore_info[0]
+                    )
+                    _lines_index += 1
+                    if _lines_index < len(_lines):
+                        _l = _lines[_lines_index]
+                _find_basic_info = _re_verbose_basic_info.findall(_l)
+                if _find_basic_info:
+                    _verbose_sub_burst["src"] = _find_basic_info[0][0]
+                    _verbose_sub_burst["dst"] = _find_basic_info[0][1]
+                    _verbose_sub_burst.update(
+                        get_verbose_line_key_value(_find_basic_info[0][2])
+                    )
+                    _lines_index += 1
+                    if _lines_index < len(_lines):
+                        _l = _lines[_lines_index]
+                _find_ol_flags = _re_verbose_ol_flags.findall(_l)
+                if _find_ol_flags:
+                    _verbose_sub_burst["ol_flags"] = {
+                        _f.strip() for _f in _find_ol_flags[0].split(" ") if _f
+                    }
+                    _lines_index += 1
+                    if _lines_index < len(_lines):
+                        _l = _lines[_lines_index]
+                _find_invalid_mbuf = _re_verbose_invalid_mbuf.findall(_l)
+                if _find_invalid_mbuf:
+                    _verbose_sub_burst["INVALID mbuf"] = _find_invalid_mbuf[0]
+                    _lines_index += 1
+                    if _lines_index < len(_lines):
+                        _l = _lines[_lines_index]
+                if (
+                    _find_restore_info
+                    or _find_basic_info
+                    or _find_ol_flags
+                    or _find_invalid_mbuf
+                ):
+                    while verbose_index + verbose_sub_burst_index >= len(verbose_list):
+                        verbose_list.append({})
+                    verbose_list[verbose_index + verbose_sub_burst_index].update(
+                        _verbose_sub_burst
+                    )
+                    verbose_sub_burst_index += 1
+                else:
+                    _lines_index += 1
+                    if _lines_index < len(_lines):
+                        _l = _lines[_lines_index]
+            while _lines_index < len(_lines) and verbose_sub_extra_index < _count:
+                _find_extra_split = ("-" * 17) in _l
+                _find_head = _re_verbose_head.findall(_l)
+                if _find_extra_split:
+                    _verbose_sub_extra = []
+                    _lines_index += 1
+                    if _lines_index < len(_lines):
+                        _l = _lines[_lines_index]
+                    while _lines_index < len(_lines) and not (
+                        ("-" * 17) in _lines[_lines_index]
+                        or _re_verbose_head.findall(_lines[_lines_index])
+                    ):
+                        _verbose_sub_extra.append(_l)
+                        _lines_index += 1
+                        if _lines_index < len(_lines):
+                            _l = _lines[_lines_index]
+                    while verbose_index + verbose_sub_extra_index >= len(verbose_list):
+                        verbose_list.append({})
+                    verbose_list[verbose_index + verbose_sub_extra_index][
+                        "extra"
+                    ] = _verbose_sub_extra
+                    verbose_sub_extra_index += 1
+                else:
+                    _lines_index += 1
+                    if _lines_index < len(_lines):
+                        _l = _lines[_lines_index]
+            verbose_index += _count
+        else:
+            _lines_index += 1
+            if _lines_index < len(_lines):
+                _l = _lines[_lines_index]
+    return verbose_list
+
+
+def get_table_stats(stats_str: str):
+    _re_fwd_stats_table_header_port = re.compile(
+        r"-+\s*Forward statistics for ([0-9a-zA-Z\s]+)\s*-+"
+    )
+    _re_fwd_stats_table_header_total = re.compile(
+        r"\++\s*Accumulated forward statistics for (all ports)\s*\++"
+    )
+    _re_nic_stats_table_header = re.compile(
+        r"#+\s*NIC statistics for ([0-9a-zA-Z\s]+)\s*#+"
+    )
+    _re_stats_table_key_value = re.compile(r"([0-9a-zA-Z\-\_]+:\s*[0-9]+)")
+    stats_lins = stats_str.splitlines()
+    stats = {}
+    _current_table = ""
+    for line in stats_lins:
+        _h_fwd_p = _re_fwd_stats_table_header_port.findall(line)
+        _h_fwd_t = _re_fwd_stats_table_header_total.findall(line)
+        _h_nic = _re_nic_stats_table_header.findall(line)
+        if _h_fwd_p:
+            _current_table = _h_fwd_p[0].strip()
+        elif _h_fwd_t:
+            _current_table = _h_fwd_t[0].strip()
+        elif _h_nic:
+            _current_table = _h_nic[0].strip()
+        elif ("-" * 76) in line or ("+" * 76) in line or ("#" * 76) in line:
+            _current_table = ""
+        elif _current_table:
+            if not _current_table in stats:
+                stats[_current_table] = {}
+            for key_value in _re_stats_table_key_value.findall(line):
+                key, value = key_value.split(":")
+                stats[_current_table][key.strip()] = int(value)
+    return stats
+
+
+def execute_fwd_scapy(
+    pmd: PmdOutput,
+    tester: Tester,
+    packets: List[Packet],
+    packet_interval: int,
+    tester_tx_interface: str,
+    tester_rx_interface: str,
+):
+    _ONCE_TX_COUNT = 1000
+    tcpdump_inst_rx = tester.tcpdump_sniff_packets(intf=tester_rx_interface)
+    time.sleep(3)
+    pmd.execute_cmd("start")
+    pmd.execute_cmd("clear port stats all")
+    # Using sendp for a stable case execution.
+    # Failed using tester.scapy_execute and packet.send_pkt_bg
+    # for long scapy command breaking the ssh session.
+    verbose = ""
+    for i in range(ceil(len(packets) / _ONCE_TX_COUNT)):
+        _packet_obj = Packet()
+        _packets_once = packets[i * _ONCE_TX_COUNT : (i + 1) * _ONCE_TX_COUNT]
+        _packet_obj.pktgen.pkts.extend(_packets_once)
+        _packet_obj.send_pkt_bg_with_pcapfile(
+            tester, tx_port=tester_tx_interface, inter=packet_interval
+        )
+        verbose += pmd.session.get_session_output()
+    # In case tcpdump working slower than expected on very limited environments,
+    # an immediate stop sniffing causes a trimed pcap file, leading to wrong
+    # packet statistic.
+    # Uncommenting the following line helps resolving this problem.
+    time.sleep(1)
+    packets_captured = tester.load_tcpdump_sniff_packets(tcpdump_inst_rx)
+    stats_str = pmd.execute_cmd("stop")
+    stats = get_table_stats(stats_str)
+
+    return packets_captured, verbose, stats
+
+
+def execute_test_checksum(
+    case: TestCase,
+    testpmd: PmdOutput,
+    tester: Tester,
+    tester_tx_interface: str,
+    tester_rx_interface: str,
+    testpmd_port_0: str,
+    testpmd_port_1: str,
+    testpmd_enable_dcf_0: bool = False,
+    testpmd_enable_dcf_1: bool = False,
+    testpmd_bitwidth: int = None,
+    testpmd_log_level: dict = {},
+    testpmd_other_eal_param: str = "",
+    testpmd_param: str = " --enable-rx-cksum ",
+    testpmd_commands: list = [],
+    packets: list = [],
+    packet_interval: int = 0.01,
+    support_rx_tunnel: bool = True,
+    allow_tx_zero_outer_ip: bool = False,
+    allow_tx_zero_outer_udp: bool = False,
+    allow_tx_zero_inner_ip: bool = False,
+    allow_tx_zero_inner_udp: bool = False,
+    allow_tx_zero_inner_tcp: bool = False,
+    allow_tx_zero_inner_sctp: bool = False,
+    allow_tx_bad_outer_ip: bool = False,
+    allow_tx_bad_outer_udp: bool = False,
+    allow_tx_bad_inner_ip: bool = False,
+    allow_tx_bad_inner_l4: bool = False,
+    allow_rx_bad_verbose: bool = False,
+    allow_rx_bad_outer_ip: bool = False,
+    allow_rx_bad_outer_l4: bool = False,
+    allow_rx_bad_inner_ip: bool = False,
+    allow_rx_bad_inner_l4: bool = False,
+):
+    testpmd_core_mask = "all"
+    testpmd_eal_param = ""
+    testpmd_eal_param += (
+        " --force-max-simd-bitwidth=%d " % testpmd_bitwidth
+        if not testpmd_bitwidth is None
+        else ""
+    )
+    testpmd_eal_param += "".join(
+        [
+            " --log-level='%s,%d' " % (_pmd, testpmd_log_level[_pmd])
+            for _pmd in testpmd_log_level
+        ]
+    )
+    testpmd_eal_param += testpmd_other_eal_param
+    testpmd_ports = [testpmd_port_0, testpmd_port_1]
+    testpmd_port_options = {
+        testpmd_port_0: ("cap=dcf" if testpmd_enable_dcf_0 else ""),
+        testpmd_port_1: ("cap=dcf" if testpmd_enable_dcf_1 else ""),
+    }
+
+    testpmd.start_testpmd(
+        cores=testpmd_core_mask,
+        param=testpmd_param,
+        eal_param=testpmd_eal_param,
+        ports=testpmd_ports,
+        port_options=testpmd_port_options,
+    )
+
+    for command in testpmd_commands:
+        testpmd.execute_cmd(command)
+    testpmd.wait_link_status_up(0, 30)
+    testpmd.wait_link_status_up(1, 30)
+
+    eth_mac = testpmd.get_port_mac(0)
+    eth = Ether(dst=eth_mac, src="52:00:00:00:00:00")
+
+    fwd_packets, fwd_verbose, fwd_stats = execute_fwd_scapy(
+        pmd=testpmd,
+        tester=tester,
+        packets=[eth / p for p in packets],
+        packet_interval=packet_interval,
+        tester_tx_interface=tester_tx_interface,
+        tester_rx_interface=tester_rx_interface,
+    )
+
+    stats = get_packets_checksum_rx_stat_list(
+        packets, support_rx_tunnel=support_rx_tunnel
+    )
+    olflags = get_packets_checksum_rx_olflag_list(
+        packets, support_rx_tunnel=support_rx_tunnel
+    )
+    fwd_verbose_list = get_verbose_list(fwd_verbose)
+    passed = True
+
+    if not len(fwd_packets) == len(packets):
+        passed = False
+        case.logger.info(
+            "Inconsist TX packet count and RX packet count.\n"
+            + "  - TX Packet Count : %d.\n" % len(packets)
+            + "  - RX Packet Count : %d.\n" % len(fwd_packets)
+        )
+
+    if not len(fwd_verbose_list) == len(packets):
+        passed = False
+        case.logger.info(
+            "Inconsist TX packet count and verbose count.\n"
+            + "  - TX Packet Count : %d.\n" % len(packets)
+            + "  - Verbose Count : %d.\n" % len(fwd_verbose_list)
+        )
+
+    if not all([fwd_stats["port 0"][k] == stats[k] for k in stats]):
+        _logged_info = "Inconsist TX packet and fwd stats.\n"
+        for k in stats:
+            _ignored = (
+                (allow_rx_bad_outer_ip and "outer-ip" in k)
+                or (allow_rx_bad_inner_ip and "ip" in k and not "outer-ip" in k)
+                or (allow_rx_bad_outer_l4 and "outer-l4" in k)
+                or (allow_rx_bad_inner_l4 and "l4" in k and not "outer-l4" in k)
+            )
+            if not (fwd_stats["port 0"][k] == stats[k]):
+                _logged_info += "  - Stat %s=%s expecting %s%s.\n" % (
+                    k,
+                    fwd_stats["port 0"][k],
+                    stats[k],
+                    " (ignored)" if _ignored else "",
+                )
+                if not _ignored:
+                    passed = False
+        case.logger.info(_logged_info)
+
+    i_fwd_packets = 0
+    for i, packet in enumerate(packets):
+        # Here to verify if a packet is forwarded for better verifying.
+        # If the fwd_packet is not matching the sending packet in the last
+        # 64 bytes we say this two packets are not matching so we log this
+        # sending packet as not forwarded and seeking the next sending packet
+        # to check for the match.
+        if i_fwd_packets >= len(fwd_packets):
+            passed &= False
+            case.logger.info(
+                "=" * 40 + "\n" + "  - Missing packet : %s.\n" % packet.command()
+            )
+            continue
+        else:
+            _sufix64len = min(
+                64, len(bytes(packet)), len(bytes(fwd_packets[i_fwd_packets]))
+            )
+            if not (
+                bytes(packet)[-_sufix64len:]
+                == bytes(fwd_packets[i_fwd_packets])[-_sufix64len:]
+            ):
+                passed &= False
+                case.logger.info(
+                    "=" * 40 + "\n" + "  - Missing packet : %s.\n" % packet.command()
+                )
+                continue
+
+        # If a packet is considered recieved.
+        fwd_packet = fwd_packets[i_fwd_packets]
+        i_fwd_packets += 1
+        fwd_verbose = fwd_verbose_list[i] if i <= len(fwd_verbose_list) else None
+        (verify_oip, verify_ol4, verify_ip, verify_l4) = check_packet_checksum_of_each(
+            fwd_packet,
+            support_rx_tunnel=support_rx_tunnel,
+            allow_zero_outer_ip=allow_tx_zero_outer_ip,
+            allow_zero_outer_udp=allow_tx_zero_outer_udp,
+            allow_zero_inner_ip=allow_tx_zero_inner_ip,
+            allow_zero_inner_udp=allow_tx_zero_inner_udp,
+            allow_zero_inner_tcp=allow_tx_zero_inner_tcp,
+            allow_zero_inner_sctp=allow_tx_zero_inner_sctp,
+        )
+        verify_oip = allow_tx_bad_outer_ip or verify_oip
+        verify_ol4 = allow_tx_bad_outer_udp or verify_ol4
+        verify_ip = allow_tx_bad_inner_ip or verify_ip
+        verify_l4 = allow_tx_bad_inner_l4 or verify_l4
+        verify_olflags = allow_rx_bad_verbose or (
+            not (fwd_verbose is None)
+            and "ol_flags" in fwd_verbose
+            and all(
+                [
+                    (_f in fwd_verbose["ol_flags"])
+                    or (allow_rx_bad_outer_ip and "RX_OUTER_IP_CKSUM" in _f)
+                    or (allow_rx_bad_inner_ip and "RX_IP_CKSUM" in _f)
+                    or (allow_rx_bad_outer_l4 and "RX_OUTER_L4_CKSUM" in _f)
+                    or (allow_rx_bad_inner_l4 and "RX_L4_CKSUM" in _f)
+                    for _f in olflags[i]
+                ]
+            )
+        )
+        verify_payload = get_packet_payload(
+            packet, support_rx_tunnel=support_rx_tunnel
+        ) == get_packet_payload(fwd_packet, support_rx_tunnel=support_rx_tunnel)
+        verify_pass = (
+            verify_oip
+            and verify_ol4
+            and verify_ip
+            and verify_l4
+            and verify_olflags
+            and verify_payload
+        )
+        passed &= verify_pass
+        if not verify_pass:
+            case.logger.info(
+                "=" * 40
+                + "\n"
+                + "  - Failed packet : %s.\n" % packet.command()
+                + "  - Received packet : %s.\n" % fwd_packet.command()
+            )
+            if not (verify_oip and verify_ol4 and verify_ip and verify_l4):
+                case.logger.info(
+                    "  - Failed verifying "
+                    + ("IP " if not verify_ip else "")
+                    + ("L4 " if not verify_l4 else "")
+                    + ("Outer-IP " if not verify_oip else "")
+                    + ("Outer-L4 " if not verify_ol4 else "")
+                    + ".\n"
+                    + "  - Received checksum : outer-ip=%s, outer-l4=%s, ip=%s, l4=%s.\n"
+                    % get_packet_checksums(
+                        fwd_packet,
+                        support_rx_tunnel=support_rx_tunnel,
+                    )
+                    + "  - Correct checksum : outer-ip=%s, outer-l4=%s, ip=%s, l4=%s.\n"
+                    % get_packet_checksum_of_each(
+                        fwd_packet,
+                        good_checksum=True,
+                        support_rx_tunnel=support_rx_tunnel,
+                    )
+                    + "  - Perfect checksum : outer-ip=%s, outer-l4=%s, ip=%s, l4=%s.\n"
+                    % get_packet_checksums(
+                        packet,
+                        good_checksum=True,
+                        support_rx_tunnel=support_rx_tunnel,
+                    )
+                )
+            if not verify_olflags:
+                if fwd_verbose is None:
+                    case.logger.info(
+                        "  - Failed verifying ol_flags.\n" + "  - Missing verbose.\n"
+                    )
+                else:
+                    case.logger.info(
+                        "  - Failed verifying ol_flags.\n"
+                        + "  - Expected flags : %s.\n" % olflags[i]
+                        + "  - Received flags : %s.\n"
+                        % (
+                            fwd_verbose["ol_flags"]
+                            if "ol_flags" in fwd_verbose
+                            else "---"
+                        ),
+                    )
+            if not verify_payload:
+                case.logger.info(
+                    "  - Failed verifying payload.\n"
+                    + "  - Expected payload : %s.\n"
+                    % get_packet_payload(packet, support_rx_tunnel=support_rx_tunnel)
+                    + "  - Received payload : %s.\n"
+                    % get_packet_payload(
+                        fwd_packet,
+                        support_rx_tunnel=support_rx_tunnel,
+                    )
+                )
+
+    case.verify(passed, "Failed verifying. Errors logged.")
+
+
+def execute_test_tso(
+    case: TestCase,
+    testpmd: PmdOutput,
+    tester: Tester,
+    tester_tx_interface: str,
+    tester_rx_interface: str,
+    testpmd_port_0: str,
+    testpmd_port_1: str,
+    testpmd_enable_dcf_0: bool = False,
+    testpmd_enable_dcf_1: bool = False,
+    testpmd_bitwidth: int = None,
+    testpmd_log_level: dict = {},
+    testpmd_other_eal_param: str = "",
+    testpmd_param: str = " --enable-rx-cksum --max-pkt-len=9000 ",
+    testpmd_commands: list = [],
+    packets: list = [],
+    packet_interval: int = 0.01,
+    packet_mtu: int = 9000,
+    segment_length: int = 800,
+    support_rx_tunnel: bool = True,
+    support_seg_non_tunnel: bool = True,
+    support_seg_tunnel: bool = True,
+    support_tso: bool = True,
+    support_ufo: bool = True,
+):
+    testpmd_core_mask = "all"
+    testpmd_eal_param = ""
+    testpmd_eal_param += (
+        " --force-max-simd-bitwidth=%d " % testpmd_bitwidth
+        if not testpmd_bitwidth is None
+        else ""
+    )
+    testpmd_eal_param += "".join(
+        [
+            " --log-level='%s,%d' " % (_pmd, testpmd_log_level[_pmd])
+            for _pmd in testpmd_log_level
+        ]
+    )
+    testpmd_eal_param += testpmd_other_eal_param
+    testpmd_ports = [testpmd_port_0, testpmd_port_1]
+    testpmd_port_options = {
+        testpmd_port_0: ("cap=dcf" if testpmd_enable_dcf_0 else ""),
+        testpmd_port_1: ("cap=dcf" if testpmd_enable_dcf_1 else ""),
+    }
+
+    tester.send_expect(
+        "ethtool -K %s rx off tx off tso off gso off gro off lro off"
+        % tester_tx_interface,
+        "# ",
+    )
+    tester.send_expect("ip link set %s up" % tester_tx_interface, "# ")
+    tester.send_expect("ifconfig %s mtu %s" % (tester_rx_interface, packet_mtu), "# ")
+
+    testpmd.start_testpmd(
+        cores=testpmd_core_mask,
+        param=testpmd_param,
+        eal_param=testpmd_eal_param,
+        ports=testpmd_ports,
+        port_options=testpmd_port_options,
+    )
+
+    for command in testpmd_commands:
+        testpmd.execute_cmd(command)
+    testpmd.wait_link_status_up(0, 30)
+    testpmd.wait_link_status_up(1, 30)
+
+    eth_mac = testpmd.get_port_mac(0)
+    eth = Ether(dst=eth_mac, src="52:00:00:00:00:00")
+
+    fwd_packets, fwd_verbose, fwd_stats = execute_fwd_scapy(
+        pmd=testpmd,
+        tester=tester,
+        packets=[eth / p for p in packets],
+        packet_interval=packet_interval,
+        tester_tx_interface=tester_tx_interface,
+        tester_rx_interface=tester_rx_interface,
+    )
+
+    segments_list = get_packets_payload_post_seg_list(
+        packets,
+        seg_len=segment_length,
+        support_rx_tunnel=support_rx_tunnel,
+        support_seg_non_tunnel=support_seg_non_tunnel,
+        support_seg_tunnel=support_seg_tunnel,
+        support_tso=support_tso,
+        support_ufo=support_ufo,
+    )
+    segments_count_list = [len(_s) for _s in segments_list]
+    fwd_segments_list = get_packets_payload_pre_seg_list(
+        fwd_packets,
+        support_rx_tunnel=support_rx_tunnel,
+        support_seg_non_tunnel=support_seg_non_tunnel,
+        support_seg_tunnel=support_seg_tunnel,
+        support_tso=support_tso,
+        support_ufo=support_ufo,
+    )
+    txflags = get_packets_tso_seg_tx_flag_list(
+        packets,
+        seg_len=segment_length,
+        support_rx_tunnel=support_rx_tunnel,
+        support_seg_non_tunnel=support_seg_non_tunnel,
+        support_seg_tunnel=support_seg_tunnel,
+        support_tso=support_tso,
+        support_ufo=support_ufo,
+    )
+    fwd_verbose_list = get_verbose_list(fwd_verbose)
+    passed = True
+
+    if not (
+        len(fwd_packets) == sum(segments_count_list)
+        and fwd_stats["port 1"]["TX-packets"] == sum(segments_count_list)
+    ):
+        passed = False
+        case.logger.info(
+            "Inconsist expected TX packet segmented count and FWD stats and RX packet count.\n"
+            + "  - TX Packet Expected Segmented Count : %d.\n"
+            % (sum(segments_count_list))
+            + "  - FWD Packet Stats Count : %d.\n" % fwd_stats["port 1"]["TX-packets"]
+            + "  - RX Packet Count : %d.\n" % len(fwd_packets)
+            + "  - Last 3 RX Packet :\n"
+            + "".join(["  - %s.\n" % _p.command() for _p in fwd_packets[-3:]])
+        )
+    if not len(fwd_verbose_list) == len(packets):
+        passed = False
+        case.logger.info(
+            "Inconsist TX packet count and verbose count.\n"
+            + "  - TX Packet Count : %d.\n" % (len(packets))
+            + "  - Verbose Count : %d.\n" % len(fwd_verbose_list)
+            + "  - Last 3 Verbose :\n"
+            + "".join(["  - %s\n" % _v for _v in fwd_verbose_list[-3:]])
+        )
+    if passed:
+        for i, packet in enumerate(packets):
+            fwd_segments_index = sum(segments_count_list[:i])
+            segments = segments_list[i]
+            fwd_segments = fwd_segments_list[
+                fwd_segments_index : fwd_segments_index + segments_count_list[i]
+            ]
+            verify_verbose = all(
+                "extra" in fwd_verbose_list[i]
+                and any([_f in _line for _line in fwd_verbose_list[i]["extra"]])
+                for _f in txflags[i]
+            )
+            verify_segment = check_packet_segment(
+                packet,
+                fwd_segments,
+                seg_len=segment_length,
+                support_rx_tunnel=support_rx_tunnel,
+                support_seg_non_tunnel=support_seg_non_tunnel,
+                support_seg_tunnel=support_seg_tunnel,
+                support_tso=support_tso,
+                support_ufo=support_ufo,
+            )
+            verify_pass = verify_verbose and verify_segment
+            passed &= verify_pass
+            if not verify_pass:
+                case.logger.info(
+                    "=" * 40
+                    + "\n"
+                    + "  - Failed packet : %s.\n" % packet.command()
+                    + "  - Received packet : %s.\n" % fwd_packets[i].command()
+                )
+                if not verify_verbose:
+                    case.logger.info(
+                        "  - Failed verifying verbose.\n"
+                        + "  - Received verbose : %s.\n" % fwd_verbose_list[i]
+                        + "  - Expected flags : %s.\n" % txflags[i],
+                    )
+                if not verify_segment:
+                    case.logger.info(
+                        "  - Failed verifying segmentation.\n"
+                        + "  - Received segment size : %s\n"
+                        % [len(_s) for _s in fwd_segments]
+                        + "  - Expected segment size : %s\n"
+                        % [len(_s) for _s in segments]
+                    )
+
+    case.verify(passed, "Failed verifying. Errors logged.")
