From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id A09CF45BE5; Mon, 28 Oct 2024 03:21:54 +0100 (CET) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id 163AD4067A; Mon, 28 Oct 2024 03:21:50 +0100 (CET) Received: from mail-pg1-f169.google.com (mail-pg1-f169.google.com [209.85.215.169]) by mails.dpdk.org (Postfix) with ESMTP id 5D7A2402A3 for ; Mon, 28 Oct 2024 03:21:43 +0100 (CET) Received: by mail-pg1-f169.google.com with SMTP id 41be03b00d2f7-7ea7e2ff5ceso2559072a12.2 for ; Sun, 27 Oct 2024 19:21:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=networkplumber-org.20230601.gappssmtp.com; s=20230601; t=1730082102; x=1730686902; 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=HwA35hjyZK2tKp1HMDLebbip7XEcncSGyz7UjVuS6GY=; b=2m9HbYW7oaLt+lW02Jo6m38D3cVUynUsZMI2ulwIALFuIveiCLqd9f7lPAXJEqtdZC zIM7q/d5vNZdieHm0X7s4Wh1uNe1yHvHyscXmtHSBlz9D+T41zDSyQnv+vsWvUIJvZUh CwyZqtFIFIlOemJer9Tt+abg0iJxGvlcdlnlVYaEvo91qD+ByFbG/P5MCcEfT901Z8X1 i9AT3tkngNnIoPZa887AffprCpw8TuiNOxDxKn5PP0lDdab7WXNXHtSyf6albxllC0EV FuP2eiOBbwJLkFiUqlK8YiwmjdH9j8DvVNy5Q+2kFd0T0NCM9Pj8XGaXxAZYZ4rTS+C6 dx7w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1730082102; x=1730686902; 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=HwA35hjyZK2tKp1HMDLebbip7XEcncSGyz7UjVuS6GY=; b=DlgwEyXI/GiBjGlAf0k6fJGJsjaDSpJ2C15IWQ6BrhwXEtH8doRAyJmvA8Kff+L3Qp tltp9qYdHIawWwiOmWUx+dPpRsFqLNRgeHAu66HzbNGNkNZshkPmp6KqO66F84pAKoIN y2wYhAJxvO2jbu/eO4BTe0YUGGsjnM4dAez1H7EwW8b1VmumPCIc0ZY6hYaXcvdEHyRc PQbVYVc8gqUktRXM5vrYmH2whNKyA9m1vTPLUiZcbdwsiaOOQw7LlGAw4iQqWGM2hElG 7kvKtXMXWr78N1fYYUovs7dJu4TLxII+J9tGdIjvMWNiUTmp9McNGa2j+4COciY7IsBC hG9A== X-Gm-Message-State: AOJu0YwzgCHSXDiq+0c3Eb14YLPvgnMX8ksKnRYjX9VSW5iXrnv9+V3h d0QA66SlLHevVF9UdRFdYgfJBeWInBJsDusiE3aZcR8PgT6NsbjZuC0wHu8lDtjMTKwqCXqdnAO SriQ= X-Google-Smtp-Source: AGHT+IF5yzXJzV5QSmcw04kgTjr/IrPjZ2z/w6BhDxDh7iPxLAyL3pXDHNETmB/J59GfMBE358A+UA== X-Received: by 2002:a17:902:e54f:b0:20b:7731:e3df with SMTP id d9443c01a7336-210c6c6a143mr115612455ad.43.1730082102426; Sun, 27 Oct 2024 19:21:42 -0700 (PDT) Received: from hermes.local (204-195-96-226.wavecable.com. [204.195.96.226]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-210bc04b8bbsm41095615ad.244.2024.10.27.19.21.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 27 Oct 2024 19:21:42 -0700 (PDT) From: Stephen Hemminger To: dev@dpdk.org Cc: Stephen Hemminger Subject: [PATCH v10 2/8] net: add new packet dissector Date: Sun, 27 Oct 2024 19:19:28 -0700 Message-ID: <20241028022131.142609-3-stephen@networkplumber.org> X-Mailer: git-send-email 2.45.2 In-Reply-To: <20241028022131.142609-1-stephen@networkplumber.org> References: <20240312220129.70667-1-stephen@networkplumber.org> <20241028022131.142609-1-stephen@networkplumber.org> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 | 429 ++++++++++++++++++++++++++++++++++++++++++ lib/net/rte_dissect.h | 45 +++++ lib/net/version.map | 7 + 4 files changed, 483 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 8afcc4ed37..ebeeb561b8 100644 --- a/lib/net/meson.build +++ b/lib/net/meson.build @@ -3,6 +3,7 @@ headers = files( 'rte_cksum.h', + 'rte_dissect.h', 'rte_ip.h', 'rte_ip4.h', 'rte_ip6.h', @@ -33,6 +34,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..80e84b8c70 --- /dev/null +++ b/lib/net/rte_dissect.c @@ -0,0 +1,429 @@ +/* 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 int +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 */ + 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) int +dissect_print(char **buf, size_t *sz, const char *fmt, ...) +{ + va_list ap; + int count; + + va_start(ap, fmt); + count = vsnprintf(*buf, *sz, fmt, ap); + va_end(ap); + + /* error or string is full */ + if (count < 0 || count >= (int)*sz) { + *sz = 0; + } else { + *buf += count; + *sz -= count; + } + return count; +} + +static int +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; + int count = 0; + char abuf[64]; + + arp = dissect_read(mb, offset, sizeof(_arp), &_arp, dump_len); + if (arp == NULL) + return snprintf(buf, size, "Missing ARP header"); + + 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)); + count += dissect_print(&buf, &size, "Who has %s? ", abuf); + + rte_ether_format_addr(abuf, sizeof(abuf), &arp->arp_data.arp_sha); + count += dissect_print(&buf, &size, "Tell %s ", abuf); + break; + + case RTE_ARP_OP_REPLY: + inet_ntop(AF_INET, &arp->arp_data.arp_sip, abuf, sizeof(abuf)); + count += dissect_print(&buf, &size, "%s is at", abuf); + + rte_ether_format_addr(abuf, sizeof(abuf), &arp->arp_data.arp_sha); + count += dissect_print(&buf, &size, "%s ", abuf); + break; + + case RTE_ARP_OP_INVREQUEST: + rte_ether_format_addr(abuf, sizeof(abuf), &arp->arp_data.arp_tha); + count += dissect_print(&buf, &size, "Who is %s? ", abuf); + + rte_ether_format_addr(abuf, sizeof(abuf), &arp->arp_data.arp_sha); + count += dissect_print(&buf, &size, "Tell %s ", abuf); + break; + + case RTE_ARP_OP_INVREPLY: + rte_ether_format_addr(abuf, sizeof(buf), &arp->arp_data.arp_sha); + count += dissect_print(&buf, &size, "%s is at ", abuf); + + inet_ntop(AF_INET, &arp->arp_data.arp_sip, abuf, sizeof(abuf)); + count += dissect_print(&buf, &size, "%s ", abuf); + break; + + default: + count += dissect_print(&buf, &size, "Unknown ARP %#x ", ar_op); + break; + } + + return count; +} + +static int +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; + int count = 0; + + vxlan = dissect_read(mb, offset, sizeof(_vxlan), &_vxlan, dump_len); + if (vxlan == NULL) + return snprintf(buf, size, "Missing VXLAN header"); + + offset += sizeof(_vxlan); + + if (vxlan->flag_i) { + uint32_t vni = rte_be_to_cpu_32(vxlan->vx_vni); + + count += dissect_print(&buf, &size, "%#x ", vni >> 8); + } + + count += dissect_eth(buf, size, mb, offset, dump_len); + return count; +} + +static int +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 snprintf(buf, size, "Missing UDP header"); + + 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: + return dissect_vxlan(buf, size, mb, offset, dump_len); + default: + return dissect_print(&buf, &size, "UDP %u %u → %u ", len, src_port, dst_port); + } +} + +static int +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; + int count; + + tcph = dissect_read(mb, offset, sizeof(_tcp), &_tcp, dump_len); + if (tcph == NULL) + return snprintf(buf, size, "Missing TCP header"); + + offset += sizeof(_tcp); + src_port = rte_be_to_cpu_16(tcph->src_port); + dst_port = rte_be_to_cpu_16(tcph->dst_port); + + count = dissect_print(&buf, &size, "TCP %u → %u", src_port, dst_port); + +#define PRINT_TCP_FLAG(flag) do { \ + if (tcph->tcp_flags & RTE_TCP_ ## flag ## _FLAG) \ + count += dissect_print(&buf, &size, " [ " #flag " ]"); \ + } while (0) + + PRINT_TCP_FLAG(URG); + PRINT_TCP_FLAG(ACK); + PRINT_TCP_FLAG(RST); + PRINT_TCP_FLAG(SYN); + PRINT_TCP_FLAG(FIN); +#undef PRINT_TCP_FLAG + + count += 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)); + return count; +} + +static int +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_ICMP_TYPE_ECHO_REPLY] = "ICMP Echo Reply", + [RTE_ICMP_TYPE_DEST_UNREACHABLE] = "ICMP Destination Unreachable", + [RTE_ICMP_TYPE_SOURCE_QUENCH] = "ICMP Source Quench", + [RTE_ICMP_TYPE_REDIRECT] = "ICMP Redirect", + [RTE_ICMP_TYPE_ECHO_REQUEST] = "ICMP Echo Request", + [RTE_ICMP_TYPE_TTL_EXCEEDED] = "ICMP TTL Exceeded", + [RTE_ICMP_TYPE_PARAM_PROBLEM] = "ICMP Parameter Problem", + [RTE_ICMP_TYPE_TIMESTAMP_REQUEST] = "ICMP Timestamp Request", + [RTE_ICMP_TYPE_TIMESTAMP_REPLY] = "ICMP Timestamp Reply", + [RTE_ICMP_TYPE_INFO_REQUEST] = "ICMP Info Request", + [RTE_ICMP_TYPE_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 snprintf(buf, size, "Missing ICMP header"); + + offset += sizeof(_icmp); + const char *name = icmp_types[icmp->icmp_type]; + if (name != NULL) + return dissect_print(&buf, &size, "%s ", name); + else + return dissect_print(&buf, &size, "ICMP %u ", icmp->icmp_type); +} + +static int +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]; + int count; + + ip_hdr = dissect_read(mb, offset, sizeof(_ip_hdr), &_ip_hdr, dump_len); + if (ip_hdr == NULL) + return snprintf(buf, size, "Missing IP header"); + + inet_ntop(AF_INET, &ip_hdr->src_addr, sbuf, sizeof(sbuf)); + inet_ntop(AF_INET, &ip_hdr->dst_addr, dbuf, sizeof(dbuf)); + count = dissect_print(&buf, &size, "%s → %s ", sbuf, dbuf); + + offset += ip_hdr->ihl * 4; + switch (ip_hdr->next_proto_id) { + case IPPROTO_UDP: + count += dissect_udp(buf, size, mb, offset, dump_len); + break; + case IPPROTO_TCP: + count += dissect_tcp(buf, size, mb, offset, dump_len); + break; + case IPPROTO_ICMP: + count += dissect_icmp(buf, size, mb, offset, dump_len); + break; + default: + /* TODO dissect tunnels */ + count += dissect_print(&buf, &size, "IP %#x ", ip_hdr->next_proto_id); + } + return count; +} + +static int +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; + int count; + + ip6_hdr = dissect_read(mb, offset, sizeof(_ip6_hdr), &_ip6_hdr, dump_len); + if (ip6_hdr == NULL) + return snprintf(buf, size, "Missing IPv6 header"); + + 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)); + count = dissect_print(&buf, &size, "%s → %s ", sbuf, dbuf); + +#define MAX_EXT_HDRS 5 + proto = ip6_hdr->proto; + for (i = 0; i < MAX_EXT_HDRS; i++) { + switch (proto) { + case IPPROTO_UDP: + count += dissect_udp(buf, size, mb, offset, dump_len); + return count; + + case IPPROTO_TCP: + count += dissect_tcp(buf, size, mb, offset, dump_len); + return count; + + case IPPROTO_ICMPV6: + count += dissect_icmp(buf, size, mb, offset, dump_len); + return count; + + 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 count; + + offset += (xh->hdr_len + 1) * 8; + proto = xh->next_hdr; + continue; + } + + case IPPROTO_FRAGMENT: + count += dissect_print(&buf, &size, "%s", "FRAG "); + return count; + + case IPPROTO_NONE: + count += dissect_print(&buf, &size, "%s", "NONE "); + return count; + + default: + count += dissect_print(&buf, &size, "IPv6 %#x ", proto); + return count; + } + } + return count; +} + +/* + * Format up a string describing contents of packet in tshark like style. + */ +static int +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; + int count = 0; + 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 snprintf(buf, size, "Missing ETH header"); + + 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; + struct rte_vlan_hdr _vh; + + vh = dissect_read(mb, offset, sizeof(_vh), &_vh, dump_len); + if (unlikely(vh == NULL)) + return snprintf(buf, size, "Missing VLAN header"); + + eth_type = vh->eth_proto; + offset += sizeof(*vh); + + count += dissect_print(&buf, &size, "%s %#x ", + (eth_type == RTE_ETHER_TYPE_VLAN) ? "VLAN" : "QINQ", + rte_be_to_cpu_16(vh->vlan_tci)); + } + + 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); + count += dissect_print(&buf, &size, "%s → %s ARP ", sbuf, dbuf); + count += dissect_arp(buf, size, mb, offset, dump_len); + break; + + case RTE_ETHER_TYPE_IPV4: + count += dissect_ipv4(buf, size, mb, offset, dump_len); + break; + + case RTE_ETHER_TYPE_IPV6: + count += dissect_ipv6(buf, size, mb, offset, dump_len); + break; + + default: + count += dissect_print(&buf, &size, "ETH %#x ", eth_type); + } + + return count; +} + +int +rte_dissect_mbuf(char *buf, size_t size, const struct rte_mbuf *m, uint32_t dump_len) +{ + int count; + + count = dissect_eth(buf, size, m, 0, dump_len); + if (count <= 0) + return count; + + /* output was truncated, but redact the trailing blank */ + if (count >= (int)size) + return count - 1; + + if (buf[count] == ' ') + buf[count--] = '\0'; + + return count; +} diff --git a/lib/net/rte_dissect.h b/lib/net/rte_dissect.h new file mode 100644 index 0000000000..343e9daa4d --- /dev/null +++ b/lib/net/rte_dissect.h @@ -0,0 +1,45 @@ +/* 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. + * @return + * Returns the number of bytes printed (excluding null byte at end of string). + * if output was truncated returns the number of bytes that would have been printed. + */ +__rte_experimental +int +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 bec4ce23ea..9a27339df5 100644 --- a/lib/net/version.map +++ b/lib/net/version.map @@ -12,3 +12,10 @@ DPDK_25 { local: *; }; + +EXPERIMENTAL { + global: + + # added in 24.11 + rte_dissect_mbuf; +}; -- 2.45.2