[PATCH v1 3/5] examples/flow_parsing: add flow parser demo

Lukas Sismis
2026-01-05T21:31:19Z

Add example application demonstrating the flow parser library API.

The example shows:
- Using parse_attr_str() to parse flow attributes from strings
- Using parse_pattern_str() to parse match patterns
- Using parse_actions_str() to parse flow actions
- Printing parsed results with RTE_FLOW_LOG macros
- Integration without EAL initialization (library is standalone)

Build with: meson configure -Dexamples=flow_parsing

Signed-off-by: Lukas Sismis <sismis@dyna-nic.com>
---
examples/flow_parsing/main.c | 348 ++++++++++++++++++++++++++++++
examples/flow_parsing/meson.build | 11 +
examples/meson.build | 1 +
3 files changed, 360 insertions(+)
create mode 100644 examples/flow_parsing/main.c
create mode 100644 examples/flow_parsing/meson.build

diff --git a/examples/flow_parsing/main.c b/examples/flow_parsing/main.c
new file mode 100644
index 0000000000..b816cd4100
--- /dev/null
+++ b/examples/flow_parsing/main.c
@@ -0,0 +1,348 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Dyna-NIC
+ */
+
+/*
+ * Flow Parsing Example
+ * ====================
+ * This example demonstrates how to use the flow_parser library to parse
+ * flow rule strings into rte_flow C structures. The library provides ONE WAY
+ * to create rte_flow structures - by parsing testpmd-style command strings.
+ *
+ * Alternative approaches:
+ * - Construct rte_flow_attr, rte_flow_item[], and rte_flow_action[]
+ * directly in C code and call rte_flow_create()/rte_flow_validate().
+ *
+ * This string-based approach is useful when:
+ * - Accepting flow rules from user input or configuration files
+ * - Reusing the familiar testpmd flow command syntax
+ * - Rapid prototyping without manually constructing C structures
+ *
+ * Key functions demonstrated:
+ * - rte_flow_parser_parse_attr_str() - Parse flow attributes (ingress/egress/priority)
+ * - rte_flow_parser_parse_pattern_str() - Parse match patterns (eth/ipv4/tcp/etc)
+ * - rte_flow_parser_parse_actions_str() - Parse actions (drop/queue/mark/etc)
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include <rte_common.h>
+#include <rte_flow.h>
+#include <rte_flow_parser.h>
+
+/* Helper to print flow attributes */
+static void
+print_attr(const struct rte_flow_attr *attr)
+{
+ printf(" Attributes:\n");
+ printf(" group=%u priority=%u\n", attr->group, attr->priority);
+ printf(" ingress=%u egress=%u transfer=%u\n",
+ attr->ingress, attr->egress, attr->transfer);
+}
+
+/* Helper to print a MAC address */
+static void
+print_mac(const char *label, const uint8_t *mac)
+{
+ printf(" %s: %02x:%02x:%02x:%02x:%02x:%02x\n", label,
+ mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+}
+
+/* Helper to print pattern items */
+static void
+print_pattern(const struct rte_flow_item *pattern, uint32_t pattern_n)
+{
+ uint32_t i;
+
+ printf(" Pattern (%u items):\n", pattern_n);
+ for (i = 0; i < pattern_n; i++) {
+ const struct rte_flow_item *item = &pattern[i];
+
+ switch (item->type) {
+ case RTE_FLOW_ITEM_TYPE_END:
+ printf(" [%u] END\n", i);
+ break;
+ case RTE_FLOW_ITEM_TYPE_ETH:
+ printf(" [%u] ETH", i);
+ if (item->spec) {
+ const struct rte_flow_item_eth *eth = item->spec;
+ printf("\n");
+ print_mac("dst", eth->hdr.dst_addr.addr_bytes);
+ print_mac("src", eth->hdr.src_addr.addr_bytes);
+ } else {
+ printf(" (any)\n");
+ }
+ break;
+ case RTE_FLOW_ITEM_TYPE_IPV4:
+ printf(" [%u] IPV4", i);
+ if (item->spec) {
+ const struct rte_flow_item_ipv4 *ipv4 = item->spec;
+ char src[INET_ADDRSTRLEN], dst[INET_ADDRSTRLEN];
+ inet_ntop(AF_INET, &ipv4->hdr.src_addr, src, sizeof(src));
+ inet_ntop(AF_INET, &ipv4->hdr.dst_addr, dst, sizeof(dst));
+ printf(" src=%s dst=%s\n", src, dst);
+ } else {
+ printf(" (any)\n");
+ }
+ break;
+ case RTE_FLOW_ITEM_TYPE_TCP:
+ printf(" [%u] TCP", i);
+ if (item->spec) {
+ const struct rte_flow_item_tcp *tcp = item->spec;
+ printf(" sport=%u dport=%u\n",
+ ntohs(tcp->hdr.src_port),
+ ntohs(tcp->hdr.dst_port));
+ } else {
+ printf(" (any)\n");
+ }
+ break;
+ case RTE_FLOW_ITEM_TYPE_UDP:
+ printf(" [%u] UDP", i);
+ if (item->spec) {
+ const struct rte_flow_item_udp *udp = item->spec;
+ printf(" sport=%u dport=%u\n",
+ ntohs(udp->hdr.src_port),
+ ntohs(udp->hdr.dst_port));
+ } else {
+ printf(" (any)\n");
+ }
+ break;
+ default:
+ printf(" [%u] type=%d\n", i, item->type);
+ break;
+ }
+ }
+}
+
+/* Helper to print actions */
+static void
+print_actions(const struct rte_flow_action *actions, uint32_t actions_n)
+{
+ uint32_t i;
+
+ printf(" Actions (%u items):\n", actions_n);
+ for (i = 0; i < actions_n; i++) {
+ const struct rte_flow_action *action = &actions[i];
+
+ switch (action->type) {
+ case RTE_FLOW_ACTION_TYPE_END:
+ printf(" [%u] END\n", i);
+ break;
+ case RTE_FLOW_ACTION_TYPE_DROP:
+ printf(" [%u] DROP\n", i);
+ break;
+ case RTE_FLOW_ACTION_TYPE_QUEUE:
+ if (action->conf) {
+ const struct rte_flow_action_queue *q = action->conf;
+ printf(" [%u] QUEUE index=%u\n", i, q->index);
+ }
+ break;
+ case RTE_FLOW_ACTION_TYPE_MARK:
+ if (action->conf) {
+ const struct rte_flow_action_mark *m = action->conf;
+ printf(" [%u] MARK id=%u\n", i, m->id);
+ }
+ break;
+ case RTE_FLOW_ACTION_TYPE_COUNT:
+ printf(" [%u] COUNT\n", i);
+ break;
+ case RTE_FLOW_ACTION_TYPE_PORT_ID:
+ if (action->conf) {
+ const struct rte_flow_action_port_id *p = action->conf;
+ printf(" [%u] PORT_ID id=%u\n", i, p->id);
+ }
+ break;
+ default:
+ printf(" [%u] type=%d\n", i, action->type);
+ break;
+ }
+ }
+}
+
+/*
+ * Demonstrate parsing flow attributes
+ */
+static void
+demo_parse_attr(void)
+{
+ static const char *attr_strings[] = {
+ "ingress",
+ "egress",
+ "ingress priority 5",
+ "ingress group 1 priority 10",
+ "transfer",
+ };
+ struct rte_flow_attr attr;
+ unsigned int i;
+ int ret;
+
+ printf("\n=== Parsing Flow Attributes ===\n");
+ printf("Use rte_flow_parser_parse_attr_str() to parse attribute strings.\n\n");
+
+ for (i = 0; i < RTE_DIM(attr_strings); i++) {
+ printf("Input: \"%s\"\n", attr_strings[i]);
+ memset(&attr, 0, sizeof(attr));
+ ret = rte_flow_parser_parse_attr_str(attr_strings[i], &attr);
+ if (ret == 0) {
+ print_attr(&attr);
+ } else {
+ printf(" ERROR: %d (%s)\n", ret, strerror(-ret));
+ }
+ printf("\n");
+ }
+}
+
+/*
+ * Demonstrate parsing flow patterns
+ */
+static void
+demo_parse_pattern(void)
+{
+ static const char *pattern_strings[] = {
+ "eth / end",
+ "eth dst is 90:61:ae:fd:41:43 / end",
+ "eth / ipv4 src is 192.168.1.1 / end",
+ "eth / ipv4 / tcp dst is 80 / end",
+ "eth / ipv4 src is 10.0.0.1 dst is 10.0.0.2 / udp src is 1234 dst is 5678 / end",
+ };
+ const struct rte_flow_item *pattern;
+ uint32_t pattern_n;
+ unsigned int i;
+ int ret;
+
+ printf("\n=== Parsing Flow Patterns ===\n");
+ printf("Use rte_flow_parser_parse_pattern_str() to parse pattern strings.\n\n");
+
+ for (i = 0; i < RTE_DIM(pattern_strings); i++) {
+ printf("Input: \"%s\"\n", pattern_strings[i]);
+ ret = rte_flow_parser_parse_pattern_str(pattern_strings[i],
+ &pattern, &pattern_n);
+ if (ret == 0) {
+ print_pattern(pattern, pattern_n);
+ } else {
+ printf(" ERROR: %d (%s)\n", ret, strerror(-ret));
+ }
+ printf("\n");
+ }
+}
+
+/*
+ * Demonstrate parsing flow actions
+ */
+static void
+demo_parse_actions(void)
+{
+ static const char *action_strings[] = {
+ "drop / end",
+ "queue index 3 / end",
+ "mark id 42 / end",
+ "count / queue index 1 / end",
+ "mark id 100 / count / queue index 5 / end",
+ };
+ const struct rte_flow_action *actions;
+ uint32_t actions_n;
+ unsigned int i;
+ int ret;
+
+ printf("\n=== Parsing Flow Actions ===\n");
+ printf("Use rte_flow_parser_parse_actions_str() to parse action strings.\n\n");
+
+ for (i = 0; i < RTE_DIM(action_strings); i++) {
+ printf("Input: \"%s\"\n", action_strings[i]);
+ ret = rte_flow_parser_parse_actions_str(action_strings[i],
+ &actions, &actions_n);
+ if (ret == 0) {
+ print_actions(actions, actions_n);
+ } else {
+ printf(" ERROR: %d (%s)\n", ret, strerror(-ret));
+ }
+ printf("\n");
+ }
+}
+
+/*
+ * Demonstrate combining parsed components
+ *
+ * NOTE: The parse functions return pointers to internal static buffers.
+ * Each subsequent parse call overwrites the previous result.
+ * Therefore, you must use the returned data before calling another parse function,
+ * or copy the data to your own storage.
+ */
+static void
+demo_combine(void)
+{
+ struct rte_flow_attr attr;
+ const struct rte_flow_item *pattern;
+ const struct rte_flow_action *actions;
+ uint32_t pattern_n, actions_n;
+ int ret;
+
+ printf("\n=== Combining Parsed Components ===\n");
+ printf("Parse attributes, patterns, and actions separately,\n");
+ printf("then use each result before the next parse call.\n\n");
+ printf("NOTE: Returned pointers are only valid until the next parse call!\n\n");
+
+ /* Parse and display attributes */
+ ret = rte_flow_parser_parse_attr_str("ingress priority 1", &attr);
+ if (ret != 0) {
+ printf("Failed to parse attributes: %d\n", ret);
+ return;
+ }
+ printf("Parsed attributes:\n");
+ print_attr(&attr);
+
+ /* Parse and display pattern */
+ ret = rte_flow_parser_parse_pattern_str(
+ "eth / ipv4 src is 192.168.1.0 / tcp dst is 443 / end",
+ &pattern, &pattern_n);
+ if (ret != 0) {
+ printf("Failed to parse pattern: %d\n", ret);
+ return;
+ }
+ printf("\nParsed pattern:\n");
+ print_pattern(pattern, pattern_n);
+
+ /* Parse and display actions */
+ ret = rte_flow_parser_parse_actions_str(
+ "mark id 1 / queue index 2 / end",
+ &actions, &actions_n);
+ if (ret != 0) {
+ printf("Failed to parse actions: %d\n", ret);
+ return;
+ }
+ printf("\nParsed actions:\n");
+ print_actions(actions, actions_n);
+
+ printf("\nIn a real application, copy pattern/actions to your own storage,\n");
+ printf("then call:\n");
+ printf(" rte_flow_validate(port_id, &attr, pattern, actions, &error)\n");
+ printf(" rte_flow_create(port_id, &attr, pattern, actions, &error)\n");
+}
+
+int
+main(void)
+{
+ int ret;
+
+ printf("Flow Parser Library Example\n");
+ printf("===========================\n");
+
+ /* Initialize the flow parser */
+ ret = rte_flow_parser_init(NULL);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to initialize flow parser: %d\n", ret);
+ return 1;
+ }
+
+ /* Run demonstrations */
+ demo_parse_attr();
+ demo_parse_pattern();
+ demo_parse_actions();
+ demo_combine();
+
+ printf("\n=== Example Complete ===\n");
+ return 0;
+}
diff --git a/examples/flow_parsing/meson.build b/examples/flow_parsing/meson.build
new file mode 100644
index 0000000000..deab338bfb
--- /dev/null
+++ b/examples/flow_parsing/meson.build
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2024 Intel Corporation
+
+# meson file, for building this example as part of a main DPDK build.
+#
+# To build this example as a standalone application with an already-installed
+# DPDK instance, use 'make'
+
+allow_experimental_apis = true
+deps += 'flow_parser'
+sources = files('main.c')
diff --git a/examples/meson.build b/examples/meson.build
index 25d9c88457..22f45e8c81 100644
--- a/examples/meson.build
+++ b/examples/meson.build
@@ -17,6 +17,7 @@ all_examples = [
'eventdev_pipeline',
'fips_validation',
'flow_filtering',
+ 'flow_parsing',
'helloworld',
'ip_fragmentation',
'ip_pipeline',
--
2.43.7