From patchwork Fri Aug 2 18:07:45 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Stephen Hemminger X-Patchwork-Id: 142876 X-Patchwork-Delegate: ferruh.yigit@amd.com 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 A0DC44571E; Fri, 2 Aug 2024 20:09:02 +0200 (CEST) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 256BE427AE; Fri, 2 Aug 2024 20:08:54 +0200 (CEST) Received: from mail-pg1-f170.google.com (mail-pg1-f170.google.com [209.85.215.170]) by mails.dpdk.org (Postfix) with ESMTP id AC7A840E22 for ; Fri, 2 Aug 2024 20:08:51 +0200 (CEST) Received: by mail-pg1-f170.google.com with SMTP id 41be03b00d2f7-6e7e23b42c3so5400011a12.1 for ; Fri, 02 Aug 2024 11:08:51 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=networkplumber-org.20230601.gappssmtp.com; s=20230601; t=1722622131; x=1723226931; darn=dpdk.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=Yx5Tgbg6/2PFLpMNXCkNcK13vymVXcjykMyX0Evo730=; b=T3aQsUdW0QXMwtEzMoYBOSNwFEDUAB33V0iYRhrGOp9Kkob7HgU8sB6G8wW6lj7ik6 8RI5IEQLL2b+SZ0dVCyIyv7N1cDC2aIIhIkAor6qW+QErx/rUDL7/t1gRj8rwdAynRKZ dDeyMC++4N09lU+lvMTo6+gUtDTDOAhH7u0GuO91IA0rWfgn+Tm/y9hR4hWcsFbPurQ9 GvsjuYV7s6X8rw3JaIRZbN00xrBZGkKyd1htbbLuOHI8zP8a3r+hQ3ZQx54OfVH4fIkx 8q4dhD9ivwIeEfXMtof6olsbm11YJG72bl4LGntGcmg44NEvSbr0n6CJvVJ4ypTUQw2d lk1g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1722622131; x=1723226931; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Yx5Tgbg6/2PFLpMNXCkNcK13vymVXcjykMyX0Evo730=; b=UyelO5CtOlcLS+ZeuxxhAXtQkdFq9/4fYgy2ZqEx1b9gVx18uz+FBw0oxIHeYgir8v MMVIiKFQbKIPheqbtD8g96N7MCRly5e4HrS+S7ab08aRkrtmMCUR3N24SrqvJsE1/L5A FvpavUlUSZH/11FyWFlbGyPsEmnDkkkO3jhqfldZfodYeFnddzOHMglucUuClUvFqRco iw68swtrTM6euNjzykvrOjkWFiAmv7Be6eh8Nb0hqbbkLNr+Q4DY/wfB0FQu46TPgHyy 2Sk94LQm10j+eBlmgvN0ch40+wdFAOHNKT7BusD79RTSfa34ZvkYqWrXSfABd8OOU68H mKLg== X-Gm-Message-State: AOJu0YyJTGrK1OQbAwmAcGzpCpMZWGp40dq28C9zTA9wxWu1rywOWRV9 QDFfehg4iXEJhrZfo4n6WyewlLsX9eMHo+yjenusWBUYaQj0lBexkteDItuVXp5F8/YiOajiC7J w X-Google-Smtp-Source: AGHT+IG5oKThV/EjxKS2ho7CjSVzPEhpqXbjqEwOBuggR6Azt2qJYJZrSf3jBtohFgmD67jwzrKfvg== X-Received: by 2002:a05:6a20:3942:b0:1c3:ea28:3c0e with SMTP id adf61e73a8af0-1c69960e196mr5984694637.33.1722622130591; Fri, 02 Aug 2024 11:08:50 -0700 (PDT) Received: from hermes.local (204-195-96-226.wavecable.com. [204.195.96.226]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-1ff58f6a540sm20228195ad.118.2024.08.02.11.08.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 02 Aug 2024 11:08:50 -0700 (PDT) From: Stephen Hemminger To: dev@dpdk.org Cc: Stephen Hemminger Subject: [PATCH v6 2/4] net: add new packet dissector Date: Fri, 2 Aug 2024 11:07:45 -0700 Message-ID: <20240802180838.862858-3-stephen@networkplumber.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20240802180838.862858-1-stephen@networkplumber.org> References: <20240312220129.70667-1-stephen@networkplumber.org> <20240802180838.862858-1-stephen@networkplumber.org> MIME-Version: 1.0 X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org The function rte_dissect_mbuf is used to decode the contents of an mbuf into ah uman readable format similar to what tshark uses. For now, handles IP, IPv6, TCP, UDP, ICMP and ARP. Signed-off-by: Stephen Hemminger --- lib/net/meson.build | 2 + lib/net/rte_dissect.c | 416 ++++++++++++++++++++++++++++++++++++++++++ lib/net/rte_dissect.h | 42 +++++ lib/net/version.map | 7 + 4 files changed, 467 insertions(+) create mode 100644 lib/net/rte_dissect.c create mode 100644 lib/net/rte_dissect.h diff --git a/lib/net/meson.build b/lib/net/meson.build index 0b69138949..48edf17ea3 100644 --- a/lib/net/meson.build +++ b/lib/net/meson.build @@ -2,6 +2,7 @@ # Copyright(c) 2017-2020 Intel Corporation headers = files( + 'rte_dissect.h', 'rte_ip.h', 'rte_tcp.h', 'rte_udp.h', @@ -30,6 +31,7 @@ headers = files( sources = files( 'rte_arp.c', + 'rte_dissect.c', 'rte_ether.c', 'rte_net.c', 'rte_net_crc.c', diff --git a/lib/net/rte_dissect.c b/lib/net/rte_dissect.c new file mode 100644 index 0000000000..ae86f7591e --- /dev/null +++ b/lib/net/rte_dissect.c @@ -0,0 +1,416 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2024 Stephen Hemminger + * + * Print packets in format similar to tshark. + * Output should be one line per mbuf + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Forward declaration - Ethernet can be nested */ +static void +dissect_eth(char *buf, size_t size, const struct rte_mbuf *mb, + uint32_t offset, uint32_t dump_len); + +/* + * Read data from segmented mbuf and put it into buf , but stop if would go past max length + * See rte_pktmbuf_read() + */ +static const void * +dissect_read(const struct rte_mbuf *m, uint32_t offset, uint32_t len, + void *buf, uint32_t dump_len) +{ + /* If this header would be past the requested length + * then unwind back to end the string. + */ + if (dump_len > 0 && offset + len > dump_len) + return NULL; + + return rte_pktmbuf_read(m, offset, len, buf); +} + +/* + * Print to string buffer and adjust result + * Returns true on success, false if buffer is exhausted. + */ +static __rte_format_printf(3, 4) bool +dissect_print(char **buf, size_t *sz, const char *fmt, ...) +{ + va_list ap; + int cnt; + + va_start(ap, fmt); + cnt = vsnprintf(*buf, *sz, fmt, ap); + va_end(ap); + + /* error or string is full */ + if (cnt < 0 || cnt >= (int)*sz) { + *sz = 0; + return false; + } + + *buf += cnt; + *sz -= cnt; + return true; +} + +static void +dissect_arp(char *buf, size_t size, const struct rte_mbuf *mb, uint32_t offset, uint32_t dump_len) +{ + const struct rte_arp_hdr *arp; + struct rte_arp_hdr _arp; + char abuf[64]; + + arp = dissect_read(mb, offset, sizeof(_arp), &_arp, dump_len); + if (arp == NULL) + return; + offset += sizeof(_arp); + + uint16_t ar_op = rte_be_to_cpu_16(arp->arp_opcode); + switch (ar_op) { + case RTE_ARP_OP_REQUEST: + inet_ntop(AF_INET, &arp->arp_data.arp_tip, abuf, sizeof(abuf)); + if (!dissect_print(&buf, &size, "Who has %s? ", abuf)) + return; + + rte_ether_format_addr(abuf, sizeof(abuf), &arp->arp_data.arp_sha); + if (!dissect_print(&buf, &size, "Tell %s ", abuf)) + return; + + break; + case RTE_ARP_OP_REPLY: + inet_ntop(AF_INET, &arp->arp_data.arp_sip, abuf, sizeof(abuf)); + if (!dissect_print(&buf, &size, "%s is at", abuf)) + return; + + rte_ether_format_addr(abuf, sizeof(abuf), &arp->arp_data.arp_sha); + if (!dissect_print(&buf, &size, "%s ", abuf)) + return; + break; + case RTE_ARP_OP_INVREQUEST: + rte_ether_format_addr(abuf, sizeof(abuf), &arp->arp_data.arp_tha); + if (!dissect_print(&buf, &size, "Who is %s? ", abuf)) + return; + + rte_ether_format_addr(abuf, sizeof(abuf), &arp->arp_data.arp_sha); + if (!dissect_print(&buf, &size, "Tell %s ", abuf)) + return; + break; + + case RTE_ARP_OP_INVREPLY: + rte_ether_format_addr(abuf, sizeof(buf), &arp->arp_data.arp_sha); + if (!dissect_print(&buf, &size, "%s is at ", abuf)) + return; + + inet_ntop(AF_INET, &arp->arp_data.arp_sip, abuf, sizeof(abuf)); + if (!dissect_print(&buf, &size, "%s ", abuf)) + return; + break; + default: + if (!dissect_print(&buf, &size, "Unknown ARP %#x ", ar_op)) + return; + break; + } +} + +static void +dissect_vxlan(char *buf, size_t size, const struct rte_mbuf *mb, uint32_t offset, uint32_t dump_len) +{ + const struct rte_vxlan_hdr *vxlan; + struct rte_vxlan_hdr _vxlan; + + vxlan = dissect_read(mb, offset, sizeof(_vxlan), &_vxlan, dump_len); + if (vxlan == NULL) + return; + offset += sizeof(_vxlan); + + if (!dissect_print(&buf, &size, "VXLAN ")) + return; + + if (vxlan->flag_i) { + uint32_t vni = rte_be_to_cpu_32(vxlan->vx_vni); + + if (!dissect_print(&buf, &size, "%#x ", vni >> 8)) + return; + } + + dissect_eth(buf, size, mb, offset, dump_len); +} + +static void +dissect_udp(char *buf, size_t size, const struct rte_mbuf *mb, uint32_t offset, uint32_t dump_len) +{ + const struct rte_udp_hdr *udph; + struct rte_udp_hdr _udp; + uint16_t src_port, dst_port, len; + + udph = dissect_read(mb, offset, sizeof(_udp), &_udp, dump_len); + if (udph == NULL) + return; + offset += sizeof(_udp); + + src_port = rte_be_to_cpu_16(udph->src_port); + dst_port = rte_be_to_cpu_16(udph->dst_port); + len = rte_be_to_cpu_16(udph->dgram_len); + + switch (dst_port) { + case RTE_VXLAN_DEFAULT_PORT: + dissect_vxlan(buf, size, mb, offset, dump_len); + break; + default: + if (!dissect_print(&buf, &size, "UDP %u %u → %u ", len, src_port, dst_port)) + return; + } +} + +static void +dissect_tcp(char *buf, size_t size, const struct rte_mbuf *mb, uint32_t offset, uint32_t dump_len) +{ + const struct rte_tcp_hdr *tcph; + struct rte_tcp_hdr _tcp; + uint16_t src_port, dst_port; + + tcph = dissect_read(mb, offset, sizeof(_tcp), &_tcp, dump_len); + if (tcph == NULL) + return; + offset += sizeof(_tcp); + + src_port = rte_be_to_cpu_16(tcph->src_port); + dst_port = rte_be_to_cpu_16(tcph->dst_port); + + if (!dissect_print(&buf, &size, "TCP %u → %u", src_port, dst_port)) + return; + +#define PRINT_TCP_FLAG(flag) { \ + if (tcph->tcp_flags & RTE_TCP_ ## flag ## _FLAG) \ + if (!dissect_print(&buf, &size, " [ " #flag " ]")) \ + return; \ + } + + PRINT_TCP_FLAG(URG); + PRINT_TCP_FLAG(ACK); + PRINT_TCP_FLAG(RST); + PRINT_TCP_FLAG(SYN); + PRINT_TCP_FLAG(FIN); +#undef PRINT_TCP_FLAG + + dissect_print(&buf, &size, "Seq=%u Ack=%u Win=%u ", + rte_be_to_cpu_16(tcph->sent_seq), + rte_be_to_cpu_16(tcph->recv_ack), + rte_be_to_cpu_16(tcph->rx_win)); +} + +static void +dissect_icmp(char *buf, size_t size, const struct rte_mbuf *mb, uint32_t offset, uint32_t dump_len) +{ + const struct rte_icmp_hdr *icmp; + struct rte_icmp_hdr _icmp; + static const char * const icmp_types[256] = { + [RTE_IP_ICMP_ECHO_REPLY] = "ICMP Echo Reply", + [RTE_IP_ICMP_DEST_UNREACH] = "ICMP Destination Unreachable", + [RTE_IP_ICMP_SOURCE_QUENCH] = "ICMP Source Quench", + [RTE_IP_ICMP_REDIRECT] = "ICMP Redirect", + [RTE_IP_ICMP_ECHO_REQUEST] = "ICMP Echo Request", + [RTE_IP_ICMP_TIME_EXCEEDED] = "ICMP Time Exceeded", + [RTE_IP_ICMP_PARAMETERPROB] = "ICMP Parameter Problem", + [RTE_IP_ICMP_TIMESTAMP] = "ICMP Timestamp Request", + [RTE_IP_ICMP_TIMESTAMPREPLY] = "ICMP Timestamp Reply", + [RTE_IP_ICMP_INFO_REQUEST] = "ICMP Info Request", + [RTE_IP_ICMP_INFO_REPLY] = "ICMP Info Reply", + + [RTE_ICMP6_ECHO_REPLY] = "ICMPv6 Echo Reply", + [RTE_ICMP6_ECHO_REQUEST] = "ICMPv6 Echo Request", + [RTE_ND_ROUTER_SOLICIT] = "ICMPv6 Router Solicitation", + [RTE_ND_ROUTER_ADVERT] = "ICMPv6 Router Advertisement", + [RTE_ND_NEIGHBOR_SOLICIT] = "ICMPv6 Neighbor Solicitation", + [RTE_ND_NEIGHBOR_ADVERT] = "ICMPv6 Neighbor Advertisement", + }; + + icmp = dissect_read(mb, offset, sizeof(_icmp), &_icmp, dump_len); + if (icmp == NULL) + return; + offset += sizeof(_icmp); + + const char *name = icmp_types[icmp->icmp_type]; + if (name != NULL) + dissect_print(&buf, &size, "%s ", name); + else + dissect_print(&buf, &size, "ICMP %u ", icmp->icmp_type); +} + +static void +dissect_ipv4(char *buf, size_t size, const struct rte_mbuf *mb, + uint32_t offset, uint32_t dump_len) +{ + const struct rte_ipv4_hdr *ip_hdr; + struct rte_ipv4_hdr _ip_hdr; + char sbuf[INET_ADDRSTRLEN], dbuf[INET_ADDRSTRLEN]; + + ip_hdr = dissect_read(mb, offset, sizeof(_ip_hdr), &_ip_hdr, dump_len); + if (ip_hdr == NULL) + return; + + inet_ntop(AF_INET, &ip_hdr->src_addr, sbuf, sizeof(sbuf)); + inet_ntop(AF_INET, &ip_hdr->dst_addr, dbuf, sizeof(dbuf)); + if (!dissect_print(&buf, &size, "%s → %s ", sbuf, dbuf)) + return; + + offset += ip_hdr->ihl * 4; + switch (ip_hdr->next_proto_id) { + case IPPROTO_UDP: + return dissect_udp(buf, size, mb, offset, dump_len); + case IPPROTO_TCP: + return dissect_tcp(buf, size, mb, offset, dump_len); + case IPPROTO_ICMP: + return dissect_icmp(buf, size, mb, offset, dump_len); + default: + /* TODO dissect tunnels */ + dissect_print(&buf, &size, "IP %#x ", ip_hdr->next_proto_id); + } +} + +static void +dissect_ipv6(char *buf, size_t size, const struct rte_mbuf *mb, + uint32_t offset, uint32_t dump_len) +{ + const struct rte_ipv6_hdr *ip6_hdr; + struct rte_ipv6_hdr _ip6_hdr; + char sbuf[INET6_ADDRSTRLEN], dbuf[INET6_ADDRSTRLEN]; + uint16_t proto; + unsigned int i; + + ip6_hdr = dissect_read(mb, offset, sizeof(_ip6_hdr), &_ip6_hdr, dump_len); + if (ip6_hdr == NULL) + return; + + offset += sizeof(*ip6_hdr); + inet_ntop(AF_INET6, ip6_hdr->src_addr, sbuf, sizeof(sbuf)); + inet_ntop(AF_INET6, ip6_hdr->dst_addr, dbuf, sizeof(dbuf)); + if (!dissect_print(&buf, &size, "%s → %s ", sbuf, dbuf)) + return; + +#define MAX_EXT_HDRS 5 + proto = ip6_hdr->proto; + for (i = 0; i < MAX_EXT_HDRS; i++) { + switch (proto) { + case IPPROTO_UDP: + return dissect_udp(buf, size, mb, offset, dump_len); + case IPPROTO_TCP: + return dissect_tcp(buf, size, mb, offset, dump_len); + case IPPROTO_ICMPV6: + return dissect_icmp(buf, size, mb, offset, dump_len); + + case IPPROTO_HOPOPTS: + case IPPROTO_ROUTING: + case IPPROTO_DSTOPTS: + { + const struct rte_ipv6_routing_ext *xh; + struct rte_ipv6_routing_ext _xh; + + xh = dissect_read(mb, offset, sizeof(xh), &_xh, dump_len); + if (xh == NULL) + return; + + offset += (xh->hdr_len + 1) * 8; + proto = xh->next_hdr; + continue; + } + + case IPPROTO_FRAGMENT: + dissect_print(&buf, &size, "%s", "FRAG "); + return; + + case IPPROTO_NONE: + dissect_print(&buf, &size, "%s", "NONE "); + return; + + default: + dissect_print(&buf, &size, "IPv6 %#x ", proto); + return; + } + } +} + +/* + * Format up a string describing contents of packet in tshark like style. + */ +static void +dissect_eth(char *buf, size_t size, const struct rte_mbuf *mb, + uint32_t offset, uint32_t dump_len) +{ + const struct rte_ether_hdr *eth_hdr; + struct rte_ether_hdr _eth_hdr; + uint16_t eth_type; + char sbuf[RTE_ETHER_ADDR_FMT_SIZE], dbuf[RTE_ETHER_ADDR_FMT_SIZE]; + + eth_hdr = dissect_read(mb, offset, sizeof(_eth_hdr), &_eth_hdr, dump_len); + if (unlikely(eth_hdr == NULL)) + return; + + offset += sizeof(*eth_hdr); + eth_type = rte_be_to_cpu_16(eth_hdr->ether_type); + if (eth_type == RTE_ETHER_TYPE_VLAN || eth_type == RTE_ETHER_TYPE_QINQ) { + const struct rte_vlan_hdr *vh + = (const struct rte_vlan_hdr *)(eth_hdr + 1); + eth_type = vh->eth_proto; + offset += sizeof(*vh); + + const char *vs = (eth_type == RTE_ETHER_TYPE_VLAN) ? "VLAN" : "QINQ"; + uint16_t tci = rte_be_to_cpu_16(vh->vlan_tci); + + if (!dissect_print(&buf, &size, "%s %#x ", vs, tci)) + return; + } + + switch (eth_type) { + case RTE_ETHER_TYPE_ARP: + rte_ether_format_addr(sbuf, sizeof(sbuf), ð_hdr->src_addr); + rte_ether_format_addr(dbuf, sizeof(dbuf), ð_hdr->dst_addr); + if (!dissect_print(&buf, &size, "%s → %s ARP ", sbuf, dbuf)) + return; + + dissect_arp(buf, size, mb, offset, dump_len); + break; + + case RTE_ETHER_TYPE_IPV4: + dissect_ipv4(buf, size, mb, offset, dump_len); + break; + + case RTE_ETHER_TYPE_IPV6: + dissect_ipv6(buf, size, mb, offset, dump_len); + break; + + default: + dissect_print(&buf, &size, "ETH %#x ", eth_type); + } +} + +void +rte_dissect_mbuf(char *buf, size_t size, const struct rte_mbuf *m, uint32_t dump_len) +{ + dissect_eth(buf, size, m, 0, dump_len); + + /* trim off trailing spaces */ + char *cp; + while ((cp = strrchr(buf, ' ')) != NULL) { + if (cp[1] != '\0') + break; + *cp = '\0'; + } +} diff --git a/lib/net/rte_dissect.h b/lib/net/rte_dissect.h new file mode 100644 index 0000000000..26af265029 --- /dev/null +++ b/lib/net/rte_dissect.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2024 Stephen Hemminger + */ + +#ifndef _RTE_NET_DISSECT_H_ +#define _RTE_NET_DISSECT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include + +struct rte_mbuf; + +/** + * + * Format description of packet to a string buffer + * + * @param buf + * A pointer to buffer for the resulting line. + * @param size + * The format buffer size. + * @param m + * The packet mbuf. + * @param dump_len + * Maximum offset in packet to examine. + * If is zero then dump the whole packet. + */ +__rte_experimental +void +rte_dissect_mbuf(char *buf, size_t size, const struct rte_mbuf *m, uint32_t dump_len); + +#ifdef __cplusplus +} +#endif + + +#endif /* _RTE_NET_DISSECT_H_ */ diff --git a/lib/net/version.map b/lib/net/version.map index 3e293c4715..d7b9e9c0e7 100644 --- a/lib/net/version.map +++ b/lib/net/version.map @@ -12,3 +12,10 @@ DPDK_24 { local: *; }; + +EXPERIMENTAL { + global: + + # added in 24.11 + rte_dissect_mbuf; +};