* [PATCH] test-pmd: add more packet decode options (verbose) @ 2024-03-12 22:01 Stephen Hemminger 2024-03-13 21:49 ` Stephen Hemminger ` (9 more replies) 0 siblings, 10 replies; 54+ 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), ð_hdr->src_addr); + rte_ether_format_addr(sbuf, sizeof(dbuf), ð_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] 54+ 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 ` (8 subsequent siblings) 9 siblings, 0 replies; 54+ 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] 54+ 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 ` (7 subsequent siblings) 9 siblings, 0 replies; 54+ 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] 54+ 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 ` (6 subsequent siblings) 9 siblings, 3 replies; 54+ 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] 54+ 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; 54+ 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), ð_hdr->src_addr); + rte_ether_format_addr(sbuf, sizeof(dbuf), ð_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] 54+ 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; 54+ 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] 54+ 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; 54+ 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] 54+ 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 ` (5 subsequent siblings) 9 siblings, 3 replies; 54+ 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] 54+ 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; 54+ 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), ð_hdr->src_addr); + rte_ether_format_addr(sbuf, sizeof(dbuf), ð_hdr->dst_addr); + if (!dissect_print(&buf, &size, "%s → %s ARP ", sbuf, dbuf)) + return; + + dissect_arp(buf, size, mb, offset, dump_len); + break; + + case RTE_ETHER_TYPE_IPV4: + dissect_ipv4(buf, size, mb, offset, dump_len); + break; + + case RTE_ETHER_TYPE_IPV6: + dissect_ipv6(buf, size, mb, offset, dump_len); + break; + + default: + dissect_print(&buf, &size, "ETH %#x ", eth_type); + } +} + +void +rte_dissect_mbuf(char *buf, size_t size, const struct rte_mbuf *m, uint32_t dump_len) +{ + dissect_eth(buf, size, m, 0, dump_len); + + /* trim off trailing spaces */ + char *cp; + while ((cp = strrchr(buf, ' ')) != NULL) { + if (cp[1] != '\0') + break; + *cp = '\0'; + } +} diff --git a/lib/net/rte_dissect.h b/lib/net/rte_dissect.h new file mode 100644 index 0000000000..26af265029 --- /dev/null +++ b/lib/net/rte_dissect.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2024 Stephen Hemminger <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] 54+ 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; 54+ 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] 54+ 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; 54+ 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] 54+ 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 ` (4 subsequent siblings) 9 siblings, 3 replies; 54+ 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] 54+ 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; 54+ 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), ð_hdr->src_addr); + rte_ether_format_addr(sbuf, sizeof(dbuf), ð_hdr->dst_addr); + if (!dissect_print(&buf, &size, "%s → %s ARP ", sbuf, dbuf)) + return; + + dissect_arp(buf, size, mb, offset, dump_len); + break; + + case RTE_ETHER_TYPE_IPV4: + dissect_ipv4(buf, size, mb, offset, dump_len); + break; + + case RTE_ETHER_TYPE_IPV6: + dissect_ipv6(buf, size, mb, offset, dump_len); + break; + + default: + dissect_print(&buf, &size, "ETH %#x ", eth_type); + } +} + +void +rte_dissect_mbuf(char *buf, size_t size, const struct rte_mbuf *m, uint32_t dump_len) +{ + dissect_eth(buf, size, m, 0, dump_len); + + /* trim off trailing spaces */ + char *cp; + while ((cp = strrchr(buf, ' ')) != NULL) { + if (cp[1] != '\0') + break; + *cp = '\0'; + } +} diff --git a/lib/net/rte_dissect.h b/lib/net/rte_dissect.h new file mode 100644 index 0000000000..26af265029 --- /dev/null +++ b/lib/net/rte_dissect.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2024 Stephen Hemminger <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] 54+ 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; 54+ 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] 54+ 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; 54+ 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] 54+ 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 ` (3 subsequent siblings) 9 siblings, 4 replies; 54+ 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] 54+ 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; 54+ 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] 54+ 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; 54+ 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), ð_hdr->src_addr); + rte_ether_format_addr(dbuf, sizeof(dbuf), ð_hdr->dst_addr); + if (!dissect_print(&buf, &size, "%s → %s ARP ", sbuf, dbuf)) + return; + + dissect_arp(buf, size, mb, offset, dump_len); + break; + + case RTE_ETHER_TYPE_IPV4: + dissect_ipv4(buf, size, mb, offset, dump_len); + break; + + case RTE_ETHER_TYPE_IPV6: + dissect_ipv6(buf, size, mb, offset, dump_len); + break; + + default: + dissect_print(&buf, &size, "ETH %#x ", eth_type); + } +} + +void +rte_dissect_mbuf(char *buf, size_t size, const struct rte_mbuf *m, uint32_t dump_len) +{ + dissect_eth(buf, size, m, 0, dump_len); + + /* trim off trailing spaces */ + char *cp; + while ((cp = strrchr(buf, ' ')) != NULL) { + if (cp[1] != '\0') + break; + *cp = '\0'; + } +} diff --git a/lib/net/rte_dissect.h b/lib/net/rte_dissect.h new file mode 100644 index 0000000000..26af265029 --- /dev/null +++ b/lib/net/rte_dissect.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2024 Stephen Hemminger <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] 54+ 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; 54+ 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] 54+ 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; 54+ 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] 54+ 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; 54+ 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] 54+ 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; 54+ 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] 54+ 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; 54+ 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] 54+ 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 ` (2 subsequent siblings) 9 siblings, 4 replies; 54+ 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] 54+ 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; 54+ 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] 54+ 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; 54+ 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), ð_hdr->src_addr); + rte_ether_format_addr(dbuf, sizeof(dbuf), ð_hdr->dst_addr); + if (!dissect_print(&buf, &size, "%s → %s ARP ", sbuf, dbuf)) + return; + + dissect_arp(buf, size, mb, offset, dump_len); + break; + + case RTE_ETHER_TYPE_IPV4: + dissect_ipv4(buf, size, mb, offset, dump_len); + break; + + case RTE_ETHER_TYPE_IPV6: + dissect_ipv6(buf, size, mb, offset, dump_len); + break; + + default: + dissect_print(&buf, &size, "ETH %#x ", eth_type); + } +} + +void +rte_dissect_mbuf(char *buf, size_t size, const struct rte_mbuf *m, uint32_t dump_len) +{ + dissect_eth(buf, size, m, 0, dump_len); + + /* trim off trailing spaces */ + char *cp; + while ((cp = strrchr(buf, ' ')) != NULL) { + if (cp[1] != '\0') + break; + *cp = '\0'; + } +} diff --git a/lib/net/rte_dissect.h b/lib/net/rte_dissect.h new file mode 100644 index 0000000000..26af265029 --- /dev/null +++ b/lib/net/rte_dissect.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2024 Stephen Hemminger <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] 54+ 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; 54+ 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] 54+ 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; 54+ 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] 54+ 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) 2024-09-17 3:27 ` [PATCH v8 0/7] Test-pmd packet decode enhancements Stephen Hemminger 2024-09-28 16:18 ` [PATCH v9 0/8] test-pmd packet decoding enhancements Stephen Hemminger 9 siblings, 4 replies; 54+ 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] 54+ 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; 54+ 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] 54+ 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; 54+ 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), ð_hdr->src_addr); + rte_ether_format_addr(dbuf, sizeof(dbuf), ð_hdr->dst_addr); + count += dissect_print(&buf, &size, "%s → %s ARP ", sbuf, dbuf); + count += dissect_arp(buf, size, mb, offset, dump_len); + break; + + case RTE_ETHER_TYPE_IPV4: + count += dissect_ipv4(buf, size, mb, offset, dump_len); + break; + + case RTE_ETHER_TYPE_IPV6: + count += dissect_ipv6(buf, size, mb, offset, dump_len); + break; + + default: + count += dissect_print(&buf, &size, "ETH %#x ", eth_type); + } + + return count; +} + +int +rte_dissect_mbuf(char *buf, size_t size, const struct rte_mbuf *m, uint32_t dump_len) +{ + int count; + + count = dissect_eth(buf, size, m, 0, dump_len); + if (count <= 0) + return count; + + /* output was truncated, but redact the trailing blank */ + if (count >= (int)size) + return count - 1; + + if (buf[count] == ' ') + buf[count--] = '\0'; + + return count; +} diff --git a/lib/net/rte_dissect.h b/lib/net/rte_dissect.h new file mode 100644 index 0000000000..343e9daa4d --- /dev/null +++ b/lib/net/rte_dissect.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2024 Stephen Hemminger <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] 54+ 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; 54+ 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] 54+ 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; 54+ 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] 54+ 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; 54+ 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] 54+ 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; 54+ 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] 54+ 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 2024-09-28 18:36 ` Stephen Hemminger 0 siblings, 1 reply; 54+ 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] 54+ messages in thread
* Re: [PATCH v7 4/4] test-pmd: add more packet verbose decode options 2024-08-22 9:04 ` Paul Szczepanek @ 2024-09-28 18:36 ` Stephen Hemminger 0 siblings, 0 replies; 54+ messages in thread From: Stephen Hemminger @ 2024-09-28 18:36 UTC (permalink / raw) To: Paul Szczepanek; +Cc: Alex Chapman, nd, dev, Ori Kam, Aman Singh, Luca.Vizzarro On Thu, 22 Aug 2024 10:04:55 +0100 Paul Szczepanek <paul.szczepanek@arm.com> wrote: > 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 See new patch series, it also has JSON output. ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v8 0/7] Test-pmd packet decode enhancements 2024-03-12 22:01 [PATCH] test-pmd: add more packet decode options (verbose) Stephen Hemminger ` (7 preceding siblings ...) 2024-08-02 19:56 ` [PATCH v7 0/4] Add network packet dissector Stephen Hemminger @ 2024-09-17 3:27 ` Stephen Hemminger 2024-09-17 3:27 ` [PATCH v8 1/7] net: add more icmp types Stephen Hemminger ` (6 more replies) 2024-09-28 16:18 ` [PATCH v9 0/8] test-pmd packet decoding enhancements Stephen Hemminger 9 siblings, 7 replies; 54+ messages in thread From: Stephen Hemminger @ 2024-09-17 3:27 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. The new commands are: testpmd> set output /tmp/packet.log testpmd> set format dissect To enable packet logging need to use: testpmd> set verbose 3 v8 - added JSON output format, and ability to redirect packet log, and don't overload verbose option. 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 (7): net: add more icmp types net: add new packet dissector test: add test for packet dissector test-pmd: add option to redirect packet log test-pmd: add hex decode test-pmd: add packet dissect format test-pmd: add a JSON packet output app/test-pmd/cmdline.c | 81 ++++ app/test-pmd/config.c | 47 +++ app/test-pmd/testpmd.c | 4 + app/test-pmd/testpmd.h | 12 + app/test-pmd/util.c | 225 +++++++++- app/test/meson.build | 1 + app/test/test_dissect.c | 302 ++++++++++++++ doc/guides/testpmd_app_ug/testpmd_funcs.rst | 22 + 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 + 13 files changed, 1187 insertions(+), 11 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.45.2 ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v8 1/7] net: add more icmp types 2024-09-17 3:27 ` [PATCH v8 0/7] Test-pmd packet decode enhancements Stephen Hemminger @ 2024-09-17 3:27 ` Stephen Hemminger 2024-09-17 3:27 ` [PATCH v8 2/7] net: add new packet dissector Stephen Hemminger ` (5 subsequent siblings) 6 siblings, 0 replies; 54+ messages in thread From: Stephen Hemminger @ 2024-09-17 3:27 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.45.2 ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v8 2/7] net: add new packet dissector 2024-09-17 3:27 ` [PATCH v8 0/7] Test-pmd packet decode enhancements Stephen Hemminger 2024-09-17 3:27 ` [PATCH v8 1/7] net: add more icmp types Stephen Hemminger @ 2024-09-17 3:27 ` Stephen Hemminger 2024-09-17 3:28 ` [PATCH v8 3/7] test: add test for " Stephen Hemminger ` (4 subsequent siblings) 6 siblings, 0 replies; 54+ messages in thread From: Stephen Hemminger @ 2024-09-17 3:27 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), ð_hdr->src_addr); + rte_ether_format_addr(dbuf, sizeof(dbuf), ð_hdr->dst_addr); + count += dissect_print(&buf, &size, "%s → %s ARP ", sbuf, dbuf); + count += dissect_arp(buf, size, mb, offset, dump_len); + break; + + case RTE_ETHER_TYPE_IPV4: + count += dissect_ipv4(buf, size, mb, offset, dump_len); + break; + + case RTE_ETHER_TYPE_IPV6: + count += dissect_ipv6(buf, size, mb, offset, dump_len); + break; + + default: + count += dissect_print(&buf, &size, "ETH %#x ", eth_type); + } + + return count; +} + +int +rte_dissect_mbuf(char *buf, size_t size, const struct rte_mbuf *m, uint32_t dump_len) +{ + int count; + + count = dissect_eth(buf, size, m, 0, dump_len); + if (count <= 0) + return count; + + /* output was truncated, but redact the trailing blank */ + if (count >= (int)size) + return count - 1; + + if (buf[count] == ' ') + buf[count--] = '\0'; + + return count; +} diff --git a/lib/net/rte_dissect.h b/lib/net/rte_dissect.h new file mode 100644 index 0000000000..343e9daa4d --- /dev/null +++ b/lib/net/rte_dissect.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2024 Stephen Hemminger <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 bec4ce23ea..9a27339df5 100644 --- a/lib/net/version.map +++ b/lib/net/version.map @@ -12,3 +12,10 @@ DPDK_25 { local: *; }; + +EXPERIMENTAL { + global: + + # added in 24.11 + rte_dissect_mbuf; +}; -- 2.45.2 ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v8 3/7] test: add test for packet dissector 2024-09-17 3:27 ` [PATCH v8 0/7] Test-pmd packet decode enhancements Stephen Hemminger 2024-09-17 3:27 ` [PATCH v8 1/7] net: add more icmp types Stephen Hemminger 2024-09-17 3:27 ` [PATCH v8 2/7] net: add new packet dissector Stephen Hemminger @ 2024-09-17 3:28 ` Stephen Hemminger 2024-09-17 3:28 ` [PATCH v8 4/7] test-pmd: add option to redirect packet log Stephen Hemminger ` (3 subsequent siblings) 6 siblings, 0 replies; 54+ messages in thread From: Stephen Hemminger @ 2024-09-17 3:28 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.45.2 ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v8 4/7] test-pmd: add option to redirect packet log 2024-09-17 3:27 ` [PATCH v8 0/7] Test-pmd packet decode enhancements Stephen Hemminger ` (2 preceding siblings ...) 2024-09-17 3:28 ` [PATCH v8 3/7] test: add test for " Stephen Hemminger @ 2024-09-17 3:28 ` Stephen Hemminger 2024-09-17 3:28 ` [PATCH v8 5/7] test-pmd: add hex decode Stephen Hemminger ` (2 subsequent siblings) 6 siblings, 0 replies; 54+ messages in thread From: Stephen Hemminger @ 2024-09-17 3:28 UTC (permalink / raw) To: dev; +Cc: Stephen Hemminger, Aman Singh When running tests in interactive mode, it is useful to be able to redirect the packet decode (verbose output) into a file. Signed-off-by: Stephen Hemminger <stephen@networkplumber.org> --- app/test-pmd/cmdline.c | 41 +++++++++++++++++++++ app/test-pmd/config.c | 23 ++++++++++++ app/test-pmd/testpmd.c | 3 ++ app/test-pmd/testpmd.h | 3 ++ app/test-pmd/util.c | 11 +++++- doc/guides/testpmd_app_ug/testpmd_funcs.rst | 7 ++++ 6 files changed, 86 insertions(+), 2 deletions(-) diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c index b7759e38a8..7c6ab191de 100644 --- a/app/test-pmd/cmdline.c +++ b/app/test-pmd/cmdline.c @@ -302,6 +302,9 @@ static void cmd_help_long_parsed(void *parsed_result, "set verbose (level)\n" " Set the debug verbosity level X.\n\n" + "set output (filename)\n" + " Set the packet debug log file\n\n" + "set log global|(type) (level)\n" " Set the log level.\n\n" @@ -3853,6 +3856,43 @@ static cmdline_parse_inst_t cmd_set_numbers = { }, }; + +/* *** SET OUTPUT FILENAME *** */ +struct cmd_set_output_result { + cmdline_fixed_string_t set; + cmdline_fixed_string_t output; + cmdline_fixed_string_t filename; +}; + +static void +cmd_set_output_parsed(void *parsed_result, + __rte_unused struct cmdline *cl, + __rte_unused void *data) +{ + struct cmd_set_output_result *res = parsed_result; + + set_output_file(res->filename); +} + +static cmdline_parse_token_string_t cmd_set_output_set = + TOKEN_STRING_INITIALIZER(struct cmd_set_output_result, set, "set"); +static cmdline_parse_token_string_t cmd_set_output_output = + TOKEN_STRING_INITIALIZER(struct cmd_set_output_result, output, "output"); +static cmdline_parse_token_string_t cmd_set_output_name = + TOKEN_STRING_INITIALIZER(struct cmd_set_output_result, filename, NULL); + +static cmdline_parse_inst_t cmd_set_output = { + .f = cmd_set_output_parsed, + .data = NULL, + .help_str = "set output <filename>", + .tokens = { + (void *)&cmd_set_output_set, + (void *)&cmd_set_output_output, + (void *)&cmd_set_output_name, + NULL, + }, +}; + /* *** SET LOG LEVEL CONFIGURATION *** */ struct cmd_set_log_result { @@ -13166,6 +13206,7 @@ static cmdline_parse_ctx_t builtin_ctx[] = { (cmdline_parse_inst_t *)&cmd_reset, (cmdline_parse_inst_t *)&cmd_set_numbers, (cmdline_parse_inst_t *)&cmd_set_log, + (cmdline_parse_inst_t *)&cmd_set_output, (cmdline_parse_inst_t *)&cmd_set_rxoffs, (cmdline_parse_inst_t *)&cmd_set_rxpkts, (cmdline_parse_inst_t *)&cmd_set_rxhdrs, diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c index 6f0beafa27..bfc5a1898b 100644 --- a/app/test-pmd/config.c +++ b/app/test-pmd/config.c @@ -6268,6 +6268,29 @@ set_verbose_level(uint16_t vb_level) configure_rxtx_dump_callbacks(verbose_level); } +void +set_output_file(const char *filename) +{ + FILE *outf, *oldf; + + if (!strcmp(filename, "-")) { + outf = stdout; + } else { + outf = fopen(filename, "a"); + if (outf == NULL) { + perror(filename); + return; + } + } + + oldf = rte_atomic_exchange_explicit(&output_file, outf, rte_memory_order_seq_cst); + if (oldf != NULL && oldf != stdout) { + /* make sure other threads are not mid print */ + rte_delay_us_sleep(US_PER_S/10); + fclose(oldf); + } +} + void vlan_extend_set(portid_t port_id, int on) { diff --git a/app/test-pmd/testpmd.c b/app/test-pmd/testpmd.c index b1401136e4..7790ba6ce0 100644 --- a/app/test-pmd/testpmd.c +++ b/app/test-pmd/testpmd.c @@ -99,6 +99,7 @@ #define EXTBUF_ZONE_SIZE (RTE_PGSIZE_2M - 4 * RTE_CACHE_LINE_SIZE) uint16_t verbose_level = 0; /**< Silent by default. */ +RTE_ATOMIC(FILE *) output_file; /**< log to console by default. */ int testpmd_logtype; /**< Log type for testpmd logs */ /* use main core for command line ? */ @@ -4543,6 +4544,8 @@ main(int argc, char** argv) rte_exit(EXIT_FAILURE, "Cannot register log type"); rte_log_set_level(testpmd_logtype, RTE_LOG_DEBUG); + output_file = stdout; + diag = rte_eal_init(argc, argv); if (diag < 0) rte_exit(EXIT_FAILURE, "Cannot init EAL: %s\n", diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h index 9facd7f281..d92cb743dd 100644 --- a/app/test-pmd/testpmd.h +++ b/app/test-pmd/testpmd.h @@ -17,6 +17,7 @@ #include <rte_ethdev.h> #include <rte_flow.h> #include <rte_mbuf_dyn.h> +#include <rte_stdatomic.h> #include <cmdline.h> #include <cmdline_parse.h> @@ -493,6 +494,7 @@ extern uint8_t xstats_hide_zero; /**< Hide zero values for xstats display */ extern uint8_t record_core_cycles; /**< Enables measurement of CPU cycles */ extern uint8_t record_burst_stats; /**< Enables display of RX and TX bursts */ extern uint16_t verbose_level; /**< Drives messages being displayed, if any. */ +extern RTE_ATOMIC(FILE *) output_file; /**< Where packet data is written */ extern int testpmd_logtype; /**< Log type for testpmd logs */ extern uint8_t interactive; extern uint8_t auto_start; @@ -1102,6 +1104,7 @@ void set_xstats_hide_zero(uint8_t on_off); void set_record_core_cycles(uint8_t on_off); void set_record_burst_stats(uint8_t on_off); void set_verbose_level(uint16_t vb_level); +void set_output_file(const char *filename); void set_rx_pkt_segments(unsigned int *seg_lengths, unsigned int nb_segs); void set_rx_pkt_hdrs(unsigned int *seg_protos, unsigned int nb_segs); void show_rx_pkt_hdrs(void); diff --git a/app/test-pmd/util.c b/app/test-pmd/util.c index bf9b639d95..2446687090 100644 --- a/app/test-pmd/util.c +++ b/app/test-pmd/util.c @@ -89,9 +89,15 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[], size_t buf_size = MAX_STRING_LEN; size_t cur_len = 0; uint64_t restore_info_dynflag; + FILE *outf; if (!nb_pkts) return; + + outf = rte_atomic_load_explicit(&output_file, rte_memory_order_relaxed); + if (!outf) + 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, @@ -292,11 +298,12 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[], MKDUMPSTR(print_buf, buf_size, cur_len, "INVALID mbuf: %s\n", reason); if (cur_len >= buf_size) - printf("%s ...\n", print_buf); + fprintf(outf, "%s ...\n", print_buf); else - printf("%s", print_buf); + fprintf(outf, "%s", print_buf); cur_len = 0; } + fflush(outf); } uint16_t diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst index f00ab07605..579432e5c7 100644 --- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst +++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst @@ -665,6 +665,13 @@ Reset forwarding to the default configuration:: testpmd> set default +set output +~~~~~~~~~~ + +Redirect the debug log:: + + testpmd> set output /tmp/packet.log + set verbose ~~~~~~~~~~~ -- 2.45.2 ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v8 5/7] test-pmd: add hex decode 2024-09-17 3:27 ` [PATCH v8 0/7] Test-pmd packet decode enhancements Stephen Hemminger ` (3 preceding siblings ...) 2024-09-17 3:28 ` [PATCH v8 4/7] test-pmd: add option to redirect packet log Stephen Hemminger @ 2024-09-17 3:28 ` Stephen Hemminger 2024-09-17 3:28 ` [PATCH v8 6/7] test-pmd: add packet dissect format Stephen Hemminger 2024-09-17 3:28 ` [PATCH v8 7/7] test-pmd: add a JSON packet output Stephen Hemminger 6 siblings, 0 replies; 54+ messages in thread From: Stephen Hemminger @ 2024-09-17 3:28 UTC (permalink / raw) To: dev; +Cc: Stephen Hemminger, Aman Singh This adds new command: testpmd> set format hex which decodes packet in hex. Signed-off-by: Stephen Hemminger <stephen@networkplumber.org> --- app/test-pmd/cmdline.c | 40 +++++++++++++++++ app/test-pmd/config.c | 11 +++++ app/test-pmd/testpmd.c | 1 + app/test-pmd/testpmd.h | 7 +++ app/test-pmd/util.c | 48 ++++++++++++++++----- doc/guides/testpmd_app_ug/testpmd_funcs.rst | 13 ++++++ 6 files changed, 109 insertions(+), 11 deletions(-) diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c index 7c6ab191de..37cce4868e 100644 --- a/app/test-pmd/cmdline.c +++ b/app/test-pmd/cmdline.c @@ -305,6 +305,9 @@ static void cmd_help_long_parsed(void *parsed_result, "set output (filename)\n" " Set the packet debug log file\n\n" + "set format (verbose|hex)\n" + " Set the format of packet log\\n" + "set log global|(type) (level)\n" " Set the log level.\n\n" @@ -3893,6 +3896,42 @@ static cmdline_parse_inst_t cmd_set_output = { }, }; +/* *** SET FORMAT OF PACKET LOG */ +struct cmd_set_format_result { + cmdline_fixed_string_t set; + cmdline_fixed_string_t format; + cmdline_fixed_string_t value; +}; + +static void +cmd_set_format_parsed(void *parsed_result, + __rte_unused struct cmdline *cl, + __rte_unused void *data) +{ + struct cmd_set_format_result *res = parsed_result; + + set_output_format(res->value); +} + +static cmdline_parse_token_string_t cmd_set_format_set = + TOKEN_STRING_INITIALIZER(struct cmd_set_format_result, set, "set"); +static cmdline_parse_token_string_t cmd_set_format_output = + TOKEN_STRING_INITIALIZER(struct cmd_set_format_result, format, "format"); +static cmdline_parse_token_string_t cmd_set_format_value = + TOKEN_STRING_INITIALIZER(struct cmd_set_format_result, value, "verbose#hex"); + +static cmdline_parse_inst_t cmd_set_format = { + .f = cmd_set_format_parsed, + .data = NULL, + .help_str = "set format verbose|hex", + .tokens = { + (void *)&cmd_set_format_set, + (void *)&cmd_set_format_output, + (void *)&cmd_set_format_value, + NULL, + }, +}; + /* *** SET LOG LEVEL CONFIGURATION *** */ struct cmd_set_log_result { @@ -13207,6 +13246,7 @@ static cmdline_parse_ctx_t builtin_ctx[] = { (cmdline_parse_inst_t *)&cmd_set_numbers, (cmdline_parse_inst_t *)&cmd_set_log, (cmdline_parse_inst_t *)&cmd_set_output, + (cmdline_parse_inst_t *)&cmd_set_format, (cmdline_parse_inst_t *)&cmd_set_rxoffs, (cmdline_parse_inst_t *)&cmd_set_rxpkts, (cmdline_parse_inst_t *)&cmd_set_rxhdrs, diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c index bfc5a1898b..f30bdfc7ff 100644 --- a/app/test-pmd/config.c +++ b/app/test-pmd/config.c @@ -6268,6 +6268,17 @@ set_verbose_level(uint16_t vb_level) configure_rxtx_dump_callbacks(verbose_level); } +void +set_output_format(const char *mode) +{ + if (!strcmp(mode, "verbose")) + output_format = OUTPUT_MODE_VERBOSE; + else if (!strcmp(mode, "hex")) + output_format = OUTPUT_MODE_HEX; + else + fprintf(stderr, "Unknown output format '%s'\n", mode); +} + void set_output_file(const char *filename) { diff --git a/app/test-pmd/testpmd.c b/app/test-pmd/testpmd.c index 7790ba6ce0..f4fd51e46f 100644 --- a/app/test-pmd/testpmd.c +++ b/app/test-pmd/testpmd.c @@ -100,6 +100,7 @@ uint16_t verbose_level = 0; /**< Silent by default. */ RTE_ATOMIC(FILE *) output_file; /**< log to console by default. */ +enum output_mode output_format; /**< default to original mode. */ int testpmd_logtype; /**< Log type for testpmd logs */ /* use main core for command line ? */ diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h index d92cb743dd..94e8f59ef0 100644 --- a/app/test-pmd/testpmd.h +++ b/app/test-pmd/testpmd.h @@ -488,6 +488,11 @@ enum dcb_mode_enable DCB_ENABLED }; +enum output_mode { + OUTPUT_MODE_VERBOSE = 0, + OUTPUT_MODE_HEX, +}; + extern uint8_t xstats_hide_zero; /**< Hide zero values for xstats display */ /* globals used for configuration */ @@ -495,6 +500,7 @@ extern uint8_t record_core_cycles; /**< Enables measurement of CPU cycles */ extern uint8_t record_burst_stats; /**< Enables display of RX and TX bursts */ extern uint16_t verbose_level; /**< Drives messages being displayed, if any. */ extern RTE_ATOMIC(FILE *) output_file; /**< Where packet data is written */ +extern enum output_mode output_format; /**< Format of packet decode */ extern int testpmd_logtype; /**< Log type for testpmd logs */ extern uint8_t interactive; extern uint8_t auto_start; @@ -1104,6 +1110,7 @@ void set_xstats_hide_zero(uint8_t on_off); void set_record_core_cycles(uint8_t on_off); void set_record_burst_stats(uint8_t on_off); void set_verbose_level(uint16_t vb_level); +void set_output_format(const char *mode); void set_output_file(const char *filename); void set_rx_pkt_segments(unsigned int *seg_lengths, unsigned int nb_segs); void set_rx_pkt_hdrs(unsigned int *seg_protos, unsigned int nb_segs); diff --git a/app/test-pmd/util.c b/app/test-pmd/util.c index 2446687090..3f05c37e2b 100644 --- a/app/test-pmd/util.c +++ b/app/test-pmd/util.c @@ -16,6 +16,7 @@ #include "testpmd.h" #define MAX_STRING_LEN 8192 +#define MAX_DUMP_LEN 1024 #define MKDUMPSTR(buf, buf_size, cur_len, ...) \ do { \ @@ -67,9 +68,9 @@ 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) +static void +dump_pkt_verbose(FILE *outf, 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; @@ -89,14 +90,6 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[], size_t buf_size = MAX_STRING_LEN; size_t cur_len = 0; uint64_t restore_info_dynflag; - FILE *outf; - - if (!nb_pkts) - return; - - outf = rte_atomic_load_explicit(&output_file, rte_memory_order_relaxed); - if (!outf) - return; restore_info_dynflag = rte_flow_restore_info_dynflag(); MKDUMPSTR(print_buf, buf_size, cur_len, @@ -303,6 +296,39 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[], fprintf(outf, "%s", print_buf); cur_len = 0; } +} + +static void +dump_pkt_hex(FILE *outf, struct rte_mbuf *pkts[], uint16_t nb_pkts) +{ + for (uint16_t i = 0; i < nb_pkts; i++) + rte_pktmbuf_dump(outf, pkts[i], MAX_DUMP_LEN); +} + + +static void +dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[], + uint16_t nb_pkts, int is_rx) +{ + FILE *outf; + + if (!nb_pkts) + return; + + outf = rte_atomic_load_explicit(&output_file, rte_memory_order_relaxed); + if (unlikely(!outf)) + return; + + switch (output_format) { + case OUTPUT_MODE_VERBOSE: + dump_pkt_verbose(outf, port_id, queue, pkts, nb_pkts, is_rx); + return; + case OUTPUT_MODE_HEX: + dump_pkt_hex(outf, pkts, nb_pkts); + break; + default: + return; + } fflush(outf); } diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst index 579432e5c7..9406af3225 100644 --- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst +++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst @@ -672,6 +672,19 @@ Redirect the debug log:: testpmd> set output /tmp/packet.log +set format +~~~~~~~~~~ + +Chose the output format for packet debug log:: + + testpmd> set format verbose|hex + +Available formats are: + +* ``verbose`` print the packet meta data information +* ``hex`` print the mbuf flags and data in hex + + set verbose ~~~~~~~~~~~ -- 2.45.2 ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v8 6/7] test-pmd: add packet dissect format 2024-09-17 3:27 ` [PATCH v8 0/7] Test-pmd packet decode enhancements Stephen Hemminger ` (4 preceding siblings ...) 2024-09-17 3:28 ` [PATCH v8 5/7] test-pmd: add hex decode Stephen Hemminger @ 2024-09-17 3:28 ` Stephen Hemminger 2024-09-17 3:28 ` [PATCH v8 7/7] test-pmd: add a JSON packet output Stephen Hemminger 6 siblings, 0 replies; 54+ messages in thread From: Stephen Hemminger @ 2024-09-17 3:28 UTC (permalink / raw) To: dev; +Cc: Stephen Hemminger, Aman Singh Add ability to get decode packet in summary tshark style format. Signed-off-by: Stephen Hemminger <stephen@networkplumber.org> --- app/test-pmd/cmdline.c | 6 ++-- app/test-pmd/config.c | 22 +++++++++---- app/test-pmd/testpmd.h | 1 + app/test-pmd/util.c | 36 +++++++++++++++++++++ doc/guides/testpmd_app_ug/testpmd_funcs.rst | 3 +- 5 files changed, 58 insertions(+), 10 deletions(-) diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c index 37cce4868e..72be5a0c06 100644 --- a/app/test-pmd/cmdline.c +++ b/app/test-pmd/cmdline.c @@ -305,7 +305,7 @@ static void cmd_help_long_parsed(void *parsed_result, "set output (filename)\n" " Set the packet debug log file\n\n" - "set format (verbose|hex)\n" + "set format (dissect|hex|verbose)\n" " Set the format of packet log\\n" "set log global|(type) (level)\n" @@ -3918,12 +3918,12 @@ static cmdline_parse_token_string_t cmd_set_format_set = static cmdline_parse_token_string_t cmd_set_format_output = TOKEN_STRING_INITIALIZER(struct cmd_set_format_result, format, "format"); static cmdline_parse_token_string_t cmd_set_format_value = - TOKEN_STRING_INITIALIZER(struct cmd_set_format_result, value, "verbose#hex"); + TOKEN_STRING_INITIALIZER(struct cmd_set_format_result, value, "dissect#hex#verbose"); static cmdline_parse_inst_t cmd_set_format = { .f = cmd_set_format_parsed, .data = NULL, - .help_str = "set format verbose|hex", + .help_str = "set format dissect|hex|verbose", .tokens = { (void *)&cmd_set_format_set, (void *)&cmd_set_format_output, diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c index f30bdfc7ff..c9b3eb7c2b 100644 --- a/app/test-pmd/config.c +++ b/app/test-pmd/config.c @@ -6271,12 +6271,22 @@ set_verbose_level(uint16_t vb_level) void set_output_format(const char *mode) { - if (!strcmp(mode, "verbose")) - output_format = OUTPUT_MODE_VERBOSE; - else if (!strcmp(mode, "hex")) - output_format = OUTPUT_MODE_HEX; - else - fprintf(stderr, "Unknown output format '%s'\n", mode); + static const char *formats[] = { + [OUTPUT_MODE_VERBOSE] = "verbose", + [OUTPUT_MODE_HEX] = "hex", + [OUTPUT_MODE_DISSECT] = "dissect", + }; + + printf("Change output format from %s to %s\n", formats[output_format], mode); + + for (unsigned int i = 0; i < RTE_DIM(formats); i++) { + if (strcasecmp(mode, formats[i]) == 0) { + output_format = i; + return; + } + } + + fprintf(stderr, "Unknown output format '%s'\n", mode); } void diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h index 94e8f59ef0..66b0317b61 100644 --- a/app/test-pmd/testpmd.h +++ b/app/test-pmd/testpmd.h @@ -491,6 +491,7 @@ enum dcb_mode_enable enum output_mode { OUTPUT_MODE_VERBOSE = 0, OUTPUT_MODE_HEX, + OUTPUT_MODE_DISSECT, }; extern uint8_t xstats_hide_zero; /**< Hide zero values for xstats display */ diff --git a/app/test-pmd/util.c b/app/test-pmd/util.c index 3f05c37e2b..392c444021 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> @@ -305,6 +306,38 @@ dump_pkt_hex(FILE *outf, struct rte_mbuf *pkts[], uint16_t nb_pkts) rte_pktmbuf_dump(outf, pkts[i], MAX_DUMP_LEN); } +/* Brief tshark style one line output which is + * number time_delta Source Destination Protocol len info + */ +static void +dump_pkt_brief(FILE *outf, 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, count; + double interval; + + /* Compute time interval from the first packet received */ + 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(); + + /* Packet counter needs to be thread safe */ + count = rte_atomic_fetch_add_explicit(&packet_count, nb_pkts, rte_memory_order_relaxed); + + for (uint16_t i = 0; i < nb_pkts; i++) { + const struct rte_mbuf *mb = pkts[i]; + char str[256]; + + rte_dissect_mbuf(str, sizeof(str), mb, 0); + fprintf(outf, "%6"PRIu64" %11.9f %4u:%-3u %c %s\n", + count + i, interval, port, queue, is_rx ? 'R' : 'T', str); + } +} static void dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[], @@ -326,6 +359,9 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[], case OUTPUT_MODE_HEX: dump_pkt_hex(outf, pkts, nb_pkts); break; + case OUTPUT_MODE_DISSECT: + dump_pkt_brief(outf, port_id, queue, pkts, nb_pkts, is_rx); + break; default: return; } diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst index 9406af3225..705b3dc3d5 100644 --- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst +++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst @@ -677,12 +677,13 @@ set format Chose the output format for packet debug log:: - testpmd> set format verbose|hex + testpmd> set format dissect|hex|verbose Available formats are: * ``verbose`` print the packet meta data information * ``hex`` print the mbuf flags and data in hex +* ``dissect`` print the packet in tshark summary format set verbose -- 2.45.2 ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v8 7/7] test-pmd: add a JSON packet output 2024-09-17 3:27 ` [PATCH v8 0/7] Test-pmd packet decode enhancements Stephen Hemminger ` (5 preceding siblings ...) 2024-09-17 3:28 ` [PATCH v8 6/7] test-pmd: add packet dissect format Stephen Hemminger @ 2024-09-17 3:28 ` Stephen Hemminger 6 siblings, 0 replies; 54+ messages in thread From: Stephen Hemminger @ 2024-09-17 3:28 UTC (permalink / raw) To: dev; +Cc: Stephen Hemminger, Aman Singh When doing automated testing it is useful to show packet meta data in JSON. Signed-off-by: Stephen Hemminger <stephen@networkplumber.org> --- app/test-pmd/cmdline.c | 6 +- app/test-pmd/config.c | 3 + app/test-pmd/testpmd.h | 1 + app/test-pmd/util.c | 142 ++++++++++++++++++++ doc/guides/testpmd_app_ug/testpmd_funcs.rst | 7 +- 5 files changed, 153 insertions(+), 6 deletions(-) diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c index 72be5a0c06..b06abda3fe 100644 --- a/app/test-pmd/cmdline.c +++ b/app/test-pmd/cmdline.c @@ -305,7 +305,7 @@ static void cmd_help_long_parsed(void *parsed_result, "set output (filename)\n" " Set the packet debug log file\n\n" - "set format (dissect|hex|verbose)\n" + "set format (dissect|hex|json|verbose)\n" " Set the format of packet log\\n" "set log global|(type) (level)\n" @@ -3918,12 +3918,12 @@ static cmdline_parse_token_string_t cmd_set_format_set = static cmdline_parse_token_string_t cmd_set_format_output = TOKEN_STRING_INITIALIZER(struct cmd_set_format_result, format, "format"); static cmdline_parse_token_string_t cmd_set_format_value = - TOKEN_STRING_INITIALIZER(struct cmd_set_format_result, value, "dissect#hex#verbose"); + TOKEN_STRING_INITIALIZER(struct cmd_set_format_result, value, "dissect#hex#json@verbose"); static cmdline_parse_inst_t cmd_set_format = { .f = cmd_set_format_parsed, .data = NULL, - .help_str = "set format dissect|hex|verbose", + .help_str = "set format dissect|hex|json|verbose", .tokens = { (void *)&cmd_set_format_set, (void *)&cmd_set_format_output, diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c index c9b3eb7c2b..71bee7e1c2 100644 --- a/app/test-pmd/config.c +++ b/app/test-pmd/config.c @@ -6275,6 +6275,9 @@ set_output_format(const char *mode) [OUTPUT_MODE_VERBOSE] = "verbose", [OUTPUT_MODE_HEX] = "hex", [OUTPUT_MODE_DISSECT] = "dissect", +#ifdef RTE_HAS_JANSSON + [OUTPUT_MODE_JSON] = "json", +#endif }; printf("Change output format from %s to %s\n", formats[output_format], mode); diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h index 66b0317b61..f8bf9b7126 100644 --- a/app/test-pmd/testpmd.h +++ b/app/test-pmd/testpmd.h @@ -492,6 +492,7 @@ enum output_mode { OUTPUT_MODE_VERBOSE = 0, OUTPUT_MODE_HEX, OUTPUT_MODE_DISSECT, + OUTPUT_MODE_JSON, }; extern uint8_t xstats_hide_zero; /**< Hide zero values for xstats display */ diff --git a/app/test-pmd/util.c b/app/test-pmd/util.c index 392c444021..98a77bbc32 100644 --- a/app/test-pmd/util.c +++ b/app/test-pmd/util.c @@ -299,6 +299,143 @@ dump_pkt_verbose(FILE *outf, uint16_t port_id, uint16_t queue, } } +#ifdef RTE_HAS_JANSSON + +/* Encode offload flags as JSON array */ +static json_t * +encode_ol_flags(uint64_t flags, int is_rx) +{ + json_t *ol_array = json_array(); + unsigned int i; + + if (is_rx) + flags &= ~RTE_MBUF_F_TX_OFFLOAD_MASK; + else + flags &= RTE_MBUF_F_TX_OFFLOAD_MASK; + + for (i = 0; i < 64; i++) { + uint64_t mask = (uint64_t)1 << i; + const char *name; + + if (!(mask & flags)) + continue; + + if (is_rx) + name = rte_get_rx_ol_flag_name(mask); + else + name = rte_get_tx_ol_flag_name(mask); + json_array_append_new(ol_array, json_string(name)); + } + return ol_array; +} + +/* Encode packet type fields as JSON object */ +static json_t * +encode_ptype(uint32_t ptype) +{ + if ((ptype & RTE_PTYPE_ALL_MASK) == RTE_PTYPE_UNKNOWN) + return json_string("UNKNOWN"); + + json_t *ptypes = json_array(); + if (ptype & RTE_PTYPE_L2_MASK) + json_array_append(ptypes, json_string(rte_get_ptype_l2_name(ptype))); + if (ptype & RTE_PTYPE_L3_MASK) + json_array_append(ptypes, json_string(rte_get_ptype_l3_name(ptype))); + if (ptype & RTE_PTYPE_L4_MASK) + json_array_append(ptypes, json_string(rte_get_ptype_l4_name(ptype))); + if (ptype & RTE_PTYPE_TUNNEL_MASK) + json_array_append(ptypes, json_string(rte_get_ptype_tunnel_name(ptype))); + if (ptype & RTE_PTYPE_INNER_L2_MASK) + json_array_append(ptypes, json_string(rte_get_ptype_inner_l2_name(ptype))); + if (ptype & RTE_PTYPE_INNER_L3_MASK) + json_array_append(ptypes, json_string(rte_get_ptype_inner_l3_name(ptype))); + if (ptype & RTE_PTYPE_INNER_L4_MASK) + json_array_append(ptypes, json_string(rte_get_ptype_inner_l4_name(ptype))); + + return ptypes; +} + +static void +dump_pkt_json(FILE *outf, uint16_t port_id, uint16_t queue, + struct rte_mbuf *pkts[], uint16_t nb_pkts, int is_rx) +{ + char buf[256]; + + for (uint16_t i = 0; i < nb_pkts; i++) { + const struct rte_mbuf *mb = pkts[i]; + json_t *jobj = json_object(); + const struct rte_ether_hdr *eth_hdr; + struct rte_ether_hdr _eth_hdr; + uint16_t eth_type; + uint64_t ol_flags = mb->ol_flags; + const char *reason = NULL; + + json_object_set_new(jobj, "port", json_integer(port_id)); + json_object_set_new(jobj, "queue", json_integer(queue)); + json_object_set_new(jobj, "direction", json_string(is_rx ? "receive" : "send")); + + if (rte_mbuf_check(mb, 1, &reason) < 0) + json_object_set_new(jobj, "invalid", json_string(reason)); + + eth_hdr = rte_pktmbuf_read(mb, 0, sizeof(_eth_hdr), &_eth_hdr); + eth_type = RTE_BE_TO_CPU_16(eth_hdr->ether_type); + + rte_ether_format_addr(buf, sizeof(buf), ð_hdr->dst_addr); + json_object_set_new(jobj, "dst", json_string(buf)); + rte_ether_format_addr(buf, sizeof(buf), ð_hdr->src_addr); + json_object_set_new(jobj, "src", json_string(buf)); + + snprintf(buf, sizeof(buf), "0x%04x", eth_type); + json_object_set_new(jobj, "type", json_string(buf)); + + json_object_set_new(jobj, "length", json_integer(mb->pkt_len)); + json_object_set_new(jobj, "nb_segs", json_integer(mb->nb_segs)); + + if (ol_flags & RTE_MBUF_F_RX_RSS_HASH) + json_object_set_new(jobj, "rss_hash", json_integer(mb->hash.rss)); + + if (is_timestamp_enabled(mb)) + json_object_set_new(jobj, "timestamp", json_integer(get_timestamp(mb))); + + if ((is_rx && (ol_flags & RTE_MBUF_F_RX_QINQ) != 0) || + (!is_rx && (ol_flags & RTE_MBUF_F_TX_QINQ) != 0)) { + json_object_set_new(jobj, "vlan_tci", json_integer(mb->vlan_tci)); + json_object_set_new(jobj, "vlan_outer_tci", json_integer(mb->vlan_tci_outer)); + } else if ((is_rx && (ol_flags & RTE_MBUF_F_RX_VLAN) != 0) || + (!is_rx && (ol_flags & RTE_MBUF_F_TX_VLAN) != 0)) { + json_object_set_new(jobj, "vlan_tci", json_integer(mb->vlan_tci)); + } + + if (mb->packet_type) + json_object_set_new(jobj, "hw_ptype", encode_ptype(mb->packet_type)); + + struct rte_net_hdr_lens hdr_lens; + uint32_t sw_packet_type = rte_net_get_ptype(mb, &hdr_lens, RTE_PTYPE_ALL_MASK); + json_object_set_new(jobj, "sw_ptype", encode_ptype(sw_packet_type)); + + if (sw_packet_type & RTE_PTYPE_L2_MASK) + json_object_set_new(jobj, "l2_len", json_integer(hdr_lens.l2_len)); + if (sw_packet_type & RTE_PTYPE_L3_MASK) + json_object_set_new(jobj, "l3_len", json_integer(hdr_lens.l3_len)); + if (sw_packet_type & RTE_PTYPE_L4_MASK) + json_object_set_new(jobj, "l4_len", json_integer(hdr_lens.l4_len)); + if (sw_packet_type & RTE_PTYPE_TUNNEL_MASK) + json_object_set_new(jobj, "tunnel_len", json_integer(hdr_lens.tunnel_len)); + if (sw_packet_type & RTE_PTYPE_INNER_L2_MASK) + json_object_set_new(jobj, "inner_l2_len", json_integer(hdr_lens.inner_l2_len)); + if (sw_packet_type & RTE_PTYPE_INNER_L3_MASK) + json_object_set_new(jobj, "inner_l3_len", json_integer(hdr_lens.inner_l3_len)); + if (sw_packet_type & RTE_PTYPE_INNER_L4_MASK) + json_object_set_new(jobj, "inner_l4_len", json_integer(hdr_lens.inner_l4_len)); + + json_object_set_new(jobj, "ol_flags", encode_ol_flags(mb->ol_flags, is_rx)); + + json_dumpf(jobj, outf, JSON_INDENT(4)); + json_decref(jobj); + } +} +#endif + static void dump_pkt_hex(FILE *outf, struct rte_mbuf *pkts[], uint16_t nb_pkts) { @@ -362,6 +499,11 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[], case OUTPUT_MODE_DISSECT: dump_pkt_brief(outf, port_id, queue, pkts, nb_pkts, is_rx); break; +#ifdef RTE_HAS_JANSSON + case OUTPUT_MODE_JSON: + dump_pkt_json(outf, port_id, queue, pkts, nb_pkts, is_rx); + break; +#endif default: return; } diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst index 705b3dc3d5..bb62960582 100644 --- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst +++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst @@ -677,13 +677,14 @@ set format Chose the output format for packet debug log:: - testpmd> set format dissect|hex|verbose + testpmd> set format dissect|hex|json|verbose Available formats are: -* ``verbose`` print the packet meta data information -* ``hex`` print the mbuf flags and data in hex * ``dissect`` print the packet in tshark summary format +* ``hex`` print the mbuf flags and data in hex +* ``json`` print the packet meta data in json +* ``verbose`` print the packet meta data information set verbose -- 2.45.2 ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v9 0/8] test-pmd packet decoding enhancements 2024-03-12 22:01 [PATCH] test-pmd: add more packet decode options (verbose) Stephen Hemminger ` (8 preceding siblings ...) 2024-09-17 3:27 ` [PATCH v8 0/7] Test-pmd packet decode enhancements Stephen Hemminger @ 2024-09-28 16:18 ` Stephen Hemminger 2024-09-28 16:18 ` [PATCH v9 1/8] net: add more icmp types Stephen Hemminger ` (7 more replies) 9 siblings, 8 replies; 54+ messages in thread From: Stephen Hemminger @ 2024-09-28 16:18 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. The new commands are: testpmd> set output /tmp/packet.log testpmd> set format dissect To enable packet logging need to use: testpmd> set verbose 3 v9 - some fixes to JSON format extend hex to print hash and port v8 - added JSON output format, and ability to redirect packet log, and don't overload verbose option. Stephen Hemminger (8): net: add more icmp types net: add new packet dissector mbuf: decode the hash and fdir info in rte_pktmbuf_dump test: add test for packet dissector test-pmd: add option to redirect packet log test-pmd: add hex decode test-pmd: add packet dissect format test-pmd: add a JSON packet output app/test-pmd/cmdline.c | 81 ++++ app/test-pmd/config.c | 48 +++ app/test-pmd/testpmd.c | 4 + app/test-pmd/testpmd.h | 12 + app/test-pmd/util.c | 256 +++++++++++- app/test/meson.build | 1 + app/test/test_dissect.c | 302 ++++++++++++++ doc/guides/testpmd_app_ug/testpmd_funcs.rst | 22 + lib/mbuf/rte_mbuf.c | 14 + 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 + 14 files changed, 1233 insertions(+), 11 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.45.2 ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v9 1/8] net: add more icmp types 2024-09-28 16:18 ` [PATCH v9 0/8] test-pmd packet decoding enhancements Stephen Hemminger @ 2024-09-28 16:18 ` Stephen Hemminger 2024-09-28 16:18 ` [PATCH v9 2/8] net: add new packet dissector Stephen Hemminger ` (6 subsequent siblings) 7 siblings, 0 replies; 54+ messages in thread From: Stephen Hemminger @ 2024-09-28 16:18 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.45.2 ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v9 2/8] net: add new packet dissector 2024-09-28 16:18 ` [PATCH v9 0/8] test-pmd packet decoding enhancements Stephen Hemminger 2024-09-28 16:18 ` [PATCH v9 1/8] net: add more icmp types Stephen Hemminger @ 2024-09-28 16:18 ` Stephen Hemminger 2024-09-28 16:18 ` [PATCH v9 3/8] mbuf: decode the hash and fdir info in rte_pktmbuf_dump Stephen Hemminger ` (5 subsequent siblings) 7 siblings, 0 replies; 54+ messages in thread From: Stephen Hemminger @ 2024-09-28 16:18 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), ð_hdr->src_addr); + rte_ether_format_addr(dbuf, sizeof(dbuf), ð_hdr->dst_addr); + count += dissect_print(&buf, &size, "%s → %s ARP ", sbuf, dbuf); + count += dissect_arp(buf, size, mb, offset, dump_len); + break; + + case RTE_ETHER_TYPE_IPV4: + count += dissect_ipv4(buf, size, mb, offset, dump_len); + break; + + case RTE_ETHER_TYPE_IPV6: + count += dissect_ipv6(buf, size, mb, offset, dump_len); + break; + + default: + count += dissect_print(&buf, &size, "ETH %#x ", eth_type); + } + + return count; +} + +int +rte_dissect_mbuf(char *buf, size_t size, const struct rte_mbuf *m, uint32_t dump_len) +{ + int count; + + count = dissect_eth(buf, size, m, 0, dump_len); + if (count <= 0) + return count; + + /* output was truncated, but redact the trailing blank */ + if (count >= (int)size) + return count - 1; + + if (buf[count] == ' ') + buf[count--] = '\0'; + + return count; +} diff --git a/lib/net/rte_dissect.h b/lib/net/rte_dissect.h new file mode 100644 index 0000000000..343e9daa4d --- /dev/null +++ b/lib/net/rte_dissect.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2024 Stephen Hemminger <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 bec4ce23ea..9a27339df5 100644 --- a/lib/net/version.map +++ b/lib/net/version.map @@ -12,3 +12,10 @@ DPDK_25 { local: *; }; + +EXPERIMENTAL { + global: + + # added in 24.11 + rte_dissect_mbuf; +}; -- 2.45.2 ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v9 3/8] mbuf: decode the hash and fdir info in rte_pktmbuf_dump 2024-09-28 16:18 ` [PATCH v9 0/8] test-pmd packet decoding enhancements Stephen Hemminger 2024-09-28 16:18 ` [PATCH v9 1/8] net: add more icmp types Stephen Hemminger 2024-09-28 16:18 ` [PATCH v9 2/8] net: add new packet dissector Stephen Hemminger @ 2024-09-28 16:18 ` Stephen Hemminger 2024-09-28 16:18 ` [PATCH v9 4/8] test: add test for packet dissector Stephen Hemminger ` (4 subsequent siblings) 7 siblings, 0 replies; 54+ messages in thread From: Stephen Hemminger @ 2024-09-28 16:18 UTC (permalink / raw) To: dev; +Cc: Stephen Hemminger Useful to be able to see the meta data in the hex dump. Signed-off-by: Stephen Hemminger <stephen@networkplumber.org> --- lib/mbuf/rte_mbuf.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/mbuf/rte_mbuf.c b/lib/mbuf/rte_mbuf.c index 559d5ad8a7..8e452ca98f 100644 --- a/lib/mbuf/rte_mbuf.c +++ b/lib/mbuf/rte_mbuf.c @@ -678,12 +678,26 @@ rte_pktmbuf_dump(FILE *f, const struct rte_mbuf *m, unsigned dump_len) fprintf(f, " pkt_len=%u, ol_flags=%#"PRIx64", nb_segs=%u, port=%u", m->pkt_len, m->ol_flags, m->nb_segs, m->port); + if (m->port != RTE_MBUF_PORT_INVALID) + fprintf(f, ", port=%u", m->port); + if (m->ol_flags & (RTE_MBUF_F_RX_QINQ | RTE_MBUF_F_TX_QINQ)) fprintf(f, ", vlan_tci_outer=%u", m->vlan_tci_outer); if (m->ol_flags & (RTE_MBUF_F_RX_VLAN | RTE_MBUF_F_TX_VLAN)) fprintf(f, ", vlan_tci=%u", m->vlan_tci); + if (m->ol_flags & RTE_MBUF_F_RX_RSS_HASH) + fprintf(f, ", rss=%#x", m->hash.rss); + else if (m->ol_flags & RTE_MBUF_F_RX_FDIR) { + if (m->ol_flags & RTE_MBUF_F_RX_FDIR_ID) + fprintf(f, ", fdir id=%u", m->hash.fdir.id); + else if (m->ol_flags & RTE_MBUF_F_RX_FDIR_FLX) + fprintf(f, ", fdir flex=%#x %x", m->hash.fdir.hi, m->hash.fdir.lo); + else + fprintf(f, " fdir hash=%#x id=%#x ", m->hash.fdir.hash, m->hash.fdir.id); + } + fprintf(f, ", ptype=%#"PRIx32"\n", m->packet_type); nb_segs = m->nb_segs; -- 2.45.2 ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v9 4/8] test: add test for packet dissector 2024-09-28 16:18 ` [PATCH v9 0/8] test-pmd packet decoding enhancements Stephen Hemminger ` (2 preceding siblings ...) 2024-09-28 16:18 ` [PATCH v9 3/8] mbuf: decode the hash and fdir info in rte_pktmbuf_dump Stephen Hemminger @ 2024-09-28 16:18 ` Stephen Hemminger 2024-09-28 16:18 ` [PATCH v9 5/8] test-pmd: add option to redirect packet log Stephen Hemminger ` (3 subsequent siblings) 7 siblings, 0 replies; 54+ messages in thread From: Stephen Hemminger @ 2024-09-28 16:18 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.45.2 ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v9 5/8] test-pmd: add option to redirect packet log 2024-09-28 16:18 ` [PATCH v9 0/8] test-pmd packet decoding enhancements Stephen Hemminger ` (3 preceding siblings ...) 2024-09-28 16:18 ` [PATCH v9 4/8] test: add test for packet dissector Stephen Hemminger @ 2024-09-28 16:18 ` Stephen Hemminger 2024-09-28 16:18 ` [PATCH v9 6/8] test-pmd: add hex decode Stephen Hemminger ` (2 subsequent siblings) 7 siblings, 0 replies; 54+ messages in thread From: Stephen Hemminger @ 2024-09-28 16:18 UTC (permalink / raw) To: dev; +Cc: Stephen Hemminger, Aman Singh When running tests in interactive mode, it is useful to be able to redirect the packet decode (verbose output) into a file. Signed-off-by: Stephen Hemminger <stephen@networkplumber.org> --- app/test-pmd/cmdline.c | 41 +++++++++++++++++++++ app/test-pmd/config.c | 23 ++++++++++++ app/test-pmd/testpmd.c | 3 ++ app/test-pmd/testpmd.h | 3 ++ app/test-pmd/util.c | 11 +++++- doc/guides/testpmd_app_ug/testpmd_funcs.rst | 7 ++++ 6 files changed, 86 insertions(+), 2 deletions(-) diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c index b7759e38a8..7c6ab191de 100644 --- a/app/test-pmd/cmdline.c +++ b/app/test-pmd/cmdline.c @@ -302,6 +302,9 @@ static void cmd_help_long_parsed(void *parsed_result, "set verbose (level)\n" " Set the debug verbosity level X.\n\n" + "set output (filename)\n" + " Set the packet debug log file\n\n" + "set log global|(type) (level)\n" " Set the log level.\n\n" @@ -3853,6 +3856,43 @@ static cmdline_parse_inst_t cmd_set_numbers = { }, }; + +/* *** SET OUTPUT FILENAME *** */ +struct cmd_set_output_result { + cmdline_fixed_string_t set; + cmdline_fixed_string_t output; + cmdline_fixed_string_t filename; +}; + +static void +cmd_set_output_parsed(void *parsed_result, + __rte_unused struct cmdline *cl, + __rte_unused void *data) +{ + struct cmd_set_output_result *res = parsed_result; + + set_output_file(res->filename); +} + +static cmdline_parse_token_string_t cmd_set_output_set = + TOKEN_STRING_INITIALIZER(struct cmd_set_output_result, set, "set"); +static cmdline_parse_token_string_t cmd_set_output_output = + TOKEN_STRING_INITIALIZER(struct cmd_set_output_result, output, "output"); +static cmdline_parse_token_string_t cmd_set_output_name = + TOKEN_STRING_INITIALIZER(struct cmd_set_output_result, filename, NULL); + +static cmdline_parse_inst_t cmd_set_output = { + .f = cmd_set_output_parsed, + .data = NULL, + .help_str = "set output <filename>", + .tokens = { + (void *)&cmd_set_output_set, + (void *)&cmd_set_output_output, + (void *)&cmd_set_output_name, + NULL, + }, +}; + /* *** SET LOG LEVEL CONFIGURATION *** */ struct cmd_set_log_result { @@ -13166,6 +13206,7 @@ static cmdline_parse_ctx_t builtin_ctx[] = { (cmdline_parse_inst_t *)&cmd_reset, (cmdline_parse_inst_t *)&cmd_set_numbers, (cmdline_parse_inst_t *)&cmd_set_log, + (cmdline_parse_inst_t *)&cmd_set_output, (cmdline_parse_inst_t *)&cmd_set_rxoffs, (cmdline_parse_inst_t *)&cmd_set_rxpkts, (cmdline_parse_inst_t *)&cmd_set_rxhdrs, diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c index 6f0beafa27..bfc5a1898b 100644 --- a/app/test-pmd/config.c +++ b/app/test-pmd/config.c @@ -6268,6 +6268,29 @@ set_verbose_level(uint16_t vb_level) configure_rxtx_dump_callbacks(verbose_level); } +void +set_output_file(const char *filename) +{ + FILE *outf, *oldf; + + if (!strcmp(filename, "-")) { + outf = stdout; + } else { + outf = fopen(filename, "a"); + if (outf == NULL) { + perror(filename); + return; + } + } + + oldf = rte_atomic_exchange_explicit(&output_file, outf, rte_memory_order_seq_cst); + if (oldf != NULL && oldf != stdout) { + /* make sure other threads are not mid print */ + rte_delay_us_sleep(US_PER_S/10); + fclose(oldf); + } +} + void vlan_extend_set(portid_t port_id, int on) { diff --git a/app/test-pmd/testpmd.c b/app/test-pmd/testpmd.c index b1401136e4..7790ba6ce0 100644 --- a/app/test-pmd/testpmd.c +++ b/app/test-pmd/testpmd.c @@ -99,6 +99,7 @@ #define EXTBUF_ZONE_SIZE (RTE_PGSIZE_2M - 4 * RTE_CACHE_LINE_SIZE) uint16_t verbose_level = 0; /**< Silent by default. */ +RTE_ATOMIC(FILE *) output_file; /**< log to console by default. */ int testpmd_logtype; /**< Log type for testpmd logs */ /* use main core for command line ? */ @@ -4543,6 +4544,8 @@ main(int argc, char** argv) rte_exit(EXIT_FAILURE, "Cannot register log type"); rte_log_set_level(testpmd_logtype, RTE_LOG_DEBUG); + output_file = stdout; + diag = rte_eal_init(argc, argv); if (diag < 0) rte_exit(EXIT_FAILURE, "Cannot init EAL: %s\n", diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h index 9facd7f281..d92cb743dd 100644 --- a/app/test-pmd/testpmd.h +++ b/app/test-pmd/testpmd.h @@ -17,6 +17,7 @@ #include <rte_ethdev.h> #include <rte_flow.h> #include <rte_mbuf_dyn.h> +#include <rte_stdatomic.h> #include <cmdline.h> #include <cmdline_parse.h> @@ -493,6 +494,7 @@ extern uint8_t xstats_hide_zero; /**< Hide zero values for xstats display */ extern uint8_t record_core_cycles; /**< Enables measurement of CPU cycles */ extern uint8_t record_burst_stats; /**< Enables display of RX and TX bursts */ extern uint16_t verbose_level; /**< Drives messages being displayed, if any. */ +extern RTE_ATOMIC(FILE *) output_file; /**< Where packet data is written */ extern int testpmd_logtype; /**< Log type for testpmd logs */ extern uint8_t interactive; extern uint8_t auto_start; @@ -1102,6 +1104,7 @@ void set_xstats_hide_zero(uint8_t on_off); void set_record_core_cycles(uint8_t on_off); void set_record_burst_stats(uint8_t on_off); void set_verbose_level(uint16_t vb_level); +void set_output_file(const char *filename); void set_rx_pkt_segments(unsigned int *seg_lengths, unsigned int nb_segs); void set_rx_pkt_hdrs(unsigned int *seg_protos, unsigned int nb_segs); void show_rx_pkt_hdrs(void); diff --git a/app/test-pmd/util.c b/app/test-pmd/util.c index bf9b639d95..2446687090 100644 --- a/app/test-pmd/util.c +++ b/app/test-pmd/util.c @@ -89,9 +89,15 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[], size_t buf_size = MAX_STRING_LEN; size_t cur_len = 0; uint64_t restore_info_dynflag; + FILE *outf; if (!nb_pkts) return; + + outf = rte_atomic_load_explicit(&output_file, rte_memory_order_relaxed); + if (!outf) + 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, @@ -292,11 +298,12 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[], MKDUMPSTR(print_buf, buf_size, cur_len, "INVALID mbuf: %s\n", reason); if (cur_len >= buf_size) - printf("%s ...\n", print_buf); + fprintf(outf, "%s ...\n", print_buf); else - printf("%s", print_buf); + fprintf(outf, "%s", print_buf); cur_len = 0; } + fflush(outf); } uint16_t diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst index f00ab07605..579432e5c7 100644 --- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst +++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst @@ -665,6 +665,13 @@ Reset forwarding to the default configuration:: testpmd> set default +set output +~~~~~~~~~~ + +Redirect the debug log:: + + testpmd> set output /tmp/packet.log + set verbose ~~~~~~~~~~~ -- 2.45.2 ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v9 6/8] test-pmd: add hex decode 2024-09-28 16:18 ` [PATCH v9 0/8] test-pmd packet decoding enhancements Stephen Hemminger ` (4 preceding siblings ...) 2024-09-28 16:18 ` [PATCH v9 5/8] test-pmd: add option to redirect packet log Stephen Hemminger @ 2024-09-28 16:18 ` Stephen Hemminger 2024-09-28 16:18 ` [PATCH v9 7/8] test-pmd: add packet dissect format Stephen Hemminger 2024-09-28 16:18 ` [PATCH v9 8/8] test-pmd: add a JSON packet output Stephen Hemminger 7 siblings, 0 replies; 54+ messages in thread From: Stephen Hemminger @ 2024-09-28 16:18 UTC (permalink / raw) To: dev; +Cc: Stephen Hemminger, Aman Singh This adds new command: testpmd> set format hex which decodes packet in hex. Signed-off-by: Stephen Hemminger <stephen@networkplumber.org> --- app/test-pmd/cmdline.c | 40 +++++++++++++++ app/test-pmd/config.c | 11 +++++ app/test-pmd/testpmd.c | 1 + app/test-pmd/testpmd.h | 7 +++ app/test-pmd/util.c | 54 ++++++++++++++++----- doc/guides/testpmd_app_ug/testpmd_funcs.rst | 13 +++++ 6 files changed, 115 insertions(+), 11 deletions(-) diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c index 7c6ab191de..37cce4868e 100644 --- a/app/test-pmd/cmdline.c +++ b/app/test-pmd/cmdline.c @@ -305,6 +305,9 @@ static void cmd_help_long_parsed(void *parsed_result, "set output (filename)\n" " Set the packet debug log file\n\n" + "set format (verbose|hex)\n" + " Set the format of packet log\\n" + "set log global|(type) (level)\n" " Set the log level.\n\n" @@ -3893,6 +3896,42 @@ static cmdline_parse_inst_t cmd_set_output = { }, }; +/* *** SET FORMAT OF PACKET LOG */ +struct cmd_set_format_result { + cmdline_fixed_string_t set; + cmdline_fixed_string_t format; + cmdline_fixed_string_t value; +}; + +static void +cmd_set_format_parsed(void *parsed_result, + __rte_unused struct cmdline *cl, + __rte_unused void *data) +{ + struct cmd_set_format_result *res = parsed_result; + + set_output_format(res->value); +} + +static cmdline_parse_token_string_t cmd_set_format_set = + TOKEN_STRING_INITIALIZER(struct cmd_set_format_result, set, "set"); +static cmdline_parse_token_string_t cmd_set_format_output = + TOKEN_STRING_INITIALIZER(struct cmd_set_format_result, format, "format"); +static cmdline_parse_token_string_t cmd_set_format_value = + TOKEN_STRING_INITIALIZER(struct cmd_set_format_result, value, "verbose#hex"); + +static cmdline_parse_inst_t cmd_set_format = { + .f = cmd_set_format_parsed, + .data = NULL, + .help_str = "set format verbose|hex", + .tokens = { + (void *)&cmd_set_format_set, + (void *)&cmd_set_format_output, + (void *)&cmd_set_format_value, + NULL, + }, +}; + /* *** SET LOG LEVEL CONFIGURATION *** */ struct cmd_set_log_result { @@ -13207,6 +13246,7 @@ static cmdline_parse_ctx_t builtin_ctx[] = { (cmdline_parse_inst_t *)&cmd_set_numbers, (cmdline_parse_inst_t *)&cmd_set_log, (cmdline_parse_inst_t *)&cmd_set_output, + (cmdline_parse_inst_t *)&cmd_set_format, (cmdline_parse_inst_t *)&cmd_set_rxoffs, (cmdline_parse_inst_t *)&cmd_set_rxpkts, (cmdline_parse_inst_t *)&cmd_set_rxhdrs, diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c index bfc5a1898b..f30bdfc7ff 100644 --- a/app/test-pmd/config.c +++ b/app/test-pmd/config.c @@ -6268,6 +6268,17 @@ set_verbose_level(uint16_t vb_level) configure_rxtx_dump_callbacks(verbose_level); } +void +set_output_format(const char *mode) +{ + if (!strcmp(mode, "verbose")) + output_format = OUTPUT_MODE_VERBOSE; + else if (!strcmp(mode, "hex")) + output_format = OUTPUT_MODE_HEX; + else + fprintf(stderr, "Unknown output format '%s'\n", mode); +} + void set_output_file(const char *filename) { diff --git a/app/test-pmd/testpmd.c b/app/test-pmd/testpmd.c index 7790ba6ce0..f4fd51e46f 100644 --- a/app/test-pmd/testpmd.c +++ b/app/test-pmd/testpmd.c @@ -100,6 +100,7 @@ uint16_t verbose_level = 0; /**< Silent by default. */ RTE_ATOMIC(FILE *) output_file; /**< log to console by default. */ +enum output_mode output_format; /**< default to original mode. */ int testpmd_logtype; /**< Log type for testpmd logs */ /* use main core for command line ? */ diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h index d92cb743dd..94e8f59ef0 100644 --- a/app/test-pmd/testpmd.h +++ b/app/test-pmd/testpmd.h @@ -488,6 +488,11 @@ enum dcb_mode_enable DCB_ENABLED }; +enum output_mode { + OUTPUT_MODE_VERBOSE = 0, + OUTPUT_MODE_HEX, +}; + extern uint8_t xstats_hide_zero; /**< Hide zero values for xstats display */ /* globals used for configuration */ @@ -495,6 +500,7 @@ extern uint8_t record_core_cycles; /**< Enables measurement of CPU cycles */ extern uint8_t record_burst_stats; /**< Enables display of RX and TX bursts */ extern uint16_t verbose_level; /**< Drives messages being displayed, if any. */ extern RTE_ATOMIC(FILE *) output_file; /**< Where packet data is written */ +extern enum output_mode output_format; /**< Format of packet decode */ extern int testpmd_logtype; /**< Log type for testpmd logs */ extern uint8_t interactive; extern uint8_t auto_start; @@ -1104,6 +1110,7 @@ void set_xstats_hide_zero(uint8_t on_off); void set_record_core_cycles(uint8_t on_off); void set_record_burst_stats(uint8_t on_off); void set_verbose_level(uint16_t vb_level); +void set_output_format(const char *mode); void set_output_file(const char *filename); void set_rx_pkt_segments(unsigned int *seg_lengths, unsigned int nb_segs); void set_rx_pkt_hdrs(unsigned int *seg_protos, unsigned int nb_segs); diff --git a/app/test-pmd/util.c b/app/test-pmd/util.c index 2446687090..130821fddb 100644 --- a/app/test-pmd/util.c +++ b/app/test-pmd/util.c @@ -16,6 +16,7 @@ #include "testpmd.h" #define MAX_STRING_LEN 8192 +#define MAX_DUMP_LEN 1024 #define MKDUMPSTR(buf, buf_size, cur_len, ...) \ do { \ @@ -67,9 +68,9 @@ 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) +static void +dump_pkt_verbose(FILE *outf, 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; @@ -89,14 +90,6 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[], size_t buf_size = MAX_STRING_LEN; size_t cur_len = 0; uint64_t restore_info_dynflag; - FILE *outf; - - if (!nb_pkts) - return; - - outf = rte_atomic_load_explicit(&output_file, rte_memory_order_relaxed); - if (!outf) - return; restore_info_dynflag = rte_flow_restore_info_dynflag(); MKDUMPSTR(print_buf, buf_size, cur_len, @@ -303,6 +296,45 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[], fprintf(outf, "%s", print_buf); cur_len = 0; } +} + +static void +dump_pkt_hex(FILE *outf, uint16_t port_id, uint16_t queue, + struct rte_mbuf *pkts[], uint16_t nb_pkts, int is_rx) +{ + fprintf(outf, "port %u/queue %u: %s %u packets\n", port_id, queue, + is_rx ? "received" : "sent", (unsigned int) nb_pkts); + + for (uint16_t i = 0; i < nb_pkts; i++) { + rte_pktmbuf_dump(outf, pkts[i], MAX_DUMP_LEN); + fprintf(outf, "\n"); + } +} + + +static void +dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[], + uint16_t nb_pkts, int is_rx) +{ + FILE *outf; + + if (!nb_pkts) + return; + + outf = rte_atomic_load_explicit(&output_file, rte_memory_order_relaxed); + if (unlikely(!outf)) + return; + + switch (output_format) { + case OUTPUT_MODE_VERBOSE: + dump_pkt_verbose(outf, port_id, queue, pkts, nb_pkts, is_rx); + return; + case OUTPUT_MODE_HEX: + dump_pkt_hex(outf, port_id, queue, pkts, nb_pkts, is_rx); + break; + default: + return; + } fflush(outf); } diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst index 579432e5c7..9406af3225 100644 --- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst +++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst @@ -672,6 +672,19 @@ Redirect the debug log:: testpmd> set output /tmp/packet.log +set format +~~~~~~~~~~ + +Chose the output format for packet debug log:: + + testpmd> set format verbose|hex + +Available formats are: + +* ``verbose`` print the packet meta data information +* ``hex`` print the mbuf flags and data in hex + + set verbose ~~~~~~~~~~~ -- 2.45.2 ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v9 7/8] test-pmd: add packet dissect format 2024-09-28 16:18 ` [PATCH v9 0/8] test-pmd packet decoding enhancements Stephen Hemminger ` (5 preceding siblings ...) 2024-09-28 16:18 ` [PATCH v9 6/8] test-pmd: add hex decode Stephen Hemminger @ 2024-09-28 16:18 ` Stephen Hemminger 2024-09-28 16:18 ` [PATCH v9 8/8] test-pmd: add a JSON packet output Stephen Hemminger 7 siblings, 0 replies; 54+ messages in thread From: Stephen Hemminger @ 2024-09-28 16:18 UTC (permalink / raw) To: dev; +Cc: Stephen Hemminger, Aman Singh Add ability to get decode packet in summary tshark style format. Signed-off-by: Stephen Hemminger <stephen@networkplumber.org> --- app/test-pmd/cmdline.c | 6 ++-- app/test-pmd/config.c | 23 +++++++++---- app/test-pmd/testpmd.h | 1 + app/test-pmd/util.c | 36 +++++++++++++++++++++ doc/guides/testpmd_app_ug/testpmd_funcs.rst | 3 +- 5 files changed, 59 insertions(+), 10 deletions(-) diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c index 37cce4868e..72be5a0c06 100644 --- a/app/test-pmd/cmdline.c +++ b/app/test-pmd/cmdline.c @@ -305,7 +305,7 @@ static void cmd_help_long_parsed(void *parsed_result, "set output (filename)\n" " Set the packet debug log file\n\n" - "set format (verbose|hex)\n" + "set format (dissect|hex|verbose)\n" " Set the format of packet log\\n" "set log global|(type) (level)\n" @@ -3918,12 +3918,12 @@ static cmdline_parse_token_string_t cmd_set_format_set = static cmdline_parse_token_string_t cmd_set_format_output = TOKEN_STRING_INITIALIZER(struct cmd_set_format_result, format, "format"); static cmdline_parse_token_string_t cmd_set_format_value = - TOKEN_STRING_INITIALIZER(struct cmd_set_format_result, value, "verbose#hex"); + TOKEN_STRING_INITIALIZER(struct cmd_set_format_result, value, "dissect#hex#verbose"); static cmdline_parse_inst_t cmd_set_format = { .f = cmd_set_format_parsed, .data = NULL, - .help_str = "set format verbose|hex", + .help_str = "set format dissect|hex|verbose", .tokens = { (void *)&cmd_set_format_set, (void *)&cmd_set_format_output, diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c index f30bdfc7ff..36b7aa1307 100644 --- a/app/test-pmd/config.c +++ b/app/test-pmd/config.c @@ -6271,12 +6271,23 @@ set_verbose_level(uint16_t vb_level) void set_output_format(const char *mode) { - if (!strcmp(mode, "verbose")) - output_format = OUTPUT_MODE_VERBOSE; - else if (!strcmp(mode, "hex")) - output_format = OUTPUT_MODE_HEX; - else - fprintf(stderr, "Unknown output format '%s'\n", mode); + static const char * const output_formats[] = { + [OUTPUT_MODE_VERBOSE] = "verbose", + [OUTPUT_MODE_HEX] = "hex", + [OUTPUT_MODE_DISSECT] = "dissect", + }; + + printf("Change output format from %s to %s\n", + output_formats[output_format], mode); + + for (unsigned int i = 0; i < RTE_DIM(output_formats); i++) { + if (strcasecmp(mode, output_formats[i]) == 0) { + output_format = i; + return; + } + } + + fprintf(stderr, "Unknown output format '%s'\n", mode); } void diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h index 94e8f59ef0..66b0317b61 100644 --- a/app/test-pmd/testpmd.h +++ b/app/test-pmd/testpmd.h @@ -491,6 +491,7 @@ enum dcb_mode_enable enum output_mode { OUTPUT_MODE_VERBOSE = 0, OUTPUT_MODE_HEX, + OUTPUT_MODE_DISSECT, }; extern uint8_t xstats_hide_zero; /**< Hide zero values for xstats display */ diff --git a/app/test-pmd/util.c b/app/test-pmd/util.c index 130821fddb..551e684e4c 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> @@ -311,6 +312,38 @@ dump_pkt_hex(FILE *outf, uint16_t port_id, uint16_t queue, } } +/* Brief tshark style one line output which is + * number time_delta Source Destination Protocol len info + */ +static void +dump_pkt_brief(FILE *outf, 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, count; + double interval; + + /* Compute time interval from the first packet received */ + 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(); + + /* Packet counter needs to be thread safe */ + count = rte_atomic_fetch_add_explicit(&packet_count, nb_pkts, rte_memory_order_relaxed); + + for (uint16_t i = 0; i < nb_pkts; i++) { + const struct rte_mbuf *mb = pkts[i]; + char str[256]; + + rte_dissect_mbuf(str, sizeof(str), mb, 0); + fprintf(outf, "%6"PRIu64" %11.9f %4u:%-3u %c %s\n", + count + i, interval, port, queue, is_rx ? 'R' : 'T', str); + } +} static void dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[], @@ -332,6 +365,9 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[], case OUTPUT_MODE_HEX: dump_pkt_hex(outf, port_id, queue, pkts, nb_pkts, is_rx); break; + case OUTPUT_MODE_DISSECT: + dump_pkt_brief(outf, port_id, queue, pkts, nb_pkts, is_rx); + break; default: return; } diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst index 9406af3225..705b3dc3d5 100644 --- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst +++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst @@ -677,12 +677,13 @@ set format Chose the output format for packet debug log:: - testpmd> set format verbose|hex + testpmd> set format dissect|hex|verbose Available formats are: * ``verbose`` print the packet meta data information * ``hex`` print the mbuf flags and data in hex +* ``dissect`` print the packet in tshark summary format set verbose -- 2.45.2 ^ permalink raw reply [flat|nested] 54+ messages in thread
* [PATCH v9 8/8] test-pmd: add a JSON packet output 2024-09-28 16:18 ` [PATCH v9 0/8] test-pmd packet decoding enhancements Stephen Hemminger ` (6 preceding siblings ...) 2024-09-28 16:18 ` [PATCH v9 7/8] test-pmd: add packet dissect format Stephen Hemminger @ 2024-09-28 16:18 ` Stephen Hemminger 7 siblings, 0 replies; 54+ messages in thread From: Stephen Hemminger @ 2024-09-28 16:18 UTC (permalink / raw) To: dev; +Cc: Stephen Hemminger, Aman Singh When doing automated testing it is useful to show packet meta data in JSON. Signed-off-by: Stephen Hemminger <stephen@networkplumber.org> --- app/test-pmd/cmdline.c | 6 +- app/test-pmd/config.c | 3 + app/test-pmd/testpmd.h | 1 + app/test-pmd/util.c | 167 ++++++++++++++++++++ doc/guides/testpmd_app_ug/testpmd_funcs.rst | 7 +- 5 files changed, 178 insertions(+), 6 deletions(-) diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c index 72be5a0c06..3d52ad4c72 100644 --- a/app/test-pmd/cmdline.c +++ b/app/test-pmd/cmdline.c @@ -305,7 +305,7 @@ static void cmd_help_long_parsed(void *parsed_result, "set output (filename)\n" " Set the packet debug log file\n\n" - "set format (dissect|hex|verbose)\n" + "set format (dissect|hex|json|verbose)\n" " Set the format of packet log\\n" "set log global|(type) (level)\n" @@ -3918,12 +3918,12 @@ static cmdline_parse_token_string_t cmd_set_format_set = static cmdline_parse_token_string_t cmd_set_format_output = TOKEN_STRING_INITIALIZER(struct cmd_set_format_result, format, "format"); static cmdline_parse_token_string_t cmd_set_format_value = - TOKEN_STRING_INITIALIZER(struct cmd_set_format_result, value, "dissect#hex#verbose"); + TOKEN_STRING_INITIALIZER(struct cmd_set_format_result, value, "dissect#hex#json#verbose"); static cmdline_parse_inst_t cmd_set_format = { .f = cmd_set_format_parsed, .data = NULL, - .help_str = "set format dissect|hex|verbose", + .help_str = "set format dissect|hex|json|verbose", .tokens = { (void *)&cmd_set_format_set, (void *)&cmd_set_format_output, diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c index 36b7aa1307..10ed58c08b 100644 --- a/app/test-pmd/config.c +++ b/app/test-pmd/config.c @@ -6275,6 +6275,9 @@ set_output_format(const char *mode) [OUTPUT_MODE_VERBOSE] = "verbose", [OUTPUT_MODE_HEX] = "hex", [OUTPUT_MODE_DISSECT] = "dissect", +#ifdef RTE_HAS_JANSSON + [OUTPUT_MODE_JSON] = "json", +#endif }; printf("Change output format from %s to %s\n", diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h index 66b0317b61..f8bf9b7126 100644 --- a/app/test-pmd/testpmd.h +++ b/app/test-pmd/testpmd.h @@ -492,6 +492,7 @@ enum output_mode { OUTPUT_MODE_VERBOSE = 0, OUTPUT_MODE_HEX, OUTPUT_MODE_DISSECT, + OUTPUT_MODE_JSON, }; extern uint8_t xstats_hide_zero; /**< Hide zero values for xstats display */ diff --git a/app/test-pmd/util.c b/app/test-pmd/util.c index 551e684e4c..7da59988db 100644 --- a/app/test-pmd/util.c +++ b/app/test-pmd/util.c @@ -299,6 +299,168 @@ dump_pkt_verbose(FILE *outf, uint16_t port_id, uint16_t queue, } } +#ifdef RTE_HAS_JANSSON + +/* Encode offload flags as JSON array */ +static json_t * +encode_ol_flags(uint64_t flags, int is_rx) +{ + json_t *ol_array = json_array(); + unsigned int i; + + if (is_rx) + flags &= ~RTE_MBUF_F_TX_OFFLOAD_MASK; + else + flags &= RTE_MBUF_F_TX_OFFLOAD_MASK; + + for (i = 0; i < 64; i++) { + uint64_t mask = (uint64_t)1 << i; + const char *name; + + if (!(mask & flags)) + continue; + + if (is_rx) + name = rte_get_rx_ol_flag_name(mask); + else + name = rte_get_tx_ol_flag_name(mask); + json_array_append_new(ol_array, json_string(name)); + } + return ol_array; +} + +/* Encode packet type fields as JSON object */ +static json_t * +encode_ptype(uint32_t ptype) +{ + if ((ptype & RTE_PTYPE_ALL_MASK) == RTE_PTYPE_UNKNOWN) + return json_string("UNKNOWN"); + + json_t *ptypes = json_array(); + if (ptype & RTE_PTYPE_L2_MASK) + json_array_append(ptypes, json_string(rte_get_ptype_l2_name(ptype))); + if (ptype & RTE_PTYPE_L3_MASK) + json_array_append(ptypes, json_string(rte_get_ptype_l3_name(ptype))); + if (ptype & RTE_PTYPE_L4_MASK) + json_array_append(ptypes, json_string(rte_get_ptype_l4_name(ptype))); + if (ptype & RTE_PTYPE_TUNNEL_MASK) + json_array_append(ptypes, json_string(rte_get_ptype_tunnel_name(ptype))); + if (ptype & RTE_PTYPE_INNER_L2_MASK) + json_array_append(ptypes, json_string(rte_get_ptype_inner_l2_name(ptype))); + if (ptype & RTE_PTYPE_INNER_L3_MASK) + json_array_append(ptypes, json_string(rte_get_ptype_inner_l3_name(ptype))); + if (ptype & RTE_PTYPE_INNER_L4_MASK) + json_array_append(ptypes, json_string(rte_get_ptype_inner_l4_name(ptype))); + + return ptypes; +} + +static void +dump_pkt_json(FILE *outf, uint16_t port_id, uint16_t queue, + struct rte_mbuf *pkts[], uint16_t nb_pkts, int is_rx) +{ + char buf[256]; + + for (uint16_t i = 0; i < nb_pkts; i++) { + const struct rte_mbuf *mb = pkts[i]; + struct rte_net_hdr_lens hdr_lens; + const struct rte_ether_hdr *eth_hdr; + struct rte_ether_hdr _eth_hdr; + uint16_t eth_type; + uint64_t ol_flags = mb->ol_flags; + const char *reason = NULL; + json_t *jobj; + + jobj = json_object(); + json_object_set_new(jobj, "port", json_integer(port_id)); + json_object_set_new(jobj, "queue", json_integer(queue)); + json_object_set_new(jobj, "is_rx", json_boolean(is_rx)); + + if (rte_mbuf_check(mb, 1, &reason) < 0) { + json_object_set_new(jobj, "invalid", json_string(reason)); + continue; + } + + eth_hdr = rte_pktmbuf_read(mb, 0, sizeof(_eth_hdr), &_eth_hdr); + eth_type = RTE_BE_TO_CPU_16(eth_hdr->ether_type); + + rte_ether_format_addr(buf, sizeof(buf), ð_hdr->dst_addr); + json_object_set_new(jobj, "dst", json_string(buf)); + rte_ether_format_addr(buf, sizeof(buf), ð_hdr->src_addr); + json_object_set_new(jobj, "src", json_string(buf)); + + snprintf(buf, sizeof(buf), "0x%04x", eth_type); + json_object_set_new(jobj, "type", json_string(buf)); + + json_object_set_new(jobj, "length", json_integer(mb->pkt_len)); + json_object_set_new(jobj, "nb_segs", json_integer(mb->nb_segs)); + + if (ol_flags & RTE_MBUF_F_RX_RSS_HASH) + json_object_set_new(jobj, "rss_hash", json_integer(mb->hash.rss)); + + if (ol_flags & RTE_MBUF_F_RX_FDIR) { + json_t *fdir = json_object(); + + if (ol_flags & RTE_MBUF_F_RX_FDIR_ID) + json_object_set_new(fdir, "id", + json_integer(mb->hash.fdir.hi)); + else if (ol_flags & RTE_MBUF_F_RX_FDIR_FLX) { + json_t *bytes = json_array(); + + json_array_append(bytes, json_integer(mb->hash.fdir.hi)); + json_array_append(bytes, json_integer(mb->hash.fdir.lo)); + json_object_set_new(fdir, "flex", bytes); + } else { + json_object_set_new(fdir, "hash", json_integer(mb->hash.fdir.hash)); + json_object_set_new(fdir, "id", json_integer(mb->hash.fdir.id)); + } + } + + if (is_timestamp_enabled(mb)) + json_object_set_new(jobj, "timestamp", json_integer(get_timestamp(mb))); + + if ((is_rx && (ol_flags & RTE_MBUF_F_RX_QINQ) != 0) || + (!is_rx && (ol_flags & RTE_MBUF_F_TX_QINQ) != 0)) { + json_object_set_new(jobj, "vlan_tci", json_integer(mb->vlan_tci)); + json_object_set_new(jobj, "vlan_outer_tci", + json_integer(mb->vlan_tci_outer)); + } else if ((is_rx && (ol_flags & RTE_MBUF_F_RX_VLAN) != 0) || + (!is_rx && (ol_flags & RTE_MBUF_F_TX_VLAN) != 0)) { + json_object_set_new(jobj, "vlan_tci", json_integer(mb->vlan_tci)); + } + + if (mb->packet_type) + json_object_set_new(jobj, "hw_ptype", encode_ptype(mb->packet_type)); + + uint32_t sw_packet_type = rte_net_get_ptype(mb, &hdr_lens, RTE_PTYPE_ALL_MASK); + json_object_set_new(jobj, "sw_ptype", encode_ptype(sw_packet_type)); + + if (sw_packet_type & RTE_PTYPE_L2_MASK) + json_object_set_new(jobj, "l2_len", json_integer(hdr_lens.l2_len)); + if (sw_packet_type & RTE_PTYPE_L3_MASK) + json_object_set_new(jobj, "l3_len", json_integer(hdr_lens.l3_len)); + if (sw_packet_type & RTE_PTYPE_L4_MASK) + json_object_set_new(jobj, "l4_len", json_integer(hdr_lens.l4_len)); + if (sw_packet_type & RTE_PTYPE_TUNNEL_MASK) + json_object_set_new(jobj, "tunnel_len", json_integer(hdr_lens.tunnel_len)); + if (sw_packet_type & RTE_PTYPE_INNER_L2_MASK) + json_object_set_new(jobj, "inner_l2_len", + json_integer(hdr_lens.inner_l2_len)); + if (sw_packet_type & RTE_PTYPE_INNER_L3_MASK) + json_object_set_new(jobj, "inner_l3_len", + json_integer(hdr_lens.inner_l3_len)); + if (sw_packet_type & RTE_PTYPE_INNER_L4_MASK) + json_object_set_new(jobj, "inner_l4_len", + json_integer(hdr_lens.inner_l4_len)); + + json_object_set_new(jobj, "ol_flags", encode_ol_flags(mb->ol_flags, is_rx)); + + json_dumpf(jobj, outf, JSON_INDENT(4)); + json_decref(jobj); + } +} +#endif + static void dump_pkt_hex(FILE *outf, uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[], uint16_t nb_pkts, int is_rx) @@ -368,6 +530,11 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[], case OUTPUT_MODE_DISSECT: dump_pkt_brief(outf, port_id, queue, pkts, nb_pkts, is_rx); break; +#ifdef RTE_HAS_JANSSON + case OUTPUT_MODE_JSON: + dump_pkt_json(outf, port_id, queue, pkts, nb_pkts, is_rx); + break; +#endif default: return; } diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst index 705b3dc3d5..bb62960582 100644 --- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst +++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst @@ -677,13 +677,14 @@ set format Chose the output format for packet debug log:: - testpmd> set format dissect|hex|verbose + testpmd> set format dissect|hex|json|verbose Available formats are: -* ``verbose`` print the packet meta data information -* ``hex`` print the mbuf flags and data in hex * ``dissect`` print the packet in tshark summary format +* ``hex`` print the mbuf flags and data in hex +* ``json`` print the packet meta data in json +* ``verbose`` print the packet meta data information set verbose -- 2.45.2 ^ permalink raw reply [flat|nested] 54+ messages in thread
end of thread, other threads:[~2024-09-28 18:36 UTC | newest] Thread overview: 54+ 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 2024-09-28 18:36 ` Stephen Hemminger 2024-09-17 3:27 ` [PATCH v8 0/7] Test-pmd packet decode enhancements Stephen Hemminger 2024-09-17 3:27 ` [PATCH v8 1/7] net: add more icmp types Stephen Hemminger 2024-09-17 3:27 ` [PATCH v8 2/7] net: add new packet dissector Stephen Hemminger 2024-09-17 3:28 ` [PATCH v8 3/7] test: add test for " Stephen Hemminger 2024-09-17 3:28 ` [PATCH v8 4/7] test-pmd: add option to redirect packet log Stephen Hemminger 2024-09-17 3:28 ` [PATCH v8 5/7] test-pmd: add hex decode Stephen Hemminger 2024-09-17 3:28 ` [PATCH v8 6/7] test-pmd: add packet dissect format Stephen Hemminger 2024-09-17 3:28 ` [PATCH v8 7/7] test-pmd: add a JSON packet output Stephen Hemminger 2024-09-28 16:18 ` [PATCH v9 0/8] test-pmd packet decoding enhancements Stephen Hemminger 2024-09-28 16:18 ` [PATCH v9 1/8] net: add more icmp types Stephen Hemminger 2024-09-28 16:18 ` [PATCH v9 2/8] net: add new packet dissector Stephen Hemminger 2024-09-28 16:18 ` [PATCH v9 3/8] mbuf: decode the hash and fdir info in rte_pktmbuf_dump Stephen Hemminger 2024-09-28 16:18 ` [PATCH v9 4/8] test: add test for packet dissector Stephen Hemminger 2024-09-28 16:18 ` [PATCH v9 5/8] test-pmd: add option to redirect packet log Stephen Hemminger 2024-09-28 16:18 ` [PATCH v9 6/8] test-pmd: add hex decode Stephen Hemminger 2024-09-28 16:18 ` [PATCH v9 7/8] test-pmd: add packet dissect format Stephen Hemminger 2024-09-28 16:18 ` [PATCH v9 8/8] test-pmd: add a JSON packet output Stephen Hemminger
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).