Add flow_ops to the null PMD that validate input structure and reject
all rules with properly typed rte_flow_error responses. Pattern items
and actions are walked individually, with cause pointers set to the
offending element. flush() succeeds as a no-op; all other operations
return appropriate error codes.

This enables testing the full rte_flow code path without hardware.

Signed-off-by: Stephen Hemminger <[email protected]>
---
 drivers/net/null/meson.build    |   6 +-
 drivers/net/null/null_flow.c    | 237 ++++++++++++++++++++++++++++++++
 drivers/net/null/null_flow.h    |  12 ++
 drivers/net/null/rte_eth_null.c |  12 ++
 4 files changed, 266 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/null/null_flow.c
 create mode 100644 drivers/net/null/null_flow.h

diff --git a/drivers/net/null/meson.build b/drivers/net/null/meson.build
index bad7dc1af7..810715de52 100644
--- a/drivers/net/null/meson.build
+++ b/drivers/net/null/meson.build
@@ -1,5 +1,9 @@
 # SPDX-License-Identifier: BSD-3-Clause
 # Copyright(c) 2017 Intel Corporation
 
-sources = files('rte_eth_null.c')
+sources = files(
+        'rte_eth_null.c',
+        'null_flow.c',
+)
+
 require_iova_in_mbuf = false
diff --git a/drivers/net/null/null_flow.c b/drivers/net/null/null_flow.c
new file mode 100644
index 0000000000..57de18f455
--- /dev/null
+++ b/drivers/net/null/null_flow.c
@@ -0,0 +1,237 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) Stephen Hemminger
+ */
+
+/*
+ * Stub flow operations for the net_null PMD.
+ *
+ * These ops provide a realistic-but-minimal implementation of
+ * rte_flow_ops that can be used for API-layer testing.  Every
+ * operation walks its input, performs basic structural validation,
+ * and then rejects the request with the most specific error type
+ * and message it can produce.  This exercises the full flow-API
+ * code path (port lookup → ops dispatch → PMD callback → error
+ * propagation) without requiring any hardware.
+ *
+ * Summary of behaviour:
+ *
+ *   validate  – walks pattern + actions; rejects each unsupported
+ *               item/action with RTE_FLOW_ERROR_TYPE_ITEM or
+ *               _ACTION, pointing `cause` at the offending element.
+ *               A structurally valid rule whose items are all VOID/END
+ *               and whose actions are all VOID/END gets rejected at
+ *               the attribute level (no ingress+egress+transfer) or
+ *               with a generic "no resources" if nothing else applies.
+ *
+ *   create    – calls validate, then returns NULL (never creates).
+ *
+ *   destroy   – returns -ENOENT (no flows exist).
+ *
+ *   flush     – succeeds (there are no flows to flush).
+ *
+ *   query     – returns -ENOTSUP (no queryable actions).
+ *
+ *   isolate   – returns -ENOTSUP (isolation not supported).
+ */
+
+#include <errno.h>
+#include <string.h>
+
+#include <rte_ethdev.h>
+#include <rte_flow.h>
+#include <rte_flow_driver.h>
+
+/* --------------------------------------------------------------------------
+ * Helpers
+ * -------------------------------------------------------------------------- 
*/
+
+/*
+ * Walk the pattern array and reject the first item that is not
+ * VOID or END.  Return 0 if nothing objectionable was found
+ * (all items are VOID/END), or -rte_errno on failure.
+ */
+static int
+null_flow_validate_pattern(const struct rte_flow_item pattern[],
+                          struct rte_flow_error *error)
+{
+       const struct rte_flow_item *item;
+
+       if (pattern == NULL)
+               return rte_flow_error_set(error, EINVAL,
+                       RTE_FLOW_ERROR_TYPE_ITEM_NUM,
+                       NULL, "NULL pattern");
+
+       for (item = pattern;
+            item->type != RTE_FLOW_ITEM_TYPE_END; item++) {
+               if (item->type == RTE_FLOW_ITEM_TYPE_VOID)
+                       continue;
+
+               /* Any real match item is unsupported. */
+               return rte_flow_error_set(error, ENOTSUP,
+                       RTE_FLOW_ERROR_TYPE_ITEM,
+                       item,
+                       "null PMD does not support pattern items");
+       }
+
+       return 0; /* only VOID + END */
+}
+
+/*
+ * Walk the action array and reject the first action that is not
+ * VOID or END.  Same semantics as above.
+ */
+static int
+null_flow_validate_actions(const struct rte_flow_action actions[],
+                          struct rte_flow_error *error)
+{
+       const struct rte_flow_action *action;
+
+       if (actions == NULL)
+               return rte_flow_error_set(error, EINVAL,
+                       RTE_FLOW_ERROR_TYPE_ACTION_NUM,
+                       NULL, "NULL action list");
+
+       for (action = actions;
+            action->type != RTE_FLOW_ACTION_TYPE_END; action++) {
+               if (action->type == RTE_FLOW_ACTION_TYPE_VOID)
+                       continue;
+
+               return rte_flow_error_set(error, ENOTSUP,
+                       RTE_FLOW_ERROR_TYPE_ACTION,
+                       action,
+                       "null PMD does not support flow actions");
+       }
+
+       return 0; /* only VOID + END */
+}
+
+/* --------------------------------------------------------------------------
+ * Flow ops callbacks
+ * -------------------------------------------------------------------------- 
*/
+
+static int
+null_flow_validate(struct rte_eth_dev *dev __rte_unused,
+                  const struct rte_flow_attr *attr,
+                  const struct rte_flow_item pattern[],
+                  const struct rte_flow_action actions[],
+                  struct rte_flow_error *error)
+{
+       int ret;
+
+       /* ---- attribute checks ---- */
+       if (attr == NULL)
+               return rte_flow_error_set(error, EINVAL,
+                       RTE_FLOW_ERROR_TYPE_ATTR,
+                       NULL, "NULL attributes");
+
+       if (!attr->ingress && !attr->egress && !attr->transfer)
+               return rte_flow_error_set(error, EINVAL,
+                       RTE_FLOW_ERROR_TYPE_ATTR,
+                       attr,
+                       "at least one of ingress/egress/transfer "
+                       "must be set");
+
+       if (attr->transfer)
+               return rte_flow_error_set(error, ENOTSUP,
+                       RTE_FLOW_ERROR_TYPE_ATTR_TRANSFER,
+                       attr,
+                       "transfer attribute not supported");
+
+       if (attr->group > 0)
+               return rte_flow_error_set(error, ENOTSUP,
+                       RTE_FLOW_ERROR_TYPE_ATTR_GROUP,
+                       attr,
+                       "only group 0 is supported");
+
+       if (attr->priority > 0)
+               return rte_flow_error_set(error, ENOTSUP,
+                       RTE_FLOW_ERROR_TYPE_ATTR_PRIORITY,
+                       attr,
+                       "only priority 0 is supported");
+
+       /* ---- pattern checks ---- */
+       ret = null_flow_validate_pattern(pattern, error);
+       if (ret)
+               return ret;
+
+       /* ---- action checks ---- */
+       ret = null_flow_validate_actions(actions, error);
+       if (ret)
+               return ret;
+
+       /*
+        * If we get here, the rule is structurally valid but contains
+        * nothing but VOID items and VOID actions — reject generically.
+        */
+       return rte_flow_error_set(error, ENOTSUP,
+               RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
+               NULL,
+               "null PMD cannot offload any flow rules");
+}
+
+static struct rte_flow *
+null_flow_create(struct rte_eth_dev *dev,
+                const struct rte_flow_attr *attr,
+                const struct rte_flow_item pattern[],
+                const struct rte_flow_action actions[],
+                struct rte_flow_error *error)
+{
+       null_flow_validate(dev, attr, pattern, actions, error);
+       return NULL;
+}
+
+static int
+null_flow_destroy(struct rte_eth_dev *dev __rte_unused,
+                 struct rte_flow *flow __rte_unused,
+                 struct rte_flow_error *error)
+{
+       return rte_flow_error_set(error, ENOENT,
+               RTE_FLOW_ERROR_TYPE_HANDLE,
+               flow,
+               "no flow rules exist on null PMD");
+}
+
+static int
+null_flow_flush(struct rte_eth_dev *dev __rte_unused,
+               struct rte_flow_error *error __rte_unused)
+{
+       /* Nothing to flush — success. */
+       return 0;
+}
+
+static int
+null_flow_query(struct rte_eth_dev *dev __rte_unused,
+               struct rte_flow *flow __rte_unused,
+               const struct rte_flow_action *action __rte_unused,
+               void *data __rte_unused,
+               struct rte_flow_error *error)
+{
+       return rte_flow_error_set(error, ENOTSUP,
+               RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
+               NULL,
+               "null PMD does not support flow queries");
+}
+
+static int
+null_flow_isolate(struct rte_eth_dev *dev __rte_unused,
+                 int set __rte_unused,
+                 struct rte_flow_error *error)
+{
+       return rte_flow_error_set(error, ENOTSUP,
+               RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
+               NULL,
+               "null PMD does not support flow isolation");
+}
+
+/* --------------------------------------------------------------------------
+ * Public ops structure — referenced by rte_eth_null.c
+ * -------------------------------------------------------------------------- 
*/
+
+const struct rte_flow_ops null_flow_ops = {
+       .validate = null_flow_validate,
+       .create   = null_flow_create,
+       .destroy  = null_flow_destroy,
+       .flush    = null_flow_flush,
+       .query    = null_flow_query,
+       .isolate  = null_flow_isolate,
+};
diff --git a/drivers/net/null/null_flow.h b/drivers/net/null/null_flow.h
new file mode 100644
index 0000000000..f589533079
--- /dev/null
+++ b/drivers/net/null/null_flow.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Stephen Hemminger
+ */
+
+#ifndef NULL_FLOW_H
+#define NULL_FLOW_H
+
+#include <rte_flow_driver.h>
+
+extern const struct rte_flow_ops null_flow_ops;
+
+#endif /* NULL_FLOW_H */
diff --git a/drivers/net/null/rte_eth_null.c b/drivers/net/null/rte_eth_null.c
index 46e7e7bd8c..b8dbef35e1 100644
--- a/drivers/net/null/rte_eth_null.c
+++ b/drivers/net/null/rte_eth_null.c
@@ -8,12 +8,15 @@
 #include <rte_mbuf.h>
 #include <ethdev_driver.h>
 #include <ethdev_vdev.h>
+#include <rte_flow.h>
 #include <rte_malloc.h>
 #include <rte_memcpy.h>
 #include <bus_vdev_driver.h>
 #include <rte_kvargs.h>
 #include <rte_spinlock.h>
 
+#include "null_flow.h"
+
 #define ETH_NULL_PACKET_SIZE_ARG       "size"
 #define ETH_NULL_PACKET_COPY_ARG       "copy"
 #define ETH_NULL_PACKET_NO_RX_ARG      "no-rx"
@@ -511,12 +514,21 @@ eth_dev_close(struct rte_eth_dev *dev)
        return 0;
 }
 
+static int
+null_dev_flow_ops_get(struct rte_eth_dev *dev __rte_unused,
+                     const struct rte_flow_ops **ops)
+{
+       *ops = &null_flow_ops;
+       return 0;
+}
+
 static const struct eth_dev_ops ops = {
        .dev_close = eth_dev_close,
        .dev_start = eth_dev_start,
        .dev_stop = eth_dev_stop,
        .dev_configure = eth_dev_configure,
        .dev_infos_get = eth_dev_info,
+       .flow_ops_get = null_dev_flow_ops_get,
        .rx_queue_setup = eth_rx_queue_setup,
        .tx_queue_setup = eth_tx_queue_setup,
        .rx_queue_release = eth_rx_queue_release,
-- 
2.51.0

Reply via email to