From patchwork Wed Jul 12 19:31:23 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ke Xu X-Patchwork-Id: 129508 Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 578F942E58; Wed, 12 Jul 2023 21:33:21 +0200 (CEST) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 0456741138; Wed, 12 Jul 2023 21:33:21 +0200 (CEST) Received: from mga06.intel.com (mga06b.intel.com [134.134.136.31]) by mails.dpdk.org (Postfix) with ESMTP id 2B7F1400D5 for ; Wed, 12 Jul 2023 21:33:19 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1689190400; x=1720726400; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=I5p6Wo0nRu313PX/tKxlQNLQHwfepKXW8HDya3RqKJc=; b=mCN2jv2nWqo0MCqRxcqf/hvSNCI/Qh9H5L5fbWJz87VK87yqWDPDfNwG H7zKyfijws6S5Xtmy7Bst1+/Kc/K4h1/DOiNQI80+hLwiS6IYFAxKQIpA XdsjI3ctq7u4c3PbrPt3aTU7u9yaVoTBFGcTD2Heppnm1oK0g01GJo9sx pLlc5Yj9pWtvkeoWANv/iYWLFB5FE9P15AANDAQU2KobRHjVp9tk+VnMY 3dXGi5M9pLGLkxqlvIGRU8ETlCSnUNLL5V+j8EclPLKOqSof7IbaRGKcN tU7cowSgfXtMo+Ad6PJOdb8r4snL4PEWsvZhzWk7PkKLZTuKsiWAxSZ8q w==; X-IronPort-AV: E=McAfee;i="6600,9927,10769"; a="428728886" X-IronPort-AV: E=Sophos;i="6.01,200,1684825200"; d="scan'208";a="428728886" Received: from fmsmga004.fm.intel.com ([10.253.24.48]) by orsmga104.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 12 Jul 2023 12:33:19 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6600,9927,10769"; a="791740096" X-IronPort-AV: E=Sophos;i="6.01,200,1684825200"; d="scan'208";a="791740096" Received: from dpdk-xuke-host.sh.intel.com ([10.67.114.220]) by fmsmga004.fm.intel.com with ESMTP; 12 Jul 2023 12:33:18 -0700 From: Ke Xu To: dts@dpdk.org Cc: ke1.xu@intel.com, tarcadia@qq.com Subject: [DTS][Patch V1 1/4] framework/packet: Add GTPU and GENEVE support for packet module. Date: Wed, 12 Jul 2023 19:31:23 +0000 Message-Id: <20230712193126.1994361-2-ke1.xu@intel.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230712193126.1994361-1-ke1.xu@intel.com> References: <20230712193126.1994361-1-ke1.xu@intel.com> MIME-Version: 1.0 X-BeenThere: dts@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: test suite reviews and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dts-bounces@dpdk.org Partially implemented GTPU and GENEVE layer in packet. Allowing limited usage of GTPU and GENEVE packets. Signed-off-by: Ke Xu --- framework/packet.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/framework/packet.py b/framework/packet.py index 2c16e7cb..c44c955f 100644 --- a/framework/packet.py +++ b/framework/packet.py @@ -17,6 +17,8 @@ import time from importlib import import_module from socket import AF_INET6 +from scapy.contrib.geneve import GENEVE +from scapy.contrib.gtp import GTP_U_Header, GTPPDUSessionContainer from scapy.contrib.lldp import LLDPDU, LLDPDUManagementAddress from scapy.contrib.mpls import MPLS from scapy.contrib.nsh import NSH @@ -51,6 +53,7 @@ scapy_modules_required = { "GTPEchoRequest", "GTPEchoResponse", ], + "scapy.contrib.geneve": ["GENEVE"], "scapy.contrib.lldp": ["LLDPDU", "LLDPDUManagementAddress"], "scapy.contrib.pfcp": ["PFCP"], "scapy.contrib.nsh": ["NSH"], @@ -90,7 +93,7 @@ LayersTypes = { # <'ether type'=0x0800 'version'=4, 'protocol'=17 'destination port'=4789> # or # <'ether type'=0x86DD 'version'=6, 'next header'=17 'destination port'=4789> - "TUNNEL": ["ip", "gre", "vxlan", "nvgre", "geneve", "grenat"], + "TUNNEL": ["ip", "gre", "vxlan", "nvgre", "gtpu", "geneve", "grenat"], "INNER L2": ["inner_mac", "inner_vlan"], # inner_ipv4_unknown, inner_ipv6_unknown "INNER L3": ["inner_ipv4", "inner_ipv4_ext", "inner_ipv6", "inner_ipv6_ext"], @@ -167,7 +170,8 @@ class scapy(object): "ipv6_in_ip": IP() / IPv6(src="::1"), "ipv6_frag_in_ip": IP() / IPv6(src="::1", nh=44) / IPv6ExtHdrFragment(), "nvgre": GRE(key_present=1, proto=0x6558, key=0x00000100), - "geneve": "Not Implement", + "gtpu": GTP_U_Header(gtp_type=255, teid=0x123456), + "geneve": GENEVE(), } def __init__(self): From patchwork Wed Jul 12 19:31:24 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ke Xu X-Patchwork-Id: 129509 Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 28A9B42E57; Wed, 12 Jul 2023 21:33:24 +0200 (CEST) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 23C8140EE7; Wed, 12 Jul 2023 21:33:24 +0200 (CEST) Received: from mga06.intel.com (mga06b.intel.com [134.134.136.31]) by mails.dpdk.org (Postfix) with ESMTP id 9157E400D5 for ; Wed, 12 Jul 2023 21:33:21 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1689190402; x=1720726402; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=3/cjcLq0mbnwq9mJKmDsdMNzo102YORPF1rZg+IA5YY=; b=XG0srs4lCacSSx/Nfr40aHtVI+SBO/wJvOctahHOcQu2qXsuwdWOxuFI u4IY/MCfdrV1Db/Wy3LAxAAD75cIarEnkXn8Dh97tCC3T9bqNlvht0dAV IaNLXu4getn/YPXZLmup6m4LIWbXDJw0FOPnHPOiVhsueXLef1gn5+1zL 93ZEdcW6kXD6ud7VFPOVZ1cIm6LnaNMgKjUMXDUg7Tn+gz+7iLU7coTvu j8mZPzW0NY3fRvr3AbQ0p7565J+b4yEdMcMvSZtP3dM34YRkC+MgNoY0w R+1z9tjamVd2cdskDCFCSteBABs8PRzasdKLoi+fMUgpfx25KvmEnefsa A==; X-IronPort-AV: E=McAfee;i="6600,9927,10769"; a="428728894" X-IronPort-AV: E=Sophos;i="6.01,200,1684825200"; d="scan'208";a="428728894" Received: from fmsmga004.fm.intel.com ([10.253.24.48]) by orsmga104.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 12 Jul 2023 12:33:20 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6600,9927,10769"; a="791740108" X-IronPort-AV: E=Sophos;i="6.01,200,1684825200"; d="scan'208";a="791740108" Received: from dpdk-xuke-host.sh.intel.com ([10.67.114.220]) by fmsmga004.fm.intel.com with ESMTP; 12 Jul 2023 12:33:19 -0700 From: Ke Xu To: dts@dpdk.org Cc: ke1.xu@intel.com, tarcadia@qq.com Subject: [DTS][Patch V1 2/4] framework/packet: Update packet module for new methods. Date: Wed, 12 Jul 2023 19:31:24 +0000 Message-Id: <20230712193126.1994361-3-ke1.xu@intel.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230712193126.1994361-1-ke1.xu@intel.com> References: <20230712193126.1994361-1-ke1.xu@intel.com> MIME-Version: 1.0 X-BeenThere: dts@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: test suite reviews and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dts-bounces@dpdk.org 1. Add util method segment_bytes. 2. Add methods get_packet_layer_index_oip_ol4_tun_ip_l4, get_packet_layer_index_to_tso_seg, get_packet_layer_index_tunnel for packet layer locating. 3. Add doc string for strip_pktload. 4. Add methods for packet payload getting, payload segmentation and checking. 5. Add methods for packet checksum getting, correction, stating and offload flag checking. Signed-off-by: Ke Xu --- framework/packet.py | 858 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 856 insertions(+), 2 deletions(-) diff --git a/framework/packet.py b/framework/packet.py index c44c955f..02b9f885 100644 --- a/framework/packet.py +++ b/framework/packet.py @@ -27,9 +27,9 @@ from scapy.layers.inet6 import IPv6, IPv6ExtHdrFragment, IPv6ExtHdrRouting from scapy.layers.l2 import ARP, GRE, Dot1Q, Ether from scapy.layers.sctp import SCTP from scapy.layers.vxlan import VXLAN -from scapy.packet import Raw +from scapy.packet import Padding, Raw from scapy.sendrecv import sendp -from scapy.utils import hexstr, rdpcap, wrpcap +from scapy.utils import hexstr, randstring, rdpcap, wrpcap from .utils import convert_int2ip, convert_ip2int, get_module_path @@ -127,6 +127,152 @@ def write_raw_pkt(pkt_str, file_name): w.close() +def segment_bytes(payload: bytes, seg_len: int) -> list: + """ + Segment a bytes according to the seg_len. + payload: bytes, a sequence to be segmented. + seg_len: int, the length of segmentation. + return: list, a list of segmented bytes. + """ + segments = [] + if seg_len > 0: + while payload: + _s = payload[:seg_len] + payload = payload[seg_len:] + segments.append(_s) + else: + segments = [payload] + return segments + + +def get_packet_layer_index_oip_ol4_tun_ip_l4( + pkt, support_rx_tunnel: bool = True +) -> tuple: + """ + Get the layer index of outer-ip, outer-l4, tunnel, inner-ip, inner-l4 in a packet. + The index counting follows the DPDK rule, that if a non-tunnel packet is counted, + the ip / l4 layer are considered the inner ip / l4 layers. + If a layer is not found, it will return None for that index. + This method asserts that a tunnel layer is to transfer a L3 tunneled packet over a L3 + or higher network, which means a tunnel packet is laid between an outer IP layer and an + inner IP layer. + pkt: Packet, the Scapy Packet object to be counted. + support_rx_tunnel: bool, flag indicating if to parse the tunnel. If false, the outer L3 / + L4 layers will be considered the only L3 / L4 layers. + return: tuple, indexes of outer IP, outer L4, tunnel, inner IP, inner L4. + """ + _LAYERS_L3 = {IP, IPv6} + _LAYERS_L4 = {UDP, TCP, SCTP} + _layers = pkt.layers() + _index_tunnel = get_packet_layer_index_tunnel(pkt) + _index_oip = None + _index_ol4 = None + _index_ip = None + _index_l4 = None + if _index_tunnel is None: + for _i, _l in enumerate(_layers): + if _l in _LAYERS_L3: + _index_ip = _i + if _l in _LAYERS_L4: + _index_l4 = _i + else: + for _i in range(0, _index_tunnel + 1): + _l = _layers[_i] + if _l in _LAYERS_L3: + _index_oip = _i + if _l in _LAYERS_L4: + _index_ol4 = _i + for _i in range(_index_tunnel + 1, len(_layers)): + _l = _layers[_i] + if _l in _LAYERS_L3: + _index_ip = _i + if _l in _LAYERS_L4: + _index_l4 = _i + if not support_rx_tunnel and not _index_tunnel is None: + (_index_oip, _index_ol4, _index_tunnel, _index_ip, _index_l4) = ( + None, + None, + None, + _index_oip, + _index_ol4, + ) + return _index_oip, _index_ol4, _index_tunnel, _index_ip, _index_l4 + + +def get_packet_layer_index_to_tso_seg( + pkt, + support_rx_tunnel: bool = True, + support_seg_non_tunnel: bool = True, + support_seg_tunnel: bool = True, + support_tso: bool = True, + support_ufo: bool = True, +) -> int: + """ + Get the layer index of TSO in a packet. + In DPDK TSO is considered Transmittion Segmentation Offload. That both TCP + and UDP are considered to be segemeted. + pkt: Packt, Scapy Packet object to be segmeted. + support_rx_tunnel: bool, flag indicating if to parse the tunnel. If false, the outer L3 / + L4 layers will be considered the only L3 / L4 layers. + support_seg_non_tunnel: bool, flag indicating if a non-tunnel packet is to segment. + support_seg_tunnel: bool, flag indicating if a tunnel packet is to segment. + support_tso: bool, flag indicating if TCP Segmentation is enabled. + support_ufd: bool, flag indicating if UDP Fragmentation is enabled. + return: int, the index of the layer to be segmented in the packet. + """ + ( + _index_oip, + _index_ol4, + _index_tunnel, + _index_ip, + _index_l4, + ) = get_packet_layer_index_oip_ol4_tun_ip_l4( + pkt, support_rx_tunnel=support_rx_tunnel + ) + if support_seg_tunnel and not _index_tunnel is None: + if not _index_l4 is None and support_tso and isinstance(pkt[_index_l4], TCP): + return _index_l4 + if not _index_l4 is None and support_ufo and isinstance(pkt[_index_l4], UDP): + return _index_l4 + if support_seg_non_tunnel and _index_tunnel is None: + if not _index_l4 is None and support_tso and isinstance(pkt[_index_l4], TCP): + return _index_l4 + if not _index_l4 is None and support_ufo and isinstance(pkt[_index_l4], UDP): + return _index_l4 + return None + + +def get_packet_layer_index_tunnel(pkt): + """ + Get the layer index of tunnel layer in a packet. + If a layer is not found, it will return None for that index. + This method asserts that a tunnel layer is to transfer a L3 tunneled packet over a L3 + or higher network, which means a tunnel packet is laid between an outer IP layer and an + inner IP layer. + pkt: Packet, Scapy Packet object to be indexed. + return: int | None, the index of the tunnel layer in the packet. + """ + # packet types + _LAYERS_TUNNEL = {VXLAN, GRE, GTP_U_Header, GENEVE} + + _layers = pkt.layers() + for _i, _l in enumerate(_layers): + _layers_outer = _layers[: _i + 1] + _layers_inner = _layers[_i + 1 :] + if ( + (_l in _LAYERS_TUNNEL) + and (IP in _layers_outer or IPv6 in _layers_outer) + and (IP in _layers_inner or IPv6 in _layers_inner) + ): + return _i + for _i, _l in enumerate(_layers): + if (_l is IP or _l is IPv6) and ( + _layers[_i + 1] is IP or _layers[_i + 1] is IPv6 + ): + return _i + return None + + class scapy(object): SCAPY_LAYERS = { "ether": Ether(dst="ff:ff:ff:ff:ff:ff"), @@ -1188,6 +1334,12 @@ def compare_pktload(pkt1=None, pkt2=None, layer="L2"): def strip_pktload(pkt=None, layer="L2", p_index=0): + """ + Strip the payload of a specific layer indicated by `layer` in a pakcet. + pkt: Packet, DTS Packet object to be stripped. + layer: str, the layer to be stripped. + return: str, the stripped layer formatted in hex string. + """ if layer == "L2": l_idx = 0 elif layer == "L3": @@ -1205,6 +1357,708 @@ def strip_pktload(pkt=None, layer="L2", p_index=0): return load +def get_packet_payload_of(pkt, layer: int) -> bytes: + """ + Get the payload of a layer in a packet. + pkt: Packet, Scapy Packet object to get the payload of. + layer: int, the layer index to get the payload in the packet. + return: bytes, the payload of a layer. + """ + if layer is None: + return None + pkt_payload = pkt.copy() + if Padding in pkt_payload: + _index_padding = pkt_payload.layers().index(Padding) + pkt_payload[_index_padding - 1].remove_payload() + if layer >= _index_padding: + return None + return bytes(pkt_payload[layer].payload) + + +def get_packet_payload( + pkt, + support_rx_tunnel: bool = True, +) -> bytes: + """ + Get the payload of a packet. + pkt: Packet, Scapy Packet object to get the payload of. + support_rx_tunnel: bool, flag indicating if to parse the tunnel. If false, the outer L3 / + L4 layers will be considered the only L3 / L4 layers. + return: bytes, the payload of a packet. If the packet has an L4, the payload is the load of + L4, or it will be the load of L3 if L3 exists, else it returns None. + """ + ( + _index_oip, + _index_ol4, + _index_tunnel, + _index_ip, + _index_l4, + ) = get_packet_layer_index_oip_ol4_tun_ip_l4( + pkt, support_rx_tunnel=support_rx_tunnel + ) + if not _index_l4 is None: + return get_packet_payload_of(pkt, _index_l4) + elif not _index_ip is None: + return get_packet_payload_of(pkt, _index_ip) + else: + return None + + +def get_packet_payload_no_tso_seg( + pkt, + support_rx_tunnel: bool = True, + support_seg_non_tunnel: bool = True, + support_seg_tunnel: bool = True, + support_tso: bool = True, + support_ufo: bool = True, +) -> bytes: + """ + Get the payload of the layer to be segmented of a packet, not segmented. + pkt: Packet, Scapy Packet object to get the payload of. + support_rx_tunnel: bool, flag indicating if to parse the tunnel. If false, the outer L3 / + L4 layers will be considered the only L3 / L4 layers. + support_seg_non_tunnel: bool, flag indicating if a non-tunnel packet is to segment. + support_seg_tunnel: bool, flag indicating if a tunnel packet is to segment. + support_tso: bool, flag indicating if TCP Segmentation is enabled. + support_ufd: bool, flag indicating if UDP Fragmentation is enabled. + return: bytes, the payload of the layer to be segmented of a packet. If no layer is to be + segmented, it returns None. + """ + _index_seg = get_packet_layer_index_to_tso_seg( + pkt, + 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, + ) + if not _index_seg is None: + return get_packet_payload_of(pkt, _index_seg) + else: + return None + + +def get_packet_payload_pre_tso_seg( + pkt, + support_rx_tunnel: bool = True, + support_seg_non_tunnel: bool = True, + support_seg_tunnel: bool = True, + support_tso: bool = True, + support_ufo: bool = True, +) -> bytes: + """ + Get the payload of a packet before considering it is segmented. + pkt: Packet, Scapy Packet object to get the payload of. + support_rx_tunnel: bool, flag indicating if to parse the tunnel. If false, the outer L3 / + L4 layers will be considered the only L3 / L4 layers. + support_seg_non_tunnel: bool, flag indicating if a non-tunnel packet is to segment. + support_seg_tunnel: bool, flag indicating if a tunnel packet is to segment. + support_tso: bool, flag indicating if TCP Segmentation is enabled. + support_ufd: bool, flag indicating if UDP Fragmentation is enabled. + return: bytes, the payload of the packet. If it can be segmented then returns the not + segmented payload, else it returns the payload despite the segmentation flags. + """ + _payload_no_seg = get_packet_payload_no_tso_seg( + pkt, + 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, + ) + _payload = get_packet_payload( + pkt, + support_rx_tunnel=support_rx_tunnel, + ) + if not _payload_no_seg is None: + return _payload_no_seg + else: + return _payload + + +def get_packet_payload_post_tso_seg( + pkt, + seg_len: 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, +) -> list: + """ + Get the list of payloads of a packet after it is segmented. + pkt: Packet, Scapy Packet object to get the payload of. + support_rx_tunnel: bool, flag indicating if to parse the tunnel. If false, the outer L3 / + L4 layers will be considered the only L3 / L4 layers. + support_seg_non_tunnel: bool, flag indicating if a non-tunnel packet is to segment. + support_seg_tunnel: bool, flag indicating if a tunnel packet is to segment. + support_tso: bool, flag indicating if TCP Segmentation is enabled. + support_ufd: bool, flag indicating if UDP Fragmentation is enabled. + return: list, sequence of the payloads after applying TSO to a packet. If a packet is not + to segment, it returns a list of only the payload. If the packet is not a proper packet, it + returns an empty list. + """ + _payload_no_seg = get_packet_payload_no_tso_seg( + pkt, + 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, + ) + _payload = get_packet_payload( + pkt, + support_rx_tunnel=support_rx_tunnel, + ) + if not _payload_no_seg is None: + _segments = segment_bytes(_payload_no_seg, seg_len) + elif not _payload is None: + _segments = segment_bytes(_payload, -1) + else: + _segments = [] + return _segments + + +def get_packet_tso_seg_tx_flag( + pkt, + seg_len: 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, +) -> list: + """ + Get the list of TSO TX offload flags of a packet. + pkt: Packet, Scapy Packet object to get the offload flag of. + support_rx_tunnel: bool, flag indicating if to parse the tunnel. If false, the outer L3 / + L4 layers will be considered the only L3 / L4 layers. + support_seg_non_tunnel: bool, flag indicating if a non-tunnel packet is to segment. + support_seg_tunnel: bool, flag indicating if a tunnel packet is to segment. + support_tso: bool, flag indicating if TCP Segmentation is enabled. + support_ufd: bool, flag indicating if UDP Fragmentation is enabled. + return: list, set of offload flags formatted in a list of str. + """ + _index_seg = get_packet_layer_index_to_tso_seg( + pkt, + 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, + ) + _flags = [] + if not _index_seg is None and len(get_packet_payload_of(pkt, _index_seg)) > seg_len: + if isinstance(pkt[_index_seg], TCP): + _flags.append("RTE_MBUF_F_TX_TCP_SEG") + elif isinstance(pkt[_index_seg], UDP): + _flags.append("RTE_MBUF_F_TX_UDP_SEG") + return _flags + + +def get_packet_checksum_of( + pkt, + layer: int, + good_checksum: bool = False, +) -> int: + """ + Get the checksum of a specific layer in a packet. + pkt: Packet, Scapy Packet object to get the checksum of. + layer: int, the layer index to get the checksum in the packet. + good_checksum: bool, flag indicating if to get the corrected checksum or to get the raw one. + return: int, the checksum of the packet at the layer. + """ + _re_checksum_pattern = re.compile(r"chksum\s*=\s*(0x[0-9a-fA-F]+|None)") + if layer is None: + return None + p_chksum = pkt[layer].copy() + if "chksum" in p_chksum.fieldtype: + if not good_checksum: + checksum_list_str = _re_checksum_pattern.findall(p_chksum.show2(dump=True)) + return int(checksum_list_str[0], 16) + else: + p_chksum.chksum = None + checksum_list_str = _re_checksum_pattern.findall(p_chksum.show2(dump=True)) + return int(checksum_list_str[0], 16) + else: + return None + + +def get_packet_checksums( + pkt, + good_checksum: bool = False, + support_rx_tunnel: bool = True, +) -> tuple: + """ + Get the checksum list of a packet. + This method calculates the checksum of the whole packet. This means if an inner layer has a + bad checksum, it will be corrected then to calculate the outer checksum if `good_checksum` is + set. + If a layer is not found, it will return None for that layer. + pkt: Packet, Scapy Packet object to get the checksum of. + good_checksum: bool, flag indicating if to get the corrected checksum or to get the raw one. + support_rx_tunnel: bool, flag indicating if to parse the tunnel. If false, the outer L3 / + L4 layers will be considered the only L3 / L4 layers. + return: tuple, checksums of outer IP, outer L4, inner IP, inner L4 in the packet. + """ + ( + _index_oip, + _index_ol4, + _index_tunnel, + _index_ip, + _index_l4, + ) = get_packet_layer_index_oip_ol4_tun_ip_l4( + pkt, support_rx_tunnel=support_rx_tunnel + ) + p_chksum = pkt.copy() + if good_checksum: + for _i in [_index_oip, _index_ol4, _index_ip, _index_l4]: + if not _i is None and "chksum" in p_chksum[_i].fieldtype: + p_chksum[_i].chksum = None + return ( + get_packet_checksum_of(p_chksum, _index_oip), + get_packet_checksum_of(p_chksum, _index_ol4), + get_packet_checksum_of(p_chksum, _index_ip), + get_packet_checksum_of(p_chksum, _index_l4), + ) + + +def get_packet_checksum_of_each( + pkt, + good_checksum: bool = False, + support_rx_tunnel: bool = True, +) -> tuple: + """ + Get the checksum list of a packet. + This method calculates the checksums of each layer seperately. This means if an inner layer + has a bad checksum, an outer checksum will be calculated based on the bad inner checksum if + `good_checksum` is set. + If a layer is not found, it will return None for that layer. + pkt: Packet, Scapy Packet object to get the checksum of. + good_checksum: bool, flag indicating if to get the corrected checksum or to get the raw one. + support_rx_tunnel: bool, flag indicating if to parse the tunnel. If false, the outer L3 / + L4 layers will be considered the only L3 / L4 layers. + return: tuple, checksums of outer IP, outer L4, inner IP, inner L4 in the packet. + """ + ( + _index_oip, + _index_ol4, + _index_tunnel, + _index_ip, + _index_l4, + ) = get_packet_layer_index_oip_ol4_tun_ip_l4( + pkt, support_rx_tunnel=support_rx_tunnel + ) + return ( + get_packet_checksum_of(pkt, _index_oip, good_checksum=good_checksum), + get_packet_checksum_of(pkt, _index_ol4, good_checksum=good_checksum), + get_packet_checksum_of(pkt, _index_ip, good_checksum=good_checksum), + get_packet_checksum_of(pkt, _index_l4, good_checksum=good_checksum), + ) + + +def get_packet_checksum_rx_stat(pkt, support_rx_tunnel: bool = True) -> tuple: + """ + Get the checksum stats of a packet. + pkt: Packet, Scapy Packet object to get the checksum stats of. + support_rx_tunnel: bool, flag indicating if to parse the tunnel. If false, the outer L3 / + L4 layers will be considered the only L3 / L4 layers. + return: tuple, checksum stats of bad outer IP, bad outer L4, bad inner IP, bad inner L4. + """ + ( + _index_oip, + _index_ol4, + _index_tunnel, + _index_ip, + _index_l4, + ) = get_packet_layer_index_oip_ol4_tun_ip_l4( + pkt, support_rx_tunnel=support_rx_tunnel + ) + _bad_oip, _bad_ol4, _bad_ip, _bad_l4 = 0, 0, 0, 0 + if not _index_oip is None and get_packet_checksum_of( + pkt, _index_oip + ) != get_packet_checksum_of(pkt, _index_oip, good_checksum=True): + _bad_oip = 1 + if not _index_oip is None and get_packet_checksum_of( + pkt, _index_ol4 + ) != get_packet_checksum_of(pkt, _index_ol4, good_checksum=True): + _bad_ol4 = 1 + if not _index_ip is None and get_packet_checksum_of( + pkt, _index_ip + ) != get_packet_checksum_of(pkt, _index_ip, good_checksum=True): + _bad_ip = 1 + if not _index_l4 is None and get_packet_checksum_of( + pkt, _index_l4 + ) != get_packet_checksum_of(pkt, _index_l4, good_checksum=True): + _bad_l4 = 1 + return _bad_oip, _bad_ol4, _bad_ip, _bad_l4 + + +def get_packet_checksum_rx_olflag(pkt, support_rx_tunnel: bool = True) -> list: + """ + Get the checksum RX offload flags of a packet. + pkt: Packet, Scapy Packet object to get the checksum RX offload flag of. + support_rx_tunnel: bool, flag indicating if to parse the tunnel. If false, the outer L3 / + L4 layers will be considered the only L3 / L4 layers. + return: list, set of offload flags formated in a list of str. + """ + _FLAG_PAT = "RTE_MBUF_F_RX_%s_CKSUM_%s" + ( + _index_oip, + _index_ol4, + _index_tunnel, + _index_ip, + _index_l4, + ) = get_packet_layer_index_oip_ol4_tun_ip_l4( + pkt, support_rx_tunnel=support_rx_tunnel + ) + _flags = [] + if not _index_oip is None and get_packet_checksum_of( + pkt, _index_oip + ) != get_packet_checksum_of(pkt, _index_oip, good_checksum=True): + _flags.append(_FLAG_PAT % ("OUTER_IP", "BAD")) + else: + ## OUTER_IP_CKSUM_GOOD is default not shown + pass + if not _index_ol4 is None and get_packet_checksum_of( + pkt, _index_ol4 + ) != get_packet_checksum_of(pkt, _index_ol4, good_checksum=True): + _flags.append(_FLAG_PAT % ("OUTER_L4", "BAD")) + elif not _index_ol4 is None: + _flags.append(_FLAG_PAT % ("OUTER_L4", "GOOD")) + elif _index_ol4 is None: + pass + else: + pass + if get_packet_checksum_of(pkt, _index_ip) != get_packet_checksum_of( + pkt, _index_ip, good_checksum=True + ): + _flags.append(_FLAG_PAT % ("IP", "BAD")) + else: + _flags.append(_FLAG_PAT % ("IP", "GOOD")) + if get_packet_checksum_of(pkt, _index_l4) != get_packet_checksum_of( + pkt, _index_l4, good_checksum=True + ): + _flags.append(_FLAG_PAT % ("L4", "BAD")) + else: + _flags.append(_FLAG_PAT % ("L4", "GOOD")) + return _flags + + +def get_packets_payload_pre_seg_list( + pkts: list, + support_rx_tunnel: bool = True, + support_seg_non_tunnel: bool = True, + support_seg_tunnel: bool = True, + support_tso: bool = True, + support_ufo: bool = True, +) -> list: + """ + Get the payload of a seq of packets before considering it is segmented. + """ + return [ + get_packet_payload_pre_tso_seg( + _p, + 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, + ) + for _p in pkts + ] + + +def get_packets_payload_post_seg_list( + pkts: list, + seg_len: 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, +) -> list: + """ + Get the list of payloads of a seq of packets after it is segmented. + """ + return [ + get_packet_payload_post_tso_seg( + _p, + seg_len=seg_len, + 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, + ) + for _p in pkts + ] + + +def get_packets_checksum_rx_stat_list( + pkts: list, support_rx_tunnel: bool = True +) -> list: + """ + Get the checksum stats of a set of packet. + """ + k_bad_ip = "Bad-ipcsum" + k_bad_l4 = "Bad-l4csum" + k_bad_oip = "Bad-outer-ipcsum" + k_bad_ol4 = "Bad-outer-l4csum" + k_rx = "RX-total" + stats = { + k_bad_ip: 0, + k_bad_l4: 0, + k_bad_oip: 0, + k_bad_ol4: 0, + k_rx: 0, + } + for pkt in pkts: + _bad_oip, _bad_ol4, _bad_ip, _bad_l4 = get_packet_checksum_rx_stat( + pkt, support_rx_tunnel=support_rx_tunnel + ) + stats[k_bad_oip] += _bad_oip + stats[k_bad_ol4] += _bad_ol4 + stats[k_bad_ip] += _bad_ip + stats[k_bad_l4] += _bad_l4 + stats[k_rx] += 1 + return stats + + +def get_packets_tso_seg_tx_flag_list( + pkts: list, + seg_len: 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, +) -> list: + """ + Get the list of TSO TX offload flags of a seq of packet. + """ + flag_list = [] + for pkt in pkts: + flag_list.append( + get_packet_tso_seg_tx_flag( + pkt, + seg_len=seg_len, + 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, + ) + ) + return flag_list + + +def get_packets_checksum_rx_olflag_list( + pkts: list, support_rx_tunnel: bool = True +) -> list: + """ + Get the checksum RX offload flags of a seq of packets. + """ + flag_list = [] + for pkt in pkts: + flag_list.append( + get_packet_checksum_rx_olflag(pkt, support_rx_tunnel=support_rx_tunnel) + ) + return flag_list + + +def check_packet_segment( + pkt, + segments: list, + seg_len: int, + support_rx_tunnel: bool = True, + support_seg_non_tunnel: bool = True, + support_seg_tunnel: bool = True, + support_tso: bool = True, + support_ufo: bool = True, +) -> bool: + """ + Check the packet segmentation. + """ + payload_expected = get_packet_payload_pre_tso_seg( + pkt, + 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_expected = get_packet_payload_post_tso_seg( + pkt, + seg_len=seg_len, + 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, + ) + if not segments: + return False + if segments == segments_expected: + return True + else: + payload_segmented = b"" + for _i, _seg in enumerate(segments, 1): + if len(segments_expected) > 1 and len(_seg) > seg_len: + return False + payload_segmented += _seg + return payload_expected == payload_segmented + + +def check_packet_checksum_of(pkt: Packet, layer: int, checksum_ref: int = None) -> bool: + """ + Check the packet checksum of a specific layer indicated by `layer`. + """ + if checksum_ref is None: + checksum_ref = get_packet_checksum_of(pkt, layer, good_checksum=True) + return get_packet_checksum_of(pkt, layer) == checksum_ref + + +def check_packet_checksums( + pkt, + checksum_ref_list: list = None, + support_rx_tunnel: bool = True, +) -> bool: + """ + Check the packet checksum. + pkt: checked packet; + checksum_ref_list: refering checksum list, recalculate the whole packet for correct checksum in default; + return: if this packet is in good checksum. + """ + if checksum_ref_list is None: + checksum_ref_list = get_packet_checksums( + pkt, support_rx_tunnel=support_rx_tunnel, good_checksum=True + ) + return ( + get_packet_checksums(pkt, support_rx_tunnel=support_rx_tunnel) + == checksum_ref_list + ) + + +def check_packet_checksum_of_each( + pkt, + checksum_ref_list: list = None, + support_rx_tunnel: bool = True, + allow_zero_outer_ip: bool = False, + allow_zero_outer_udp: bool = False, + allow_zero_inner_ip: bool = False, + allow_zero_inner_udp: bool = False, + allow_zero_inner_tcp: bool = False, + allow_zero_inner_sctp: bool = False, +) -> tuple: + """ + Check the packet checksum of each layer. + pkt: checked packet; + checksum_ref_list: refering checksum list, recalculate each layer seperately for correct checksum in default; + allow_zero_*: allowing zero checksum validated passed on specific layer; + return: if this packet is validated passed on outer-ip, outer-l4, inner-ip, inner-l4 layer checksum. + """ + ( + _index_oip, + _index_ol4, + _index_tunnel, + _index_ip, + _index_l4, + ) = get_packet_layer_index_oip_ol4_tun_ip_l4( + pkt, support_rx_tunnel=support_rx_tunnel + ) + if checksum_ref_list and len(checksum_ref_list) == 2: + (_checksum_ref_ip, _checksum_ref_l4) = ( + checksum_ref_list[0], + checksum_ref_list[1], + ) + return ( + True, + True, + check_packet_checksum_of(pkt, _index_ip, _checksum_ref_ip), + check_packet_checksum_of(pkt, _index_l4, _checksum_ref_l4), + ) + elif checksum_ref_list and len(checksum_ref_list) == 4: + (_checksum_ref_oip, _checksum_ref_ol4, _checksum_ref_ip, _checksum_ref_l4) = ( + checksum_ref_list[0], + checksum_ref_list[1], + checksum_ref_list[2], + checksum_ref_list[3], + ) + return ( + check_packet_checksum_of(pkt, _index_oip, _checksum_ref_oip), + check_packet_checksum_of(pkt, _index_ol4, _checksum_ref_ol4), + check_packet_checksum_of(pkt, _index_ip, _checksum_ref_ip), + check_packet_checksum_of(pkt, _index_l4, _checksum_ref_l4), + ) + elif checksum_ref_list: + raise ValueError("Wrong checksum ref list length.") + else: + _verified_l4 = ( + _index_l4 is None + or check_packet_checksum_of(pkt, _index_l4) + or ( + not _index_l4 is None + and allow_zero_inner_udp + and isinstance(pkt[_index_l4], UDP) + and check_packet_checksum_of(pkt, _index_l4, 0x00) + ) + or ( + not _index_l4 is None + and allow_zero_inner_tcp + and isinstance(pkt[_index_l4], TCP) + and check_packet_checksum_of(pkt, _index_l4, 0x00) + ) + or ( + not _index_l4 is None + and allow_zero_inner_sctp + and isinstance(pkt[_index_l4], SCTP) + and check_packet_checksum_of(pkt, _index_l4, 0x0000) + ) + ) + _verified_ip = ( + _index_ip is None + or check_packet_checksum_of(pkt, _index_ip) + or ( + not _index_ip is None + and allow_zero_inner_ip + and isinstance(pkt[_index_ip], IP) + and check_packet_checksum_of(pkt, _index_ip, 0x00) + ) + ) + _verified_ol4 = ( + _index_ol4 is None + or check_packet_checksum_of(pkt, _index_ol4) + or ( + not _index_ol4 is None + and allow_zero_outer_udp + and isinstance(pkt[_index_ol4], UDP) + and get_packet_checksum_of(pkt, _index_ol4) == 0x00 + ) + ) + _verified_oip = ( + _index_oip is None + or check_packet_checksum_of(pkt, _index_oip) + or ( + not _index_oip is None + and allow_zero_outer_ip + and isinstance(pkt[_index_oip], IP) + and get_packet_checksum_of(pkt, _index_oip) == 0x00 + ) + ) + return _verified_oip, _verified_ol4, _verified_ip, _verified_l4 + + +def check_packet_verbose(verbose: str, verbose_ref_list: list) -> bool: + """ + Check the verbose with the reference list. + """ + for _ref in verbose_ref_list: + if not _ref in verbose: + return False + return True + + ############################################################################### ############################################################################### if __name__ == "__main__": From patchwork Wed Jul 12 19:31:25 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ke Xu X-Patchwork-Id: 129510 Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 4E3B242E58; Wed, 12 Jul 2023 21:33:25 +0200 (CEST) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 499DE427EE; Wed, 12 Jul 2023 21:33:25 +0200 (CEST) Received: from mga06.intel.com (mga06b.intel.com [134.134.136.31]) by mails.dpdk.org (Postfix) with ESMTP id 81D3E40EE7 for ; Wed, 12 Jul 2023 21:33:23 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1689190403; x=1720726403; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=h7bkiW8vkrKpKTG2hJpWdSwnvM3MXT+3iG0NRXVJZGM=; b=GcT0tP4KzGi43rrlStzwvIeCDxA+GJXTDYPmeb9q+OW63h2zllTwPv84 QLwJtpn3RRVOK3rdOh9Wbm9bhiOxTfqLdW0xrRxePnxLZb4xbtXKdyRy5 FczN2Fi0WrI0hGXlfxL59IHANWGW0C6Qt21QQ0nGjsKL61BJO3W9X6x2i c8GfUMOVFHUuWpH9N+/mFXFnauGT4oj8r57OaNCJ+qRDHRU1vnx9X1y6O LZ8qOEaDOLnBjHuvNmifGSwhy91I0YW0eESTeYHu4WKql2mXVDXVmnm7O Mg7FQZbngzP9Ip7ZNeRw71xJG0ORBiLOZ70UYUqNMnT2lEIND2o+h5L+x g==; X-IronPort-AV: E=McAfee;i="6600,9927,10769"; a="428728896" X-IronPort-AV: E=Sophos;i="6.01,200,1684825200"; d="scan'208";a="428728896" Received: from fmsmga004.fm.intel.com ([10.253.24.48]) by orsmga104.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 12 Jul 2023 12:33:22 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6600,9927,10769"; a="791740116" X-IronPort-AV: E=Sophos;i="6.01,200,1684825200"; d="scan'208";a="791740116" Received: from dpdk-xuke-host.sh.intel.com ([10.67.114.220]) by fmsmga004.fm.intel.com with ESMTP; 12 Jul 2023 12:33:21 -0700 From: Ke Xu To: dts@dpdk.org Cc: ke1.xu@intel.com, tarcadia@qq.com Subject: [DTS][Patch V1 3/4] framework/test_case: Add skip_unsupported decorator. Date: Wed, 12 Jul 2023 19:31:25 +0000 Message-Id: <20230712193126.1994361-4-ke1.xu@intel.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230712193126.1994361-1-ke1.xu@intel.com> References: <20230712193126.1994361-1-ke1.xu@intel.com> MIME-Version: 1.0 X-BeenThere: dts@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: test suite reviews and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dts-bounces@dpdk.org Add a new decorator for marking unsupported cases. Signed-off-by: Ke Xu --- framework/test_case.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/framework/test_case.py b/framework/test_case.py index 68f89407..35342de0 100644 --- a/framework/test_case.py +++ b/framework/test_case.py @@ -628,3 +628,24 @@ def skip_unsupported_host_driver(drivers): return wrapper return decorator + + +def skip_unsupported(do_skip: bool = True, reason: str = None): + """ + Skip case which are not supported. This means a case may be supported in future or in other branches. + """ + + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + test_case = args[0] + if do_skip: + raise VerifySkip( + "DPDK currently do not support this case" + + (" for %s" % reason if reason else "") + ) + return func(*args, **kwargs) + + return wrapper + + return decorator From patchwork Wed Jul 12 19:31:26 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ke Xu X-Patchwork-Id: 129511 Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id 77BF542E57; Wed, 12 Jul 2023 21:33:27 +0200 (CEST) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 72B794114A; Wed, 12 Jul 2023 21:33:27 +0200 (CEST) Received: from mga06.intel.com (mga06b.intel.com [134.134.136.31]) by mails.dpdk.org (Postfix) with ESMTP id 04001400D5 for ; Wed, 12 Jul 2023 21:33:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1689190405; x=1720726405; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=OvTeXBW2gGV814v+7njTJE3I+kUsJQaCWRVn/lzSyio=; b=O8pTQNgvTsCdin85bdV3ErPM0+xnb6xN9bhvGuLkGadkckBeJ6bMzCO9 T267+uc4GrQgHRq7RJSYqnyNQMsfgyaD0QQM9UzyYG1n0HUTlDch7Qufo iMv3lRr3cdqSmnOULTc9D2a67dqizViN7puiu81TxL5F1bglmfo8VleFp 3tj4WRRteUie2fzMQ5fOMtjVfaJaF7/Abmgu96c9wb4Yv52Vtwnx6oYAh IfsJav9VsAFLI2soXbFBNXQO0ZEJ5IoeG3zvXu6rr4QofSw3M+scfHmcH 7FCda21WwYVbYK38cEz6HreyVgj+F8vpl/w4s/EHuse73HyECOZYWAlPq w==; X-IronPort-AV: E=McAfee;i="6600,9927,10769"; a="428728901" X-IronPort-AV: E=Sophos;i="6.01,200,1684825200"; d="scan'208";a="428728901" Received: from fmsmga004.fm.intel.com ([10.253.24.48]) by orsmga104.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 12 Jul 2023 12:33:23 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6600,9927,10769"; a="791740127" X-IronPort-AV: E=Sophos;i="6.01,200,1684825200"; d="scan'208";a="791740127" Received: from dpdk-xuke-host.sh.intel.com ([10.67.114.220]) by fmsmga004.fm.intel.com with ESMTP; 12 Jul 2023 12:33:22 -0700 From: Ke Xu To: dts@dpdk.org Cc: ke1.xu@intel.com, tarcadia@qq.com Subject: [DTS][Patch V1 4/4] tests/offload_common: Add offload_common module. Date: Wed, 12 Jul 2023 19:31:26 +0000 Message-Id: <20230712193126.1994361-5-ke1.xu@intel.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230712193126.1994361-1-ke1.xu@intel.com> References: <20230712193126.1994361-1-ke1.xu@intel.com> MIME-Version: 1.0 X-BeenThere: dts@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: test suite reviews and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dts-bounces@dpdk.org Add new module for common packet sending, validation and test steps for stateless offload cases. 1. Add common parameters and prepared packets for checksum offload and TSO cases. 2. Add packet organizing methods to get packets with expected inner and outer, l3 and l4 checksums. 3. Add methods get_verbose_line_key_value and get_verbose_list for verbose parsing. 4. Add method get_table_stats for port stats and fwd stats parsing. 5. Add method execute_fwd_scapy for common packet sending, capturing, verbose getting and fwd stats getting. 6. Add method execute_test_checksum for common checksum case execution. 7. Add method execute_test_tso for common TSO case execution. Signed-off-by: Ke Xu --- tests/offload_common.py | 926 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 926 insertions(+) create mode 100644 tests/offload_common.py 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.")