DPDK patches and discussions
 help / color / mirror / Atom feed
* [PATCH] test-pmd: add more packet decode options (verbose)
@ 2024-03-12 22:01 Stephen Hemminger
  2024-03-13 21:49 ` Stephen Hemminger
                   ` (7 more replies)
  0 siblings, 8 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-03-12 22:01 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Ori Kam, Aman Singh, Yuying Zhang

The existing verbose levels 1..3 provide a messy multi-line
output per packet. This is unhelpful when diagnosing many
types of problems like packet flow.

This patch adds two new levels:
4: one line per packet is printed in a format resembling
   tshark output. With addresses and protocol info.
5: dump packet in hex.
   Useful if the driver is messing up the data.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 app/test-pmd/cmdline_flow.c                 |   3 +-
 app/test-pmd/config.c                       |  33 +-
 app/test-pmd/testpmd.h                      |  11 +
 app/test-pmd/util.c                         | 355 +++++++++++++++++++-
 doc/guides/testpmd_app_ug/testpmd_funcs.rst |   5 +-
 5 files changed, 391 insertions(+), 16 deletions(-)

diff --git a/app/test-pmd/cmdline_flow.c b/app/test-pmd/cmdline_flow.c
index 5f761903c1d1..03141c04cae5 100644
--- a/app/test-pmd/cmdline_flow.c
+++ b/app/test-pmd/cmdline_flow.c
@@ -14002,7 +14002,8 @@ cmd_set_raw_parsed(const struct buffer *in)
 			upper_layer = proto;
 		}
 	}
-	if (verbose_level & 0x1)
+
+	if (verbose_level > 0)
 		printf("total data size is %zu\n", (*total_size));
 	RTE_ASSERT((*total_size) <= ACTION_RAW_ENCAP_MAX_DATA);
 	memmove(data, (data_tail - (*total_size)), *total_size);
diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c
index 968d2164ab35..8e83b78721fc 100644
--- a/app/test-pmd/config.c
+++ b/app/test-pmd/config.c
@@ -6250,26 +6250,37 @@ configure_rxtx_dump_callbacks(uint16_t verbose)
 		return;
 #endif
 
-	RTE_ETH_FOREACH_DEV(portid)
-	{
-		if (verbose == 1 || verbose > 2)
+	RTE_ETH_FOREACH_DEV(portid) {
+		switch (verbose) {
+		case VERBOSE_OFF:
+			remove_rx_dump_callbacks(portid);
+			remove_tx_dump_callbacks(portid);
+			break;
+		case VERBOSE_RX:
 			add_rx_dump_callbacks(portid);
-		else
+			remove_tx_dump_callbacks(portid);
+			break;
+		case VERBOSE_TX:
+			add_tx_dump_callbacks(portid);
 			remove_rx_dump_callbacks(portid);
-		if (verbose >= 2)
+			break;
+		default:
+			add_rx_dump_callbacks(portid);
 			add_tx_dump_callbacks(portid);
-		else
-			remove_tx_dump_callbacks(portid);
+		}
 	}
 }
 
 void
 set_verbose_level(uint16_t vb_level)
 {
-	printf("Change verbose level from %u to %u\n",
-	       (unsigned int) verbose_level, (unsigned int) vb_level);
-	verbose_level = vb_level;
-	configure_rxtx_dump_callbacks(verbose_level);
+	if (vb_level < VERBOSE_MAX) {
+		printf("Change verbose level from %u to %u\n", verbose_level, vb_level);
+		verbose_level = vb_level;
+		configure_rxtx_dump_callbacks(verbose_level);
+	} else {
+		fprintf(stderr, "Verbose level %u is out of range\n", vb_level);
+	}
 }
 
 void
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
index 55df12033a39..26801226bb47 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -489,6 +489,17 @@ enum dcb_mode_enable
 
 extern uint8_t xstats_hide_zero; /**< Hide zero values for xstats display */
 
+enum verbose_mode {
+	VERBOSE_OFF = 0,
+	VERBOSE_RX,
+	VERBOSE_TX,
+	VERBOSE_BOTH,
+	VERBOSE_DISSECT,
+	VERBOSE_HEX,
+	VERBOSE_MAX
+};
+
+
 /* globals used for configuration */
 extern uint8_t record_core_cycles; /**< Enables measurement of CPU cycles */
 extern uint8_t record_burst_stats; /**< Enables display of RX and TX bursts */
diff --git a/app/test-pmd/util.c b/app/test-pmd/util.c
index 5aa69ed545af..f9dfa4d7ec07 100644
--- a/app/test-pmd/util.c
+++ b/app/test-pmd/util.c
@@ -9,6 +9,11 @@
 #include <rte_net.h>
 #include <rte_mbuf.h>
 #include <rte_ether.h>
+#include <rte_arp.h>
+#include <rte_icmp.h>
+#include <rte_ip.h>
+#include <rte_tcp.h>
+#include <rte_udp.h>
 #include <rte_vxlan.h>
 #include <rte_ethdev.h>
 #include <rte_flow.h>
@@ -16,6 +21,7 @@
 #include "testpmd.h"
 
 #define MAX_STRING_LEN 8192
+#define MAX_DUMP_LEN   1024
 
 #define MKDUMPSTR(buf, buf_size, cur_len, ...) \
 do { \
@@ -67,9 +73,10 @@ get_timestamp(const struct rte_mbuf *mbuf)
 			timestamp_dynfield_offset, rte_mbuf_timestamp_t *);
 }
 
-static inline void
-dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
-	      uint16_t nb_pkts, int is_rx)
+/* More verbose older style packet decode */
+static void
+dump_pkt_verbose(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
+		 uint16_t nb_pkts, int is_rx)
 {
 	struct rte_mbuf  *mb;
 	const struct rte_ether_hdr *eth_hdr;
@@ -297,6 +304,348 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
 	}
 }
 
+static void
+dissect_arp(const struct rte_mbuf *mb, uint16_t offset)
+{
+	const struct rte_arp_hdr *arp;
+	struct rte_arp_hdr _arp;
+	uint16_t ar_op;
+	char buf[128];
+
+	arp = rte_pktmbuf_read(mb, offset, sizeof(*arp), &_arp);
+	if (unlikely(arp == NULL)) {
+		printf("truncated ARP! ");
+		return;
+	}
+
+	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, buf, sizeof(buf));
+		printf("Who has %s? ", buf);
+
+		rte_ether_format_addr(buf, sizeof(buf), &arp->arp_data.arp_sha);
+		printf("Tell %s ", buf);
+		break;
+	case RTE_ARP_OP_REPLY:
+		inet_ntop(AF_INET, &arp->arp_data.arp_sip, buf, sizeof(buf));
+		printf("%s is at", buf);
+
+		rte_ether_format_addr(buf, sizeof(buf), &arp->arp_data.arp_sha);
+		printf("%s ", buf);
+		break;
+	case RTE_ARP_OP_INVREQUEST:
+		rte_ether_format_addr(buf, sizeof(buf), &arp->arp_data.arp_tha);
+		printf("Who is %s? ", buf);
+
+		rte_ether_format_addr(buf, sizeof(buf), &arp->arp_data.arp_sha);
+		printf("Tell %s ", buf);
+		break;
+
+	case RTE_ARP_OP_INVREPLY:
+		rte_ether_format_addr(buf, sizeof(buf), &arp->arp_data.arp_sha);
+		printf("%s is at ", buf);
+
+		inet_ntop(AF_INET, &arp->arp_data.arp_sip, buf, sizeof(buf));
+		printf("%s ", buf);
+		break;
+	default:
+		printf("Unknown ARP %#x ", ar_op);
+		break;
+	}
+}
+
+static void
+dissect_udp(const struct rte_mbuf *mb, uint16_t offset)
+{
+	const struct rte_udp_hdr *udph;
+	struct rte_udp_hdr _udp;
+	uint16_t src_port, dst_port;
+
+	udph = rte_pktmbuf_read(mb, offset, sizeof(*udph), &_udp);
+	if (unlikely(udph == NULL)) {
+		printf("truncated UDP! ");
+		return;
+	}
+
+	src_port = RTE_BE_TO_CPU_16(udph->src_port);
+	dst_port = RTE_BE_TO_CPU_16(udph->dst_port);
+
+	/* TODO handle vxlan */
+
+	printf("UDP %u %u → %u ",
+		  RTE_BE_TO_CPU_16(udph->dgram_len),
+		  src_port, dst_port);
+
+}
+
+static void
+dissect_tcp(const struct rte_mbuf *mb, uint16_t offset)
+{
+	const struct rte_tcp_hdr *tcph;
+	struct rte_tcp_hdr _tcp;
+	uint16_t src_port, dst_port;
+
+	tcph = rte_pktmbuf_read(mb, offset, sizeof(*tcph), &_tcp);
+	if (unlikely(tcph == NULL)) {
+		printf("truncated TCP! ");
+		return;
+	}
+
+	src_port = RTE_BE_TO_CPU_16(tcph->src_port);
+	dst_port = RTE_BE_TO_CPU_16(tcph->dst_port);
+
+	printf("TCP %u → %u",
+		  src_port, dst_port);
+#define PRINT_TCP_FLAG(flag) \
+	if (tcph->tcp_flags & RTE_TCP_ ## flag ## _FLAG) \
+		printf(" [" #flag" ]")
+
+	PRINT_TCP_FLAG(URG);
+	PRINT_TCP_FLAG(ACK);
+	PRINT_TCP_FLAG(RST);
+	PRINT_TCP_FLAG(SYN);
+	PRINT_TCP_FLAG(FIN);
+#undef PRINT_TCP_FLAG
+
+	printf("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(const struct rte_mbuf *mb, uint16_t offset)
+{
+	const struct rte_icmp_hdr *icmp;
+	struct rte_icmp_hdr _icmp;
+	static const char * const icmp_types[256] = {
+		[RTE_IP_ICMP_ECHO_REPLY]   = "ICMP Reply",
+		[RTE_IP_ICMP_ECHO_REQUEST] = "ICMP Request",
+		[RTE_ICMP6_ECHO_REPLY]     = "ICMPv6 Reply",
+		[RTE_ICMP6_ECHO_REQUEST]   = "ICMPv6 Request",
+		[133]                      = "ICMPv6 Router Solicitation",
+		[134]                      = "ICMPv6 Router Solicitation",
+	};
+
+
+	icmp = rte_pktmbuf_read(mb, offset, sizeof(*icmp), &_icmp);
+	if (unlikely(icmp == NULL)) {
+		printf("truncated ICMP! ");
+	} else {
+		const char *name = icmp_types[icmp->icmp_type];
+
+		if (name != NULL)
+			printf("%s ", name);
+		else
+			printf("ICMP type %u ", icmp->icmp_type);
+	}
+}
+
+static void
+dissect_ipv4(const struct rte_mbuf *mb, uint16_t offset)
+{
+	const struct rte_ipv4_hdr *ip_hdr;
+	struct rte_ipv4_hdr _ip_hdr;
+	char sbuf[INET_ADDRSTRLEN], dbuf[INET_ADDRSTRLEN];
+
+	ip_hdr = rte_pktmbuf_read(mb, offset, sizeof(*ip_hdr), &_ip_hdr);
+	if (unlikely(ip_hdr == NULL)) {
+		printf("truncated IP! ");
+		return;
+	}
+
+	inet_ntop(AF_INET, &ip_hdr->src_addr, sbuf, sizeof(sbuf));
+	inet_ntop(AF_INET, &ip_hdr->dst_addr, dbuf, sizeof(dbuf));
+	printf("%s → %s ", sbuf, dbuf);
+
+	offset += ip_hdr->ihl * 4;
+	switch (ip_hdr->next_proto_id) {
+	case IPPROTO_UDP:
+		return dissect_udp(mb, offset);
+	case IPPROTO_TCP:
+		return dissect_tcp(mb, offset);
+	case IPPROTO_ICMP:
+		return dissect_icmp(mb, offset);
+	default:
+		/* TODO dissect tunnels */
+		printf("IP proto %#x ", ip_hdr->next_proto_id);
+	}
+}
+
+static void
+dissect_ipv6(const struct rte_mbuf *mb, uint16_t offset)
+{
+	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 = rte_pktmbuf_read(mb, offset, sizeof(*ip6_hdr), &_ip6_hdr);
+	if (unlikely(ip6_hdr == NULL)) {
+		printf("truncated IPv6! ");
+		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));
+	printf("%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:
+			return dissect_udp(mb, offset);
+		case IPPROTO_TCP:
+			return dissect_tcp(mb, offset);
+		case IPPROTO_ICMPV6:
+			return dissect_icmp(mb, offset);
+
+		case IPPROTO_HOPOPTS:
+		case IPPROTO_ROUTING:
+		case IPPROTO_DSTOPTS:
+		{
+			const struct rte_ipv6_routing_ext *xh;
+			struct rte_ipv6_routing_ext _xh;
+
+			xh = rte_pktmbuf_read(mb, offset, sizeof(*xh), &_xh);
+			if (unlikely(xh == NULL)) {
+				printf("truncated IPV6 option! ");
+				return;
+			}
+			offset += (xh->hdr_len + 1) * 8;
+			proto = xh->next_hdr;
+			continue;
+		}
+
+		case IPPROTO_FRAGMENT:
+			printf("FRAG ");
+			return;
+
+		case IPPROTO_NONE:
+			printf("NONE ");
+			return;
+
+		default:
+			printf("IPv6 proto %u ", proto);
+			return;
+		}
+	}
+
+	printf("Too many extensions! ");
+}
+
+static void
+dissect_eth(const struct rte_mbuf *mb, uint16_t offset)
+{
+	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 = rte_pktmbuf_read(mb, offset, sizeof(struct rte_ether_hdr), &_eth_hdr);
+	if (unlikely(eth_hdr == NULL)) {
+		printf("missing Eth header! offset=%u", offset);
+		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);
+
+		printf("%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), &eth_hdr->src_addr);
+		rte_ether_format_addr(sbuf, sizeof(dbuf), &eth_hdr->dst_addr);
+		printf("%s → %s ARP ", sbuf, dbuf);
+
+		dissect_arp(mb, offset);
+		break;
+	case RTE_ETHER_TYPE_IPV4:
+		dissect_ipv4(mb, offset);
+		break;
+
+	case RTE_ETHER_TYPE_IPV6:
+		dissect_ipv6(mb, offset);
+		break;
+	default:
+		printf("Ethernet proto %#x ", eth_type);
+	}
+}
+
+/* Brief tshark style one line output which is
+ * number time_delta Source Destination Protocol len info
+ */
+static void
+dump_pkt_brief(uint16_t queue, struct rte_mbuf *pkts[], uint16_t nb_pkts)
+{
+	static uint64_t start_cycles;
+	static uint64_t packet_count;
+	uint64_t now;
+	uint64_t count;
+	double interval;
+	uint16_t i;
+
+	if (!nb_pkts)
+		return;
+
+	now = rte_rdtsc();
+	if (start_cycles == 0)
+		start_cycles = now;
+	interval = (double)(now - start_cycles) / (double)rte_get_tsc_hz();
+
+	count = __atomic_fetch_add(&packet_count, nb_pkts, __ATOMIC_RELAXED);
+
+	for (i = 0; i < nb_pkts; i++) {
+		const struct rte_mbuf *mb = pkts[i];
+
+		printf("%6"PRIu64" %11.9f %4u:%-3u ", count + i, interval, mb->port, queue);
+		dissect_eth(mb, 0);
+		putchar('\n');
+	}
+	fflush(stdout);
+}
+
+/* Hex dump of packet data */
+static void
+dump_pkt_hex(struct rte_mbuf *pkts[], uint16_t nb_pkts)
+{
+	uint16_t i;
+
+	for (i = 0; i < nb_pkts; i++)
+		rte_pktmbuf_dump(stdout, pkts[i], MAX_DUMP_LEN);
+
+	fflush(stdout);
+}
+
+static uint16_t
+dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
+	      uint16_t nb_pkts, int is_rx)
+{
+	switch (verbose_level) {
+	case VERBOSE_RX ... VERBOSE_BOTH:
+		dump_pkt_verbose(port_id, queue, pkts, nb_pkts, is_rx);
+		break;
+	case VERBOSE_DISSECT:
+		dump_pkt_brief(queue, pkts, nb_pkts);
+		break;
+	case VERBOSE_HEX:
+		dump_pkt_hex(pkts, nb_pkts);
+	}
+	return nb_pkts;
+}
+
 uint16_t
 dump_rx_pkts(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
 	     uint16_t nb_pkts, __rte_unused uint16_t max_pkts,
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
index 2fbf9220d8a9..f8b75b09afd0 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -677,7 +677,10 @@ Available levels are as following:
 * ``0`` silent except for error.
 * ``1`` fully verbose except for Tx packets.
 * ``2`` fully verbose except for Rx packets.
-* ``> 2`` fully verbose.
+* ``3`` fully verbose except for Tx and Rx packets.
+* ``4`` dissected protocol information for Tx and Rx packets.
+* ``5`` hex dump of packets
+
 
 set log
 ~~~~~~~
-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH] test-pmd: add more packet decode options (verbose)
  2024-03-12 22:01 [PATCH] test-pmd: add more packet decode options (verbose) Stephen Hemminger
@ 2024-03-13 21:49 ` Stephen Hemminger
  2024-07-05  7:13 ` David Marchand
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-03-13 21:49 UTC (permalink / raw)
  To: dev; +Cc: Ori Kam, Aman Singh, Yuying Zhang

On Tue, 12 Mar 2024 15:01:27 -0700
Stephen Hemminger <stephen@networkplumber.org> wrote:

> The existing verbose levels 1..3 provide a messy multi-line
> output per packet. This is unhelpful when diagnosing many
> types of problems like packet flow.
> 
> This patch adds two new levels:
> 4: one line per packet is printed in a format resembling
>    tshark output. With addresses and protocol info.
> 5: dump packet in hex.
>    Useful if the driver is messing up the data.
> 
> Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>

The build on mingw (ie. GCC on Windows) fails because of mismatch
in the function prototype. It is already fixed upstream in mingw
could the CI infrastructure please be updated. 

The commit upstream that fixed the bug was almost 5 years ago.

commit 1b5ae4b567ee1699f69cd918ae2bc693898edbb2
Author: Martin Storsjö <martin@martin.st>
Date:   Mon Apr 22 15:09:14 2019 +0300

    headers: Use proper types for InetNtopA/W
    
    PCVOID is not commonly defined, only LPCVOID.
    
    Signed-off-by: Martin Storsjö <martin@martin.st>


This commit is in the 7.0 release.
The current version of MinGw is 11.0 which is what CI should be using!

Unfortunately, DPDK documentation and build does not have a version listed.

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH] test-pmd: add more packet decode options (verbose)
  2024-03-12 22:01 [PATCH] test-pmd: add more packet decode options (verbose) Stephen Hemminger
  2024-03-13 21:49 ` Stephen Hemminger
@ 2024-07-05  7:13 ` David Marchand
  2024-07-23  2:44 ` [PATCH v2 0/3] Add packet dissector Stephen Hemminger
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 36+ messages in thread
From: David Marchand @ 2024-07-05  7:13 UTC (permalink / raw)
  To: Stephen Hemminger; +Cc: dev, Ori Kam, Aman Singh, Yuying Zhang, Robin Jarry

On Tue, Mar 12, 2024 at 11:01 PM Stephen Hemminger
<stephen@networkplumber.org> wrote:
>
> The existing verbose levels 1..3 provide a messy multi-line
> output per packet. This is unhelpful when diagnosing many
> types of problems like packet flow.
>
> This patch adds two new levels:
> 4: one line per packet is printed in a format resembling
>    tshark output. With addresses and protocol info.
> 5: dump packet in hex.
>    Useful if the driver is messing up the data.
>
> Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>

That's an interesting addition.
I like the tshark like output.
I had to write similar (more limited) parsing for debug.


I know some project decodes packets and it could probably reuse what
DPDK provides.
Could the parsing function you propose go to lib/net/ maybe?
Something like rte_net_format_packet(const struct rte_mbuf *m, char
*buf, size_t size);
It could be worth instructing the parser to decode only a part of the
packet too (via an offset/size and a hint of the layer).


An additional comment: when you have multiple cores processing packets
(even at low rate), getting all those output mixed with no context at
all would be hard to follow.
I would prefer we retain the information of whether the displayed
packet was caught on Rx/Tx and the queue and port on which it
happened.

As an alternative, there is the possibility of only displaying
received or transmitted packets (if verbose is used as a bitmask with
0x1 and 0x2 controlling rx / tx filtering, and the new modes enhancing
output beauty).


-- 
David Marchand


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v2 0/3] Add packet dissector
  2024-03-12 22:01 [PATCH] test-pmd: add more packet decode options (verbose) Stephen Hemminger
  2024-03-13 21:49 ` Stephen Hemminger
  2024-07-05  7:13 ` David Marchand
@ 2024-07-23  2:44 ` Stephen Hemminger
  2024-07-23  2:44   ` [PATCH v2 1/3] net: add new " Stephen Hemminger
                     ` (2 more replies)
  2024-07-23 20:33 ` [PATCH v3 0/3] add packet dissector function Stephen Hemminger
                   ` (4 subsequent siblings)
  7 siblings, 3 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-07-23  2:44 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Take earlier patch which enhanced mbuf printout for test-pmd
and generalize this to be a function in net library.

v2 - splits the patch up and adds standalone functional tests

Stephen Hemminger (3):
  net: add new packet dissector
  test: add test for packet dissector
  test-pmd: add more packet verbose decode options

 app/test-pmd/cmdline_flow.c                 |   3 +-
 app/test-pmd/config.c                       |  33 +-
 app/test-pmd/testpmd.h                      |  11 +
 app/test-pmd/util.c                         |  71 +++-
 app/test/meson.build                        |   1 +
 app/test/test_dissect.c                     | 289 ++++++++++++++++
 doc/guides/testpmd_app_ug/testpmd_funcs.rst |   5 +-
 lib/net/meson.build                         |   2 +
 lib/net/rte_dissect.c                       | 357 ++++++++++++++++++++
 lib/net/rte_dissect.h                       |  40 +++
 lib/net/version.map                         |   7 +
 11 files changed, 801 insertions(+), 18 deletions(-)
 create mode 100644 app/test/test_dissect.c
 create mode 100644 lib/net/rte_dissect.c
 create mode 100644 lib/net/rte_dissect.h

-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v2 1/3] net: add new packet dissector
  2024-07-23  2:44 ` [PATCH v2 0/3] Add packet dissector Stephen Hemminger
@ 2024-07-23  2:44   ` Stephen Hemminger
  2024-07-23  2:44   ` [PATCH v2 2/3] test: add test for " Stephen Hemminger
  2024-07-23  2:44   ` [PATCH v2 3/3] test-pmd: add more packet verbose decode options Stephen Hemminger
  2 siblings, 0 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-07-23  2:44 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

The function rte_dissect_mbuf is used to decode the contents
of an mbuf and print them in human readable format similar
to what tshark uses.

For now, handles IP, IPv6, TCP, UDP, ICMP and ARP.
The intention is to extend this to be able to handle more
nested tunnel types as well as higher level protocols
such as DNS and HTTP.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 lib/net/meson.build   |   2 +
 lib/net/rte_dissect.c | 357 ++++++++++++++++++++++++++++++++++++++++++
 lib/net/rte_dissect.h |  40 +++++
 lib/net/version.map   |   7 +
 4 files changed, 406 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..40376a8842
--- /dev/null
+++ b/lib/net/rte_dissect.c
@@ -0,0 +1,357 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2024 Stephen Hemminger <stephen@networkplumber.org>
+ *
+ * Print packets in format similar to tshark.
+ * Output should be one line per mbuf
+ */
+
+#include <setjmp.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+#include <rte_arp.h>
+#include <rte_branch_prediction.h>
+#include <rte_byteorder.h>
+#include <rte_dissect.h>
+#include <rte_ether.h>
+#include <rte_icmp.h>
+#include <rte_ip.h>
+#include <rte_mbuf.h>
+#include <rte_tcp.h>
+#include <rte_udp.h>
+#include <rte_vxlan.h>
+
+typedef struct dissect_ctx {
+	jmp_buf jmpenv;		/* unwind when dump_len is reached */
+	uint32_t offset;	/* current offset */
+	uint32_t dump_len;	/* maximum depth in packet to look at */
+} dissect_ctx_t;
+
+static void
+dissect_eth(dissect_ctx_t *ctx, FILE *f, const struct rte_mbuf *mb);
+
+/* Read data from segmented mbuf, but stop if would go past max length */
+static const void *
+dissect_read(dissect_ctx_t *ctx, const struct rte_mbuf *m,
+	     void *buf, size_t len)
+{
+	/* If this header would be past the requested length
+	 * then unwind back to end the string.
+	 */
+	if (ctx->dump_len != 0 && ctx->offset + len > ctx->dump_len)
+		longjmp(ctx->jmpenv, 1);
+
+	return rte_pktmbuf_read(m, ctx->offset, len, buf);
+}
+
+static void
+dissect_arp(dissect_ctx_t *ctx, FILE *f, const struct rte_mbuf *mb)
+{
+	const struct rte_arp_hdr *arp;
+	struct rte_arp_hdr _arp;
+	uint16_t ar_op;
+	char buf[128];
+
+	arp = dissect_read(ctx, mb, &_arp, sizeof(_arp));
+	if (unlikely(arp == NULL)) {
+		fprintf(f, "truncated ARP! ");
+		return;
+	}
+
+	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, buf, sizeof(buf));
+		fprintf(f, "Who has %s? ", buf);
+
+		rte_ether_format_addr(buf, sizeof(buf), &arp->arp_data.arp_sha);
+		fprintf(f, "Tell %s ", buf);
+		break;
+	case RTE_ARP_OP_REPLY:
+		inet_ntop(AF_INET, &arp->arp_data.arp_sip, buf, sizeof(buf));
+		fprintf(f, "%s is at", buf);
+
+		rte_ether_format_addr(buf, sizeof(buf), &arp->arp_data.arp_sha);
+		fprintf(f, "%s ", buf);
+		break;
+	case RTE_ARP_OP_INVREQUEST:
+		rte_ether_format_addr(buf, sizeof(buf), &arp->arp_data.arp_tha);
+		fprintf(f, "Who is %s? ", buf);
+
+		rte_ether_format_addr(buf, sizeof(buf), &arp->arp_data.arp_sha);
+		fprintf(f, "Tell %s ", buf);
+		break;
+
+	case RTE_ARP_OP_INVREPLY:
+		rte_ether_format_addr(buf, sizeof(buf), &arp->arp_data.arp_sha);
+		fprintf(f, "%s is at ", buf);
+
+		inet_ntop(AF_INET, &arp->arp_data.arp_sip, buf, sizeof(buf));
+		fprintf(f, "%s ", buf);
+		break;
+	default:
+		fprintf(f, "Unknown ARP %#x ", ar_op);
+		break;
+	}
+}
+
+static void
+dissect_vxlan(dissect_ctx_t *ctx, FILE *f, const struct rte_mbuf *mb)
+{
+	const struct rte_vxlan_hdr *vxlan;
+	struct rte_vxlan_hdr _vxlan;
+
+	vxlan = dissect_read(ctx, mb, &_vxlan, sizeof(_vxlan));
+	fprintf(f, "VXLAN ");
+	if (vxlan->flag_i) {
+		uint32_t vni = rte_be_to_cpu_32(vxlan->vx_vni);
+
+		fprintf(f, "%#x ", vni >> 8);
+	}
+	dissect_eth(ctx, f, mb);
+}
+
+static void
+dissect_udp(dissect_ctx_t *ctx, FILE *f, const struct rte_mbuf *mb)
+{
+	const struct rte_udp_hdr *udph;
+	struct rte_udp_hdr _udp;
+	uint16_t src_port, dst_port;
+
+	udph = dissect_read(ctx, mb, &_udp, sizeof(_udp));
+	if (unlikely(udph == NULL)) {
+		fprintf(f, "truncated UDP! ");
+		return;
+	}
+
+	src_port = rte_be_to_cpu_16(udph->src_port);
+	dst_port = rte_be_to_cpu_16(udph->dst_port);
+
+	switch (dst_port) {
+	case RTE_VXLAN_DEFAULT_PORT:
+		dissect_vxlan(ctx, f, mb);
+		break;
+	default:
+		fprintf(f, "UDP %u %u → %u ", rte_be_to_cpu_16(udph->dgram_len), src_port, dst_port);
+	}
+}
+
+static void
+dissect_tcp(dissect_ctx_t *ctx, FILE *f, const struct rte_mbuf *mb)
+{
+	const struct rte_tcp_hdr *tcph;
+	struct rte_tcp_hdr _tcp;
+	uint16_t src_port, dst_port;
+
+	tcph = dissect_read(ctx, mb, &_tcp, sizeof(_tcp));
+	if (unlikely(tcph == NULL)) {
+		fprintf(f, "truncated TCP! ");
+		return;
+	}
+
+	src_port = rte_be_to_cpu_16(tcph->src_port);
+	dst_port = rte_be_to_cpu_16(tcph->dst_port);
+
+	fprintf(f, "TCP %u → %u",
+		  src_port, dst_port);
+#define PRINT_TCP_FLAG(flag) \
+	if (tcph->tcp_flags & RTE_TCP_ ## flag ## _FLAG) \
+		fprintf(f, " [" #flag" ]")
+
+	PRINT_TCP_FLAG(URG);
+	PRINT_TCP_FLAG(ACK);
+	PRINT_TCP_FLAG(RST);
+	PRINT_TCP_FLAG(SYN);
+	PRINT_TCP_FLAG(FIN);
+#undef PRINT_TCP_FLAG
+
+	fprintf(f, "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(dissect_ctx_t *ctx, FILE *f, const struct rte_mbuf *mb)
+{
+	const struct rte_icmp_hdr *icmp;
+	struct rte_icmp_hdr _icmp;
+	static const char * const icmp_types[256] = {
+		[RTE_IP_ICMP_ECHO_REPLY]   = "ICMP Reply",
+		[RTE_IP_ICMP_ECHO_REQUEST] = "ICMP Request",
+		[RTE_ICMP6_ECHO_REPLY]     = "ICMPv6 Reply",
+		[RTE_ICMP6_ECHO_REQUEST]   = "ICMPv6 Request",
+		[133]                      = "ICMPv6 Router Solicitation",
+		[134]                      = "ICMPv6 Router Solicitation",
+	};
+
+	icmp = dissect_read(ctx, mb, &_icmp, sizeof(_icmp));
+	if (unlikely(icmp == NULL)) {
+		fprintf(f, "truncated ICMP! ");
+	} else {
+		const char *name = icmp_types[icmp->icmp_type];
+
+		if (name != NULL)
+			fprintf(f, "%s ", name);
+		else
+			fprintf(f, "ICMP type %u ", icmp->icmp_type);
+	}
+}
+
+static void
+dissect_ipv4(dissect_ctx_t *ctx, FILE *f, const struct rte_mbuf *mb)
+{
+	const struct rte_ipv4_hdr *ip_hdr;
+	struct rte_ipv4_hdr _ip_hdr;
+	char sbuf[INET_ADDRSTRLEN], dbuf[INET_ADDRSTRLEN];
+
+	ip_hdr = dissect_read(ctx, mb, &_ip_hdr, sizeof(_ip_hdr));
+	if (unlikely(ip_hdr == NULL)) {
+		fprintf(f, "truncated IP! ");
+		return;
+	}
+
+	inet_ntop(AF_INET, &ip_hdr->src_addr, sbuf, sizeof(sbuf));
+	inet_ntop(AF_INET, &ip_hdr->dst_addr, dbuf, sizeof(dbuf));
+	fprintf(f, "%s → %s ", sbuf, dbuf);
+
+	ctx->offset += ip_hdr->ihl * 4;
+	switch (ip_hdr->next_proto_id) {
+	case IPPROTO_UDP:
+		return dissect_udp(ctx, f, mb);
+	case IPPROTO_TCP:
+		return dissect_tcp(ctx, f, mb);
+	case IPPROTO_ICMP:
+		return dissect_icmp(ctx, f, mb);
+	default:
+		/* TODO dissect tunnels */
+		fprintf(f, "IP proto %#x ", ip_hdr->next_proto_id);
+	}
+}
+
+static void
+dissect_ipv6(dissect_ctx_t *ctx, FILE *f, const struct rte_mbuf *mb)
+{
+	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(ctx, mb, &_ip6_hdr, sizeof(_ip6_hdr));
+	if (unlikely(ip6_hdr == NULL)) {
+		fprintf(f, "truncated IPv6! ");
+		return;
+	}
+	ctx->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));
+	fprintf(f, "%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:
+			return dissect_udp(ctx, f, mb);
+		case IPPROTO_TCP:
+			return dissect_tcp(ctx, f, mb);
+		case IPPROTO_ICMPV6:
+			return dissect_icmp(ctx, f, mb);
+
+		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(ctx, mb, &_xh, sizeof(xh));
+			if (unlikely(xh == NULL)) {
+				fprintf(f, "truncated IPV6 option! ");
+				return;
+			}
+			ctx->offset += (xh->hdr_len + 1) * 8;
+			proto = xh->next_hdr;
+			continue;
+		}
+
+		case IPPROTO_FRAGMENT:
+			fprintf(f, "FRAG ");
+			return;
+
+		case IPPROTO_NONE:
+			fprintf(f, "NONE ");
+			return;
+
+		default:
+			fprintf(f, "IPv6 proto %u ", proto);
+			return;
+		}
+	}
+
+	fprintf(f, "Too many extensions!");
+}
+
+static void
+dissect_eth(dissect_ctx_t *ctx, FILE *f, const struct rte_mbuf *mb)
+{
+	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(ctx, mb, &_eth_hdr, sizeof(_eth_hdr));
+	if (unlikely(eth_hdr == NULL)) {
+		fprintf(f, "missing Eth header!");
+		return;
+	}
+
+	ctx->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;
+		ctx->offset += sizeof(*vh);
+
+		fprintf(f, "%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), &eth_hdr->src_addr);
+		rte_ether_format_addr(sbuf, sizeof(dbuf), &eth_hdr->dst_addr);
+		fprintf(f, "%s → %s ARP ", sbuf, dbuf);
+
+		dissect_arp(ctx, f, mb);
+		break;
+	case RTE_ETHER_TYPE_IPV4:
+		dissect_ipv4(ctx, f, mb);
+		break;
+
+	case RTE_ETHER_TYPE_IPV6:
+		dissect_ipv6(ctx, f, mb);
+		break;
+	default:
+		fprintf(f, "Ethernet proto %#x ", eth_type);
+	}
+}
+
+void
+rte_dissect_mbuf(FILE *f, const struct rte_mbuf *m, uint32_t dump_len)
+{
+	dissect_ctx_t ctx = {
+		.dump_len = dump_len,
+	};
+
+	if (setjmp(ctx.jmpenv) == 0)
+		dissect_eth(&ctx, f, m);
+
+	putc('\n', f);
+}
diff --git a/lib/net/rte_dissect.h b/lib/net/rte_dissect.h
new file mode 100644
index 0000000000..de14eeeb02
--- /dev/null
+++ b/lib/net/rte_dissect.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2024 Stephen Hemminger <stephen@networkplumber.org>
+ */
+
+#ifndef _RTE_NET_DISSECT_H_
+#define _RTE_NET_DISSECT_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <stdint.h>
+
+#include <rte_compat.h>
+
+struct rte_mbuf;
+
+/**
+ *
+ * Print packets in format (single line) similar to tshark to a file.
+ *
+ * @param f
+ *   A pointer to a file for output
+ * @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(FILE *f, 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;
+};
-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v2 2/3] test: add test for packet dissector
  2024-07-23  2:44 ` [PATCH v2 0/3] Add packet dissector Stephen Hemminger
  2024-07-23  2:44   ` [PATCH v2 1/3] net: add new " Stephen Hemminger
@ 2024-07-23  2:44   ` Stephen Hemminger
  2024-07-23  2:44   ` [PATCH v2 3/3] test-pmd: add more packet verbose decode options Stephen Hemminger
  2 siblings, 0 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-07-23  2:44 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Some tests for new packet dissector.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 app/test/meson.build    |   1 +
 app/test/test_dissect.c | 289 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 290 insertions(+)
 create mode 100644 app/test/test_dissect.c

diff --git a/app/test/meson.build b/app/test/meson.build
index e29258e6ec..9cd2051320 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -62,6 +62,7 @@ source_file_deps = {
     'test_debug.c': [],
     'test_devargs.c': ['kvargs'],
     'test_dispatcher.c': ['dispatcher'],
+    'test_dissect.c': ['net'],
     'test_distributor.c': ['distributor'],
     'test_distributor_perf.c': ['distributor'],
     'test_dmadev.c': ['dmadev', 'bus_vdev'],
diff --git a/app/test/test_dissect.c b/app/test/test_dissect.c
new file mode 100644
index 0000000000..a5e5c310aa
--- /dev/null
+++ b/app/test/test_dissect.c
@@ -0,0 +1,289 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2024 Stephen Hemminger <stephen@networkplumber.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include <rte_bus_vdev.h>
+#include <rte_dissect.h>
+#include <rte_ethdev.h>
+#include <rte_ether.h>
+#include <rte_ip.h>
+#include <rte_mbuf.h>
+#include <rte_net.h>
+#include <rte_random.h>
+#include <rte_udp.h>
+#include <rte_vxlan.h>
+
+#include "test.h"
+
+#define TOTAL_PACKETS	100
+#define PACKET_LEN	1000
+#define ETH_IP_UDP_VXLAN_SIZE (sizeof(struct rte_ether_hdr) + \
+			       sizeof(struct rte_ipv4_hdr) + \
+			       sizeof(struct rte_udp_hdr) + \
+			       sizeof(struct rte_vxlan_hdr))
+
+
+static uint16_t port_id;
+static const char null_dev[] = "net_null0";
+
+static void
+add_header(struct rte_mbuf *mb, uint32_t plen,
+	   rte_be16_t src_port, rte_be16_t dst_port)
+{
+	struct {
+		struct rte_ether_hdr eth;
+		struct rte_ipv4_hdr ip;
+		struct rte_udp_hdr udp;
+	} pkt = {
+		.eth = {
+			.dst_addr.addr_bytes = "\xff\xff\xff\xff\xff\xff",
+			.ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4),
+		},
+		.ip = {
+			.version_ihl = RTE_IPV4_VHL_DEF,
+			.time_to_live = 1,
+			.next_proto_id = IPPROTO_UDP,
+			.src_addr = rte_cpu_to_be_32(RTE_IPV4_LOOPBACK),
+			.dst_addr = rte_cpu_to_be_32(RTE_IPV4_BROADCAST),
+		},
+		.udp = {
+			.dst_port = dst_port,
+			.src_port = src_port,
+		},
+	};
+
+	rte_eth_random_addr(pkt.eth.src_addr.addr_bytes);
+
+	plen -= sizeof(struct rte_ether_hdr);
+	pkt.ip.total_length = rte_cpu_to_be_16(plen);
+	pkt.ip.hdr_checksum = rte_ipv4_cksum(&pkt.ip);
+
+	plen -= sizeof(struct rte_ipv4_hdr);
+	pkt.udp.src_port = rte_rand();
+	pkt.udp.dgram_len = rte_cpu_to_be_16(plen);
+
+	/* Copy header into mbuf */
+	memcpy(rte_pktmbuf_append(mb, sizeof(pkt)), &pkt, sizeof(pkt));
+}
+
+static void
+add_vxlan(struct rte_mbuf *mb, rte_be32_t vni)
+{
+	struct rte_vxlan_hdr *vxlan;
+
+	vxlan = (struct rte_vxlan_hdr *)rte_pktmbuf_append(mb, sizeof(*vxlan));
+	memset(vxlan, 0, sizeof(*vxlan));
+	vxlan->flag_i = 1;
+	vxlan->vx_vni = vni;
+}
+
+
+static void
+fill_data(struct rte_mbuf *mb, uint32_t len)
+{
+	uint32_t i;
+	char *ptr = rte_pktmbuf_append(mb, len);
+	char c = '!';
+
+	/* traditional barber pole pattern */
+	for (i = 0; i < len; i++) {
+		ptr[i] = c++;
+		if (c == 0x7f) c = '!';
+	}
+}
+
+static void
+mbuf_prep(struct rte_mbuf *mb, uint8_t buf[], uint32_t buf_len)
+{
+	mb->buf_addr = buf;
+	rte_mbuf_iova_set(mb, (uintptr_t)buf);
+	mb->buf_len = buf_len;
+	rte_mbuf_refcnt_set(mb, 1);
+
+	/* set pool pointer to dummy value, test doesn't use it */
+	mb->pool = (void *)buf;
+
+	rte_pktmbuf_reset(mb);
+}
+
+static int
+test_setup(void)
+{
+	port_id = rte_eth_dev_count_avail();
+
+	/* Make a dummy null device to snoop on */
+	if (rte_vdev_init(null_dev, NULL) != 0) {
+		fprintf(stderr, "Failed to create vdev '%s'\n", null_dev);
+		goto fail;
+	}
+	return 0;
+
+fail:
+	rte_vdev_uninit(null_dev);
+	return -1;
+}
+
+static void
+test_cleanup(void)
+{
+	rte_vdev_uninit(null_dev);
+}
+
+static int
+count_lines(FILE *f)
+{
+	char line[LINE_MAX];
+	int count = 0;
+
+	while (fgets(line, sizeof(line), f) != NULL)
+		++count;
+
+	return count;
+}
+
+static int
+test_simple(void)
+{
+	struct rte_mbuf mb;
+	uint8_t buf[RTE_MBUF_DEFAULT_BUF_SIZE];
+	uint32_t data_len = PACKET_LEN;
+	int lines;
+	FILE *tmp;
+	uint16_t src_port = rte_rand();
+	const uint16_t dst_port = rte_cpu_to_be_16(9); /* Discard port */
+
+	tmp = tmpfile();
+	TEST_ASSERT(tmp != NULL, "Tmpfile() failed!");
+
+	/* make a dummy packet */
+	mbuf_prep(&mb, buf, sizeof(buf));
+	add_header(&mb, data_len, src_port, dst_port);
+	fill_data(&mb, data_len - mb.data_off);
+
+	for (unsigned int i = 0; i < TOTAL_PACKETS; i++)
+		rte_dissect_mbuf(tmp, &mb, 0);
+
+	rewind(tmp);
+	lines = count_lines(tmp);
+
+	TEST_ASSERT_EQUAL(lines, TOTAL_PACKETS,
+			  "Expected %d lines but got %d",
+			  TOTAL_PACKETS, lines);
+
+	fclose(tmp);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_truncated(void)
+{
+	struct rte_mbuf mb;
+	uint8_t buf[RTE_MBUF_DEFAULT_BUF_SIZE];
+	uint32_t pkt_len, data_len = PACKET_LEN;
+	uint16_t dst_port = rte_cpu_to_be_16(RTE_VXLAN_DEFAULT_PORT);
+	int lines;
+	FILE *tmp;
+
+	tmp = tmpfile();
+	TEST_ASSERT(tmp != NULL, "Tmpfile() failed!");
+
+	/* make a vxlan packet */
+	mbuf_prep(&mb, buf, sizeof(buf));
+	pkt_len = data_len;
+	do {
+		uint16_t src_port = rte_rand();
+		uint32_t vni = rte_rand_max(1ul << 24);
+
+		add_header(&mb, data_len, src_port, dst_port);
+		add_vxlan(&mb, vni);
+		pkt_len -= ETH_IP_UDP_VXLAN_SIZE;
+	} while (pkt_len > ETH_IP_UDP_VXLAN_SIZE);
+
+	fill_data(&mb, pkt_len);
+
+	for (unsigned int i = 0; i < TOTAL_PACKETS; i++) {
+		uint32_t snaplen = rte_rand_max(pkt_len);
+
+		rte_dissect_mbuf(tmp, &mb, snaplen);
+	}
+
+	rewind(tmp);
+	lines = count_lines(tmp);
+
+	TEST_ASSERT_EQUAL(lines, TOTAL_PACKETS,
+			  "Expected %d lines but got %d",
+			  TOTAL_PACKETS, lines);
+
+	fclose(tmp);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_fuzz(void)
+{
+	struct rte_mbuf mb;
+	uint8_t buf[RTE_MBUF_DEFAULT_BUF_SIZE];
+	uint32_t data_len = PACKET_LEN;
+	const uint16_t dst_port = rte_cpu_to_be_16(rte_rand_max(1024));
+	const uint16_t src_port = rte_cpu_to_be_16(rte_rand());
+	int lines;
+	FILE *tmp;
+
+	tmp = tmpfile();
+	TEST_ASSERT(tmp != NULL, "Tmpfile() failed!");
+
+	/* make a dummy packet */
+	mbuf_prep(&mb, buf, sizeof(buf));
+	add_header(&mb, data_len, src_port, dst_port);
+	fill_data(&mb, data_len - mb.data_off);
+
+	for (unsigned int i = 0; i < TOTAL_PACKETS; i++) {
+		uint32_t bit = rte_rand_max(data_len) * 8;
+		uint8_t *bp = buf + bit / 8;
+		uint8_t mask = 1u << (bit % 8);
+
+		/* twiddle one bit */
+		*bp ^= mask;
+		rte_dissect_mbuf(tmp, &mb, 0);
+		*bp ^= mask;
+	}
+
+	rewind(tmp);
+	lines = count_lines(tmp);
+
+	TEST_ASSERT_EQUAL(lines, TOTAL_PACKETS,
+			  "Expected %d lines but got %d",
+			  TOTAL_PACKETS, lines);
+
+	fclose(tmp);
+
+	return TEST_SUCCESS;
+}
+
+static struct
+unit_test_suite test_dissect_suite  = {
+	.setup = test_setup,
+	.teardown = test_cleanup,
+	.suite_name = "Test Dissect Unit Test Suite",
+	.unit_test_cases = {
+		TEST_CASE(test_simple),
+		TEST_CASE(test_truncated),
+		TEST_CASE(test_fuzz),
+		TEST_CASES_END()
+	}
+};
+
+static int
+test_dissect(void)
+{
+	return unit_test_suite_runner(&test_dissect_suite);
+}
+
+REGISTER_FAST_TEST(dissect_autotest, true, true, test_dissect);
-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v2 3/3] test-pmd: add more packet verbose decode options
  2024-07-23  2:44 ` [PATCH v2 0/3] Add packet dissector Stephen Hemminger
  2024-07-23  2:44   ` [PATCH v2 1/3] net: add new " Stephen Hemminger
  2024-07-23  2:44   ` [PATCH v2 2/3] test: add test for " Stephen Hemminger
@ 2024-07-23  2:44   ` Stephen Hemminger
  2 siblings, 0 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-07-23  2:44 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Ori Kam, Aman Singh

The existing verbose levels 1..3 provide a messy multi-line
output per packet. I found this unhelpful when diagnosing many
types of problems like packet flow.

This patch keeps the previous levels and adds two new levels:
4: one line per packet is printed in a format resembling
   tshark output. With addresses and protocol info.

5: dump packet in hex.
   Useful if the driver is messing up the data.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 app/test-pmd/cmdline_flow.c                 |  3 +-
 app/test-pmd/config.c                       | 33 ++++++----
 app/test-pmd/testpmd.h                      | 11 ++++
 app/test-pmd/util.c                         | 71 +++++++++++++++++++--
 doc/guides/testpmd_app_ug/testpmd_funcs.rst |  5 +-
 5 files changed, 105 insertions(+), 18 deletions(-)

diff --git a/app/test-pmd/cmdline_flow.c b/app/test-pmd/cmdline_flow.c
index a76b44bf39..f553828755 100644
--- a/app/test-pmd/cmdline_flow.c
+++ b/app/test-pmd/cmdline_flow.c
@@ -14076,7 +14076,8 @@ cmd_set_raw_parsed(const struct buffer *in)
 			upper_layer = proto;
 		}
 	}
-	if (verbose_level & 0x1)
+
+	if (verbose_level > 0)
 		printf("total data size is %zu\n", (*total_size));
 	RTE_ASSERT((*total_size) <= ACTION_RAW_ENCAP_MAX_DATA);
 	memmove(data, (data_tail - (*total_size)), *total_size);
diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c
index 66c3a68c1d..5f01e70875 100644
--- a/app/test-pmd/config.c
+++ b/app/test-pmd/config.c
@@ -6246,26 +6246,37 @@ configure_rxtx_dump_callbacks(uint16_t verbose)
 		return;
 #endif
 
-	RTE_ETH_FOREACH_DEV(portid)
-	{
-		if (verbose == 1 || verbose > 2)
+	RTE_ETH_FOREACH_DEV(portid) {
+		switch (verbose) {
+		case VERBOSE_OFF:
+			remove_rx_dump_callbacks(portid);
+			remove_tx_dump_callbacks(portid);
+			break;
+		case VERBOSE_RX:
 			add_rx_dump_callbacks(portid);
-		else
+			remove_tx_dump_callbacks(portid);
+			break;
+		case VERBOSE_TX:
+			add_tx_dump_callbacks(portid);
 			remove_rx_dump_callbacks(portid);
-		if (verbose >= 2)
+			break;
+		default:
+			add_rx_dump_callbacks(portid);
 			add_tx_dump_callbacks(portid);
-		else
-			remove_tx_dump_callbacks(portid);
+		}
 	}
 }
 
 void
 set_verbose_level(uint16_t vb_level)
 {
-	printf("Change verbose level from %u to %u\n",
-	       (unsigned int) verbose_level, (unsigned int) vb_level);
-	verbose_level = vb_level;
-	configure_rxtx_dump_callbacks(verbose_level);
+	if (vb_level < VERBOSE_MAX) {
+		printf("Change verbose level from %u to %u\n", verbose_level, vb_level);
+		verbose_level = vb_level;
+		configure_rxtx_dump_callbacks(verbose_level);
+	} else {
+		fprintf(stderr, "Verbose level %u is out of range\n", vb_level);
+	}
 }
 
 void
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
index 9facd7f281..3d7a2b6dac 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -489,6 +489,17 @@ enum dcb_mode_enable
 
 extern uint8_t xstats_hide_zero; /**< Hide zero values for xstats display */
 
+enum verbose_mode {
+	VERBOSE_OFF = 0,
+	VERBOSE_RX,
+	VERBOSE_TX,
+	VERBOSE_BOTH,
+	VERBOSE_DISSECT,
+	VERBOSE_HEX,
+	VERBOSE_MAX
+};
+
+
 /* globals used for configuration */
 extern uint8_t record_core_cycles; /**< Enables measurement of CPU cycles */
 extern uint8_t record_burst_stats; /**< Enables display of RX and TX bursts */
diff --git a/app/test-pmd/util.c b/app/test-pmd/util.c
index bf9b639d95..209c46b10d 100644
--- a/app/test-pmd/util.c
+++ b/app/test-pmd/util.c
@@ -8,6 +8,7 @@
 #include <rte_bitops.h>
 #include <rte_net.h>
 #include <rte_mbuf.h>
+#include <rte_dissect.h>
 #include <rte_ether.h>
 #include <rte_vxlan.h>
 #include <rte_ethdev.h>
@@ -16,6 +17,7 @@
 #include "testpmd.h"
 
 #define MAX_STRING_LEN 8192
+#define MAX_DUMP_LEN   1024
 
 #define MKDUMPSTR(buf, buf_size, cur_len, ...) \
 do { \
@@ -67,9 +69,10 @@ get_timestamp(const struct rte_mbuf *mbuf)
 			timestamp_dynfield_offset, rte_mbuf_timestamp_t *);
 }
 
-static inline void
-dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
-	      uint16_t nb_pkts, int is_rx)
+/* More verbose older style packet decode */
+static void
+dump_pkt_verbose(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
+		 uint16_t nb_pkts, int is_rx)
 {
 	struct rte_mbuf  *mb;
 	const struct rte_ether_hdr *eth_hdr;
@@ -90,8 +93,6 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
 	size_t cur_len = 0;
 	uint64_t restore_info_dynflag;
 
-	if (!nb_pkts)
-		return;
 	restore_info_dynflag = rte_flow_restore_info_dynflag();
 	MKDUMPSTR(print_buf, buf_size, cur_len,
 		  "port %u/queue %u: %s %u packets\n", port_id, queue,
@@ -299,6 +300,66 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
 	}
 }
 
+/* Brief tshark style one line output which is
+ * number time_delta Source Destination Protocol len info
+ */
+static void
+dump_pkt_brief(uint16_t queue, struct rte_mbuf *pkts[], uint16_t nb_pkts)
+{
+	static uint64_t start_cycles;
+	static uint64_t packet_count;
+	uint64_t now;
+	uint64_t count;
+	double interval;
+	uint16_t i;
+
+	now = rte_rdtsc();
+	if (start_cycles == 0)
+		start_cycles = now;
+	interval = (double)(now - start_cycles) / (double)rte_get_tsc_hz();
+
+	count = __atomic_fetch_add(&packet_count, nb_pkts, __ATOMIC_RELAXED);
+
+	for (i = 0; i < nb_pkts; i++) {
+		const struct rte_mbuf *mb = pkts[i];
+
+		printf("%6"PRIu64" %11.9f %4u:%-3u ", count + i, interval, mb->port, queue);
+		rte_dissect_mbuf(stdout, mb, 0);
+	}
+}
+
+/* Hex dump of packet data */
+static void
+dump_pkt_hex(struct rte_mbuf *pkts[], uint16_t nb_pkts)
+{
+	uint16_t i;
+
+	for (i = 0; i < nb_pkts; i++)
+		rte_pktmbuf_dump(stdout, pkts[i], MAX_DUMP_LEN);
+
+}
+
+static uint16_t
+dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
+	      uint16_t nb_pkts, int is_rx)
+{
+	if (unlikely(nb_pkts == 0))
+		return 0;
+
+	switch (verbose_level) {
+	case VERBOSE_RX ... VERBOSE_BOTH:
+		dump_pkt_verbose(port_id, queue, pkts, nb_pkts, is_rx);
+		break;
+	case VERBOSE_DISSECT:
+		dump_pkt_brief(queue, pkts, nb_pkts);
+		break;
+	case VERBOSE_HEX:
+		dump_pkt_hex(pkts, nb_pkts);
+	}
+	fflush(stdout);
+	return nb_pkts;
+}
+
 uint16_t
 dump_rx_pkts(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
 	     uint16_t nb_pkts, __rte_unused uint16_t max_pkts,
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
index c19b4f8958..688806754a 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -676,7 +676,10 @@ Available levels are as following:
 * ``0`` silent except for error.
 * ``1`` fully verbose except for Tx packets.
 * ``2`` fully verbose except for Rx packets.
-* ``> 2`` fully verbose.
+* ``3`` fully verbose except for Tx and Rx packets.
+* ``4`` dissected protocol information for Tx and Rx packets.
+* ``5`` hex dump of packets
+
 
 set log
 ~~~~~~~
-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v3 0/3] add packet dissector function
  2024-03-12 22:01 [PATCH] test-pmd: add more packet decode options (verbose) Stephen Hemminger
                   ` (2 preceding siblings ...)
  2024-07-23  2:44 ` [PATCH v2 0/3] Add packet dissector Stephen Hemminger
@ 2024-07-23 20:33 ` Stephen Hemminger
  2024-07-23 20:33   ` [PATCH v3 1/3] net: add new packet dissector Stephen Hemminger
                     ` (2 more replies)
  2024-07-24 18:46 ` [PATCH v4 0/3] Add packet dissector Stephen Hemminger
                   ` (3 subsequent siblings)
  7 siblings, 3 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-07-23 20:33 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

While debugging TAP rte_flow discovered that test pmd verbose output
was confusing and unhelpful. Instead, made a simple dissector that
prints one line per packet like this in test-pmd with verbose level 4

Seq#   Time        Port:Que Description
     1 0.000000000    0:0   :: → ff02::16 ICMP 143
     2 0.000000000    0:0   :: → ff02::1:ff98:2b46 ICMP 135
     
    36 0.512762002    0:0   fe80::285f:1fff:fe98:2b46 → ff02::fb UDP 120 5353 → 5353
    37 0.512779925    0:0   fe80::285f:1fff:fe98:2b46 → ff02::fb UDP 120 5353 → 5353


v3 - use a string buffer instead of use stdio stream
     because that can get garbled when dealing with multiple threads.
   - don't use longjmp unwind, as it can lead to bugs later.

Stephen Hemminger (3):
  net: add new packet dissector
  test: add test for packet dissector
  test-pmd: add more packet verbose decode options

 app/test-pmd/cmdline_flow.c                 |   3 +-
 app/test-pmd/config.c                       |  33 +-
 app/test-pmd/testpmd.h                      |  11 +
 app/test-pmd/util.c                         |  77 +++-
 app/test/meson.build                        |   1 +
 app/test/test_dissect.c                     | 241 ++++++++++++
 doc/guides/testpmd_app_ug/testpmd_funcs.rst |   5 +-
 lib/net/meson.build                         |   2 +
 lib/net/rte_dissect.c                       | 407 ++++++++++++++++++++
 lib/net/rte_dissect.h                       |  42 ++
 lib/net/version.map                         |   7 +
 11 files changed, 811 insertions(+), 18 deletions(-)
 create mode 100644 app/test/test_dissect.c
 create mode 100644 lib/net/rte_dissect.c
 create mode 100644 lib/net/rte_dissect.h

-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v3 1/3] net: add new packet dissector
  2024-07-23 20:33 ` [PATCH v3 0/3] add packet dissector function Stephen Hemminger
@ 2024-07-23 20:33   ` Stephen Hemminger
  2024-07-23 20:33   ` [PATCH v3 2/3] test: add test for " Stephen Hemminger
  2024-07-23 20:33   ` [PATCH v3 3/3] test-pmd: add more packet verbose decode options Stephen Hemminger
  2 siblings, 0 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-07-23 20:33 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

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 <stephen@networkplumber.org>
---
 lib/net/meson.build   |   2 +
 lib/net/rte_dissect.c | 407 ++++++++++++++++++++++++++++++++++++++++++
 lib/net/rte_dissect.h |  42 +++++
 lib/net/version.map   |   7 +
 4 files changed, 458 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..714fdc105c
--- /dev/null
+++ b/lib/net/rte_dissect.c
@@ -0,0 +1,407 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2024 Stephen Hemminger <stephen@networkplumber.org>
+ *
+ * Print packets in format similar to tshark.
+ * Output should be one line per mbuf
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+#include <rte_arp.h>
+#include <rte_branch_prediction.h>
+#include <rte_byteorder.h>
+#include <rte_common.h>
+#include <rte_dissect.h>
+#include <rte_ether.h>
+#include <rte_icmp.h>
+#include <rte_ip.h>
+#include <rte_mbuf.h>
+#include <rte_tcp.h>
+#include <rte_udp.h>
+#include <rte_vxlan.h>
+
+/* 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 Reply",
+		[RTE_IP_ICMP_ECHO_REQUEST] = "ICMP Request",
+		[RTE_ICMP6_ECHO_REPLY]     = "ICMPv6 Reply",
+		[RTE_ICMP6_ECHO_REQUEST]   = "ICMPv6 Request",
+		[133]                      = "ICMPv6 Router Solicitation",
+		[134]                      = "ICMPv6 Router Solicitation",
+	};
+
+	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), &eth_hdr->src_addr);
+		rte_ether_format_addr(sbuf, sizeof(dbuf), &eth_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 <stephen@networkplumber.org>
+ */
+
+#ifndef _RTE_NET_DISSECT_H_
+#define _RTE_NET_DISSECT_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <stdint.h>
+
+#include <rte_compat.h>
+
+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;
+};
-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v3 2/3] test: add test for packet dissector
  2024-07-23 20:33 ` [PATCH v3 0/3] add packet dissector function Stephen Hemminger
  2024-07-23 20:33   ` [PATCH v3 1/3] net: add new packet dissector Stephen Hemminger
@ 2024-07-23 20:33   ` Stephen Hemminger
  2024-07-23 20:33   ` [PATCH v3 3/3] test-pmd: add more packet verbose decode options Stephen Hemminger
  2 siblings, 0 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-07-23 20:33 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Some tests for new packet dissector.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 app/test/meson.build    |   1 +
 app/test/test_dissect.c | 241 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 242 insertions(+)
 create mode 100644 app/test/test_dissect.c

diff --git a/app/test/meson.build b/app/test/meson.build
index e29258e6ec..9cd2051320 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -62,6 +62,7 @@ source_file_deps = {
     'test_debug.c': [],
     'test_devargs.c': ['kvargs'],
     'test_dispatcher.c': ['dispatcher'],
+    'test_dissect.c': ['net'],
     'test_distributor.c': ['distributor'],
     'test_distributor_perf.c': ['distributor'],
     'test_dmadev.c': ['dmadev', 'bus_vdev'],
diff --git a/app/test/test_dissect.c b/app/test/test_dissect.c
new file mode 100644
index 0000000000..ae9d90db22
--- /dev/null
+++ b/app/test/test_dissect.c
@@ -0,0 +1,241 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2024 Stephen Hemminger <stephen@networkplumber.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include <rte_bus_vdev.h>
+#include <rte_dissect.h>
+#include <rte_ethdev.h>
+#include <rte_ether.h>
+#include <rte_ip.h>
+#include <rte_mbuf.h>
+#include <rte_net.h>
+#include <rte_random.h>
+#include <rte_udp.h>
+#include <rte_vxlan.h>
+
+#include "test.h"
+
+#define TOTAL_PACKETS	100
+#define PACKET_LEN	1000
+#define ETH_IP_UDP_VXLAN_SIZE (sizeof(struct rte_ether_hdr) + \
+			       sizeof(struct rte_ipv4_hdr) + \
+			       sizeof(struct rte_udp_hdr) + \
+			       sizeof(struct rte_vxlan_hdr))
+
+
+static uint16_t port_id;
+static const char null_dev[] = "net_null0";
+
+static void
+add_header(struct rte_mbuf *mb, uint32_t plen,
+	   rte_be16_t src_port, rte_be16_t dst_port)
+{
+	struct {
+		struct rte_ether_hdr eth;
+		struct rte_ipv4_hdr ip;
+		struct rte_udp_hdr udp;
+	} pkt = {
+		.eth = {
+			.dst_addr.addr_bytes = "\xff\xff\xff\xff\xff\xff",
+			.ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4),
+		},
+		.ip = {
+			.version_ihl = RTE_IPV4_VHL_DEF,
+			.time_to_live = 1,
+			.next_proto_id = IPPROTO_UDP,
+			.src_addr = rte_cpu_to_be_32(RTE_IPV4_LOOPBACK),
+			.dst_addr = rte_cpu_to_be_32(RTE_IPV4_BROADCAST),
+		},
+		.udp = {
+			.dst_port = dst_port,
+			.src_port = src_port,
+		},
+	};
+
+	rte_eth_random_addr(pkt.eth.src_addr.addr_bytes);
+
+	plen -= sizeof(struct rte_ether_hdr);
+	pkt.ip.total_length = rte_cpu_to_be_16(plen);
+	pkt.ip.hdr_checksum = rte_ipv4_cksum(&pkt.ip);
+
+	plen -= sizeof(struct rte_ipv4_hdr);
+	pkt.udp.src_port = rte_rand();
+	pkt.udp.dgram_len = rte_cpu_to_be_16(plen);
+
+	/* Copy header into mbuf */
+	memcpy(rte_pktmbuf_append(mb, sizeof(pkt)), &pkt, sizeof(pkt));
+}
+
+static void
+add_vxlan(struct rte_mbuf *mb, rte_be32_t vni)
+{
+	struct rte_vxlan_hdr *vxlan;
+
+	vxlan = (struct rte_vxlan_hdr *)rte_pktmbuf_append(mb, sizeof(*vxlan));
+	memset(vxlan, 0, sizeof(*vxlan));
+	vxlan->flag_i = 1;
+	vxlan->vx_vni = vni;
+}
+
+
+static void
+fill_data(struct rte_mbuf *mb, uint32_t len)
+{
+	uint32_t i;
+	char *ptr = rte_pktmbuf_append(mb, len);
+	char c = '!';
+
+	/* traditional barber pole pattern */
+	for (i = 0; i < len; i++) {
+		ptr[i] = c++;
+		if (c == 0x7f)
+			c = '!';
+	}
+}
+
+static void
+mbuf_prep(struct rte_mbuf *mb, uint8_t buf[], uint32_t buf_len)
+{
+	mb->buf_addr = buf;
+	rte_mbuf_iova_set(mb, (uintptr_t)buf);
+	mb->buf_len = buf_len;
+	rte_mbuf_refcnt_set(mb, 1);
+
+	/* set pool pointer to dummy value, test doesn't use it */
+	mb->pool = (void *)buf;
+
+	rte_pktmbuf_reset(mb);
+}
+
+static int
+test_setup(void)
+{
+	port_id = rte_eth_dev_count_avail();
+
+	/* Make a dummy null device to snoop on */
+	if (rte_vdev_init(null_dev, NULL) != 0) {
+		fprintf(stderr, "Failed to create vdev '%s'\n", null_dev);
+		goto fail;
+	}
+	return 0;
+
+fail:
+	rte_vdev_uninit(null_dev);
+	return -1;
+}
+
+static void
+test_cleanup(void)
+{
+	rte_vdev_uninit(null_dev);
+}
+
+
+static int
+test_simple(void)
+{
+	struct rte_mbuf mb;
+	uint8_t buf[RTE_MBUF_DEFAULT_BUF_SIZE];
+	uint32_t data_len = PACKET_LEN;
+	uint16_t src_port = rte_rand();
+	const uint16_t dst_port = rte_cpu_to_be_16(9); /* Discard port */
+	char obuf[LINE_MAX];
+
+	/* make a dummy packet */
+	mbuf_prep(&mb, buf, sizeof(buf));
+	add_header(&mb, data_len, src_port, dst_port);
+	fill_data(&mb, data_len - mb.data_off);
+
+	rte_dissect_mbuf(obuf, sizeof(obuf), &mb, 0);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_truncated(void)
+{
+	struct rte_mbuf mb;
+	uint8_t buf[RTE_MBUF_DEFAULT_BUF_SIZE];
+	uint32_t pkt_len, data_len = PACKET_LEN;
+	uint16_t dst_port = rte_cpu_to_be_16(RTE_VXLAN_DEFAULT_PORT);
+	char obuf[LINE_MAX];
+
+	/* make a really nested vxlan packet */
+	mbuf_prep(&mb, buf, sizeof(buf));
+	pkt_len = data_len;
+	do {
+		uint16_t src_port = rte_rand();
+		uint32_t vni = rte_rand_max(1ul << 24);
+
+		add_header(&mb, data_len, src_port, dst_port);
+		add_vxlan(&mb, vni);
+		pkt_len -= ETH_IP_UDP_VXLAN_SIZE;
+	} while (pkt_len > ETH_IP_UDP_VXLAN_SIZE);
+
+	fill_data(&mb, pkt_len);
+
+	/* dissect it but snip off some amount of data */
+	for (unsigned int i = 0; i < TOTAL_PACKETS; i++) {
+		uint32_t snaplen = rte_rand_max(pkt_len);
+
+		rte_dissect_mbuf(obuf, sizeof(obuf), &mb, snaplen);
+	}
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_fuzz(void)
+{
+	struct rte_mbuf mb;
+	uint8_t buf[RTE_MBUF_DEFAULT_BUF_SIZE];
+	uint32_t data_len = PACKET_LEN;
+	const uint16_t dst_port = rte_cpu_to_be_16(rte_rand_max(1024));
+	const uint16_t src_port = rte_cpu_to_be_16(rte_rand());
+	char obuf[LINE_MAX];
+
+	/* make a dummy packet */
+	mbuf_prep(&mb, buf, sizeof(buf));
+	add_header(&mb, data_len, src_port, dst_port);
+	fill_data(&mb, data_len - mb.data_off);
+
+	/* randomly flip bits in it */
+	for (unsigned int i = 0; i < TOTAL_PACKETS; i++) {
+		uint32_t bit = rte_rand_max(data_len) * 8;
+		uint8_t *bp = buf + bit / 8;
+		uint8_t mask = 1u << (bit % 8);
+
+		/* twiddle one bit */
+		*bp ^= mask;
+		rte_dissect_mbuf(obuf, sizeof(obuf), &mb, 0);
+		*bp ^= mask;
+	}
+
+	return TEST_SUCCESS;
+}
+
+static struct
+unit_test_suite test_dissect_suite  = {
+	.setup = test_setup,
+	.teardown = test_cleanup,
+	.suite_name = "Test Dissect Unit Test Suite",
+	.unit_test_cases = {
+		TEST_CASE(test_simple),
+		TEST_CASE(test_truncated),
+		TEST_CASE(test_fuzz),
+		TEST_CASES_END()
+	}
+};
+
+static int
+test_dissect(void)
+{
+	return unit_test_suite_runner(&test_dissect_suite);
+}
+
+REGISTER_FAST_TEST(dissect_autotest, true, true, test_dissect);
-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v3 3/3] test-pmd: add more packet verbose decode options
  2024-07-23 20:33 ` [PATCH v3 0/3] add packet dissector function Stephen Hemminger
  2024-07-23 20:33   ` [PATCH v3 1/3] net: add new packet dissector Stephen Hemminger
  2024-07-23 20:33   ` [PATCH v3 2/3] test: add test for " Stephen Hemminger
@ 2024-07-23 20:33   ` Stephen Hemminger
  2 siblings, 0 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-07-23 20:33 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Ori Kam, Aman Singh

The existing verbose levels 1..3 provide a messy multi-line
output per packet. I found this unhelpful when diagnosing many
types of problems like packet flow.

This patch keeps the previous levels and adds two new levels:
4: one line per packet is printed in a format resembling
   tshark output. With addresses and protocol info.

5: dump packet in hex.
   Useful if the driver is messing up the data.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 app/test-pmd/cmdline_flow.c                 |  3 +-
 app/test-pmd/config.c                       | 33 ++++++---
 app/test-pmd/testpmd.h                      | 11 +++
 app/test-pmd/util.c                         | 77 +++++++++++++++++++--
 doc/guides/testpmd_app_ug/testpmd_funcs.rst |  5 +-
 5 files changed, 111 insertions(+), 18 deletions(-)

diff --git a/app/test-pmd/cmdline_flow.c b/app/test-pmd/cmdline_flow.c
index fb6a552863..013134272d 100644
--- a/app/test-pmd/cmdline_flow.c
+++ b/app/test-pmd/cmdline_flow.c
@@ -14076,7 +14076,8 @@ cmd_set_raw_parsed(const struct buffer *in)
 			upper_layer = proto;
 		}
 	}
-	if (verbose_level & 0x1)
+
+	if (verbose_level > 0)
 		printf("total data size is %zu\n", (*total_size));
 	RTE_ASSERT((*total_size) <= ACTION_RAW_ENCAP_MAX_DATA);
 	memmove(data, (data_tail - (*total_size)), *total_size);
diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c
index 6f0beafa27..b5b5f3b464 100644
--- a/app/test-pmd/config.c
+++ b/app/test-pmd/config.c
@@ -6246,26 +6246,37 @@ configure_rxtx_dump_callbacks(uint16_t verbose)
 		return;
 #endif
 
-	RTE_ETH_FOREACH_DEV(portid)
-	{
-		if (verbose == 1 || verbose > 2)
+	RTE_ETH_FOREACH_DEV(portid) {
+		switch (verbose) {
+		case VERBOSE_OFF:
+			remove_rx_dump_callbacks(portid);
+			remove_tx_dump_callbacks(portid);
+			break;
+		case VERBOSE_RX:
 			add_rx_dump_callbacks(portid);
-		else
+			remove_tx_dump_callbacks(portid);
+			break;
+		case VERBOSE_TX:
+			add_tx_dump_callbacks(portid);
 			remove_rx_dump_callbacks(portid);
-		if (verbose >= 2)
+			break;
+		default:
+			add_rx_dump_callbacks(portid);
 			add_tx_dump_callbacks(portid);
-		else
-			remove_tx_dump_callbacks(portid);
+		}
 	}
 }
 
 void
 set_verbose_level(uint16_t vb_level)
 {
-	printf("Change verbose level from %u to %u\n",
-	       (unsigned int) verbose_level, (unsigned int) vb_level);
-	verbose_level = vb_level;
-	configure_rxtx_dump_callbacks(verbose_level);
+	if (vb_level < VERBOSE_MAX) {
+		printf("Change verbose level from %u to %u\n", verbose_level, vb_level);
+		verbose_level = vb_level;
+		configure_rxtx_dump_callbacks(verbose_level);
+	} else {
+		fprintf(stderr, "Verbose level %u is out of range\n", vb_level);
+	}
 }
 
 void
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
index 9facd7f281..3d7a2b6dac 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -489,6 +489,17 @@ enum dcb_mode_enable
 
 extern uint8_t xstats_hide_zero; /**< Hide zero values for xstats display */
 
+enum verbose_mode {
+	VERBOSE_OFF = 0,
+	VERBOSE_RX,
+	VERBOSE_TX,
+	VERBOSE_BOTH,
+	VERBOSE_DISSECT,
+	VERBOSE_HEX,
+	VERBOSE_MAX
+};
+
+
 /* globals used for configuration */
 extern uint8_t record_core_cycles; /**< Enables measurement of CPU cycles */
 extern uint8_t record_burst_stats; /**< Enables display of RX and TX bursts */
diff --git a/app/test-pmd/util.c b/app/test-pmd/util.c
index bf9b639d95..2334d230a5 100644
--- a/app/test-pmd/util.c
+++ b/app/test-pmd/util.c
@@ -5,9 +5,11 @@
 
 #include <stdio.h>
 
+#include <rte_atomic.h>
 #include <rte_bitops.h>
 #include <rte_net.h>
 #include <rte_mbuf.h>
+#include <rte_dissect.h>
 #include <rte_ether.h>
 #include <rte_vxlan.h>
 #include <rte_ethdev.h>
@@ -16,6 +18,7 @@
 #include "testpmd.h"
 
 #define MAX_STRING_LEN 8192
+#define MAX_DUMP_LEN   1024
 
 #define MKDUMPSTR(buf, buf_size, cur_len, ...) \
 do { \
@@ -67,9 +70,10 @@ get_timestamp(const struct rte_mbuf *mbuf)
 			timestamp_dynfield_offset, rte_mbuf_timestamp_t *);
 }
 
-static inline void
-dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
-	      uint16_t nb_pkts, int is_rx)
+/* More verbose older style packet decode */
+static void
+dump_pkt_verbose(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
+		 uint16_t nb_pkts, int is_rx)
 {
 	struct rte_mbuf  *mb;
 	const struct rte_ether_hdr *eth_hdr;
@@ -90,8 +94,6 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
 	size_t cur_len = 0;
 	uint64_t restore_info_dynflag;
 
-	if (!nb_pkts)
-		return;
 	restore_info_dynflag = rte_flow_restore_info_dynflag();
 	MKDUMPSTR(print_buf, buf_size, cur_len,
 		  "port %u/queue %u: %s %u packets\n", port_id, queue,
@@ -299,6 +301,71 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
 	}
 }
 
+/* Brief tshark style one line output which is
+ * number time_delta Source Destination Protocol len info
+ */
+static void
+dump_pkt_brief(uint16_t port, uint16_t queue, struct rte_mbuf *pkts[], uint16_t nb_pkts)
+{
+	static uint64_t start_cycles;
+	static RTE_ATOMIC(uint64_t) packet_count = 1;
+	uint64_t now;
+	uint64_t count;
+	double interval;
+	uint16_t i;
+
+	now = rte_rdtsc();
+	if (start_cycles == 0) {
+		start_cycles = now;
+		printf("Seq#   Time        Port:Que Description\n");
+	}
+
+	interval = (double)(now - start_cycles) / (double)rte_get_tsc_hz();
+
+	count = rte_atomic_fetch_add_explicit(&packet_count, nb_pkts, rte_memory_order_relaxed);
+
+	for (i = 0; i < nb_pkts; i++) {
+		const struct rte_mbuf *mb = pkts[i];
+		char str[LINE_MAX];
+
+		rte_dissect_mbuf(str, sizeof(str), mb, 0);
+		printf("%6"PRIu64" %11.9f %4u:%-3u %s\n",
+		       count + i, interval, port, queue, str);
+	}
+}
+
+/* Hex dump of packet data */
+static void
+dump_pkt_hex(struct rte_mbuf *pkts[], uint16_t nb_pkts)
+{
+	uint16_t i;
+
+	for (i = 0; i < nb_pkts; i++)
+		rte_pktmbuf_dump(stdout, pkts[i], MAX_DUMP_LEN);
+
+}
+
+static uint16_t
+dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
+	      uint16_t nb_pkts, int is_rx)
+{
+	if (unlikely(nb_pkts == 0))
+		return 0;
+
+	switch (verbose_level) {
+	case VERBOSE_RX ... VERBOSE_BOTH:
+		dump_pkt_verbose(port_id, queue, pkts, nb_pkts, is_rx);
+		break;
+	case VERBOSE_DISSECT:
+		dump_pkt_brief(port_id, queue, pkts, nb_pkts);
+		break;
+	case VERBOSE_HEX:
+		dump_pkt_hex(pkts, nb_pkts);
+	}
+	fflush(stdout);
+	return nb_pkts;
+}
+
 uint16_t
 dump_rx_pkts(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
 	     uint16_t nb_pkts, __rte_unused uint16_t max_pkts,
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
index c19b4f8958..688806754a 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -676,7 +676,10 @@ Available levels are as following:
 * ``0`` silent except for error.
 * ``1`` fully verbose except for Tx packets.
 * ``2`` fully verbose except for Rx packets.
-* ``> 2`` fully verbose.
+* ``3`` fully verbose except for Tx and Rx packets.
+* ``4`` dissected protocol information for Tx and Rx packets.
+* ``5`` hex dump of packets
+
 
 set log
 ~~~~~~~
-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v4 0/3] Add packet dissector
  2024-03-12 22:01 [PATCH] test-pmd: add more packet decode options (verbose) Stephen Hemminger
                   ` (3 preceding siblings ...)
  2024-07-23 20:33 ` [PATCH v3 0/3] add packet dissector function Stephen Hemminger
@ 2024-07-24 18:46 ` Stephen Hemminger
  2024-07-24 18:46   ` [PATCH v4 1/3] net: add new " Stephen Hemminger
                     ` (2 more replies)
  2024-08-01 19:04 ` [PATCH v5 0/4] Add network packet dissector Stephen Hemminger
                   ` (2 subsequent siblings)
  7 siblings, 3 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-07-24 18:46 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

While debugging TAP rte_flow discovered that test pmd verbose output
was confusing and unhelpful. Instead, made a simple dissector that
prints one line per packet like this in test-pmd with verbose level 4.

Seq#   Time        Port:Que R Description
     1 0.000000000    0:0   R :: → ff02::16 ICMP 143
     2 0.000000000    0:0   R :: → ff02::1:ffbc:89e5 ICMP 135
     3 0.000000000    0:0   R :: → ff02::16 ICMP 143
...
    38 0.313868235    0:0   T fe80::7442:55ff:febc:89e5 → ff02::fb UDP 70 5353 → 5353
    39 4.635121071    0:0   R fe80::7442:55ff:febc:89e5 → ff02::2 ICMPv6 Router Solicitation
    40 4.635138535    0:0   T fe80::7442:55ff:febc:89e5 → ff02::2 ICMPv6 Router Solicitation

v4 - add direction flag to output
   - fix build on Windows

Stephen Hemminger (3):
  net: add new packet dissector
  test: add test for packet dissector
  test-pmd: add more packet verbose decode options

 app/test-pmd/cmdline_flow.c                 |   3 +-
 app/test-pmd/config.c                       |  33 +-
 app/test-pmd/testpmd.h                      |  11 +
 app/test-pmd/util.c                         |  77 +++-
 app/test/meson.build                        |   1 +
 app/test/test_dissect.c                     | 245 ++++++++++++
 doc/guides/testpmd_app_ug/testpmd_funcs.rst |   5 +-
 lib/net/meson.build                         |   2 +
 lib/net/rte_dissect.c                       | 404 ++++++++++++++++++++
 lib/net/rte_dissect.h                       |  42 ++
 lib/net/version.map                         |   7 +
 11 files changed, 812 insertions(+), 18 deletions(-)
 create mode 100644 app/test/test_dissect.c
 create mode 100644 lib/net/rte_dissect.c
 create mode 100644 lib/net/rte_dissect.h

-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v4 1/3] net: add new packet dissector
  2024-07-24 18:46 ` [PATCH v4 0/3] Add packet dissector Stephen Hemminger
@ 2024-07-24 18:46   ` Stephen Hemminger
  2024-07-24 18:46   ` [PATCH v4 2/3] test: add test for " Stephen Hemminger
  2024-07-24 18:46   ` [PATCH v4 3/3] test-pmd: add more packet verbose decode options Stephen Hemminger
  2 siblings, 0 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-07-24 18:46 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

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 <stephen@networkplumber.org>
---
 lib/net/meson.build   |   2 +
 lib/net/rte_dissect.c | 404 ++++++++++++++++++++++++++++++++++++++++++
 lib/net/rte_dissect.h |  42 +++++
 lib/net/version.map   |   7 +
 4 files changed, 455 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..acaa62ec4d
--- /dev/null
+++ b/lib/net/rte_dissect.c
@@ -0,0 +1,404 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2024 Stephen Hemminger <stephen@networkplumber.org>
+ *
+ * Print packets in format similar to tshark.
+ * Output should be one line per mbuf
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <rte_arp.h>
+#include <rte_branch_prediction.h>
+#include <rte_byteorder.h>
+#include <rte_common.h>
+#include <rte_dissect.h>
+#include <rte_ether.h>
+#include <rte_icmp.h>
+#include <rte_ip.h>
+#include <rte_mbuf.h>
+#include <rte_tcp.h>
+#include <rte_udp.h>
+#include <rte_vxlan.h>
+
+/* 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 Reply",
+		[RTE_IP_ICMP_ECHO_REQUEST] = "ICMP Request",
+		[RTE_ICMP6_ECHO_REPLY]     = "ICMPv6 Reply",
+		[RTE_ICMP6_ECHO_REQUEST]   = "ICMPv6 Request",
+		[133]                      = "ICMPv6 Router Solicitation",
+		[134]                      = "ICMPv6 Router Solicitation",
+	};
+
+	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), &eth_hdr->src_addr);
+		rte_ether_format_addr(sbuf, sizeof(dbuf), &eth_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 <stephen@networkplumber.org>
+ */
+
+#ifndef _RTE_NET_DISSECT_H_
+#define _RTE_NET_DISSECT_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <stdint.h>
+
+#include <rte_compat.h>
+
+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;
+};
-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v4 2/3] test: add test for packet dissector
  2024-07-24 18:46 ` [PATCH v4 0/3] Add packet dissector Stephen Hemminger
  2024-07-24 18:46   ` [PATCH v4 1/3] net: add new " Stephen Hemminger
@ 2024-07-24 18:46   ` Stephen Hemminger
  2024-07-24 18:46   ` [PATCH v4 3/3] test-pmd: add more packet verbose decode options Stephen Hemminger
  2 siblings, 0 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-07-24 18:46 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Some tests for new packet dissector.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 app/test/meson.build    |   1 +
 app/test/test_dissect.c | 245 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 246 insertions(+)
 create mode 100644 app/test/test_dissect.c

diff --git a/app/test/meson.build b/app/test/meson.build
index e29258e6ec..9cd2051320 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -62,6 +62,7 @@ source_file_deps = {
     'test_debug.c': [],
     'test_devargs.c': ['kvargs'],
     'test_dispatcher.c': ['dispatcher'],
+    'test_dissect.c': ['net'],
     'test_distributor.c': ['distributor'],
     'test_distributor_perf.c': ['distributor'],
     'test_dmadev.c': ['dmadev', 'bus_vdev'],
diff --git a/app/test/test_dissect.c b/app/test/test_dissect.c
new file mode 100644
index 0000000000..2c79acf766
--- /dev/null
+++ b/app/test/test_dissect.c
@@ -0,0 +1,245 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2024 Stephen Hemminger <stephen@networkplumber.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include <rte_bus_vdev.h>
+#include <rte_dissect.h>
+#include <rte_ethdev.h>
+#include <rte_ether.h>
+#include <rte_ip.h>
+#include <rte_mbuf.h>
+#include <rte_net.h>
+#include <rte_random.h>
+#include <rte_udp.h>
+#include <rte_vxlan.h>
+
+#include "test.h"
+
+#ifndef LINE_MAX
+#define LINE_MAX	2048
+#endif
+
+#define TOTAL_PACKETS	100
+#define PACKET_LEN	1000
+#define ETH_IP_UDP_VXLAN_SIZE (sizeof(struct rte_ether_hdr) + \
+			       sizeof(struct rte_ipv4_hdr) + \
+			       sizeof(struct rte_udp_hdr) + \
+			       sizeof(struct rte_vxlan_hdr))
+
+
+static uint16_t port_id;
+static const char null_dev[] = "net_null0";
+
+static void
+add_header(struct rte_mbuf *mb, uint32_t plen,
+	   rte_be16_t src_port, rte_be16_t dst_port)
+{
+	struct {
+		struct rte_ether_hdr eth;
+		struct rte_ipv4_hdr ip;
+		struct rte_udp_hdr udp;
+	} pkt = {
+		.eth = {
+			.dst_addr.addr_bytes = "\xff\xff\xff\xff\xff\xff",
+			.ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4),
+		},
+		.ip = {
+			.version_ihl = RTE_IPV4_VHL_DEF,
+			.time_to_live = 1,
+			.next_proto_id = IPPROTO_UDP,
+			.src_addr = rte_cpu_to_be_32(RTE_IPV4_LOOPBACK),
+			.dst_addr = rte_cpu_to_be_32(RTE_IPV4_BROADCAST),
+		},
+		.udp = {
+			.dst_port = dst_port,
+			.src_port = src_port,
+		},
+	};
+
+	rte_eth_random_addr(pkt.eth.src_addr.addr_bytes);
+
+	plen -= sizeof(struct rte_ether_hdr);
+	pkt.ip.total_length = rte_cpu_to_be_16(plen);
+	pkt.ip.hdr_checksum = rte_ipv4_cksum(&pkt.ip);
+
+	plen -= sizeof(struct rte_ipv4_hdr);
+	pkt.udp.src_port = rte_rand();
+	pkt.udp.dgram_len = rte_cpu_to_be_16(plen);
+
+	/* Copy header into mbuf */
+	memcpy(rte_pktmbuf_append(mb, sizeof(pkt)), &pkt, sizeof(pkt));
+}
+
+static void
+add_vxlan(struct rte_mbuf *mb, rte_be32_t vni)
+{
+	struct rte_vxlan_hdr *vxlan;
+
+	vxlan = (struct rte_vxlan_hdr *)rte_pktmbuf_append(mb, sizeof(*vxlan));
+	memset(vxlan, 0, sizeof(*vxlan));
+	vxlan->flag_i = 1;
+	vxlan->vx_vni = vni;
+}
+
+
+static void
+fill_data(struct rte_mbuf *mb, uint32_t len)
+{
+	uint32_t i;
+	char *ptr = rte_pktmbuf_append(mb, len);
+	char c = '!';
+
+	/* traditional barber pole pattern */
+	for (i = 0; i < len; i++) {
+		ptr[i] = c++;
+		if (c == 0x7f)
+			c = '!';
+	}
+}
+
+static void
+mbuf_prep(struct rte_mbuf *mb, uint8_t buf[], uint32_t buf_len)
+{
+	mb->buf_addr = buf;
+	rte_mbuf_iova_set(mb, (uintptr_t)buf);
+	mb->buf_len = buf_len;
+	rte_mbuf_refcnt_set(mb, 1);
+
+	/* set pool pointer to dummy value, test doesn't use it */
+	mb->pool = (void *)buf;
+
+	rte_pktmbuf_reset(mb);
+}
+
+static int
+test_setup(void)
+{
+	port_id = rte_eth_dev_count_avail();
+
+	/* Make a dummy null device to snoop on */
+	if (rte_vdev_init(null_dev, NULL) != 0) {
+		fprintf(stderr, "Failed to create vdev '%s'\n", null_dev);
+		goto fail;
+	}
+	return 0;
+
+fail:
+	rte_vdev_uninit(null_dev);
+	return -1;
+}
+
+static void
+test_cleanup(void)
+{
+	rte_vdev_uninit(null_dev);
+}
+
+
+static int
+test_simple(void)
+{
+	struct rte_mbuf mb;
+	uint8_t buf[RTE_MBUF_DEFAULT_BUF_SIZE];
+	uint32_t data_len = PACKET_LEN;
+	uint16_t src_port = rte_rand();
+	const uint16_t dst_port = rte_cpu_to_be_16(9); /* Discard port */
+	char obuf[LINE_MAX];
+
+	/* make a dummy packet */
+	mbuf_prep(&mb, buf, sizeof(buf));
+	add_header(&mb, data_len, src_port, dst_port);
+	fill_data(&mb, data_len - mb.data_off);
+
+	rte_dissect_mbuf(obuf, sizeof(obuf), &mb, 0);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_truncated(void)
+{
+	struct rte_mbuf mb;
+	uint8_t buf[RTE_MBUF_DEFAULT_BUF_SIZE];
+	uint32_t pkt_len, data_len = PACKET_LEN;
+	uint16_t dst_port = rte_cpu_to_be_16(RTE_VXLAN_DEFAULT_PORT);
+	char obuf[LINE_MAX];
+
+	/* make a really nested vxlan packet */
+	mbuf_prep(&mb, buf, sizeof(buf));
+	pkt_len = data_len;
+	do {
+		uint16_t src_port = rte_rand();
+		uint32_t vni = rte_rand_max(1ul << 24);
+
+		add_header(&mb, data_len, src_port, dst_port);
+		add_vxlan(&mb, vni);
+		pkt_len -= ETH_IP_UDP_VXLAN_SIZE;
+	} while (pkt_len > ETH_IP_UDP_VXLAN_SIZE);
+
+	fill_data(&mb, pkt_len);
+
+	/* dissect it but snip off some amount of data */
+	for (unsigned int i = 0; i < TOTAL_PACKETS; i++) {
+		uint32_t snaplen = rte_rand_max(pkt_len);
+
+		rte_dissect_mbuf(obuf, sizeof(obuf), &mb, snaplen);
+	}
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_fuzz(void)
+{
+	struct rte_mbuf mb;
+	uint8_t buf[RTE_MBUF_DEFAULT_BUF_SIZE];
+	uint32_t data_len = PACKET_LEN;
+	const uint16_t dst_port = rte_cpu_to_be_16(rte_rand_max(1024));
+	const uint16_t src_port = rte_cpu_to_be_16(rte_rand());
+	char obuf[LINE_MAX];
+
+	/* make a dummy packet */
+	mbuf_prep(&mb, buf, sizeof(buf));
+	add_header(&mb, data_len, src_port, dst_port);
+	fill_data(&mb, data_len - mb.data_off);
+
+	/* randomly flip bits in it */
+	for (unsigned int i = 0; i < TOTAL_PACKETS; i++) {
+		uint32_t bit = rte_rand_max(data_len) * 8;
+		uint8_t *bp = buf + bit / 8;
+		uint8_t mask = 1u << (bit % 8);
+
+		/* twiddle one bit */
+		*bp ^= mask;
+		rte_dissect_mbuf(obuf, sizeof(obuf), &mb, 0);
+		*bp ^= mask;
+	}
+
+	return TEST_SUCCESS;
+}
+
+static struct
+unit_test_suite test_dissect_suite  = {
+	.setup = test_setup,
+	.teardown = test_cleanup,
+	.suite_name = "Test Dissect Unit Test Suite",
+	.unit_test_cases = {
+		TEST_CASE(test_simple),
+		TEST_CASE(test_truncated),
+		TEST_CASE(test_fuzz),
+		TEST_CASES_END()
+	}
+};
+
+static int
+test_dissect(void)
+{
+	return unit_test_suite_runner(&test_dissect_suite);
+}
+
+REGISTER_FAST_TEST(dissect_autotest, true, true, test_dissect);
-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v4 3/3] test-pmd: add more packet verbose decode options
  2024-07-24 18:46 ` [PATCH v4 0/3] Add packet dissector Stephen Hemminger
  2024-07-24 18:46   ` [PATCH v4 1/3] net: add new " Stephen Hemminger
  2024-07-24 18:46   ` [PATCH v4 2/3] test: add test for " Stephen Hemminger
@ 2024-07-24 18:46   ` Stephen Hemminger
  2 siblings, 0 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-07-24 18:46 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Ori Kam, Aman Singh

The existing verbose levels 1..3 provide a messy multi-line
output per packet. I found this unhelpful when diagnosing many
types of problems like packet flow.

This patch keeps the previous levels and adds two new levels:
4: one line per packet is printed in a format resembling
   tshark output. With addresses and protocol info.

5: dump packet in hex.
   Useful if the driver is messing up the data.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 app/test-pmd/cmdline_flow.c                 |  3 +-
 app/test-pmd/config.c                       | 33 ++++++---
 app/test-pmd/testpmd.h                      | 11 +++
 app/test-pmd/util.c                         | 77 +++++++++++++++++++--
 doc/guides/testpmd_app_ug/testpmd_funcs.rst |  5 +-
 5 files changed, 111 insertions(+), 18 deletions(-)

diff --git a/app/test-pmd/cmdline_flow.c b/app/test-pmd/cmdline_flow.c
index d04280eb3e..a010fcf61a 100644
--- a/app/test-pmd/cmdline_flow.c
+++ b/app/test-pmd/cmdline_flow.c
@@ -14143,7 +14143,8 @@ cmd_set_raw_parsed(const struct buffer *in)
 			upper_layer = proto;
 		}
 	}
-	if (verbose_level & 0x1)
+
+	if (verbose_level > 0)
 		printf("total data size is %zu\n", (*total_size));
 	RTE_ASSERT((*total_size) <= ACTION_RAW_ENCAP_MAX_DATA);
 	memmove(data, (data_tail - (*total_size)), *total_size);
diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c
index 6f0beafa27..b5b5f3b464 100644
--- a/app/test-pmd/config.c
+++ b/app/test-pmd/config.c
@@ -6246,26 +6246,37 @@ configure_rxtx_dump_callbacks(uint16_t verbose)
 		return;
 #endif
 
-	RTE_ETH_FOREACH_DEV(portid)
-	{
-		if (verbose == 1 || verbose > 2)
+	RTE_ETH_FOREACH_DEV(portid) {
+		switch (verbose) {
+		case VERBOSE_OFF:
+			remove_rx_dump_callbacks(portid);
+			remove_tx_dump_callbacks(portid);
+			break;
+		case VERBOSE_RX:
 			add_rx_dump_callbacks(portid);
-		else
+			remove_tx_dump_callbacks(portid);
+			break;
+		case VERBOSE_TX:
+			add_tx_dump_callbacks(portid);
 			remove_rx_dump_callbacks(portid);
-		if (verbose >= 2)
+			break;
+		default:
+			add_rx_dump_callbacks(portid);
 			add_tx_dump_callbacks(portid);
-		else
-			remove_tx_dump_callbacks(portid);
+		}
 	}
 }
 
 void
 set_verbose_level(uint16_t vb_level)
 {
-	printf("Change verbose level from %u to %u\n",
-	       (unsigned int) verbose_level, (unsigned int) vb_level);
-	verbose_level = vb_level;
-	configure_rxtx_dump_callbacks(verbose_level);
+	if (vb_level < VERBOSE_MAX) {
+		printf("Change verbose level from %u to %u\n", verbose_level, vb_level);
+		verbose_level = vb_level;
+		configure_rxtx_dump_callbacks(verbose_level);
+	} else {
+		fprintf(stderr, "Verbose level %u is out of range\n", vb_level);
+	}
 }
 
 void
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
index 9facd7f281..3d7a2b6dac 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -489,6 +489,17 @@ enum dcb_mode_enable
 
 extern uint8_t xstats_hide_zero; /**< Hide zero values for xstats display */
 
+enum verbose_mode {
+	VERBOSE_OFF = 0,
+	VERBOSE_RX,
+	VERBOSE_TX,
+	VERBOSE_BOTH,
+	VERBOSE_DISSECT,
+	VERBOSE_HEX,
+	VERBOSE_MAX
+};
+
+
 /* globals used for configuration */
 extern uint8_t record_core_cycles; /**< Enables measurement of CPU cycles */
 extern uint8_t record_burst_stats; /**< Enables display of RX and TX bursts */
diff --git a/app/test-pmd/util.c b/app/test-pmd/util.c
index bf9b639d95..f277e7f035 100644
--- a/app/test-pmd/util.c
+++ b/app/test-pmd/util.c
@@ -5,9 +5,11 @@
 
 #include <stdio.h>
 
+#include <rte_atomic.h>
 #include <rte_bitops.h>
 #include <rte_net.h>
 #include <rte_mbuf.h>
+#include <rte_dissect.h>
 #include <rte_ether.h>
 #include <rte_vxlan.h>
 #include <rte_ethdev.h>
@@ -16,6 +18,7 @@
 #include "testpmd.h"
 
 #define MAX_STRING_LEN 8192
+#define MAX_DUMP_LEN   1024
 
 #define MKDUMPSTR(buf, buf_size, cur_len, ...) \
 do { \
@@ -67,9 +70,10 @@ get_timestamp(const struct rte_mbuf *mbuf)
 			timestamp_dynfield_offset, rte_mbuf_timestamp_t *);
 }
 
-static inline void
-dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
-	      uint16_t nb_pkts, int is_rx)
+/* More verbose older style packet decode */
+static void
+dump_pkt_verbose(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
+		 uint16_t nb_pkts, int is_rx)
 {
 	struct rte_mbuf  *mb;
 	const struct rte_ether_hdr *eth_hdr;
@@ -90,8 +94,6 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
 	size_t cur_len = 0;
 	uint64_t restore_info_dynflag;
 
-	if (!nb_pkts)
-		return;
 	restore_info_dynflag = rte_flow_restore_info_dynflag();
 	MKDUMPSTR(print_buf, buf_size, cur_len,
 		  "port %u/queue %u: %s %u packets\n", port_id, queue,
@@ -299,6 +301,71 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
 	}
 }
 
+/* Brief tshark style one line output which is
+ * number time_delta Source Destination Protocol len info
+ */
+static void
+dump_pkt_brief(uint16_t port, uint16_t queue, struct rte_mbuf *pkts[], uint16_t nb_pkts, int is_rx)
+{
+	static uint64_t start_cycles;
+	static RTE_ATOMIC(uint64_t) packet_count = 1;
+	uint64_t now;
+	uint64_t count;
+	double interval;
+	uint16_t i;
+
+	now = rte_rdtsc();
+	if (start_cycles == 0) {
+		start_cycles = now;
+		printf("Seq#   Time        Port:Que R Description\n");
+	}
+
+	interval = (double)(now - start_cycles) / (double)rte_get_tsc_hz();
+
+	count = rte_atomic_fetch_add_explicit(&packet_count, nb_pkts, rte_memory_order_relaxed);
+
+	for (i = 0; i < nb_pkts; i++) {
+		const struct rte_mbuf *mb = pkts[i];
+		char str[256];
+
+		rte_dissect_mbuf(str, sizeof(str), mb, 0);
+		printf("%6"PRIu64" %11.9f %4u:%-3u %c %s\n",
+		       count + i, interval, port, queue, is_rx ? 'R' : 'T', str);
+	}
+}
+
+/* Hex dump of packet data */
+static void
+dump_pkt_hex(struct rte_mbuf *pkts[], uint16_t nb_pkts)
+{
+	uint16_t i;
+
+	for (i = 0; i < nb_pkts; i++)
+		rte_pktmbuf_dump(stdout, pkts[i], MAX_DUMP_LEN);
+
+}
+
+static uint16_t
+dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
+	      uint16_t nb_pkts, int is_rx)
+{
+	if (unlikely(nb_pkts == 0))
+		return 0;
+
+	switch (verbose_level) {
+	case VERBOSE_RX ... VERBOSE_BOTH:
+		dump_pkt_verbose(port_id, queue, pkts, nb_pkts, is_rx);
+		break;
+	case VERBOSE_DISSECT:
+		dump_pkt_brief(port_id, queue, pkts, nb_pkts, is_rx);
+		break;
+	case VERBOSE_HEX:
+		dump_pkt_hex(pkts, nb_pkts);
+	}
+	fflush(stdout);
+	return nb_pkts;
+}
+
 uint16_t
 dump_rx_pkts(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
 	     uint16_t nb_pkts, __rte_unused uint16_t max_pkts,
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
index f00ab07605..b9ce7698db 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -677,7 +677,10 @@ Available levels are as following:
 * ``0`` silent except for error.
 * ``1`` fully verbose except for Tx packets.
 * ``2`` fully verbose except for Rx packets.
-* ``> 2`` fully verbose.
+* ``3`` fully verbose except for Tx and Rx packets.
+* ``4`` dissected protocol information for Tx and Rx packets.
+* ``5`` hex dump of packets
+
 
 set log
 ~~~~~~~
-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v5 0/4] Add network packet dissector
  2024-03-12 22:01 [PATCH] test-pmd: add more packet decode options (verbose) Stephen Hemminger
                   ` (4 preceding siblings ...)
  2024-07-24 18:46 ` [PATCH v4 0/3] Add packet dissector Stephen Hemminger
@ 2024-08-01 19:04 ` Stephen Hemminger
  2024-08-01 19:04   ` [PATCH v5 1/4] net: add more icmp types Stephen Hemminger
                     ` (3 more replies)
  2024-08-02 18:07 ` [PATCH v6 0/4] Add network packet dissector Stephen Hemminger
  2024-08-02 19:56 ` [PATCH v7 0/4] Add network packet dissector Stephen Hemminger
  7 siblings, 4 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-08-01 19:04 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

While debugging TAP rte_flow discovered that test pmd verbose output
was confusing and unhelpful. Instead, made a simple dissector that
prints one line per packet like this in test-pmd with verbose level 4.

v5 - breakout the additional ICMP types into header
   - fix some decoding bugs in ARP and ICMP

Stephen Hemminger (4):
  net: add more icmp types
  net: add new packet dissector
  test: add test for packet dissector
  test-pmd: add more packet verbose decode options

 app/test-pmd/cmdline_flow.c                 |   3 +-
 app/test-pmd/config.c                       |  33 +-
 app/test-pmd/testpmd.h                      |  11 +
 app/test-pmd/util.c                         |  77 +++-
 app/test/meson.build                        |   1 +
 app/test/test_dissect.c                     | 245 ++++++++++++
 doc/guides/testpmd_app_ug/testpmd_funcs.rst |   5 +-
 lib/net/meson.build                         |   2 +
 lib/net/rte_dissect.c                       | 416 ++++++++++++++++++++
 lib/net/rte_dissect.h                       |  42 ++
 lib/net/rte_icmp.h                          |  22 +-
 lib/net/version.map                         |   7 +
 12 files changed, 842 insertions(+), 22 deletions(-)
 create mode 100644 app/test/test_dissect.c
 create mode 100644 lib/net/rte_dissect.c
 create mode 100644 lib/net/rte_dissect.h

-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v5 1/4] net: add more icmp types
  2024-08-01 19:04 ` [PATCH v5 0/4] Add network packet dissector Stephen Hemminger
@ 2024-08-01 19:04   ` Stephen Hemminger
  2024-08-01 19:04   ` [PATCH v5 2/4] net: add new packet dissector Stephen Hemminger
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-08-01 19:04 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Add more defines for additional defined ICMP types.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 lib/net/rte_icmp.h | 22 ++++++++++++++++++----
 1 file changed, 18 insertions(+), 4 deletions(-)

diff --git a/lib/net/rte_icmp.h b/lib/net/rte_icmp.h
index 4bf64d70ad..b51b60a6d2 100644
--- a/lib/net/rte_icmp.h
+++ b/lib/net/rte_icmp.h
@@ -54,10 +54,24 @@ struct rte_icmp_hdr {
 } __rte_packed;
 
 /* ICMP packet types */
-#define RTE_IP_ICMP_ECHO_REPLY   0
-#define RTE_IP_ICMP_ECHO_REQUEST 8
-#define RTE_ICMP6_ECHO_REQUEST 128
-#define RTE_ICMP6_ECHO_REPLY   129
+#define RTE_IP_ICMP_ECHO_REPLY      0
+#define RTE_IP_ICMP_DEST_UNREACH    3
+#define RTE_IP_ICMP_SOURCE_QUENCH   4
+#define RTE_IP_ICMP_REDIRECT        5
+#define RTE_IP_ICMP_ECHO_REQUEST    8
+#define RTE_IP_ICMP_TIME_EXCEEDED  11
+#define RTE_IP_ICMP_PARAMETERPROB  12
+#define RTE_IP_ICMP_TIMESTAMP      13
+#define RTE_IP_ICMP_TIMESTAMPREPLY 14
+#define RTE_IP_ICMP_INFO_REQUEST   15
+#define RTE_IP_ICMP_INFO_REPLY     16
+
+#define RTE_ICMP6_ECHO_REQUEST  128
+#define RTE_ICMP6_ECHO_REPLY    129
+#define RTE_ND_ROUTER_SOLICIT   133
+#define RTE_ND_ROUTER_ADVERT    134
+#define RTE_ND_NEIGHBOR_SOLICIT 135
+#define RTE_ND_NEIGHBOR_ADVERT  136
 
 #ifdef __cplusplus
 }
-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v5 2/4] net: add new packet dissector
  2024-08-01 19:04 ` [PATCH v5 0/4] Add network packet dissector Stephen Hemminger
  2024-08-01 19:04   ` [PATCH v5 1/4] net: add more icmp types Stephen Hemminger
@ 2024-08-01 19:04   ` Stephen Hemminger
  2024-08-01 19:04   ` [PATCH v5 3/4] test: add test for " Stephen Hemminger
  2024-08-01 19:04   ` [PATCH v5 4/4] test-pmd: add more packet verbose decode options Stephen Hemminger
  3 siblings, 0 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-08-01 19:04 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

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 <stephen@networkplumber.org>
---
 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 <stephen@networkplumber.org>
+ *
+ * Print packets in format similar to tshark.
+ * Output should be one line per mbuf
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <rte_arp.h>
+#include <rte_branch_prediction.h>
+#include <rte_byteorder.h>
+#include <rte_common.h>
+#include <rte_dissect.h>
+#include <rte_ether.h>
+#include <rte_icmp.h>
+#include <rte_ip.h>
+#include <rte_mbuf.h>
+#include <rte_tcp.h>
+#include <rte_udp.h>
+#include <rte_vxlan.h>
+
+/* 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), &eth_hdr->src_addr);
+		rte_ether_format_addr(dbuf, sizeof(dbuf), &eth_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 <stephen@networkplumber.org>
+ */
+
+#ifndef _RTE_NET_DISSECT_H_
+#define _RTE_NET_DISSECT_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <stdint.h>
+
+#include <rte_compat.h>
+
+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;
+};
-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v5 3/4] test: add test for packet dissector
  2024-08-01 19:04 ` [PATCH v5 0/4] Add network packet dissector Stephen Hemminger
  2024-08-01 19:04   ` [PATCH v5 1/4] net: add more icmp types Stephen Hemminger
  2024-08-01 19:04   ` [PATCH v5 2/4] net: add new packet dissector Stephen Hemminger
@ 2024-08-01 19:04   ` Stephen Hemminger
  2024-08-02  8:38     ` Bruce Richardson
  2024-08-01 19:04   ` [PATCH v5 4/4] test-pmd: add more packet verbose decode options Stephen Hemminger
  3 siblings, 1 reply; 36+ messages in thread
From: Stephen Hemminger @ 2024-08-01 19:04 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Some tests for new packet dissector.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 app/test/meson.build    |   1 +
 app/test/test_dissect.c | 245 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 246 insertions(+)
 create mode 100644 app/test/test_dissect.c

diff --git a/app/test/meson.build b/app/test/meson.build
index e29258e6ec..9cd2051320 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -62,6 +62,7 @@ source_file_deps = {
     'test_debug.c': [],
     'test_devargs.c': ['kvargs'],
     'test_dispatcher.c': ['dispatcher'],
+    'test_dissect.c': ['net'],
     'test_distributor.c': ['distributor'],
     'test_distributor_perf.c': ['distributor'],
     'test_dmadev.c': ['dmadev', 'bus_vdev'],
diff --git a/app/test/test_dissect.c b/app/test/test_dissect.c
new file mode 100644
index 0000000000..2c79acf766
--- /dev/null
+++ b/app/test/test_dissect.c
@@ -0,0 +1,245 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2024 Stephen Hemminger <stephen@networkplumber.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include <rte_bus_vdev.h>
+#include <rte_dissect.h>
+#include <rte_ethdev.h>
+#include <rte_ether.h>
+#include <rte_ip.h>
+#include <rte_mbuf.h>
+#include <rte_net.h>
+#include <rte_random.h>
+#include <rte_udp.h>
+#include <rte_vxlan.h>
+
+#include "test.h"
+
+#ifndef LINE_MAX
+#define LINE_MAX	2048
+#endif
+
+#define TOTAL_PACKETS	100
+#define PACKET_LEN	1000
+#define ETH_IP_UDP_VXLAN_SIZE (sizeof(struct rte_ether_hdr) + \
+			       sizeof(struct rte_ipv4_hdr) + \
+			       sizeof(struct rte_udp_hdr) + \
+			       sizeof(struct rte_vxlan_hdr))
+
+
+static uint16_t port_id;
+static const char null_dev[] = "net_null0";
+
+static void
+add_header(struct rte_mbuf *mb, uint32_t plen,
+	   rte_be16_t src_port, rte_be16_t dst_port)
+{
+	struct {
+		struct rte_ether_hdr eth;
+		struct rte_ipv4_hdr ip;
+		struct rte_udp_hdr udp;
+	} pkt = {
+		.eth = {
+			.dst_addr.addr_bytes = "\xff\xff\xff\xff\xff\xff",
+			.ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4),
+		},
+		.ip = {
+			.version_ihl = RTE_IPV4_VHL_DEF,
+			.time_to_live = 1,
+			.next_proto_id = IPPROTO_UDP,
+			.src_addr = rte_cpu_to_be_32(RTE_IPV4_LOOPBACK),
+			.dst_addr = rte_cpu_to_be_32(RTE_IPV4_BROADCAST),
+		},
+		.udp = {
+			.dst_port = dst_port,
+			.src_port = src_port,
+		},
+	};
+
+	rte_eth_random_addr(pkt.eth.src_addr.addr_bytes);
+
+	plen -= sizeof(struct rte_ether_hdr);
+	pkt.ip.total_length = rte_cpu_to_be_16(plen);
+	pkt.ip.hdr_checksum = rte_ipv4_cksum(&pkt.ip);
+
+	plen -= sizeof(struct rte_ipv4_hdr);
+	pkt.udp.src_port = rte_rand();
+	pkt.udp.dgram_len = rte_cpu_to_be_16(plen);
+
+	/* Copy header into mbuf */
+	memcpy(rte_pktmbuf_append(mb, sizeof(pkt)), &pkt, sizeof(pkt));
+}
+
+static void
+add_vxlan(struct rte_mbuf *mb, rte_be32_t vni)
+{
+	struct rte_vxlan_hdr *vxlan;
+
+	vxlan = (struct rte_vxlan_hdr *)rte_pktmbuf_append(mb, sizeof(*vxlan));
+	memset(vxlan, 0, sizeof(*vxlan));
+	vxlan->flag_i = 1;
+	vxlan->vx_vni = vni;
+}
+
+
+static void
+fill_data(struct rte_mbuf *mb, uint32_t len)
+{
+	uint32_t i;
+	char *ptr = rte_pktmbuf_append(mb, len);
+	char c = '!';
+
+	/* traditional barber pole pattern */
+	for (i = 0; i < len; i++) {
+		ptr[i] = c++;
+		if (c == 0x7f)
+			c = '!';
+	}
+}
+
+static void
+mbuf_prep(struct rte_mbuf *mb, uint8_t buf[], uint32_t buf_len)
+{
+	mb->buf_addr = buf;
+	rte_mbuf_iova_set(mb, (uintptr_t)buf);
+	mb->buf_len = buf_len;
+	rte_mbuf_refcnt_set(mb, 1);
+
+	/* set pool pointer to dummy value, test doesn't use it */
+	mb->pool = (void *)buf;
+
+	rte_pktmbuf_reset(mb);
+}
+
+static int
+test_setup(void)
+{
+	port_id = rte_eth_dev_count_avail();
+
+	/* Make a dummy null device to snoop on */
+	if (rte_vdev_init(null_dev, NULL) != 0) {
+		fprintf(stderr, "Failed to create vdev '%s'\n", null_dev);
+		goto fail;
+	}
+	return 0;
+
+fail:
+	rte_vdev_uninit(null_dev);
+	return -1;
+}
+
+static void
+test_cleanup(void)
+{
+	rte_vdev_uninit(null_dev);
+}
+
+
+static int
+test_simple(void)
+{
+	struct rte_mbuf mb;
+	uint8_t buf[RTE_MBUF_DEFAULT_BUF_SIZE];
+	uint32_t data_len = PACKET_LEN;
+	uint16_t src_port = rte_rand();
+	const uint16_t dst_port = rte_cpu_to_be_16(9); /* Discard port */
+	char obuf[LINE_MAX];
+
+	/* make a dummy packet */
+	mbuf_prep(&mb, buf, sizeof(buf));
+	add_header(&mb, data_len, src_port, dst_port);
+	fill_data(&mb, data_len - mb.data_off);
+
+	rte_dissect_mbuf(obuf, sizeof(obuf), &mb, 0);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_truncated(void)
+{
+	struct rte_mbuf mb;
+	uint8_t buf[RTE_MBUF_DEFAULT_BUF_SIZE];
+	uint32_t pkt_len, data_len = PACKET_LEN;
+	uint16_t dst_port = rte_cpu_to_be_16(RTE_VXLAN_DEFAULT_PORT);
+	char obuf[LINE_MAX];
+
+	/* make a really nested vxlan packet */
+	mbuf_prep(&mb, buf, sizeof(buf));
+	pkt_len = data_len;
+	do {
+		uint16_t src_port = rte_rand();
+		uint32_t vni = rte_rand_max(1ul << 24);
+
+		add_header(&mb, data_len, src_port, dst_port);
+		add_vxlan(&mb, vni);
+		pkt_len -= ETH_IP_UDP_VXLAN_SIZE;
+	} while (pkt_len > ETH_IP_UDP_VXLAN_SIZE);
+
+	fill_data(&mb, pkt_len);
+
+	/* dissect it but snip off some amount of data */
+	for (unsigned int i = 0; i < TOTAL_PACKETS; i++) {
+		uint32_t snaplen = rte_rand_max(pkt_len);
+
+		rte_dissect_mbuf(obuf, sizeof(obuf), &mb, snaplen);
+	}
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_fuzz(void)
+{
+	struct rte_mbuf mb;
+	uint8_t buf[RTE_MBUF_DEFAULT_BUF_SIZE];
+	uint32_t data_len = PACKET_LEN;
+	const uint16_t dst_port = rte_cpu_to_be_16(rte_rand_max(1024));
+	const uint16_t src_port = rte_cpu_to_be_16(rte_rand());
+	char obuf[LINE_MAX];
+
+	/* make a dummy packet */
+	mbuf_prep(&mb, buf, sizeof(buf));
+	add_header(&mb, data_len, src_port, dst_port);
+	fill_data(&mb, data_len - mb.data_off);
+
+	/* randomly flip bits in it */
+	for (unsigned int i = 0; i < TOTAL_PACKETS; i++) {
+		uint32_t bit = rte_rand_max(data_len) * 8;
+		uint8_t *bp = buf + bit / 8;
+		uint8_t mask = 1u << (bit % 8);
+
+		/* twiddle one bit */
+		*bp ^= mask;
+		rte_dissect_mbuf(obuf, sizeof(obuf), &mb, 0);
+		*bp ^= mask;
+	}
+
+	return TEST_SUCCESS;
+}
+
+static struct
+unit_test_suite test_dissect_suite  = {
+	.setup = test_setup,
+	.teardown = test_cleanup,
+	.suite_name = "Test Dissect Unit Test Suite",
+	.unit_test_cases = {
+		TEST_CASE(test_simple),
+		TEST_CASE(test_truncated),
+		TEST_CASE(test_fuzz),
+		TEST_CASES_END()
+	}
+};
+
+static int
+test_dissect(void)
+{
+	return unit_test_suite_runner(&test_dissect_suite);
+}
+
+REGISTER_FAST_TEST(dissect_autotest, true, true, test_dissect);
-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v5 4/4] test-pmd: add more packet verbose decode options
  2024-08-01 19:04 ` [PATCH v5 0/4] Add network packet dissector Stephen Hemminger
                     ` (2 preceding siblings ...)
  2024-08-01 19:04   ` [PATCH v5 3/4] test: add test for " Stephen Hemminger
@ 2024-08-01 19:04   ` Stephen Hemminger
  3 siblings, 0 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-08-01 19:04 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Ori Kam, Aman Singh

The existing verbose levels 1..3 provide a messy multi-line
output per packet. I found this unhelpful when diagnosing many
types of problems like packet flow.

This patch keeps the previous levels and adds two new levels:
4: one line per packet is printed in a format resembling
   tshark output. With addresses and protocol info.

5: dump packet in hex.
   Useful if the driver is messing up the data.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 app/test-pmd/cmdline_flow.c                 |  3 +-
 app/test-pmd/config.c                       | 33 ++++++---
 app/test-pmd/testpmd.h                      | 11 +++
 app/test-pmd/util.c                         | 77 +++++++++++++++++++--
 doc/guides/testpmd_app_ug/testpmd_funcs.rst |  5 +-
 5 files changed, 111 insertions(+), 18 deletions(-)

diff --git a/app/test-pmd/cmdline_flow.c b/app/test-pmd/cmdline_flow.c
index d04280eb3e..a010fcf61a 100644
--- a/app/test-pmd/cmdline_flow.c
+++ b/app/test-pmd/cmdline_flow.c
@@ -14143,7 +14143,8 @@ cmd_set_raw_parsed(const struct buffer *in)
 			upper_layer = proto;
 		}
 	}
-	if (verbose_level & 0x1)
+
+	if (verbose_level > 0)
 		printf("total data size is %zu\n", (*total_size));
 	RTE_ASSERT((*total_size) <= ACTION_RAW_ENCAP_MAX_DATA);
 	memmove(data, (data_tail - (*total_size)), *total_size);
diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c
index 6f0beafa27..b5b5f3b464 100644
--- a/app/test-pmd/config.c
+++ b/app/test-pmd/config.c
@@ -6246,26 +6246,37 @@ configure_rxtx_dump_callbacks(uint16_t verbose)
 		return;
 #endif
 
-	RTE_ETH_FOREACH_DEV(portid)
-	{
-		if (verbose == 1 || verbose > 2)
+	RTE_ETH_FOREACH_DEV(portid) {
+		switch (verbose) {
+		case VERBOSE_OFF:
+			remove_rx_dump_callbacks(portid);
+			remove_tx_dump_callbacks(portid);
+			break;
+		case VERBOSE_RX:
 			add_rx_dump_callbacks(portid);
-		else
+			remove_tx_dump_callbacks(portid);
+			break;
+		case VERBOSE_TX:
+			add_tx_dump_callbacks(portid);
 			remove_rx_dump_callbacks(portid);
-		if (verbose >= 2)
+			break;
+		default:
+			add_rx_dump_callbacks(portid);
 			add_tx_dump_callbacks(portid);
-		else
-			remove_tx_dump_callbacks(portid);
+		}
 	}
 }
 
 void
 set_verbose_level(uint16_t vb_level)
 {
-	printf("Change verbose level from %u to %u\n",
-	       (unsigned int) verbose_level, (unsigned int) vb_level);
-	verbose_level = vb_level;
-	configure_rxtx_dump_callbacks(verbose_level);
+	if (vb_level < VERBOSE_MAX) {
+		printf("Change verbose level from %u to %u\n", verbose_level, vb_level);
+		verbose_level = vb_level;
+		configure_rxtx_dump_callbacks(verbose_level);
+	} else {
+		fprintf(stderr, "Verbose level %u is out of range\n", vb_level);
+	}
 }
 
 void
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
index 9facd7f281..3d7a2b6dac 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -489,6 +489,17 @@ enum dcb_mode_enable
 
 extern uint8_t xstats_hide_zero; /**< Hide zero values for xstats display */
 
+enum verbose_mode {
+	VERBOSE_OFF = 0,
+	VERBOSE_RX,
+	VERBOSE_TX,
+	VERBOSE_BOTH,
+	VERBOSE_DISSECT,
+	VERBOSE_HEX,
+	VERBOSE_MAX
+};
+
+
 /* globals used for configuration */
 extern uint8_t record_core_cycles; /**< Enables measurement of CPU cycles */
 extern uint8_t record_burst_stats; /**< Enables display of RX and TX bursts */
diff --git a/app/test-pmd/util.c b/app/test-pmd/util.c
index bf9b639d95..f277e7f035 100644
--- a/app/test-pmd/util.c
+++ b/app/test-pmd/util.c
@@ -5,9 +5,11 @@
 
 #include <stdio.h>
 
+#include <rte_atomic.h>
 #include <rte_bitops.h>
 #include <rte_net.h>
 #include <rte_mbuf.h>
+#include <rte_dissect.h>
 #include <rte_ether.h>
 #include <rte_vxlan.h>
 #include <rte_ethdev.h>
@@ -16,6 +18,7 @@
 #include "testpmd.h"
 
 #define MAX_STRING_LEN 8192
+#define MAX_DUMP_LEN   1024
 
 #define MKDUMPSTR(buf, buf_size, cur_len, ...) \
 do { \
@@ -67,9 +70,10 @@ get_timestamp(const struct rte_mbuf *mbuf)
 			timestamp_dynfield_offset, rte_mbuf_timestamp_t *);
 }
 
-static inline void
-dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
-	      uint16_t nb_pkts, int is_rx)
+/* More verbose older style packet decode */
+static void
+dump_pkt_verbose(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
+		 uint16_t nb_pkts, int is_rx)
 {
 	struct rte_mbuf  *mb;
 	const struct rte_ether_hdr *eth_hdr;
@@ -90,8 +94,6 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
 	size_t cur_len = 0;
 	uint64_t restore_info_dynflag;
 
-	if (!nb_pkts)
-		return;
 	restore_info_dynflag = rte_flow_restore_info_dynflag();
 	MKDUMPSTR(print_buf, buf_size, cur_len,
 		  "port %u/queue %u: %s %u packets\n", port_id, queue,
@@ -299,6 +301,71 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
 	}
 }
 
+/* Brief tshark style one line output which is
+ * number time_delta Source Destination Protocol len info
+ */
+static void
+dump_pkt_brief(uint16_t port, uint16_t queue, struct rte_mbuf *pkts[], uint16_t nb_pkts, int is_rx)
+{
+	static uint64_t start_cycles;
+	static RTE_ATOMIC(uint64_t) packet_count = 1;
+	uint64_t now;
+	uint64_t count;
+	double interval;
+	uint16_t i;
+
+	now = rte_rdtsc();
+	if (start_cycles == 0) {
+		start_cycles = now;
+		printf("Seq#   Time        Port:Que R Description\n");
+	}
+
+	interval = (double)(now - start_cycles) / (double)rte_get_tsc_hz();
+
+	count = rte_atomic_fetch_add_explicit(&packet_count, nb_pkts, rte_memory_order_relaxed);
+
+	for (i = 0; i < nb_pkts; i++) {
+		const struct rte_mbuf *mb = pkts[i];
+		char str[256];
+
+		rte_dissect_mbuf(str, sizeof(str), mb, 0);
+		printf("%6"PRIu64" %11.9f %4u:%-3u %c %s\n",
+		       count + i, interval, port, queue, is_rx ? 'R' : 'T', str);
+	}
+}
+
+/* Hex dump of packet data */
+static void
+dump_pkt_hex(struct rte_mbuf *pkts[], uint16_t nb_pkts)
+{
+	uint16_t i;
+
+	for (i = 0; i < nb_pkts; i++)
+		rte_pktmbuf_dump(stdout, pkts[i], MAX_DUMP_LEN);
+
+}
+
+static uint16_t
+dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
+	      uint16_t nb_pkts, int is_rx)
+{
+	if (unlikely(nb_pkts == 0))
+		return 0;
+
+	switch (verbose_level) {
+	case VERBOSE_RX ... VERBOSE_BOTH:
+		dump_pkt_verbose(port_id, queue, pkts, nb_pkts, is_rx);
+		break;
+	case VERBOSE_DISSECT:
+		dump_pkt_brief(port_id, queue, pkts, nb_pkts, is_rx);
+		break;
+	case VERBOSE_HEX:
+		dump_pkt_hex(pkts, nb_pkts);
+	}
+	fflush(stdout);
+	return nb_pkts;
+}
+
 uint16_t
 dump_rx_pkts(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
 	     uint16_t nb_pkts, __rte_unused uint16_t max_pkts,
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
index f00ab07605..b9ce7698db 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -677,7 +677,10 @@ Available levels are as following:
 * ``0`` silent except for error.
 * ``1`` fully verbose except for Tx packets.
 * ``2`` fully verbose except for Rx packets.
-* ``> 2`` fully verbose.
+* ``3`` fully verbose except for Tx and Rx packets.
+* ``4`` dissected protocol information for Tx and Rx packets.
+* ``5`` hex dump of packets
+
 
 set log
 ~~~~~~~
-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH v5 3/4] test: add test for packet dissector
  2024-08-01 19:04   ` [PATCH v5 3/4] test: add test for " Stephen Hemminger
@ 2024-08-02  8:38     ` Bruce Richardson
  2024-08-02 15:31       ` Stephen Hemminger
  2024-08-02 18:06       ` Stephen Hemminger
  0 siblings, 2 replies; 36+ messages in thread
From: Bruce Richardson @ 2024-08-02  8:38 UTC (permalink / raw)
  To: Stephen Hemminger; +Cc: dev

On Thu, Aug 01, 2024 at 12:04:42PM -0700, Stephen Hemminger wrote:
> Some tests for new packet dissector.
> 
> Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
> ---
>  app/test/meson.build    |   1 +
>  app/test/test_dissect.c | 245 ++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 246 insertions(+)
>  create mode 100644 app/test/test_dissect.c
> 
> diff --git a/app/test/meson.build b/app/test/meson.build
> index e29258e6ec..9cd2051320 100644
> --- a/app/test/meson.build
> +++ b/app/test/meson.build
> @@ -62,6 +62,7 @@ source_file_deps = {
>      'test_debug.c': [],
>      'test_devargs.c': ['kvargs'],
>      'test_dispatcher.c': ['dispatcher'],
> +    'test_dissect.c': ['net'],
>      'test_distributor.c': ['distributor'],
>      'test_distributor_perf.c': ['distributor'],
>      'test_dmadev.c': ['dmadev', 'bus_vdev'],
> diff --git a/app/test/test_dissect.c b/app/test/test_dissect.c
> new file mode 100644
> index 0000000000..2c79acf766
> --- /dev/null
> +++ b/app/test/test_dissect.c

<snip>

> +static int
> +test_simple(void)
> +{
> +	struct rte_mbuf mb;
> +	uint8_t buf[RTE_MBUF_DEFAULT_BUF_SIZE];
> +	uint32_t data_len = PACKET_LEN;
> +	uint16_t src_port = rte_rand();
> +	const uint16_t dst_port = rte_cpu_to_be_16(9); /* Discard port */
> +	char obuf[LINE_MAX];
> +
> +	/* make a dummy packet */
> +	mbuf_prep(&mb, buf, sizeof(buf));
> +	add_header(&mb, data_len, src_port, dst_port);
> +	fill_data(&mb, data_len - mb.data_off);
> +
> +	rte_dissect_mbuf(obuf, sizeof(obuf), &mb, 0);
> +
> +	return TEST_SUCCESS;
> +}

What are these test cases actually verifying - can any of them actually
fail? I don't see the output being checked anywhere.

/Bruce

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH v5 3/4] test: add test for packet dissector
  2024-08-02  8:38     ` Bruce Richardson
@ 2024-08-02 15:31       ` Stephen Hemminger
  2024-08-02 18:06       ` Stephen Hemminger
  1 sibling, 0 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-08-02 15:31 UTC (permalink / raw)
  To: Bruce Richardson; +Cc: dev

On Fri, 2 Aug 2024 09:38:23 +0100
Bruce Richardson <bruce.richardson@intel.com> wrote:

> What are these test cases actually verifying - can any of them actually
> fail? I don't see the output being checked anywhere.

Now that it returns a string could easily check expected result.

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH v5 3/4] test: add test for packet dissector
  2024-08-02  8:38     ` Bruce Richardson
  2024-08-02 15:31       ` Stephen Hemminger
@ 2024-08-02 18:06       ` Stephen Hemminger
  1 sibling, 0 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-08-02 18:06 UTC (permalink / raw)
  To: Bruce Richardson; +Cc: dev

On Fri, 2 Aug 2024 09:38:23 +0100
Bruce Richardson <bruce.richardson@intel.com> wrote:

> > +	rte_dissect_mbuf(obuf, sizeof(obuf), &mb, 0);
> > +
> > +	return TEST_SUCCESS;
> > +}  
> 
> What are these test cases actually verifying - can any of them actually
> fail? I don't see the output being checked anywhere.
> 
> /Bruce

Mostly the tests are to make sure that code doesn't crash when
given invalid packet data or truncated packet.

^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v6 0/4] Add network packet dissector
  2024-03-12 22:01 [PATCH] test-pmd: add more packet decode options (verbose) Stephen Hemminger
                   ` (5 preceding siblings ...)
  2024-08-01 19:04 ` [PATCH v5 0/4] Add network packet dissector Stephen Hemminger
@ 2024-08-02 18:07 ` Stephen Hemminger
  2024-08-02 18:07   ` [PATCH v6 1/4] net: add more icmp types Stephen Hemminger
                     ` (3 more replies)
  2024-08-02 19:56 ` [PATCH v7 0/4] Add network packet dissector Stephen Hemminger
  7 siblings, 4 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-08-02 18:07 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

While debugging TAP rte_flow discovered that test pmd verbose output
was confusing and unhelpful. Instead, made a simple dissector that
prints one line per packet like this in test-pmd with verbose level 4.

v6 - validate result of rte_dissect for simple packet

v5 - breakout the additional ICMP types into header
   - fix some decoding bugs in ARP and ICMP

Stephen Hemminger (4):
  net: add more icmp types
  net: add new packet dissector
  test: add test for packet dissector
  test-pmd: add more packet verbose decode options

 app/test-pmd/cmdline_flow.c                 |   3 +-
 app/test-pmd/config.c                       |  33 +-
 app/test-pmd/testpmd.h                      |  11 +
 app/test-pmd/util.c                         |  77 +++-
 app/test/meson.build                        |   1 +
 app/test/test_dissect.c                     | 253 ++++++++++++
 doc/guides/testpmd_app_ug/testpmd_funcs.rst |   5 +-
 lib/net/meson.build                         |   2 +
 lib/net/rte_dissect.c                       | 416 ++++++++++++++++++++
 lib/net/rte_dissect.h                       |  42 ++
 lib/net/rte_icmp.h                          |  22 +-
 lib/net/version.map                         |   7 +
 12 files changed, 850 insertions(+), 22 deletions(-)
 create mode 100644 app/test/test_dissect.c
 create mode 100644 lib/net/rte_dissect.c
 create mode 100644 lib/net/rte_dissect.h

-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v6 1/4] net: add more icmp types
  2024-08-02 18:07 ` [PATCH v6 0/4] Add network packet dissector Stephen Hemminger
@ 2024-08-02 18:07   ` Stephen Hemminger
  2024-08-02 18:07   ` [PATCH v6 2/4] net: add new packet dissector Stephen Hemminger
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-08-02 18:07 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Add more defines for additional defined ICMP types.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 lib/net/rte_icmp.h | 22 ++++++++++++++++++----
 1 file changed, 18 insertions(+), 4 deletions(-)

diff --git a/lib/net/rte_icmp.h b/lib/net/rte_icmp.h
index 4bf64d70ad..b51b60a6d2 100644
--- a/lib/net/rte_icmp.h
+++ b/lib/net/rte_icmp.h
@@ -54,10 +54,24 @@ struct rte_icmp_hdr {
 } __rte_packed;
 
 /* ICMP packet types */
-#define RTE_IP_ICMP_ECHO_REPLY   0
-#define RTE_IP_ICMP_ECHO_REQUEST 8
-#define RTE_ICMP6_ECHO_REQUEST 128
-#define RTE_ICMP6_ECHO_REPLY   129
+#define RTE_IP_ICMP_ECHO_REPLY      0
+#define RTE_IP_ICMP_DEST_UNREACH    3
+#define RTE_IP_ICMP_SOURCE_QUENCH   4
+#define RTE_IP_ICMP_REDIRECT        5
+#define RTE_IP_ICMP_ECHO_REQUEST    8
+#define RTE_IP_ICMP_TIME_EXCEEDED  11
+#define RTE_IP_ICMP_PARAMETERPROB  12
+#define RTE_IP_ICMP_TIMESTAMP      13
+#define RTE_IP_ICMP_TIMESTAMPREPLY 14
+#define RTE_IP_ICMP_INFO_REQUEST   15
+#define RTE_IP_ICMP_INFO_REPLY     16
+
+#define RTE_ICMP6_ECHO_REQUEST  128
+#define RTE_ICMP6_ECHO_REPLY    129
+#define RTE_ND_ROUTER_SOLICIT   133
+#define RTE_ND_ROUTER_ADVERT    134
+#define RTE_ND_NEIGHBOR_SOLICIT 135
+#define RTE_ND_NEIGHBOR_ADVERT  136
 
 #ifdef __cplusplus
 }
-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v6 2/4] net: add new packet dissector
  2024-08-02 18:07 ` [PATCH v6 0/4] Add network packet dissector Stephen Hemminger
  2024-08-02 18:07   ` [PATCH v6 1/4] net: add more icmp types Stephen Hemminger
@ 2024-08-02 18:07   ` Stephen Hemminger
  2024-08-02 18:07   ` [PATCH v6 3/4] test: add test for " Stephen Hemminger
  2024-08-02 18:07   ` [PATCH v6 4/4] test-pmd: add more packet verbose decode options Stephen Hemminger
  3 siblings, 0 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-08-02 18:07 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

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 <stephen@networkplumber.org>
---
 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 <stephen@networkplumber.org>
+ *
+ * Print packets in format similar to tshark.
+ * Output should be one line per mbuf
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <rte_arp.h>
+#include <rte_branch_prediction.h>
+#include <rte_byteorder.h>
+#include <rte_common.h>
+#include <rte_dissect.h>
+#include <rte_ether.h>
+#include <rte_icmp.h>
+#include <rte_ip.h>
+#include <rte_mbuf.h>
+#include <rte_tcp.h>
+#include <rte_udp.h>
+#include <rte_vxlan.h>
+
+/* 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), &eth_hdr->src_addr);
+		rte_ether_format_addr(dbuf, sizeof(dbuf), &eth_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 <stephen@networkplumber.org>
+ */
+
+#ifndef _RTE_NET_DISSECT_H_
+#define _RTE_NET_DISSECT_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <stdint.h>
+
+#include <rte_compat.h>
+
+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;
+};
-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v6 3/4] test: add test for packet dissector
  2024-08-02 18:07 ` [PATCH v6 0/4] Add network packet dissector Stephen Hemminger
  2024-08-02 18:07   ` [PATCH v6 1/4] net: add more icmp types Stephen Hemminger
  2024-08-02 18:07   ` [PATCH v6 2/4] net: add new packet dissector Stephen Hemminger
@ 2024-08-02 18:07   ` Stephen Hemminger
  2024-08-02 18:07   ` [PATCH v6 4/4] test-pmd: add more packet verbose decode options Stephen Hemminger
  3 siblings, 0 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-08-02 18:07 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Add some tests for new packet dissector.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 app/test/meson.build    |   1 +
 app/test/test_dissect.c | 253 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 254 insertions(+)
 create mode 100644 app/test/test_dissect.c

diff --git a/app/test/meson.build b/app/test/meson.build
index e29258e6ec..9cd2051320 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -62,6 +62,7 @@ source_file_deps = {
     'test_debug.c': [],
     'test_devargs.c': ['kvargs'],
     'test_dispatcher.c': ['dispatcher'],
+    'test_dissect.c': ['net'],
     'test_distributor.c': ['distributor'],
     'test_distributor_perf.c': ['distributor'],
     'test_dmadev.c': ['dmadev', 'bus_vdev'],
diff --git a/app/test/test_dissect.c b/app/test/test_dissect.c
new file mode 100644
index 0000000000..0644d644d5
--- /dev/null
+++ b/app/test/test_dissect.c
@@ -0,0 +1,253 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2024 Stephen Hemminger <stephen@networkplumber.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include <rte_bus_vdev.h>
+#include <rte_dissect.h>
+#include <rte_ethdev.h>
+#include <rte_ether.h>
+#include <rte_ip.h>
+#include <rte_mbuf.h>
+#include <rte_net.h>
+#include <rte_random.h>
+#include <rte_udp.h>
+#include <rte_vxlan.h>
+
+#include "test.h"
+
+#ifndef LINE_MAX
+#define LINE_MAX	2048
+#endif
+
+#define TOTAL_PACKETS	100
+#define PACKET_LEN	1000
+#define ETH_IP_UDP_VXLAN_SIZE (sizeof(struct rte_ether_hdr) + \
+			       sizeof(struct rte_ipv4_hdr) + \
+			       sizeof(struct rte_udp_hdr) + \
+			       sizeof(struct rte_vxlan_hdr))
+
+
+static uint16_t port_id;
+static const char null_dev[] = "net_null0";
+
+static void
+add_header(struct rte_mbuf *mb, uint32_t plen,
+	   rte_be16_t src_port, rte_be16_t dst_port)
+{
+	struct {
+		struct rte_ether_hdr eth;
+		struct rte_ipv4_hdr ip;
+		struct rte_udp_hdr udp;
+	} pkt = {
+		.eth = {
+			.dst_addr.addr_bytes = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
+			.ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4),
+		},
+		.ip = {
+			.version_ihl = RTE_IPV4_VHL_DEF,
+			.time_to_live = 1,
+			.next_proto_id = IPPROTO_UDP,
+			.src_addr = rte_cpu_to_be_32(RTE_IPV4_LOOPBACK),
+			.dst_addr = rte_cpu_to_be_32(RTE_IPV4_BROADCAST),
+		},
+		.udp = {
+			.dst_port = dst_port,
+			.src_port = src_port,
+		},
+	};
+
+	rte_eth_random_addr(pkt.eth.src_addr.addr_bytes);
+
+	plen -= sizeof(struct rte_ether_hdr);
+	pkt.ip.total_length = rte_cpu_to_be_16(plen);
+	pkt.ip.hdr_checksum = rte_ipv4_cksum(&pkt.ip);
+
+	plen -= sizeof(struct rte_ipv4_hdr);
+	pkt.udp.dgram_len = rte_cpu_to_be_16(plen);
+
+	/* Copy header into mbuf */
+	memcpy(rte_pktmbuf_append(mb, sizeof(pkt)), &pkt, sizeof(pkt));
+}
+
+static void
+add_vxlan(struct rte_mbuf *mb, rte_be32_t vni)
+{
+	struct rte_vxlan_hdr *vxlan;
+
+	vxlan = (struct rte_vxlan_hdr *)rte_pktmbuf_append(mb, sizeof(*vxlan));
+	memset(vxlan, 0, sizeof(*vxlan));
+	vxlan->flag_i = 1;
+	vxlan->vx_vni = vni;
+}
+
+
+static void
+fill_data(struct rte_mbuf *mb, uint32_t len)
+{
+	uint32_t i;
+	char *ptr = rte_pktmbuf_append(mb, len);
+	char c = '!';
+
+	/* traditional barber pole pattern */
+	for (i = 0; i < len; i++) {
+		ptr[i] = c++;
+		if (c == 0x7f)
+			c = '!';
+	}
+}
+
+static void
+mbuf_prep(struct rte_mbuf *mb, uint8_t buf[], uint32_t buf_len)
+{
+	mb->buf_addr = buf;
+	rte_mbuf_iova_set(mb, (uintptr_t)buf);
+	mb->buf_len = buf_len;
+	rte_mbuf_refcnt_set(mb, 1);
+
+	/* set pool pointer to dummy value, test doesn't use it */
+	mb->pool = (void *)buf;
+
+	rte_pktmbuf_reset(mb);
+}
+
+static int
+test_setup(void)
+{
+	port_id = rte_eth_dev_count_avail();
+
+	/* Make a dummy null device to snoop on */
+	if (rte_vdev_init(null_dev, NULL) != 0) {
+		fprintf(stderr, "Failed to create vdev '%s'\n", null_dev);
+		goto fail;
+	}
+	return 0;
+
+fail:
+	rte_vdev_uninit(null_dev);
+	return -1;
+}
+
+static void
+test_cleanup(void)
+{
+	rte_vdev_uninit(null_dev);
+}
+
+
+static int
+test_simple(void)
+{
+	struct rte_mbuf mb;
+	uint8_t buf[RTE_MBUF_DEFAULT_BUF_SIZE];
+	uint32_t data_len = PACKET_LEN;
+	rte_be16_t src_port = rte_rand_max(UINT16_MAX);
+	const rte_be16_t dst_port = rte_cpu_to_be_16(9); /* Discard port */
+	char obuf[LINE_MAX] = { };
+	char result[LINE_MAX] = { };
+
+	/* make a dummy packet */
+	mbuf_prep(&mb, buf, sizeof(buf));
+	add_header(&mb, data_len, src_port, dst_port);
+	fill_data(&mb, data_len - mb.data_off);
+
+	/* construct the expected result */
+	int len = snprintf(result, sizeof(result),
+			   "127.0.0.1 → 224.0.0.0 UDP 966 %u → 9",
+			   rte_be_to_cpu_16(src_port));
+
+	rte_dissect_mbuf(obuf, sizeof(obuf), &mb, 0);
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(obuf, result, len,
+				      "Dissect string differs:\nexpect \"%s\"\n   got \"%s\"",
+				      result, obuf);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_truncated(void)
+{
+	struct rte_mbuf mb;
+	uint8_t buf[RTE_MBUF_DEFAULT_BUF_SIZE];
+	uint32_t pkt_len, data_len = PACKET_LEN;
+	rte_be16_t dst_port = rte_cpu_to_be_16(RTE_VXLAN_DEFAULT_PORT);
+	char obuf[LINE_MAX];
+
+	/* make a really nested vxlan packet */
+	mbuf_prep(&mb, buf, sizeof(buf));
+	pkt_len = data_len;
+	do {
+		rte_be16_t src_port = rte_rand_max(UINT16_MAX);
+		uint32_t vni = rte_rand_max(1ul << 24);
+
+		add_header(&mb, data_len, src_port, dst_port);
+		add_vxlan(&mb, vni);
+		pkt_len -= ETH_IP_UDP_VXLAN_SIZE;
+	} while (pkt_len > ETH_IP_UDP_VXLAN_SIZE);
+
+	fill_data(&mb, pkt_len);
+
+	/* dissect it but snip off some amount of data */
+	for (unsigned int i = 0; i < TOTAL_PACKETS; i++) {
+		uint32_t snaplen = rte_rand_max(pkt_len);
+
+		rte_dissect_mbuf(obuf, sizeof(obuf), &mb, snaplen);
+	}
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_fuzz(void)
+{
+	struct rte_mbuf mb;
+	uint8_t buf[RTE_MBUF_DEFAULT_BUF_SIZE];
+	uint32_t data_len = PACKET_LEN;
+	const rte_be16_t dst_port = rte_cpu_to_be_16(rte_rand_max(1024));
+	const rte_be16_t src_port = rte_rand_max(UINT16_MAX);
+	char obuf[LINE_MAX];
+
+	/* make a dummy packet */
+	mbuf_prep(&mb, buf, sizeof(buf));
+	add_header(&mb, data_len, src_port, dst_port);
+	fill_data(&mb, data_len - mb.data_off);
+
+	/* randomly flip bits in it */
+	for (unsigned int i = 0; i < TOTAL_PACKETS; i++) {
+		uint32_t bit = rte_rand_max(data_len) * 8;
+		uint8_t *bp = buf + bit / 8;
+		uint8_t mask = 1u << (bit % 8);
+
+		/* twiddle one bit */
+		*bp ^= mask;
+		rte_dissect_mbuf(obuf, sizeof(obuf), &mb, 0);
+		*bp ^= mask;
+	}
+
+	return TEST_SUCCESS;
+}
+
+static struct
+unit_test_suite test_dissect_suite  = {
+	.setup = test_setup,
+	.teardown = test_cleanup,
+	.suite_name = "Test Dissect Unit Test Suite",
+	.unit_test_cases = {
+		TEST_CASE(test_simple),
+		TEST_CASE(test_truncated),
+		TEST_CASE(test_fuzz),
+		TEST_CASES_END()
+	}
+};
+
+static int
+test_dissect(void)
+{
+	return unit_test_suite_runner(&test_dissect_suite);
+}
+
+REGISTER_FAST_TEST(dissect_autotest, true, true, test_dissect);
-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v6 4/4] test-pmd: add more packet verbose decode options
  2024-08-02 18:07 ` [PATCH v6 0/4] Add network packet dissector Stephen Hemminger
                     ` (2 preceding siblings ...)
  2024-08-02 18:07   ` [PATCH v6 3/4] test: add test for " Stephen Hemminger
@ 2024-08-02 18:07   ` Stephen Hemminger
  3 siblings, 0 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-08-02 18:07 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Ori Kam, Aman Singh

The existing verbose levels 1..3 provide a messy multi-line
output per packet. I found this unhelpful when diagnosing many
types of problems like packet flow.

This patch keeps the previous levels and adds two new levels:
4: one line per packet is printed in a format resembling
   tshark output. With addresses and protocol info.

5: dump packet in hex.
   Useful if the driver is messing up the data.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 app/test-pmd/cmdline_flow.c                 |  3 +-
 app/test-pmd/config.c                       | 33 ++++++---
 app/test-pmd/testpmd.h                      | 11 +++
 app/test-pmd/util.c                         | 77 +++++++++++++++++++--
 doc/guides/testpmd_app_ug/testpmd_funcs.rst |  5 +-
 5 files changed, 111 insertions(+), 18 deletions(-)

diff --git a/app/test-pmd/cmdline_flow.c b/app/test-pmd/cmdline_flow.c
index d04280eb3e..a010fcf61a 100644
--- a/app/test-pmd/cmdline_flow.c
+++ b/app/test-pmd/cmdline_flow.c
@@ -14143,7 +14143,8 @@ cmd_set_raw_parsed(const struct buffer *in)
 			upper_layer = proto;
 		}
 	}
-	if (verbose_level & 0x1)
+
+	if (verbose_level > 0)
 		printf("total data size is %zu\n", (*total_size));
 	RTE_ASSERT((*total_size) <= ACTION_RAW_ENCAP_MAX_DATA);
 	memmove(data, (data_tail - (*total_size)), *total_size);
diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c
index 6f0beafa27..b5b5f3b464 100644
--- a/app/test-pmd/config.c
+++ b/app/test-pmd/config.c
@@ -6246,26 +6246,37 @@ configure_rxtx_dump_callbacks(uint16_t verbose)
 		return;
 #endif
 
-	RTE_ETH_FOREACH_DEV(portid)
-	{
-		if (verbose == 1 || verbose > 2)
+	RTE_ETH_FOREACH_DEV(portid) {
+		switch (verbose) {
+		case VERBOSE_OFF:
+			remove_rx_dump_callbacks(portid);
+			remove_tx_dump_callbacks(portid);
+			break;
+		case VERBOSE_RX:
 			add_rx_dump_callbacks(portid);
-		else
+			remove_tx_dump_callbacks(portid);
+			break;
+		case VERBOSE_TX:
+			add_tx_dump_callbacks(portid);
 			remove_rx_dump_callbacks(portid);
-		if (verbose >= 2)
+			break;
+		default:
+			add_rx_dump_callbacks(portid);
 			add_tx_dump_callbacks(portid);
-		else
-			remove_tx_dump_callbacks(portid);
+		}
 	}
 }
 
 void
 set_verbose_level(uint16_t vb_level)
 {
-	printf("Change verbose level from %u to %u\n",
-	       (unsigned int) verbose_level, (unsigned int) vb_level);
-	verbose_level = vb_level;
-	configure_rxtx_dump_callbacks(verbose_level);
+	if (vb_level < VERBOSE_MAX) {
+		printf("Change verbose level from %u to %u\n", verbose_level, vb_level);
+		verbose_level = vb_level;
+		configure_rxtx_dump_callbacks(verbose_level);
+	} else {
+		fprintf(stderr, "Verbose level %u is out of range\n", vb_level);
+	}
 }
 
 void
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
index 9facd7f281..3d7a2b6dac 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -489,6 +489,17 @@ enum dcb_mode_enable
 
 extern uint8_t xstats_hide_zero; /**< Hide zero values for xstats display */
 
+enum verbose_mode {
+	VERBOSE_OFF = 0,
+	VERBOSE_RX,
+	VERBOSE_TX,
+	VERBOSE_BOTH,
+	VERBOSE_DISSECT,
+	VERBOSE_HEX,
+	VERBOSE_MAX
+};
+
+
 /* globals used for configuration */
 extern uint8_t record_core_cycles; /**< Enables measurement of CPU cycles */
 extern uint8_t record_burst_stats; /**< Enables display of RX and TX bursts */
diff --git a/app/test-pmd/util.c b/app/test-pmd/util.c
index bf9b639d95..f277e7f035 100644
--- a/app/test-pmd/util.c
+++ b/app/test-pmd/util.c
@@ -5,9 +5,11 @@
 
 #include <stdio.h>
 
+#include <rte_atomic.h>
 #include <rte_bitops.h>
 #include <rte_net.h>
 #include <rte_mbuf.h>
+#include <rte_dissect.h>
 #include <rte_ether.h>
 #include <rte_vxlan.h>
 #include <rte_ethdev.h>
@@ -16,6 +18,7 @@
 #include "testpmd.h"
 
 #define MAX_STRING_LEN 8192
+#define MAX_DUMP_LEN   1024
 
 #define MKDUMPSTR(buf, buf_size, cur_len, ...) \
 do { \
@@ -67,9 +70,10 @@ get_timestamp(const struct rte_mbuf *mbuf)
 			timestamp_dynfield_offset, rte_mbuf_timestamp_t *);
 }
 
-static inline void
-dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
-	      uint16_t nb_pkts, int is_rx)
+/* More verbose older style packet decode */
+static void
+dump_pkt_verbose(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
+		 uint16_t nb_pkts, int is_rx)
 {
 	struct rte_mbuf  *mb;
 	const struct rte_ether_hdr *eth_hdr;
@@ -90,8 +94,6 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
 	size_t cur_len = 0;
 	uint64_t restore_info_dynflag;
 
-	if (!nb_pkts)
-		return;
 	restore_info_dynflag = rte_flow_restore_info_dynflag();
 	MKDUMPSTR(print_buf, buf_size, cur_len,
 		  "port %u/queue %u: %s %u packets\n", port_id, queue,
@@ -299,6 +301,71 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
 	}
 }
 
+/* Brief tshark style one line output which is
+ * number time_delta Source Destination Protocol len info
+ */
+static void
+dump_pkt_brief(uint16_t port, uint16_t queue, struct rte_mbuf *pkts[], uint16_t nb_pkts, int is_rx)
+{
+	static uint64_t start_cycles;
+	static RTE_ATOMIC(uint64_t) packet_count = 1;
+	uint64_t now;
+	uint64_t count;
+	double interval;
+	uint16_t i;
+
+	now = rte_rdtsc();
+	if (start_cycles == 0) {
+		start_cycles = now;
+		printf("Seq#   Time        Port:Que R Description\n");
+	}
+
+	interval = (double)(now - start_cycles) / (double)rte_get_tsc_hz();
+
+	count = rte_atomic_fetch_add_explicit(&packet_count, nb_pkts, rte_memory_order_relaxed);
+
+	for (i = 0; i < nb_pkts; i++) {
+		const struct rte_mbuf *mb = pkts[i];
+		char str[256];
+
+		rte_dissect_mbuf(str, sizeof(str), mb, 0);
+		printf("%6"PRIu64" %11.9f %4u:%-3u %c %s\n",
+		       count + i, interval, port, queue, is_rx ? 'R' : 'T', str);
+	}
+}
+
+/* Hex dump of packet data */
+static void
+dump_pkt_hex(struct rte_mbuf *pkts[], uint16_t nb_pkts)
+{
+	uint16_t i;
+
+	for (i = 0; i < nb_pkts; i++)
+		rte_pktmbuf_dump(stdout, pkts[i], MAX_DUMP_LEN);
+
+}
+
+static uint16_t
+dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
+	      uint16_t nb_pkts, int is_rx)
+{
+	if (unlikely(nb_pkts == 0))
+		return 0;
+
+	switch (verbose_level) {
+	case VERBOSE_RX ... VERBOSE_BOTH:
+		dump_pkt_verbose(port_id, queue, pkts, nb_pkts, is_rx);
+		break;
+	case VERBOSE_DISSECT:
+		dump_pkt_brief(port_id, queue, pkts, nb_pkts, is_rx);
+		break;
+	case VERBOSE_HEX:
+		dump_pkt_hex(pkts, nb_pkts);
+	}
+	fflush(stdout);
+	return nb_pkts;
+}
+
 uint16_t
 dump_rx_pkts(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
 	     uint16_t nb_pkts, __rte_unused uint16_t max_pkts,
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
index f00ab07605..b9ce7698db 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -677,7 +677,10 @@ Available levels are as following:
 * ``0`` silent except for error.
 * ``1`` fully verbose except for Tx packets.
 * ``2`` fully verbose except for Rx packets.
-* ``> 2`` fully verbose.
+* ``3`` fully verbose except for Tx and Rx packets.
+* ``4`` dissected protocol information for Tx and Rx packets.
+* ``5`` hex dump of packets
+
 
 set log
 ~~~~~~~
-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v7 0/4] Add network packet dissector
  2024-03-12 22:01 [PATCH] test-pmd: add more packet decode options (verbose) Stephen Hemminger
                   ` (6 preceding siblings ...)
  2024-08-02 18:07 ` [PATCH v6 0/4] Add network packet dissector Stephen Hemminger
@ 2024-08-02 19:56 ` Stephen Hemminger
  2024-08-02 19:56   ` [PATCH v7 1/4] net: add more icmp types Stephen Hemminger
                     ` (3 more replies)
  7 siblings, 4 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-08-02 19:56 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

While debugging TAP rte_flow discovered that test pmd verbose output
was confusing and unhelpful. Instead, made a simple dissector that
prints one line per packet like this in test-pmd with verbose level 4.

v7 - change rte_dissect to return number of characters that would
     be printed (same as snprintf). And tests around that.

v6 - validate result of rte_dissect for simple packet

v5 - breakout the additional ICMP types into header
   - fix some decoding bugs in ARP and ICMP

Stephen Hemminger (4):
  net: add more icmp types
  net: add new packet dissector
  test: add test for packet dissector
  test-pmd: add more packet verbose decode options

 app/test-pmd/cmdline_flow.c                 |   3 +-
 app/test-pmd/config.c                       |  33 +-
 app/test-pmd/testpmd.h                      |  11 +
 app/test-pmd/util.c                         |  77 +++-
 app/test/meson.build                        |   1 +
 app/test/test_dissect.c                     | 302 ++++++++++++++
 doc/guides/testpmd_app_ug/testpmd_funcs.rst |   5 +-
 lib/net/meson.build                         |   2 +
 lib/net/rte_dissect.c                       | 428 ++++++++++++++++++++
 lib/net/rte_dissect.h                       |  45 ++
 lib/net/rte_icmp.h                          |  22 +-
 lib/net/version.map                         |   7 +
 12 files changed, 914 insertions(+), 22 deletions(-)
 create mode 100644 app/test/test_dissect.c
 create mode 100644 lib/net/rte_dissect.c
 create mode 100644 lib/net/rte_dissect.h

-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v7 1/4] net: add more icmp types
  2024-08-02 19:56 ` [PATCH v7 0/4] Add network packet dissector Stephen Hemminger
@ 2024-08-02 19:56   ` Stephen Hemminger
  2024-08-02 19:56   ` [PATCH v7 2/4] net: add new packet dissector Stephen Hemminger
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-08-02 19:56 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Add more defines for additional defined ICMP types.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 lib/net/rte_icmp.h | 22 ++++++++++++++++++----
 1 file changed, 18 insertions(+), 4 deletions(-)

diff --git a/lib/net/rte_icmp.h b/lib/net/rte_icmp.h
index 4bf64d70ad..b51b60a6d2 100644
--- a/lib/net/rte_icmp.h
+++ b/lib/net/rte_icmp.h
@@ -54,10 +54,24 @@ struct rte_icmp_hdr {
 } __rte_packed;
 
 /* ICMP packet types */
-#define RTE_IP_ICMP_ECHO_REPLY   0
-#define RTE_IP_ICMP_ECHO_REQUEST 8
-#define RTE_ICMP6_ECHO_REQUEST 128
-#define RTE_ICMP6_ECHO_REPLY   129
+#define RTE_IP_ICMP_ECHO_REPLY      0
+#define RTE_IP_ICMP_DEST_UNREACH    3
+#define RTE_IP_ICMP_SOURCE_QUENCH   4
+#define RTE_IP_ICMP_REDIRECT        5
+#define RTE_IP_ICMP_ECHO_REQUEST    8
+#define RTE_IP_ICMP_TIME_EXCEEDED  11
+#define RTE_IP_ICMP_PARAMETERPROB  12
+#define RTE_IP_ICMP_TIMESTAMP      13
+#define RTE_IP_ICMP_TIMESTAMPREPLY 14
+#define RTE_IP_ICMP_INFO_REQUEST   15
+#define RTE_IP_ICMP_INFO_REPLY     16
+
+#define RTE_ICMP6_ECHO_REQUEST  128
+#define RTE_ICMP6_ECHO_REPLY    129
+#define RTE_ND_ROUTER_SOLICIT   133
+#define RTE_ND_ROUTER_ADVERT    134
+#define RTE_ND_NEIGHBOR_SOLICIT 135
+#define RTE_ND_NEIGHBOR_ADVERT  136
 
 #ifdef __cplusplus
 }
-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v7 2/4] net: add new packet dissector
  2024-08-02 19:56 ` [PATCH v7 0/4] Add network packet dissector Stephen Hemminger
  2024-08-02 19:56   ` [PATCH v7 1/4] net: add more icmp types Stephen Hemminger
@ 2024-08-02 19:56   ` Stephen Hemminger
  2024-08-02 19:56   ` [PATCH v7 3/4] test: add test for " Stephen Hemminger
  2024-08-02 19:56   ` [PATCH v7 4/4] test-pmd: add more packet verbose decode options Stephen Hemminger
  3 siblings, 0 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-08-02 19:56 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

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 <stephen@networkplumber.org>
---
 lib/net/meson.build   |   2 +
 lib/net/rte_dissect.c | 428 ++++++++++++++++++++++++++++++++++++++++++
 lib/net/rte_dissect.h |  45 +++++
 lib/net/version.map   |   7 +
 4 files changed, 482 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..de9e8f4c56
--- /dev/null
+++ b/lib/net/rte_dissect.c
@@ -0,0 +1,428 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2024 Stephen Hemminger <stephen@networkplumber.org>
+ *
+ * Print packets in format similar to tshark.
+ * Output should be one line per mbuf
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <rte_arp.h>
+#include <rte_branch_prediction.h>
+#include <rte_byteorder.h>
+#include <rte_common.h>
+#include <rte_dissect.h>
+#include <rte_ether.h>
+#include <rte_icmp.h>
+#include <rte_ip.h>
+#include <rte_mbuf.h>
+#include <rte_tcp.h>
+#include <rte_udp.h>
+#include <rte_vxlan.h>
+
+/* 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_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 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), &eth_hdr->src_addr);
+		rte_ether_format_addr(dbuf, sizeof(dbuf), &eth_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 <stephen@networkplumber.org>
+ */
+
+#ifndef _RTE_NET_DISSECT_H_
+#define _RTE_NET_DISSECT_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <stdint.h>
+
+#include <rte_compat.h>
+
+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 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;
+};
-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v7 3/4] test: add test for packet dissector
  2024-08-02 19:56 ` [PATCH v7 0/4] Add network packet dissector Stephen Hemminger
  2024-08-02 19:56   ` [PATCH v7 1/4] net: add more icmp types Stephen Hemminger
  2024-08-02 19:56   ` [PATCH v7 2/4] net: add new packet dissector Stephen Hemminger
@ 2024-08-02 19:56   ` Stephen Hemminger
  2024-08-02 19:56   ` [PATCH v7 4/4] test-pmd: add more packet verbose decode options Stephen Hemminger
  3 siblings, 0 replies; 36+ messages in thread
From: Stephen Hemminger @ 2024-08-02 19:56 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger

Add some tests for new packet dissector.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 app/test/meson.build    |   1 +
 app/test/test_dissect.c | 302 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 303 insertions(+)
 create mode 100644 app/test/test_dissect.c

diff --git a/app/test/meson.build b/app/test/meson.build
index e29258e6ec..9cd2051320 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -62,6 +62,7 @@ source_file_deps = {
     'test_debug.c': [],
     'test_devargs.c': ['kvargs'],
     'test_dispatcher.c': ['dispatcher'],
+    'test_dissect.c': ['net'],
     'test_distributor.c': ['distributor'],
     'test_distributor_perf.c': ['distributor'],
     'test_dmadev.c': ['dmadev', 'bus_vdev'],
diff --git a/app/test/test_dissect.c b/app/test/test_dissect.c
new file mode 100644
index 0000000000..08734134d5
--- /dev/null
+++ b/app/test/test_dissect.c
@@ -0,0 +1,302 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2024 Stephen Hemminger <stephen@networkplumber.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include <rte_bus_vdev.h>
+#include <rte_dissect.h>
+#include <rte_ethdev.h>
+#include <rte_ether.h>
+#include <rte_ip.h>
+#include <rte_mbuf.h>
+#include <rte_net.h>
+#include <rte_random.h>
+#include <rte_udp.h>
+#include <rte_vxlan.h>
+
+#include "test.h"
+
+#ifndef LINE_MAX
+#define LINE_MAX	2048
+#endif
+
+#define TOTAL_PACKETS	100
+#define PACKET_LEN	1000
+#define ETH_IP_UDP_VXLAN_SIZE (sizeof(struct rte_ether_hdr) + \
+			       sizeof(struct rte_ipv4_hdr) + \
+			       sizeof(struct rte_udp_hdr) + \
+			       sizeof(struct rte_vxlan_hdr))
+
+
+static uint16_t port_id;
+static const char null_dev[] = "net_null0";
+
+static void
+add_header(struct rte_mbuf *mb, uint32_t plen,
+	   rte_be16_t src_port, rte_be16_t dst_port)
+{
+	struct {
+		struct rte_ether_hdr eth;
+		struct rte_ipv4_hdr ip;
+		struct rte_udp_hdr udp;
+	} pkt = {
+		.eth = {
+			.dst_addr.addr_bytes = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
+			.ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4),
+		},
+		.ip = {
+			.version_ihl = RTE_IPV4_VHL_DEF,
+			.time_to_live = 1,
+			.next_proto_id = IPPROTO_UDP,
+			.src_addr = rte_cpu_to_be_32(RTE_IPV4_LOOPBACK),
+			.dst_addr = rte_cpu_to_be_32(RTE_IPV4_BROADCAST),
+		},
+		.udp = {
+			.dst_port = dst_port,
+			.src_port = src_port,
+		},
+	};
+
+	rte_eth_random_addr(pkt.eth.src_addr.addr_bytes);
+
+	plen -= sizeof(struct rte_ether_hdr);
+	pkt.ip.total_length = rte_cpu_to_be_16(plen);
+	pkt.ip.hdr_checksum = rte_ipv4_cksum(&pkt.ip);
+
+	plen -= sizeof(struct rte_ipv4_hdr);
+	pkt.udp.dgram_len = rte_cpu_to_be_16(plen);
+
+	/* Copy header into mbuf */
+	memcpy(rte_pktmbuf_append(mb, sizeof(pkt)), &pkt, sizeof(pkt));
+}
+
+static void
+add_vxlan(struct rte_mbuf *mb, rte_be32_t vni)
+{
+	struct rte_vxlan_hdr *vxlan;
+
+	vxlan = (struct rte_vxlan_hdr *)rte_pktmbuf_append(mb, sizeof(*vxlan));
+	memset(vxlan, 0, sizeof(*vxlan));
+	vxlan->flag_i = 1;
+	vxlan->vx_vni = vni;
+}
+
+
+static void
+fill_data(struct rte_mbuf *mb, uint32_t len)
+{
+	uint32_t i;
+	char *ptr = rte_pktmbuf_append(mb, len);
+	char c = '!';
+
+	/* traditional barber pole pattern */
+	for (i = 0; i < len; i++) {
+		ptr[i] = c++;
+		if (c == 0x7f)
+			c = '!';
+	}
+}
+
+static void
+mbuf_prep(struct rte_mbuf *mb, uint8_t buf[], uint32_t buf_len)
+{
+	mb->buf_addr = buf;
+	rte_mbuf_iova_set(mb, (uintptr_t)buf);
+	mb->buf_len = buf_len;
+	rte_mbuf_refcnt_set(mb, 1);
+
+	/* set pool pointer to dummy value, test doesn't use it */
+	mb->pool = (void *)buf;
+
+	rte_pktmbuf_reset(mb);
+}
+
+static int
+test_setup(void)
+{
+	port_id = rte_eth_dev_count_avail();
+
+	/* Make a dummy null device to snoop on */
+	if (rte_vdev_init(null_dev, NULL) != 0) {
+		fprintf(stderr, "Failed to create vdev '%s'\n", null_dev);
+		goto fail;
+	}
+	return 0;
+
+fail:
+	rte_vdev_uninit(null_dev);
+	return -1;
+}
+
+static void
+test_cleanup(void)
+{
+	rte_vdev_uninit(null_dev);
+}
+
+
+static int
+test_simple(void)
+{
+	struct rte_mbuf mb;
+	uint8_t buf[RTE_MBUF_DEFAULT_BUF_SIZE];
+	uint32_t data_len = PACKET_LEN;
+	rte_be16_t src_port = rte_rand_max(UINT16_MAX);
+	const rte_be16_t dst_port = rte_cpu_to_be_16(9); /* Discard port */
+	char obuf[LINE_MAX] = { };
+	char result[LINE_MAX] = { };
+	int ret;
+
+	/* make a dummy packet */
+	mbuf_prep(&mb, buf, sizeof(buf));
+	add_header(&mb, data_len, src_port, dst_port);
+	fill_data(&mb, data_len - mb.data_off);
+
+	/* construct the expected result */
+	int len = snprintf(result, sizeof(result),
+			   "127.0.0.1 → 224.0.0.0 UDP 966 %u → 9",
+			   rte_be_to_cpu_16(src_port));
+
+	ret = rte_dissect_mbuf(obuf, sizeof(obuf), &mb, 0);
+	TEST_ASSERT(ret > 0, "Dissect returned: %d", ret);
+
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(obuf, result, len,
+				      "Dissect string differs:\nexpect \"%s\"\n   got \"%s\"",
+				      result, obuf);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_buffer(void)
+{
+	struct rte_mbuf mb;
+	uint8_t buf[RTE_MBUF_DEFAULT_BUF_SIZE];
+	uint32_t data_len = PACKET_LEN;
+	rte_be16_t src_port = rte_rand_max(UINT16_MAX);
+	const rte_be16_t dst_port = rte_cpu_to_be_16(9); /* Discard port */
+	char *obuf = NULL;
+	char result[LINE_MAX] = { };
+	int ret;
+
+	/* make a dummy packet */
+	mbuf_prep(&mb, buf, sizeof(buf));
+	add_header(&mb, data_len, src_port, dst_port);
+	fill_data(&mb, data_len - mb.data_off);
+
+	/* construct the expected result */
+	int len = snprintf(result, sizeof(result),
+			   "127.0.0.1 → 224.0.0.0 UDP 966 %u → 9",
+			   rte_be_to_cpu_16(src_port));
+
+	/* call rte_dissect first to determine buffer length needed. */
+	ret = rte_dissect_mbuf(obuf, 0, &mb, 0);
+	TEST_ASSERT(ret == len, "Dissect with NULL returned %d not %d", ret, len);
+
+	size_t size = (size_t) ret + 1;      /* One extra byte for '\0' */
+	obuf = malloc(size);
+	TEST_ASSERT_NOT_NULL(obuf, "Malloc for buf failed");
+
+	ret = rte_dissect_mbuf(obuf, size, &mb, 0);
+	TEST_ASSERT(ret == len, "Dissect with buffer returned %d not %d", ret, len);
+
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(obuf, result, len,
+				      "Dissect string differs:\nexpect \"%s\"\n   got \"%s\"",
+				      result, obuf);
+	free(obuf);
+	return TEST_SUCCESS;
+}
+
+static int
+test_truncated(void)
+{
+	struct rte_mbuf mb;
+	uint8_t buf[RTE_MBUF_DEFAULT_BUF_SIZE];
+	uint32_t pkt_len, data_len = PACKET_LEN;
+	rte_be16_t dst_port = rte_cpu_to_be_16(RTE_VXLAN_DEFAULT_PORT);
+	char obuf[LINE_MAX];
+	int ret;
+
+	/* make a really nested vxlan packet */
+	mbuf_prep(&mb, buf, sizeof(buf));
+	pkt_len = data_len;
+	do {
+		rte_be16_t src_port = rte_rand_max(UINT16_MAX);
+		uint32_t vni = rte_rand_max(1ul << 24);
+
+		add_header(&mb, data_len, src_port, dst_port);
+		add_vxlan(&mb, vni);
+		pkt_len -= ETH_IP_UDP_VXLAN_SIZE;
+	} while (pkt_len > ETH_IP_UDP_VXLAN_SIZE);
+
+	fill_data(&mb, pkt_len);
+
+	/* dissect it but snip off some amount of data */
+	for (unsigned int i = 0; i < TOTAL_PACKETS; i++) {
+		uint32_t snaplen = rte_rand_max(pkt_len);
+
+		ret = rte_dissect_mbuf(obuf, sizeof(obuf), &mb, snaplen);
+		TEST_ASSERT(ret > 0, "Truncated len %u failed: %d",
+				    snaplen, ret);
+	}
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_fuzz(void)
+{
+	struct rte_mbuf mb;
+	uint8_t buf[RTE_MBUF_DEFAULT_BUF_SIZE];
+	uint32_t data_len = PACKET_LEN;
+	const rte_be16_t dst_port = rte_cpu_to_be_16(rte_rand_max(1024));
+	const rte_be16_t src_port = rte_rand_max(UINT16_MAX);
+	char obuf[LINE_MAX];
+	int ret;
+
+	/* make a dummy packet */
+	mbuf_prep(&mb, buf, sizeof(buf));
+	add_header(&mb, data_len, src_port, dst_port);
+	fill_data(&mb, data_len - mb.data_off);
+
+	/* randomly flip bits in it */
+	for (unsigned int i = 0; i < TOTAL_PACKETS; i++) {
+		uint32_t bit = rte_rand_max(data_len) * 8;
+		uint8_t *bp = buf + bit / 8;
+		uint8_t mask = 1u << (bit % 8);
+
+		/* twiddle one bit */
+		*bp ^= mask;
+		ret = rte_dissect_mbuf(obuf, sizeof(obuf), &mb, 0);
+		TEST_ASSERT(ret > 0, "Fuzz bit %u failed", bit);
+		*bp ^= mask;
+	}
+
+	return TEST_SUCCESS;
+}
+
+static struct
+unit_test_suite test_dissect_suite  = {
+	.setup = test_setup,
+	.teardown = test_cleanup,
+	.suite_name = "Test Dissect Unit Test Suite",
+	.unit_test_cases = {
+		TEST_CASE(test_simple),
+		TEST_CASE(test_buffer),
+		TEST_CASE(test_truncated),
+		TEST_CASE(test_fuzz),
+		TEST_CASES_END()
+	}
+};
+
+static int
+test_dissect(void)
+{
+	return unit_test_suite_runner(&test_dissect_suite);
+}
+
+REGISTER_FAST_TEST(dissect_autotest, true, true, test_dissect);
-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v7 4/4] test-pmd: add more packet verbose decode options
  2024-08-02 19:56 ` [PATCH v7 0/4] Add network packet dissector Stephen Hemminger
                     ` (2 preceding siblings ...)
  2024-08-02 19:56   ` [PATCH v7 3/4] test: add test for " Stephen Hemminger
@ 2024-08-02 19:56   ` Stephen Hemminger
  2024-08-20 13:42     ` Alex Chapman
  3 siblings, 1 reply; 36+ messages in thread
From: Stephen Hemminger @ 2024-08-02 19:56 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger, Ori Kam, Aman Singh

The existing verbose levels 1..3 provide a messy multi-line
output per packet. I found this unhelpful when diagnosing many
types of problems like packet flow.

This patch keeps the previous levels and adds two new levels:
4: one line per packet is printed in a format resembling
   tshark output. With addresses and protocol info.

5: dump packet in hex.
   Useful if the driver is messing up the data.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 app/test-pmd/cmdline_flow.c                 |  3 +-
 app/test-pmd/config.c                       | 33 ++++++---
 app/test-pmd/testpmd.h                      | 11 +++
 app/test-pmd/util.c                         | 77 +++++++++++++++++++--
 doc/guides/testpmd_app_ug/testpmd_funcs.rst |  5 +-
 5 files changed, 111 insertions(+), 18 deletions(-)

diff --git a/app/test-pmd/cmdline_flow.c b/app/test-pmd/cmdline_flow.c
index d04280eb3e..a010fcf61a 100644
--- a/app/test-pmd/cmdline_flow.c
+++ b/app/test-pmd/cmdline_flow.c
@@ -14143,7 +14143,8 @@ cmd_set_raw_parsed(const struct buffer *in)
 			upper_layer = proto;
 		}
 	}
-	if (verbose_level & 0x1)
+
+	if (verbose_level > 0)
 		printf("total data size is %zu\n", (*total_size));
 	RTE_ASSERT((*total_size) <= ACTION_RAW_ENCAP_MAX_DATA);
 	memmove(data, (data_tail - (*total_size)), *total_size);
diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c
index 6f0beafa27..b5b5f3b464 100644
--- a/app/test-pmd/config.c
+++ b/app/test-pmd/config.c
@@ -6246,26 +6246,37 @@ configure_rxtx_dump_callbacks(uint16_t verbose)
 		return;
 #endif
 
-	RTE_ETH_FOREACH_DEV(portid)
-	{
-		if (verbose == 1 || verbose > 2)
+	RTE_ETH_FOREACH_DEV(portid) {
+		switch (verbose) {
+		case VERBOSE_OFF:
+			remove_rx_dump_callbacks(portid);
+			remove_tx_dump_callbacks(portid);
+			break;
+		case VERBOSE_RX:
 			add_rx_dump_callbacks(portid);
-		else
+			remove_tx_dump_callbacks(portid);
+			break;
+		case VERBOSE_TX:
+			add_tx_dump_callbacks(portid);
 			remove_rx_dump_callbacks(portid);
-		if (verbose >= 2)
+			break;
+		default:
+			add_rx_dump_callbacks(portid);
 			add_tx_dump_callbacks(portid);
-		else
-			remove_tx_dump_callbacks(portid);
+		}
 	}
 }
 
 void
 set_verbose_level(uint16_t vb_level)
 {
-	printf("Change verbose level from %u to %u\n",
-	       (unsigned int) verbose_level, (unsigned int) vb_level);
-	verbose_level = vb_level;
-	configure_rxtx_dump_callbacks(verbose_level);
+	if (vb_level < VERBOSE_MAX) {
+		printf("Change verbose level from %u to %u\n", verbose_level, vb_level);
+		verbose_level = vb_level;
+		configure_rxtx_dump_callbacks(verbose_level);
+	} else {
+		fprintf(stderr, "Verbose level %u is out of range\n", vb_level);
+	}
 }
 
 void
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
index 9facd7f281..3d7a2b6dac 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -489,6 +489,17 @@ enum dcb_mode_enable
 
 extern uint8_t xstats_hide_zero; /**< Hide zero values for xstats display */
 
+enum verbose_mode {
+	VERBOSE_OFF = 0,
+	VERBOSE_RX,
+	VERBOSE_TX,
+	VERBOSE_BOTH,
+	VERBOSE_DISSECT,
+	VERBOSE_HEX,
+	VERBOSE_MAX
+};
+
+
 /* globals used for configuration */
 extern uint8_t record_core_cycles; /**< Enables measurement of CPU cycles */
 extern uint8_t record_burst_stats; /**< Enables display of RX and TX bursts */
diff --git a/app/test-pmd/util.c b/app/test-pmd/util.c
index bf9b639d95..f277e7f035 100644
--- a/app/test-pmd/util.c
+++ b/app/test-pmd/util.c
@@ -5,9 +5,11 @@
 
 #include <stdio.h>
 
+#include <rte_atomic.h>
 #include <rte_bitops.h>
 #include <rte_net.h>
 #include <rte_mbuf.h>
+#include <rte_dissect.h>
 #include <rte_ether.h>
 #include <rte_vxlan.h>
 #include <rte_ethdev.h>
@@ -16,6 +18,7 @@
 #include "testpmd.h"
 
 #define MAX_STRING_LEN 8192
+#define MAX_DUMP_LEN   1024
 
 #define MKDUMPSTR(buf, buf_size, cur_len, ...) \
 do { \
@@ -67,9 +70,10 @@ get_timestamp(const struct rte_mbuf *mbuf)
 			timestamp_dynfield_offset, rte_mbuf_timestamp_t *);
 }
 
-static inline void
-dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
-	      uint16_t nb_pkts, int is_rx)
+/* More verbose older style packet decode */
+static void
+dump_pkt_verbose(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
+		 uint16_t nb_pkts, int is_rx)
 {
 	struct rte_mbuf  *mb;
 	const struct rte_ether_hdr *eth_hdr;
@@ -90,8 +94,6 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
 	size_t cur_len = 0;
 	uint64_t restore_info_dynflag;
 
-	if (!nb_pkts)
-		return;
 	restore_info_dynflag = rte_flow_restore_info_dynflag();
 	MKDUMPSTR(print_buf, buf_size, cur_len,
 		  "port %u/queue %u: %s %u packets\n", port_id, queue,
@@ -299,6 +301,71 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
 	}
 }
 
+/* Brief tshark style one line output which is
+ * number time_delta Source Destination Protocol len info
+ */
+static void
+dump_pkt_brief(uint16_t port, uint16_t queue, struct rte_mbuf *pkts[], uint16_t nb_pkts, int is_rx)
+{
+	static uint64_t start_cycles;
+	static RTE_ATOMIC(uint64_t) packet_count = 1;
+	uint64_t now;
+	uint64_t count;
+	double interval;
+	uint16_t i;
+
+	now = rte_rdtsc();
+	if (start_cycles == 0) {
+		start_cycles = now;
+		printf("Seq#   Time        Port:Que R Description\n");
+	}
+
+	interval = (double)(now - start_cycles) / (double)rte_get_tsc_hz();
+
+	count = rte_atomic_fetch_add_explicit(&packet_count, nb_pkts, rte_memory_order_relaxed);
+
+	for (i = 0; i < nb_pkts; i++) {
+		const struct rte_mbuf *mb = pkts[i];
+		char str[256];
+
+		rte_dissect_mbuf(str, sizeof(str), mb, 0);
+		printf("%6"PRIu64" %11.9f %4u:%-3u %c %s\n",
+		       count + i, interval, port, queue, is_rx ? 'R' : 'T', str);
+	}
+}
+
+/* Hex dump of packet data */
+static void
+dump_pkt_hex(struct rte_mbuf *pkts[], uint16_t nb_pkts)
+{
+	uint16_t i;
+
+	for (i = 0; i < nb_pkts; i++)
+		rte_pktmbuf_dump(stdout, pkts[i], MAX_DUMP_LEN);
+
+}
+
+static uint16_t
+dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
+	      uint16_t nb_pkts, int is_rx)
+{
+	if (unlikely(nb_pkts == 0))
+		return 0;
+
+	switch (verbose_level) {
+	case VERBOSE_RX ... VERBOSE_BOTH:
+		dump_pkt_verbose(port_id, queue, pkts, nb_pkts, is_rx);
+		break;
+	case VERBOSE_DISSECT:
+		dump_pkt_brief(port_id, queue, pkts, nb_pkts, is_rx);
+		break;
+	case VERBOSE_HEX:
+		dump_pkt_hex(pkts, nb_pkts);
+	}
+	fflush(stdout);
+	return nb_pkts;
+}
+
 uint16_t
 dump_rx_pkts(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
 	     uint16_t nb_pkts, __rte_unused uint16_t max_pkts,
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
index f00ab07605..b9ce7698db 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -677,7 +677,10 @@ Available levels are as following:
 * ``0`` silent except for error.
 * ``1`` fully verbose except for Tx packets.
 * ``2`` fully verbose except for Rx packets.
-* ``> 2`` fully verbose.
+* ``3`` fully verbose except for Tx and Rx packets.
+* ``4`` dissected protocol information for Tx and Rx packets.
+* ``5`` hex dump of packets
+
 
 set log
 ~~~~~~~
-- 
2.43.0


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH v7 4/4] test-pmd: add more packet verbose decode options
  2024-08-02 19:56   ` [PATCH v7 4/4] test-pmd: add more packet verbose decode options Stephen Hemminger
@ 2024-08-20 13:42     ` Alex Chapman
  2024-08-20 15:54       ` Stephen Hemminger
  0 siblings, 1 reply; 36+ messages in thread
From: Alex Chapman @ 2024-08-20 13:42 UTC (permalink / raw)
  To: Stephen Hemminger, dev
  Cc: Ori Kam, Aman Singh, Luca.Vizzarro, Paul.Szczepanek

Hi Stephen,

I have gone through your patch series and the hexdump option would be 
quite valuable for use in DTS.

However I am currently facing the issue of distinguishing noise packets 
from intentional packets within the verbose output. Prior to your patch, 
the intention was to use the Layer 4 port to distinguish between them, 
however with the hexdump option, the plan is to now use a custom payload.

The one issue is that with verbose level 5 does not print the required 
RSS hash and RSS queue information.

Would it be possible for you to add this to your patch series? 
Otherwise, do you have any ideas and/or solutions to tackle this 
specific problem?

On 8/2/24 20:56, Stephen Hemminger wrote:
<snip>
> +static uint16_t
> +dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
> +	      uint16_t nb_pkts, int is_rx)
> +{
> +	if (unlikely(nb_pkts == 0))
> +		return 0;
> +
> +	switch (verbose_level) {
> +	case VERBOSE_RX ... VERBOSE_BOTH:
> +		dump_pkt_verbose(port_id, queue, pkts, nb_pkts, is_rx);
> +		break;
> +	case VERBOSE_DISSECT:
> +		dump_pkt_brief(port_id, queue, pkts, nb_pkts, is_rx);
> +		break;
> +	case VERBOSE_HEX:
> +		dump_pkt_hex(pkts, nb_pkts);
> +	}
> +	fflush(stdout);
> +	return nb_pkts;
> +}

I have identified the function above that would require altering in 
order to accommodate the additional RSS information.

Thanks,
Alex


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH v7 4/4] test-pmd: add more packet verbose decode options
  2024-08-20 13:42     ` Alex Chapman
@ 2024-08-20 15:54       ` Stephen Hemminger
  2024-08-22  9:04         ` Paul Szczepanek
  0 siblings, 1 reply; 36+ messages in thread
From: Stephen Hemminger @ 2024-08-20 15:54 UTC (permalink / raw)
  To: Alex Chapman; +Cc: dev, Ori Kam, Aman Singh, Luca.Vizzarro, Paul.Szczepanek

On Tue, 20 Aug 2024 14:42:56 +0100
Alex Chapman <alex.chapman@arm.com> wrote:

> Hi Stephen,
> 
> I have gone through your patch series and the hexdump option would be 
> quite valuable for use in DTS.
> 
> However I am currently facing the issue of distinguishing noise packets 
> from intentional packets within the verbose output. Prior to your patch, 
> the intention was to use the Layer 4 port to distinguish between them, 
> however with the hexdump option, the plan is to now use a custom payload.
> 
> The one issue is that with verbose level 5 does not print the required 
> RSS hash and RSS queue information.

The queue is there, in the output. Not sure if the hash matters.
I wanted to keep to tshark format as much as possible.

For DTS, maybe having a yet another JSON verbose format would be best.
Something with mbuf bits in JSON.

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH v7 4/4] test-pmd: add more packet verbose decode options
  2024-08-20 15:54       ` Stephen Hemminger
@ 2024-08-22  9:04         ` Paul Szczepanek
  0 siblings, 0 replies; 36+ messages in thread
From: Paul Szczepanek @ 2024-08-22  9:04 UTC (permalink / raw)
  To: Stephen Hemminger, Alex Chapman
  Cc: nd, dev, Ori Kam, Aman Singh, Luca.Vizzarro

On 20/08/2024 16:54, Stephen Hemminger wrote:
> On Tue, 20 Aug 2024 14:42:56 +0100
> Alex Chapman <alex.chapman@arm.com> wrote:
> 
>> Hi Stephen,
>>
>> I have gone through your patch series and the hexdump option would be 
>> quite valuable for use in DTS.
>>
>> However I am currently facing the issue of distinguishing noise packets 
>> from intentional packets within the verbose output. Prior to your patch, 
>> the intention was to use the Layer 4 port to distinguish between them, 
>> however with the hexdump option, the plan is to now use a custom payload.
>>
>> The one issue is that with verbose level 5 does not print the required 
>> RSS hash and RSS queue information.
> 
> The queue is there, in the output. Not sure if the hash matters.
> I wanted to keep to tshark format as much as possible.
> 

We appreciate that but the RSS info is valuable to us. Seeing as
different enum values offer different info rather than different levels,
maybe we could change the enum to flags

+enum verbose_mode {
+	VERBOSE_OFF = 0,
+	VERBOSE_RX = 0x1,
+	VERBOSE_TX = 0x2,
+	VERBOSE_BOTH = 0x4,
+	VERBOSE_DISSECT = 0x8,
+	VERBOSE_HEX = 0x10
+};

Then the flags can be ORed together:

verbose_flags = VERBOSE_RX | VERBOSE_TX | VERBOSE_HEX | VERBOSE_RSS

And then instead of switch each print is an if that checks
for flag being set in the verbose_flags. This way you only get the RSS
if you request it.

^ permalink raw reply	[flat|nested] 36+ messages in thread

end of thread, other threads:[~2024-08-22  9:05 UTC | newest]

Thread overview: 36+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-03-12 22:01 [PATCH] test-pmd: add more packet decode options (verbose) Stephen Hemminger
2024-03-13 21:49 ` Stephen Hemminger
2024-07-05  7:13 ` David Marchand
2024-07-23  2:44 ` [PATCH v2 0/3] Add packet dissector Stephen Hemminger
2024-07-23  2:44   ` [PATCH v2 1/3] net: add new " Stephen Hemminger
2024-07-23  2:44   ` [PATCH v2 2/3] test: add test for " Stephen Hemminger
2024-07-23  2:44   ` [PATCH v2 3/3] test-pmd: add more packet verbose decode options Stephen Hemminger
2024-07-23 20:33 ` [PATCH v3 0/3] add packet dissector function Stephen Hemminger
2024-07-23 20:33   ` [PATCH v3 1/3] net: add new packet dissector Stephen Hemminger
2024-07-23 20:33   ` [PATCH v3 2/3] test: add test for " Stephen Hemminger
2024-07-23 20:33   ` [PATCH v3 3/3] test-pmd: add more packet verbose decode options Stephen Hemminger
2024-07-24 18:46 ` [PATCH v4 0/3] Add packet dissector Stephen Hemminger
2024-07-24 18:46   ` [PATCH v4 1/3] net: add new " Stephen Hemminger
2024-07-24 18:46   ` [PATCH v4 2/3] test: add test for " Stephen Hemminger
2024-07-24 18:46   ` [PATCH v4 3/3] test-pmd: add more packet verbose decode options Stephen Hemminger
2024-08-01 19:04 ` [PATCH v5 0/4] Add network packet dissector Stephen Hemminger
2024-08-01 19:04   ` [PATCH v5 1/4] net: add more icmp types Stephen Hemminger
2024-08-01 19:04   ` [PATCH v5 2/4] net: add new packet dissector Stephen Hemminger
2024-08-01 19:04   ` [PATCH v5 3/4] test: add test for " Stephen Hemminger
2024-08-02  8:38     ` Bruce Richardson
2024-08-02 15:31       ` Stephen Hemminger
2024-08-02 18:06       ` Stephen Hemminger
2024-08-01 19:04   ` [PATCH v5 4/4] test-pmd: add more packet verbose decode options Stephen Hemminger
2024-08-02 18:07 ` [PATCH v6 0/4] Add network packet dissector Stephen Hemminger
2024-08-02 18:07   ` [PATCH v6 1/4] net: add more icmp types Stephen Hemminger
2024-08-02 18:07   ` [PATCH v6 2/4] net: add new packet dissector Stephen Hemminger
2024-08-02 18:07   ` [PATCH v6 3/4] test: add test for " Stephen Hemminger
2024-08-02 18:07   ` [PATCH v6 4/4] test-pmd: add more packet verbose decode options Stephen Hemminger
2024-08-02 19:56 ` [PATCH v7 0/4] Add network packet dissector Stephen Hemminger
2024-08-02 19:56   ` [PATCH v7 1/4] net: add more icmp types Stephen Hemminger
2024-08-02 19:56   ` [PATCH v7 2/4] net: add new packet dissector Stephen Hemminger
2024-08-02 19:56   ` [PATCH v7 3/4] test: add test for " Stephen Hemminger
2024-08-02 19:56   ` [PATCH v7 4/4] test-pmd: add more packet verbose decode options Stephen Hemminger
2024-08-20 13:42     ` Alex Chapman
2024-08-20 15:54       ` Stephen Hemminger
2024-08-22  9:04         ` Paul Szczepanek

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).