Skip to contentSkip to conversation

lib/ethdev: support inline calculating masked item value

PATCH Superseded Bing Zhao 4 participants 26 messages Nov 17, 2025 – Oct 16

lore
Share & tools
lore

Bing Zhao

author
#0 lore raw

PATCH 1/2 lib/ethdev: support inline calculating masked item value

In the asynchronous API definition and some drivers, the rte_flow_item spec value may not be calculated by the driver due to the reason of speed of light rule insertion rate and sometimes the input parameters will be copied and changed internally.

After copying, the spec and last will be protected by the keyword const and cannot be changed in the code itself. And also the driver needs some extra memory to do the calculation and extra conditions to understand the length of each item spec. This is not efficient.

To solve the issue and support usage of the following fix, a new OP was introduced to calculate the spec and last values after applying the mask inline.

---
 lib/ethdev/rte_flow.c | 29 +++++++++++++++++++++++------
 lib/ethdev/rte_flow.h | 12 ++++++++++++
 2 files changed, 35 insertions(+), 6 deletions(-)
lib/ethdev/rte_flow.c +23 -6
index fe8f43caff..b94f0e304d 100644
@@ -831,6 +831,8 @@ rte_flow_conv_action_conf(void *buf, const size_t size,
  *   RTE_FLOW_ITEM_TYPE_END is encountered.
  * @param[out] error
  *   Perform verbose error reporting if not NULL.
+ * @param[in] with_mask
+ *   If true, @p src mask will be applied to spec and last.
  *
  * @return
  *   A positive value representing the number of bytes needed to store
@@ -843,12 +845,14 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
 		      const size_t size,
 		      const struct rte_flow_item *src,
 		      unsigned int num,
+		      bool with_mask,
 		      struct rte_flow_error *error)
 {
 	uintptr_t data = (uintptr_t)dst;
 	size_t off;
 	size_t ret;
-	unsigned int i;
+	unsigned int i, j;
+	uint8_t *c_spec = NULL, *c_last = NULL;
 
 	for (i = 0, off = 0; !num || i != num; ++i, ++src, ++dst) {
 		/**
@@ -878,9 +882,14 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
 				((void *)(data + off),
 				 size > off ? size - off : 0, src,
 				 RTE_FLOW_CONV_ITEM_SPEC);
-			if (size && size >= off + ret)
+			if (size && size >= off + ret) {
 				dst->spec = (void *)(data + off);
+				c_spec = (uint8_t *)(data + off);
+			}
 			off += ret;
+			if (with_mask && c_spec && src->mask)
+				for (j = 0; j < ret; j++)
+					*(c_spec + j) &= *((const uint8_t *)src->mask + j);
 
 		}
 		if (src->last) {
@@ -889,9 +898,14 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
 				((void *)(data + off),
 				 size > off ? size - off : 0, src,
 				 RTE_FLOW_CONV_ITEM_LAST);
-			if (size && size >= off + ret)
+			if (size && size >= off + ret) {
 				dst->last = (void *)(data + off);
+				c_last = (uint8_t *)(data + off);
+			}
 			off += ret;
+			if (with_mask && c_last && src->mask)
+				for (j = 0; j < ret; j++)
+					*(c_last + j) &= *((const uint8_t *)src->mask + j);
 		}
 		if (src->mask) {
 			off = RTE_ALIGN_CEIL(off, sizeof(double));
@@ -1038,7 +1052,7 @@ rte_flow_conv_rule(struct rte_flow_conv_rule *dst,
 		off = RTE_ALIGN_CEIL(off, sizeof(double));
 		ret = rte_flow_conv_pattern((void *)((uintptr_t)dst + off),
 					    size > off ? size - off : 0,
-					    src->pattern_ro, 0, error);
+					    src->pattern_ro, 0, false, error);
 		if (ret < 0)
 			return ret;
 		if (size && size >= off + (size_t)ret)
@@ -1139,7 +1153,7 @@ rte_flow_conv(enum rte_flow_conv_op op,
 		ret = sizeof(*attr);
 		break;
 	case RTE_FLOW_CONV_OP_ITEM:
-		ret = rte_flow_conv_pattern(dst, size, src, 1, error);
+		ret = rte_flow_conv_pattern(dst, size, src, 1, false, error);
 		break;
 	case RTE_FLOW_CONV_OP_ITEM_MASK:
 		item = src;
@@ -1154,7 +1168,7 @@ rte_flow_conv(enum rte_flow_conv_op op,
 		ret = rte_flow_conv_actions(dst, size, src, 1, error);
 		break;
 	case RTE_FLOW_CONV_OP_PATTERN:
-		ret = rte_flow_conv_pattern(dst, size, src, 0, error);
+		ret = rte_flow_conv_pattern(dst, size, src, 0, false, error);
 		break;
 	case RTE_FLOW_CONV_OP_ACTIONS:
 		ret = rte_flow_conv_actions(dst, size, src, 0, error);
@@ -1174,6 +1188,9 @@ rte_flow_conv(enum rte_flow_conv_op op,
 	case RTE_FLOW_CONV_OP_ACTION_NAME_PTR:
 		ret = rte_flow_conv_name(1, 1, dst, size, src, error);
 		break;
+	case RTE_FLOW_CONV_OP_PATTERN_MASKED:
+		ret = rte_flow_conv_pattern(dst, size, src, 0, true, error);
+		break;
 	default:
 		ret = rte_flow_error_set
 		(error, ENOTSUP, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
lib/ethdev/rte_flow.h +12 -0
index 3d2ccdeb92..85acb26752 100644
@@ -4559,6 +4559,18 @@ enum rte_flow_conv_op {
 	 *   @code const char ** @endcode
 	 */
 	RTE_FLOW_CONV_OP_ACTION_NAME_PTR,
+
+	/**
+	 * Convert an entire pattern.
+	 *
+	 Duplicates all pattern items at once, applying @p mask to @p spec and @p spec.
+	 *
+	 * - @p src type:
+	 *   @code const struct rte_flow_item * @endcode
+	 * - @p dst type:
+	 *   @code struct rte_flow_item * @endcode
+	 */
+	RTE_FLOW_CONV_OP_PATTERN_MASKED,
 };
 
 /**
Signature · 1 line
2.34.1
#1 lore raw

lib/ethdev/rte_flow.c rte_flow_conv_action_conf(void *buf, const size_t size, · line 838

Quoted 13 lines · On Mon, 17 Nov 2025 09:54:07 +0200 Bing Zhao <[email protected]> wrote:
lib/ethdev/rte_flow.c +2 -0
index fe8f43caff..b94f0e304d 100644
@@ -831,6 +831,8 @@ rte_flow_conv_action_conf(void *buf, const size_t size,
  *   RTE_FLOW_ITEM_TYPE_END is encountered.
  * @param[out] error
  *   Perform verbose error reporting if not NULL.
+ * @param[in] with_mask
+ *   If true, @p src mask will be applied to spec and last.
  *
  * @return
  *   A positive value representing the number of bytes needed to store

Sorry, this patch is submitted too late to make it into 25.11 release. Missing any release note.

This would be a good example of where to use API versioning in a later release.

author
#2 lore raw

Hi Stephen,

Thanks for the comments. What do you mean by missing release notes? Should I add some description of the new conversion type? It will not break the current usage of any user. So do you mean I should let the user know that they can do the inline calculation with the new API?

Thanks

Quoted 38 lines

-----Original Message-----
From: Stephen Hemminger <[email protected]>
Sent: Tuesday, November 18, 2025 1:25 AM
To: Bing Zhao <[email protected]>
Cc: Slava Ovsiienko <[email protected]>; [email protected]; Raslan Darawsheh <[email protected]>; Ori Kam <[email protected]>; Dariusz Sosnowski <[email protected]>; Suanming Mou <[email protected]>; Matan Azrad <[email protected]>; NBU-Contact-Thomas Monjalon (EXTERNAL) <[email protected]>
Subject: Re: [PATCH 1/2] lib/ethdev: support inline calculating masked item value

External email: Use caution opening links or attachments

lib/ethdev/rte_flow.c rte_flow_conv_action_conf(void *buf, const size_t

Quoted 5 lines · On Mon, 17 Nov 2025 09:54:07 +0200 Bing Zhao <[email protected]> wrote:
lib/ethdev/rte_flow.c index renamed from lib/ethdev/rte_flow.c +0 -0

fe8f43caff..b94f0e304d 100644

lib/ethdev/rte_flow.c +0 -0
@@ -831,6 +831,8 @@ rte_flow_conv_action_conf(void *buf, const size_t

size,

lib/ethdev/rte_flow.c rte_flow_conv_action_conf(void *buf, const size_t size, · line 838

Quoted 8 lines
lib/ethdev/rte_flow.c +2 -0
  *   RTE_FLOW_ITEM_TYPE_END is encountered.
  * @param[out] error
  *   Perform verbose error reporting if not NULL.
+ * @param[in] with_mask
+ *   If true, @p src mask will be applied to spec and last.
  *
  * @return
  *   A positive value representing the number of bytes needed to store

Sorry, this patch is submitted too late to make it into 25.11 release. Missing any release note.

This would be a good example of where to use API versioning in a later release.

#3 lore raw
In reply to Bing Zhao
Quoted 10 lines · On Mon, 17 Nov 2025 09:54:07 +0200 Bing Zhao <[email protected]> wrote:

In the asynchronous API definition and some drivers, the rte_flow_item spec value may not be calculated by the driver due to the reason of speed of light rule insertion rate and sometimes the input parameters will be copied and changed internally.

After copying, the spec and last will be protected by the keyword const and cannot be changed in the code itself. And also the driver needs some extra memory to do the calculation and extra conditions to understand the length of each item spec. This is not efficient.

Quoted 6 lines

To solve the issue and support usage of the following fix, a new OP was introduced to calculate the spec and last values after applying the mask inline.

---

Looked at this patch again:
1. No API/ABI breakage adding a single flow type is going
to work great. rte_flow_conv_pattern is internal so changing is no problem.
2. No need for release note for single flow change.

3. Do need a test to cover the new code. I know rte_flow is way under tested right now. Maybe a chance to use AI to generate a unit test for rte_flow. This is important because it is a bug trap to add code that is not covered.

author
#4 lore raw

Sure. I will add some tests.

Quoted 47 lines

-----Original Message-----
From: Stephen Hemminger <[email protected]>
Sent: Friday, February 6, 2026 12:43 AM
To: Bing Zhao <[email protected]>
Cc: Slava Ovsiienko <[email protected]>; [email protected]; Raslan Darawsheh <[email protected]>; Ori Kam <[email protected]>; Dariusz Sosnowski <[email protected]>; Suanming Mou <[email protected]>; Matan Azrad <[email protected]>; NBU-Contact-Thomas Monjalon (EXTERNAL) <[email protected]>
Subject: Re: [PATCH 1/2] lib/ethdev: support inline calculating masked item value

External email: Use caution opening links or attachments

Quoted 10 lines · On Mon, 17 Nov 2025 09:54:07 +0200 Bing Zhao <[email protected]> wrote:

In the asynchronous API definition and some drivers, the rte_flow_item spec value may not be calculated by the driver due to the reason of speed of light rule insertion rate and sometimes the input parameters will be copied and changed internally.

After copying, the spec and last will be protected by the keyword const and cannot be changed in the code itself. And also the driver needs some extra memory to do the calculation and extra conditions to understand the length of each item spec. This is not efficient.

Quoted 6 lines

To solve the issue and support usage of the following fix, a new OP was introduced to calculate the spec and last values after applying the mask inline.

---

Looked at this patch again:
1. No API/ABI breakage adding a single flow type is going to work great. rte_flow_conv_pattern is internal so changing is no
problem.
2. No need for release note for single flow change.

3. Do need a test to cover the new code. I know rte_flow is way under
tested
right now. Maybe a chance to use AI to generate a unit test for
rte_flow.
This is important because it is a bug trap to add code that is not
covered.

author
#5 lore raw

Hi Stephen,

Since there is no UT for the flow API. Such application would require some design discussions, and it is too short for -rc1. Could we focus on the change itself and try to close it? Thanks a lot.

Quoted 61 lines

-----Original Message-----
From: Bing Zhao <[email protected]>
Sent: Monday, February 9, 2026 12:24 PM
To: Stephen Hemminger <[email protected]>
Cc: Slava Ovsiienko <[email protected]>; [email protected]; Raslan Darawsheh <[email protected]>; Ori Kam <[email protected]>; Dariusz Sosnowski <[email protected]>; Suanming Mou <[email protected]>; Matan Azrad <[email protected]>; NBU-Contact-Thomas Monjalon (EXTERNAL) <[email protected]>
Subject: RE: [PATCH 1/2] lib/ethdev: support inline calculating masked item value

Sure. I will add some tests.

Quoted 47 lines

-----Original Message-----
From: Stephen Hemminger <[email protected]>
Sent: Friday, February 6, 2026 12:43 AM
To: Bing Zhao <[email protected]>
Cc: Slava Ovsiienko <[email protected]>; [email protected]; Raslan Darawsheh <[email protected]>; Ori Kam <[email protected]>; Dariusz Sosnowski <[email protected]>; Suanming Mou <[email protected]>; Matan Azrad <[email protected]>; NBU-Contact-Thomas Monjalon (EXTERNAL) <[email protected]>
Subject: Re: [PATCH 1/2] lib/ethdev: support inline calculating masked item value

External email: Use caution opening links or attachments

Quoted 10 lines · On Mon, 17 Nov 2025 09:54:07 +0200 Bing Zhao <[email protected]> wrote:

In the asynchronous API definition and some drivers, the rte_flow_item spec value may not be calculated by the driver due to the reason of speed of light rule insertion rate and sometimes the input parameters will be copied and changed internally.

After copying, the spec and last will be protected by the keyword const and cannot be changed in the code itself. And also the driver needs some extra memory to do the calculation and extra conditions to understand the length of each item spec. This is not efficient.

Quoted 6 lines

To solve the issue and support usage of the following fix, a new OP was introduced to calculate the spec and last values after applying the mask inline.

---

Looked at this patch again:
1. No API/ABI breakage adding a single flow type is going to work great. rte_flow_conv_pattern is internal so changing is
no problem.
2. No need for release note for single flow change.

3. Do need a test to cover the new code. I know rte_flow is way
under tested
right now. Maybe a chance to use AI to generate a unit test for
rte_flow.
This is important because it is a bug trap to add code that is
not covered.

#6 lore raw
Quoted 3 lines · On Mon, 9 Feb 2026 14:17:27 +0000 Bing Zhao <[email protected]> wrote:

Hi Stephen,

Since there is no UT for the flow API. Such application would require some design discussions, and it is too short for -rc1. Could we focus on the change itself and try to close it? Thanks a lot.

I have discovered that AI does pretty good job of generating basic tests. That is all we need to get some basic CI coverage.

#7 lore raw

09/02/2026 19:58, Stephen Hemminger:

Quoted 5 lines
Quoted 2 lines · On Mon, 9 Feb 2026 14:17:27 +0000 Bing Zhao <[email protected]> wrote:

Hi Stephen,

Quoted 7 lines
Quoted 4 lines

Since there is no UT for the flow API.
Such application would require some design discussions,
and it is too short for -rc1.
Could we focus on the change itself and try to close it? Thanks a lot.

I have discovered that AI does pretty good job of generating basic tests. That is all we need to get some basic CI coverage.

As you know, we don't have a stub implementation working without specific HW. So far, the decision discussed in the techboard was to implement flow API tests in DTS and run it in UNH. This work is in progress in the DTS team.

I don't think we should require something else at this point.

author
#8 lore raw

PATCH v2 1/1 lib/ethdev: support inline calculating masked item value

In reply to Bing Zhao

In the asynchronous API definition and some drivers, the rte_flow_item spec value may not be calculated by the driver due to the reason of speed of light rule insertion rate and sometimes the input parameters will be copied and changed internally.

After copying, the spec and last will be protected by the keyword const and cannot be changed in the code itself. And also the driver needs some extra memory to do the calculation and extra conditions to understand the length of each item spec. This is not efficient.

To solve the issue and support usage of the following fix, a new OP was introduced to calculate the spec and last values after applying the mask inline.

---
 lib/ethdev/rte_flow.c | 29 +++++++++++++++++++++++------
 lib/ethdev/rte_flow.h | 12 ++++++++++++
 2 files changed, 35 insertions(+), 6 deletions(-)
lib/ethdev/rte_flow.c +23 -6
index fe8f43caff..b94f0e304d 100644
@@ -831,6 +831,8 @@ rte_flow_conv_action_conf(void *buf, const size_t size,
  *   RTE_FLOW_ITEM_TYPE_END is encountered.
  * @param[out] error
  *   Perform verbose error reporting if not NULL.
+ * @param[in] with_mask
+ *   If true, @p src mask will be applied to spec and last.
  *
  * @return
  *   A positive value representing the number of bytes needed to store
@@ -843,12 +845,14 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
 		      const size_t size,
 		      const struct rte_flow_item *src,
 		      unsigned int num,
+		      bool with_mask,
 		      struct rte_flow_error *error)
 {
 	uintptr_t data = (uintptr_t)dst;
 	size_t off;
 	size_t ret;
-	unsigned int i;
+	unsigned int i, j;
+	uint8_t *c_spec = NULL, *c_last = NULL;
 
 	for (i = 0, off = 0; !num || i != num; ++i, ++src, ++dst) {
 		/**
@@ -878,9 +882,14 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
 				((void *)(data + off),
 				 size > off ? size - off : 0, src,
 				 RTE_FLOW_CONV_ITEM_SPEC);
-			if (size && size >= off + ret)
+			if (size && size >= off + ret) {
 				dst->spec = (void *)(data + off);
+				c_spec = (uint8_t *)(data + off);
+			}
 			off += ret;
+			if (with_mask && c_spec && src->mask)
+				for (j = 0; j < ret; j++)
+					*(c_spec + j) &= *((const uint8_t *)src->mask + j);
 
 		}
 		if (src->last) {
@@ -889,9 +898,14 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
 				((void *)(data + off),
 				 size > off ? size - off : 0, src,
 				 RTE_FLOW_CONV_ITEM_LAST);
-			if (size && size >= off + ret)
+			if (size && size >= off + ret) {
 				dst->last = (void *)(data + off);
+				c_last = (uint8_t *)(data + off);
+			}
 			off += ret;
+			if (with_mask && c_last && src->mask)
+				for (j = 0; j < ret; j++)
+					*(c_last + j) &= *((const uint8_t *)src->mask + j);
 		}
 		if (src->mask) {
 			off = RTE_ALIGN_CEIL(off, sizeof(double));
@@ -1038,7 +1052,7 @@ rte_flow_conv_rule(struct rte_flow_conv_rule *dst,
 		off = RTE_ALIGN_CEIL(off, sizeof(double));
 		ret = rte_flow_conv_pattern((void *)((uintptr_t)dst + off),
 					    size > off ? size - off : 0,
-					    src->pattern_ro, 0, error);
+					    src->pattern_ro, 0, false, error);
 		if (ret < 0)
 			return ret;
 		if (size && size >= off + (size_t)ret)
@@ -1139,7 +1153,7 @@ rte_flow_conv(enum rte_flow_conv_op op,
 		ret = sizeof(*attr);
 		break;
 	case RTE_FLOW_CONV_OP_ITEM:
-		ret = rte_flow_conv_pattern(dst, size, src, 1, error);
+		ret = rte_flow_conv_pattern(dst, size, src, 1, false, error);
 		break;
 	case RTE_FLOW_CONV_OP_ITEM_MASK:
 		item = src;
@@ -1154,7 +1168,7 @@ rte_flow_conv(enum rte_flow_conv_op op,
 		ret = rte_flow_conv_actions(dst, size, src, 1, error);
 		break;
 	case RTE_FLOW_CONV_OP_PATTERN:
-		ret = rte_flow_conv_pattern(dst, size, src, 0, error);
+		ret = rte_flow_conv_pattern(dst, size, src, 0, false, error);
 		break;
 	case RTE_FLOW_CONV_OP_ACTIONS:
 		ret = rte_flow_conv_actions(dst, size, src, 0, error);
@@ -1174,6 +1188,9 @@ rte_flow_conv(enum rte_flow_conv_op op,
 	case RTE_FLOW_CONV_OP_ACTION_NAME_PTR:
 		ret = rte_flow_conv_name(1, 1, dst, size, src, error);
 		break;
+	case RTE_FLOW_CONV_OP_PATTERN_MASKED:
+		ret = rte_flow_conv_pattern(dst, size, src, 0, true, error);
+		break;
 	default:
 		ret = rte_flow_error_set
 		(error, ENOTSUP, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
lib/ethdev/rte_flow.h +12 -0
index ba3bcc89a3..f56b328537 100644
@@ -4558,6 +4558,18 @@ enum rte_flow_conv_op {
 	 *   @code const char ** @endcode
 	 */
 	RTE_FLOW_CONV_OP_ACTION_NAME_PTR,
+
+	/**
+	 * Convert an entire pattern.
+	 *
+	 Duplicates all pattern items at once, applying @p mask to @p spec and @p spec.
+	 *
+	 * - @p src type:
+	 *   @code const struct rte_flow_item * @endcode
+	 * - @p dst type:
+	 *   @code struct rte_flow_item * @endcode
+	 */
+	RTE_FLOW_CONV_OP_PATTERN_MASKED,
 };
 
 /**
Signature · 1 line
2.34.1
#9 lore raw
Quoted 10 lines · On Fri, 13 Feb 2026 15:31:43 +0200 Bing Zhao <[email protected]> wrote:

In the asynchronous API definition and some drivers, the rte_flow_item spec value may not be calculated by the driver due to the reason of speed of light rule insertion rate and sometimes the input parameters will be copied and changed internally.

After copying, the spec and last will be protected by the keyword const and cannot be changed in the code itself. And also the driver needs some extra memory to do the calculation and extra conditions to understand the length of each item spec. This is not efficient.

Quoted 6 lines

To solve the issue and support usage of the following fix, a new OP was introduced to calculate the spec and last values after applying the mask inline.

I didn't see any direct issue, but AI deep dive did.
Please consider these and resubmit (or say why they are false positives).

PS: I would just pull the Doxygen comment, this is internal and never gets processed for inclusion in doc.

Summary
Severity Finding
Error Stale c_spec/c_last pointers across loop iterations — can corrupt previously copied item data
Error Potential out-of-bounds read on src->mask when ret exceeds actual mask size
Error Wrong component prefix (lib/ethdev: → ethdev:)
Error Broken Doxygen comment (missing * prefix) and typo (spec → last)
Warning New enum value not marked experimental
Warning Missing release notes for API change
Warning Missing functional tests
Info Pointer arithmetic vs array indexing style

---

## Deep Dive Review: [PATCH v2] lib/ethdev: support inline calculating masked item value

### Commit Message Analysis

**Subject line:** lib/ethdev: support inline calculating masked item value

**Error — Wrong component prefix.** The subject uses lib/ethdev: but per AGENTS.md §Common Prefix Mistakes, the correct prefix for ethdev is ethdev: (without lib/). Should be:

```
ethdev: support inline calculating masked item value
```

Let me verify the character count of the current subject: lib/ethdev: support inline calculating masked item value = 56 characters — within the 60-character limit, but after fixing the prefix to ethdev: it would be shorter still.

**Commit body** — the description is functional but uses somewhat unclear phrasing: "the reason of speed of light rule insertion rate" is vague and would benefit from clearer technical justification. However, per AGENTS.md guidance, I'll only flag clarity issues if genuinely confusing — this is borderline but understandable in context to a flow API developer.

**Tags:** Signed-off-by and Reviewed-by are present and in correct order (Group 2 tags only, no Group 1 tags needed). No issues there.

---

### Correctness Bugs (Highest Priority)

**1. Error — c_spec / c_last not reset per loop iteration: stale pointer may cause masking wrong item's data (~80% confidence)**

The variables c_spec and c_last are declared outside the for loop and initialized to NULL, but they are **never reset to NULL** at the start of each iteration. Consider this scenario:

- Iteration N: src->spec exists, size is sufficient, so c_spec is set to a valid pointer. The masking is applied correctly.
- Iteration N+1: src->spec exists, but size is *not* sufficient (the size && size >= off + ret check fails). c_spec retains the pointer from iteration N. Then the with_mask && c_spec && src->mask check passes, and the masking loop applies the **new item's mask** to the **previous item's already-copied spec data**, corrupting it.

The same issue applies to c_last.

**Suggested fix:** Reset both pointers at the top of each loop iteration, or (better) scope them inside the relevant if blocks:

```c
for (i = 0, off = 0; !num || i != num; ++i, ++src, ++dst) {
    uint8_t *c_spec = NULL, *c_last = NULL;
    /* ... rest of loop body ... */
```

**2. Error — Masking applied to converted data using ret as length, but ret includes alignment padding (~60% confidence)**

The return value ret from rte_flow_conv_pattern internal call via rte_flow_conv_item_spec may include padding from RTE_ALIGN_CEIL. The masking loop uses ret as the byte count:

```c
for (j = 0; j < ret; j++)
    *(c_spec + j) &= *((const uint8_t *)src->mask + j);
```

If ret is larger than the actual item spec size, this reads past the end of src->mask, which may only be sizeof(struct rte_flow_item_<type>) bytes. This is a potential **out-of-bounds read** on src->mask.

The mask is user-provided via src->mask and its actual size corresponds to the item type, not the aligned/padded conversion size. If ret exceeds the true item spec size, the extra bytes of src->mask are being read from unknown memory.

**Suggested fix:** The masking should use the actual item spec size rather than the conversion return value, or at minimum clamp the loop to min(ret, item_spec_size).

**3. Warning — No bounds validation between ret and mask size (~50% confidence)**

Related to the above: there's no assertion or runtime check that src->mask points to at least ret bytes of valid data. The rte_flow_conv infrastructure knows the item type sizes, but this masking code makes an assumption that the mask buffer is at least as large as the converted spec. If a caller provides a shorter mask, this is undefined behavior.

---

### API / Process Issues

**4. Warning — New public enum value RTE_FLOW_CONV_OP_PATTERN_MASKED not marked experimental.**

A new value is added to the public enum rte_flow_conv_op in rte_flow.h. Per AGENTS.md, new APIs must be marked as __rte_experimental. While enum values can't directly carry the __rte_experimental tag, this is a new public API feature exposed through rte_flow_conv() and should be documented as experimental, or the corresponding code path should have experimental gating.

**5. Warning — Missing release notes.**

This patch adds a new conversion operation (RTE_FLOW_CONV_OP_PATTERN_MASKED) to the public rte_flow_conv() API. Per AGENTS.md, API changes require release notes updates in doc/guides/rel_notes/.

**6. Warning — Missing tests.**

No test code is included for the new RTE_FLOW_CONV_OP_PATTERN_MASKED operation. Per AGENTS.md, new API functions/operations should have functional tests.

---

### Documentation Issues

**7. Error — Doxygen comment has broken formatting and a typo.**

In rte_flow.h, the new enum doc block:

```c
/**
 * Convert an entire pattern.
 *
 Duplicates all pattern items at once, applying @p mask to @p spec and @p spec.
```

Two problems:
- The line Duplicates all pattern items at once... is missing the leading * comment continuation prefix — this will break Doxygen parsing.
- The text says "applying @p mask to @p spec and @p spec" — the second @p spec should be @p last.

**Suggested fix:**

```c
/**
 * Convert an entire pattern.
 *
 * Duplicates all pattern items at once, applying @p mask to @p spec and @p last.
```

---

### Style Observations

**8. Info — Pointer arithmetic style.**

The masking uses *(c_spec + j) rather than the more idiomatic c_spec[j], and *((const uint8_t *)src->mask + j) rather than ((const uint8_t *)src->mask)[j]. Array indexing notation would be clearer and more consistent with typical DPDK style.

---

### Summary

| Severity | Finding |
|----------|---------|
| **Error** | Stale `c_spec`/`c_last` pointers across loop iterations — can corrupt previously copied item data |
| **Error** | Potential out-of-bounds read on `src->mask` when `ret` exceeds actual mask size |
| **Error** | Wrong component prefix (`lib/ethdev:` → `ethdev:`) |
| **Error** | Broken Doxygen comment (missing `*` prefix) and typo (`spec` → `last`) |
| **Warning** | New enum value not marked experimental |
| **Warning** | Missing release notes for API change |
| **Warning** | Missing functional tests |
| **Info** | Pointer arithmetic vs array indexing style |

The most critical finding is #1 — the stale pointer bug. It's a logic error that would cause data corruption whenever rte_flow_conv is called with RTE_FLOW_CONV_OP_PATTERN_MASKED on a pattern containing multiple items where the output buffer is too small for some but not all items.

author
#10 lore raw

PATCH v3 1/1 ethdev: support inline calculating masked item value

In reply to Bing Zhao

In the asynchronous API definition and some drivers, the rte_flow_item spec value may not be calculated by the driver due to the reason of speed of light rule insertion rate and sometimes the input parameters will be copied and changed internally.

After copying, the spec and last will be protected by the keyword const and cannot be changed in the code itself. And also the driver needs some extra memory to do the calculation and extra conditions to understand the length of each item spec. This is not efficient.

To solve the issue and support usage of the following fix, a new OP was introduced to calculate the spec and last values after applying the mask inline.

---
v3:
- add test code
- fix the issue found by AI

---
 app/test/test_ethdev_api.c             | 76 ++++++++++++++++++++++++++
 doc/guides/rel_notes/release_26_07.rst |  6 ++
 lib/ethdev/rte_flow.c                  | 46 ++++++++++++++--
 lib/ethdev/rte_flow.h                  | 13 +++++
 4 files changed, 135 insertions(+), 6 deletions(-)
app/test/test_ethdev_api.c +76 -0
index 76afd0345c..5cae1cdc1d 100644
@@ -4,6 +4,7 @@
 
 #include <rte_log.h>
 #include <rte_ethdev.h>
+#include <rte_flow.h>
 
 #include <rte_test.h>
 #include "test.h"
@@ -15,6 +16,80 @@
 #define NUM_MBUF 1024
 #define MBUF_CACHE_SIZE 256
 
+static int32_t
+ethdev_api_flow_conv_pattern_masked(void)
+{
+	const struct rte_flow_item_eth spec = {
+		.hdr.dst_addr.addr_bytes = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 },
+		.hdr.src_addr.addr_bytes = { 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f },
+		.hdr.ether_type = RTE_BE16(0x1234),
+	};
+	const struct rte_flow_item_eth last = {
+		.hdr.dst_addr.addr_bytes = { 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 },
+		.hdr.src_addr.addr_bytes = { 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f },
+		.hdr.ether_type = RTE_BE16(0x5678),
+	};
+	const struct rte_flow_item_eth mask = {
+		.hdr.dst_addr.addr_bytes = { 0xff, 0xff, 0x00, 0x00, 0xff, 0xff },
+		.hdr.src_addr.addr_bytes = { 0xff, 0x00, 0xff, 0x00, 0xff, 0x00 },
+		.hdr.ether_type = RTE_BE16(0xffff),
+	};
+	const struct rte_flow_item pattern[] = {
+		{
+			.type = RTE_FLOW_ITEM_TYPE_ETH,
+			.spec = &spec,
+			.last = &last,
+			.mask = &mask,
+		},
+		{ .type = RTE_FLOW_ITEM_TYPE_END },
+	};
+	union {
+		struct rte_flow_item item;
+		struct rte_flow_item_eth eth;
+		double align;
+		uint8_t raw[256];
+	} dst;
+	const struct rte_flow_item *item;
+	const struct rte_flow_item_eth *conv_spec;
+	const struct rte_flow_item_eth *conv_last;
+	int ret;
+
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN_MASKED, NULL, 0, pattern, NULL);
+	TEST_ASSERT(ret > 0, "Masked pattern conversion size query failed");
+	TEST_ASSERT((size_t)ret <= sizeof(dst.raw),
+		    "Masked pattern conversion needs too much storage");
+
+	memset(&dst, 0, sizeof(dst));
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN_MASKED, dst.raw,
+			    sizeof(dst.raw), pattern, NULL);
+	TEST_ASSERT(ret > 0, "Masked pattern conversion failed");
+
+	item = (const struct rte_flow_item *)dst.raw;
+	conv_spec = item[0].spec;
+	conv_last = item[0].last;
+	TEST_ASSERT_NOT_NULL(conv_spec, "Converted spec must be set");
+	TEST_ASSERT_NOT_NULL(conv_last, "Converted last must be set");
+
+	TEST_ASSERT_EQUAL(conv_spec->hdr.dst_addr.addr_bytes[0], 0x01,
+			  "Masked spec dst byte 0 mismatch");
+	TEST_ASSERT_EQUAL(conv_spec->hdr.dst_addr.addr_bytes[2], 0x00,
+			  "Masked spec dst byte 2 mismatch");
+	TEST_ASSERT_EQUAL(conv_spec->hdr.src_addr.addr_bytes[1], 0x00,
+			  "Masked spec src byte 1 mismatch");
+	TEST_ASSERT_EQUAL(conv_spec->hdr.ether_type, RTE_BE16(0x1234),
+			  "Masked spec ether type mismatch");
+	TEST_ASSERT_EQUAL(conv_last->hdr.dst_addr.addr_bytes[0], 0x11,
+			  "Masked last dst byte 0 mismatch");
+	TEST_ASSERT_EQUAL(conv_last->hdr.dst_addr.addr_bytes[2], 0x00,
+			  "Masked last dst byte 2 mismatch");
+	TEST_ASSERT_EQUAL(conv_last->hdr.src_addr.addr_bytes[1], 0x00,
+			  "Masked last src byte 1 mismatch");
+	TEST_ASSERT_EQUAL(conv_last->hdr.ether_type, RTE_BE16(0x5678),
+			  "Masked last ether type mismatch");
+
+	return TEST_SUCCESS;
+}
+
 static int32_t
 ethdev_api_queue_status(void)
 {
@@ -167,6 +242,7 @@ static struct unit_test_suite ethdev_api_testsuite = {
 	.setup = NULL,
 	.teardown = NULL,
 	.unit_test_cases = {
+		TEST_CASE(ethdev_api_flow_conv_pattern_masked),
 		TEST_CASE(ethdev_api_queue_status),
 		/* TODO: Add deferred_start queue status test */
 		TEST_CASES_END() /**< NULL terminate unit test array */
doc/guides/rel_notes/release_26_07.rst +6 -0
index 8b4f8401e2..46a83cfea5 100644
@@ -116,6 +116,12 @@ API Changes
    Also, make sure to start the actual text at the margin.
    =======================================================
 
+* ethdev: Added masked pattern conversion.
+
+  Added ``RTE_FLOW_CONV_OP_PATTERN_MASKED`` to ``rte_flow_conv()``
+  to copy an entire pattern while applying each item's mask to its
+  ``spec`` and ``last`` fields.
+
 
 ABI Changes
 -----------
lib/ethdev/rte_flow.c +40 -6

115 lines not on this page · raw message

lib/ethdev/rte_flow.h +13 -0
index ba3bcc89a3..f3102f4bb0 100644
@@ -4558,6 +4558,19 @@ enum rte_flow_conv_op {
 	 *   @code const char ** @endcode
 	 */
 	RTE_FLOW_CONV_OP_ACTION_NAME_PTR,
+
+	/**
+	 * Convert an entire pattern.
+	 *
+	 * Duplicates all pattern items at once, applying @p mask to @p spec
+	 * and @p last.
+	 *
+	 * - @p src type:
+	 *   @code const struct rte_flow_item * @endcode
+	 * - @p dst type:
+	 *   @code struct rte_flow_item * @endcode
+	 */
+	RTE_FLOW_CONV_OP_PATTERN_MASKED,
 };
 
 /**
Signature · 1 line
2.34.1
author
#11 lore raw

PATCH v4 1/1 ethdev: support inline calculating masked item value

In the asynchronous API definition and some drivers, the rte_flow_item spec value may not be calculated by the driver due to the reason of speed of light rule insertion rate and sometimes the input parameters will be copied and changed internally.

After copying, the spec and last will be protected by the keyword const and cannot be changed in the code itself. And also the driver needs some extra memory to do the calculation and extra conditions to understand the length of each item spec. This is not efficient.

To solve the issue and support usage of the following fix, a new OP was introduced to calculate the spec and last values after applying the mask inline.

---
v3:
- add test code
- fix the issue found by AI
v4: reabse on top of the main

---
 app/test/test_ethdev_api.c             | 76 ++++++++++++++++++++++++++
 doc/guides/rel_notes/release_26_07.rst |  6 ++
 lib/ethdev/rte_flow.c                  | 46 ++++++++++++++--
 lib/ethdev/rte_flow.h                  | 13 +++++
 4 files changed, 135 insertions(+), 6 deletions(-)
app/test/test_ethdev_api.c +76 -0
index 76afd0345c..5cae1cdc1d 100644
@@ -4,6 +4,7 @@
 
 #include <rte_log.h>
 #include <rte_ethdev.h>
+#include <rte_flow.h>
 
 #include <rte_test.h>
 #include "test.h"
@@ -15,6 +16,80 @@
 #define NUM_MBUF 1024
 #define MBUF_CACHE_SIZE 256
 
+static int32_t
+ethdev_api_flow_conv_pattern_masked(void)
+{
+	const struct rte_flow_item_eth spec = {
+		.hdr.dst_addr.addr_bytes = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 },
+		.hdr.src_addr.addr_bytes = { 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f },
+		.hdr.ether_type = RTE_BE16(0x1234),
+	};
+	const struct rte_flow_item_eth last = {
+		.hdr.dst_addr.addr_bytes = { 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 },
+		.hdr.src_addr.addr_bytes = { 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f },
+		.hdr.ether_type = RTE_BE16(0x5678),
+	};
+	const struct rte_flow_item_eth mask = {
+		.hdr.dst_addr.addr_bytes = { 0xff, 0xff, 0x00, 0x00, 0xff, 0xff },
+		.hdr.src_addr.addr_bytes = { 0xff, 0x00, 0xff, 0x00, 0xff, 0x00 },
+		.hdr.ether_type = RTE_BE16(0xffff),
+	};
+	const struct rte_flow_item pattern[] = {
+		{
+			.type = RTE_FLOW_ITEM_TYPE_ETH,
+			.spec = &spec,
+			.last = &last,
+			.mask = &mask,
+		},
+		{ .type = RTE_FLOW_ITEM_TYPE_END },
+	};
+	union {
+		struct rte_flow_item item;
+		struct rte_flow_item_eth eth;
+		double align;
+		uint8_t raw[256];
+	} dst;
+	const struct rte_flow_item *item;
+	const struct rte_flow_item_eth *conv_spec;
+	const struct rte_flow_item_eth *conv_last;
+	int ret;
+
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN_MASKED, NULL, 0, pattern, NULL);
+	TEST_ASSERT(ret > 0, "Masked pattern conversion size query failed");
+	TEST_ASSERT((size_t)ret <= sizeof(dst.raw),
+		    "Masked pattern conversion needs too much storage");
+
+	memset(&dst, 0, sizeof(dst));
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN_MASKED, dst.raw,
+			    sizeof(dst.raw), pattern, NULL);
+	TEST_ASSERT(ret > 0, "Masked pattern conversion failed");
+
+	item = (const struct rte_flow_item *)dst.raw;
+	conv_spec = item[0].spec;
+	conv_last = item[0].last;
+	TEST_ASSERT_NOT_NULL(conv_spec, "Converted spec must be set");
+	TEST_ASSERT_NOT_NULL(conv_last, "Converted last must be set");
+
+	TEST_ASSERT_EQUAL(conv_spec->hdr.dst_addr.addr_bytes[0], 0x01,
+			  "Masked spec dst byte 0 mismatch");
+	TEST_ASSERT_EQUAL(conv_spec->hdr.dst_addr.addr_bytes[2], 0x00,
+			  "Masked spec dst byte 2 mismatch");
+	TEST_ASSERT_EQUAL(conv_spec->hdr.src_addr.addr_bytes[1], 0x00,
+			  "Masked spec src byte 1 mismatch");
+	TEST_ASSERT_EQUAL(conv_spec->hdr.ether_type, RTE_BE16(0x1234),
+			  "Masked spec ether type mismatch");
+	TEST_ASSERT_EQUAL(conv_last->hdr.dst_addr.addr_bytes[0], 0x11,
+			  "Masked last dst byte 0 mismatch");
+	TEST_ASSERT_EQUAL(conv_last->hdr.dst_addr.addr_bytes[2], 0x00,
+			  "Masked last dst byte 2 mismatch");
+	TEST_ASSERT_EQUAL(conv_last->hdr.src_addr.addr_bytes[1], 0x00,
+			  "Masked last src byte 1 mismatch");
+	TEST_ASSERT_EQUAL(conv_last->hdr.ether_type, RTE_BE16(0x5678),
+			  "Masked last ether type mismatch");
+
+	return TEST_SUCCESS;
+}
+
 static int32_t
 ethdev_api_queue_status(void)
 {
@@ -167,6 +242,7 @@ static struct unit_test_suite ethdev_api_testsuite = {
 	.setup = NULL,
 	.teardown = NULL,
 	.unit_test_cases = {
+		TEST_CASE(ethdev_api_flow_conv_pattern_masked),
 		TEST_CASE(ethdev_api_queue_status),
 		/* TODO: Add deferred_start queue status test */
 		TEST_CASES_END() /**< NULL terminate unit test array */
doc/guides/rel_notes/release_26_07.rst +6 -0
index b8a3e2ced9..5f01420d2b 100644
@@ -134,6 +134,12 @@ API Changes
    Also, make sure to start the actual text at the margin.
    =======================================================
 
+* ethdev: Added masked pattern conversion.
+
+  Added ``RTE_FLOW_CONV_OP_PATTERN_MASKED`` to ``rte_flow_conv()``
+  to copy an entire pattern while applying each item's mask to its
+  ``spec`` and ``last`` fields.
+
 
 ABI Changes
 -----------
lib/ethdev/rte_flow.c +40 -6

115 lines not on this page · raw message

lib/ethdev/rte_flow.h +13 -0
index eacfd7c5f7..5f8d4b00f0 100644
@@ -4558,6 +4558,19 @@ enum rte_flow_conv_op {
 	 *   @code const char ** @endcode
 	 */
 	RTE_FLOW_CONV_OP_ACTION_NAME_PTR,
+
+	/**
+	 * Convert an entire pattern.
+	 *
+	 * Duplicates all pattern items at once, applying @p mask to @p spec
+	 * and @p last.
+	 *
+	 * - @p src type:
+	 *   @code const struct rte_flow_item * @endcode
+	 * - @p dst type:
+	 *   @code struct rte_flow_item * @endcode
+	 */
+	RTE_FLOW_CONV_OP_PATTERN_MASKED,
 };
 
 /**
Signature · 1 line
2.34.1
Acked-by Ori Kam on v4 1/1
#12 lore raw

Re: [PATCH v4] ethdev: support inline calculating masked item value

Quoted 10 lines · Bing Zhao

In the asynchronous API definition and some drivers, the rte_flow_item spec value may not be calculated by the driver due to the reason of speed of light rule insertion rate and sometimes the input parameters will be copied and changed internally.

After copying, the spec and last will be protected by the keyword const and cannot be changed in the code itself. And also the driver needs some extra memory to do the calculation and extra conditions to understand the length of each item spec. This is not efficient.

Quoted 5 lines

To solve the issue and support usage of the following fix, a new OP was introduced to calculate the spec and last values after applying the mask inline.

Ori has some technical issues with plain text emails on his side. On his behalf:

Best regards,
Dariusz Sosnowski

#13 lore raw

Re: [PATCH v4] ethdev: support inline calculating masked item value

In reply to Bing Zhao
Quoted 10 lines · On Wed, 3 Jun 2026 12:28:05 +0300 Bing Zhao <[email protected]> wrote:

In the asynchronous API definition and some drivers, the rte_flow_item spec value may not be calculated by the driver due to the reason of speed of light rule insertion rate and sometimes the input parameters will be copied and changed internally.

After copying, the spec and last will be protected by the keyword const and cannot be changed in the code itself. And also the driver needs some extra memory to do the calculation and extra conditions to understand the length of each item spec. This is not efficient.

Quoted 6 lines

To solve the issue and support usage of the following fix, a new OP was introduced to calculate the spec and last values after applying the mask inline.

---

More detailed AI review found some things that still need addressing.

Quoted 1 line · Bing Zhao

Subject: [PATCH v4] ethdev: support inline calculating masked item value

Error: byte-wise masking corrupts embedded pointers in deep-copy item types (RAW, FLEX, GENEVE_OPT).

In rte_flow_conv_pattern(), the new mask application runs over the fixed item struct:

	size_t item_mask_size = mask ? rte_flow_conv_item_mask_size(src) : 0;
	...
	size_t mask_size = RTE_MIN(ret, item_mask_size);

	for (j = 0; j < mask_size; j++)
		c_spec[j] &= mask[j];

item_mask_size is rte_flow_desc_item[type].size, the size of the fixed item struct. For RTE_FLOW_ITEM_TYPE_RAW, FLEX, and GENEVE_OPT, that fixed struct ends in an embedded pointer that rte_flow_conv_item_spec() has just populated to point at the deep-copied trailing data (rte_flow_item_raw.pattern, rte_flow_item_flex.pattern, rte_flow_item_geneve_opt.data). Because the masked range covers the whole fixed struct, the loop ANDs the bytes of that pointer with the mask's corresponding bytes (typically a NULL mask pointer), zeroing or garbling it.

The converted item's pattern/data pointer is clobbered while the copied payload it should reference is left unreachable. A consumer that follows conv->pattern then dereferences NULL or a corrupt address. Plain value items (eth, ipv4, ...) are unaffected; only the deep-copy item types break, which is exactly what the test does not exercise.

Suggested fix: do not blind-mask the entire fixed struct for items that carry an embedded pointer / desc_fn deep copy. Either skip masking when rte_flow_desc_item[type].desc_fn != NULL, or mask only the leading plain-data region and leave the pointer field (and trailing copied bytes) intact.

Warning: the new test validates only an ETH pattern, so the RAW/FLEX/GENEVE_OPT path above is untested. A RAW item case would have surfaced the pointer corruption.

Info: the Doxygen block for RTE_FLOW_CONV_OP_PATTERN_MASKED uses @p mask, @p spec, @p last, but those are item fields, not parameters of the op; the neighboring enum entries only document the @p src / @p dst types.

author
#14 lore raw

RE: [PATCH v4] ethdev: support inline calculating masked item value

Hi,

Let me check and update with v5.

Quoted 85 lines

-----Original Message-----
From: Stephen Hemminger <[email protected]>
Sent: Monday, June 8, 2026 11:46 PM
To: Bing Zhao <[email protected]>
Cc: Slava Ovsiienko <[email protected]>; [email protected]; Raslan Darawsheh <[email protected]>; Ori Kam <[email protected]>; Dariusz Sosnowski <[email protected]>; Suanming Mou <[email protected]>; Matan Azrad <[email protected]>; NBU-Contact-Thomas Monjalon (EXTERNAL) <[email protected]>
Subject: Re: [PATCH v4] ethdev: support inline calculating masked item value

External email: Use caution opening links or attachments

Quoted 10 lines · On Wed, 3 Jun 2026 12:28:05 +0300 Bing Zhao <[email protected]> wrote:

In the asynchronous API definition and some drivers, the rte_flow_item spec value may not be calculated by the driver due to the reason of speed of light rule insertion rate and sometimes the input parameters will be copied and changed internally.

After copying, the spec and last will be protected by the keyword const and cannot be changed in the code itself. And also the driver needs some extra memory to do the calculation and extra conditions to understand the length of each item spec. This is not efficient.

Quoted 6 lines

To solve the issue and support usage of the following fix, a new OP was introduced to calculate the spec and last values after applying the mask inline.

---

More detailed AI review found some things that still need addressing.

Quoted 2 lines · Bing Zhao

Subject: [PATCH v4] ethdev: support inline calculating masked item value

Error: byte-wise masking corrupts embedded pointers in deep-copy item types (RAW, FLEX, GENEVE_OPT).

In rte_flow_conv_pattern(), the new mask application runs over the fixed item struct:

size_t item_mask_size = mask ? rte_flow_conv_item_mask_size(src) :

0;
        ...
        size_t mask_size = RTE_MIN(ret, item_mask_size);

        for (j = 0; j < mask_size; j++)
                c_spec[j] &= mask[j];

item_mask_size is rte_flow_desc_item[type].size, the size of the fixed item struct. For RTE_FLOW_ITEM_TYPE_RAW, FLEX, and GENEVE_OPT, that fixed struct ends in an embedded pointer that rte_flow_conv_item_spec() has just populated to point at the deep-copied trailing data (rte_flow_item_raw.pattern, rte_flow_item_flex.pattern, rte_flow_item_geneve_opt.data). Because the masked range covers the whole fixed struct, the loop ANDs the bytes of that pointer with the mask's corresponding bytes (typically a NULL mask pointer), zeroing or garbling it.

The converted item's pattern/data pointer is clobbered while the copied payload it should reference is left unreachable. A consumer that follows conv->pattern then dereferences NULL or a corrupt address. Plain value conv->items
(eth, ipv4, ...) are unaffected; only the deep-copy item types break, which is exactly what the test does not exercise.

Suggested fix: do not blind-mask the entire fixed struct for items that carry an embedded pointer / desc_fn deep copy. Either skip masking when rte_flow_desc_item[type].desc_fn != NULL, or mask only the leading plain- data region and leave the pointer field (and trailing copied bytes) intact.

Warning: the new test validates only an ETH pattern, so the RAW/FLEX/GENEVE_OPT path above is untested. A RAW item case would have surfaced the pointer corruption.

Info: the Doxygen block for RTE_FLOW_CONV_OP_PATTERN_MASKED uses @p mask, @p spec, @p last, but those are item fields, not parameters of the op; the neighboring enum entries only document the @p src / @p dst types.

author
#15 lore raw

PATCH v5 1/1 ethdev: support inline calculating masked item value

In reply to Bing Zhao

In the asynchronous API definition and some drivers, the rte_flow_item spec value may not be calculated by the driver due to the reason of speed of light rule insertion rate and sometimes the input parameters will be copied and changed internally.

After copying, the spec and last will be protected by the keyword const and cannot be changed in the code itself. And also the driver needs some extra memory to do the calculation and extra conditions to understand the length of each item spec. This is not efficient.

To solve the issue and support usage of the following fix, a new OP was introduced to calculate the spec and last values after applying the mask inline.

---
v3:
- add test code
- fix the issue found by AI
v4: reabse on top of the main
v5: handle some items separately and add test for them

---
 app/test/test_ethdev_api.c             | 76 ++++++++++++++++++++++++++
 doc/guides/rel_notes/release_26_07.rst |  6 ++
 lib/ethdev/rte_flow.c                  | 46 ++++++++++++++--
 lib/ethdev/rte_flow.h                  | 13 +++++
 4 files changed, 135 insertions(+), 6 deletions(-)
app/test/test_ethdev_api.c +76 -0
index 76afd0345c..5cae1cdc1d 100644
@@ -4,6 +4,7 @@
 
 #include <rte_log.h>
 #include <rte_ethdev.h>
+#include <rte_flow.h>
 
 #include <rte_test.h>
 #include "test.h"
@@ -15,6 +16,80 @@
 #define NUM_MBUF 1024
 #define MBUF_CACHE_SIZE 256
 
+static int32_t
+ethdev_api_flow_conv_pattern_masked(void)
+{
+	const struct rte_flow_item_eth spec = {
+		.hdr.dst_addr.addr_bytes = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 },
+		.hdr.src_addr.addr_bytes = { 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f },
+		.hdr.ether_type = RTE_BE16(0x1234),
+	};
+	const struct rte_flow_item_eth last = {
+		.hdr.dst_addr.addr_bytes = { 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 },
+		.hdr.src_addr.addr_bytes = { 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f },
+		.hdr.ether_type = RTE_BE16(0x5678),
+	};
+	const struct rte_flow_item_eth mask = {
+		.hdr.dst_addr.addr_bytes = { 0xff, 0xff, 0x00, 0x00, 0xff, 0xff },
+		.hdr.src_addr.addr_bytes = { 0xff, 0x00, 0xff, 0x00, 0xff, 0x00 },
+		.hdr.ether_type = RTE_BE16(0xffff),
+	};
+	const struct rte_flow_item pattern[] = {
+		{
+			.type = RTE_FLOW_ITEM_TYPE_ETH,
+			.spec = &spec,
+			.last = &last,
+			.mask = &mask,
+		},
+		{ .type = RTE_FLOW_ITEM_TYPE_END },
+	};
+	union {
+		struct rte_flow_item item;
+		struct rte_flow_item_eth eth;
+		double align;
+		uint8_t raw[256];
+	} dst;
+	const struct rte_flow_item *item;
+	const struct rte_flow_item_eth *conv_spec;
+	const struct rte_flow_item_eth *conv_last;
+	int ret;
+
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN_MASKED, NULL, 0, pattern, NULL);
+	TEST_ASSERT(ret > 0, "Masked pattern conversion size query failed");
+	TEST_ASSERT((size_t)ret <= sizeof(dst.raw),
+		    "Masked pattern conversion needs too much storage");
+
+	memset(&dst, 0, sizeof(dst));
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN_MASKED, dst.raw,
+			    sizeof(dst.raw), pattern, NULL);
+	TEST_ASSERT(ret > 0, "Masked pattern conversion failed");
+
+	item = (const struct rte_flow_item *)dst.raw;
+	conv_spec = item[0].spec;
+	conv_last = item[0].last;
+	TEST_ASSERT_NOT_NULL(conv_spec, "Converted spec must be set");
+	TEST_ASSERT_NOT_NULL(conv_last, "Converted last must be set");
+
+	TEST_ASSERT_EQUAL(conv_spec->hdr.dst_addr.addr_bytes[0], 0x01,
+			  "Masked spec dst byte 0 mismatch");
+	TEST_ASSERT_EQUAL(conv_spec->hdr.dst_addr.addr_bytes[2], 0x00,
+			  "Masked spec dst byte 2 mismatch");
+	TEST_ASSERT_EQUAL(conv_spec->hdr.src_addr.addr_bytes[1], 0x00,
+			  "Masked spec src byte 1 mismatch");
+	TEST_ASSERT_EQUAL(conv_spec->hdr.ether_type, RTE_BE16(0x1234),
+			  "Masked spec ether type mismatch");
+	TEST_ASSERT_EQUAL(conv_last->hdr.dst_addr.addr_bytes[0], 0x11,
+			  "Masked last dst byte 0 mismatch");
+	TEST_ASSERT_EQUAL(conv_last->hdr.dst_addr.addr_bytes[2], 0x00,
+			  "Masked last dst byte 2 mismatch");
+	TEST_ASSERT_EQUAL(conv_last->hdr.src_addr.addr_bytes[1], 0x00,
+			  "Masked last src byte 1 mismatch");
+	TEST_ASSERT_EQUAL(conv_last->hdr.ether_type, RTE_BE16(0x5678),
+			  "Masked last ether type mismatch");
+
+	return TEST_SUCCESS;
+}
+
 static int32_t
 ethdev_api_queue_status(void)
 {
@@ -167,6 +242,7 @@ static struct unit_test_suite ethdev_api_testsuite = {
 	.setup = NULL,
 	.teardown = NULL,
 	.unit_test_cases = {
+		TEST_CASE(ethdev_api_flow_conv_pattern_masked),
 		TEST_CASE(ethdev_api_queue_status),
 		/* TODO: Add deferred_start queue status test */
 		TEST_CASES_END() /**< NULL terminate unit test array */
doc/guides/rel_notes/release_26_07.rst +6 -0
index b5285af5fe..4f5d21d576 100644
@@ -190,6 +190,12 @@ API Changes
   - ``rte_pmd_mlx5_enable_steering``
   - ``rte_pmd_mlx5_disable_steering``
 
+* ethdev: Added masked pattern conversion.
+
+  Added ``RTE_FLOW_CONV_OP_PATTERN_MASKED`` to ``rte_flow_conv()``
+  to copy an entire pattern while applying each item's mask to its
+  ``spec`` and ``last`` fields.
+
 
 ABI Changes
 -----------
lib/ethdev/rte_flow.c +40 -6

115 lines not on this page · raw message

lib/ethdev/rte_flow.h +13 -0
index b495409406..959a2f903b 100644
@@ -4556,6 +4556,19 @@ enum rte_flow_conv_op {
 	 *   @code const char ** @endcode
 	 */
 	RTE_FLOW_CONV_OP_ACTION_NAME_PTR,
+
+	/**
+	 * Convert an entire pattern.
+	 *
+	 * Duplicates all pattern items at once, applying @p mask to @p spec
+	 * and @p last.
+	 *
+	 * - @p src type:
+	 *   @code const struct rte_flow_item * @endcode
+	 * - @p dst type:
+	 *   @code struct rte_flow_item * @endcode
+	 */
+	RTE_FLOW_CONV_OP_PATTERN_MASKED,
 };
 
 /**
Signature · 1 line
2.34.1
#16 lore raw

Re: [PATCH v5] ethdev: support inline calculating masked item value

Quoted 10 lines · On Wed, 10 Jun 2026 08:27:29 +0300 Bing Zhao <[email protected]> wrote:

In the asynchronous API definition and some drivers, the rte_flow_item spec value may not be calculated by the driver due to the reason of speed of light rule insertion rate and sometimes the input parameters will be copied and changed internally.

After copying, the spec and last will be protected by the keyword const and cannot be changed in the code itself. And also the driver needs some extra memory to do the calculation and extra conditions to understand the length of each item spec. This is not efficient.

Quoted 7 lines

To solve the issue and support usage of the following fix, a new OP was introduced to calculate the spec and last values after applying the mask inline.

---

Detailed AI review still sees issues here:

Quoted 2 lines · Bing Zhao

Subject: [PATCH v5] ethdev: support inline calculating masked item value
v5: handle some items separately and add test for them

The v5 lib/ethdev/rte_flow.c and app/test/test_ethdev_api.c hunks are identical to v4 -- the masking loop is unchanged and the test still only covers ETH. The changelog says items are handled separately and a test was added, but no such change is present in the diff. The v4 issue is still open.

Error: byte-wise masking corrupts embedded pointers in deep-copy item types (RAW, FLEX, GENEVE_OPT).

In rte_flow_conv_pattern(), the mask is applied over the fixed item struct:

	size_t item_mask_size = mask ? rte_flow_conv_item_mask_size(src) : 0;
	...
	size_t mask_size = RTE_MIN(ret, item_mask_size);

	for (j = 0; j < mask_size; j++)
		c_spec[j] &= mask[j];

item_mask_size is rte_flow_desc_item[type].size, the fixed item struct size. For RTE_FLOW_ITEM_TYPE_RAW, FLEX, and GENEVE_OPT, that fixed struct ends in an embedded pointer that rte_flow_conv_item_spec() has just populated to point at the deep-copied trailing data (rte_flow_item_raw.pattern, rte_flow_item_flex.pattern, rte_flow_item_geneve_opt.data). Because the masked range covers the whole fixed struct, the loop ANDs the bytes of that pointer with the mask's corresponding bytes (typically a NULL mask pointer), zeroing or garbling it.

The converted item's pattern/data pointer is clobbered while the copied payload it should reference is left unreachable. A consumer that follows conv->pattern then dereferences NULL or a corrupt address. Plain value items (eth, ipv4, ...) are unaffected; only the deep-copy item types break.

Suggested fix: do not blind-mask the entire fixed struct for items that carry an embedded pointer / desc_fn deep copy. Either skip masking when rte_flow_desc_item[type].desc_fn != NULL, or mask only the leading plain-data region and leave the pointer field (and trailing copied bytes) intact.

Warning: the new test validates only an ETH pattern, so the RAW/FLEX/GENEVE_OPT path above is untested. A RAW item case would have surfaced the pointer corruption -- and is what the v5 changelog claims to have added but did not.

Info: the Doxygen block for RTE_FLOW_CONV_OP_PATTERN_MASKED uses @p mask, @p spec, @p last, but those are item fields, not parameters of the op; the neighboring enum entries only document the @p src / @p dst types.

author
#17 lore raw

RE: [PATCH v5] ethdev: support inline calculating masked item value

In reply to Bing Zhao

Hi,

In my local code,

lib/ethdev/rte_flow.c +11 -2
index 7cf9f6f6f3..7a2721af00 100644
@@ -181,9 +181,18 @@ static const struct rte_flow_desc_data rte_flow_desc_item[] = {
 static inline size_t
 rte_flow_conv_item_mask_size(const struct rte_flow_item *item)
 {
-       if ((int)item->type >= 0)
+       if ((int)item->type < 0)
+               return sizeof(void *);
+       switch (item->type) {
+       case RTE_FLOW_ITEM_TYPE_RAW:
+               return offsetof(struct rte_flow_item_raw, pattern);
+       case RTE_FLOW_ITEM_TYPE_GENEVE_OPT:
+               return offsetof(struct rte_flow_item_geneve_opt, data);
+       default:
+               if (rte_flow_desc_item[item->type].desc_fn != NULL)
+                       return 0;
                return rte_flow_desc_item[item->type].size;
-       return sizeof(void *);
+       }
 }
 

// This is the code before my latest change.

Review

Quoted 7 lines
(quoted diff) +7 -0
+static inline size_t
+rte_flow_conv_item_mask_size(const struct rte_flow_item *item) {
+       if ((int)item->type >= 0)
+               return rte_flow_desc_item[item->type].size;
+       return sizeof(void *);
+}
+

I didn't understand why the patch I sent is still using the old code.

Quoted 330 lines

-----Original Message-----
From: Bing Zhao <[email protected]>
Sent: Wednesday, June 10, 2026 1:27 PM
To: Slava Ovsiienko <[email protected]>; [email protected]; Raslan Darawsheh <[email protected]>; [email protected]
Cc: Ori Kam <[email protected]>; Dariusz Sosnowski <[email protected]>; Suanming Mou <[email protected]>; Matan Azrad <[email protected]>; NBU- Contact-Thomas Monjalon (EXTERNAL) <[email protected]>
Subject: [PATCH v5] ethdev: support inline calculating masked item value

External email: Use caution opening links or attachments

In the asynchronous API definition and some drivers, the rte_flow_item spec value may not be calculated by the driver due to the reason of speed of light rule insertion rate and sometimes the input parameters will be copied and changed internally.

After copying, the spec and last will be protected by the keyword const and cannot be changed in the code itself. And also the driver needs some extra memory to do the calculation and extra conditions to understand the length of each item spec. This is not efficient.

To solve the issue and support usage of the following fix, a new OP was introduced to calculate the spec and last values after applying the mask inline.

---
v3:
- add test code
- fix the issue found by AI
v4: reabse on top of the main
v5: handle some items separately and add test for them

---
 app/test/test_ethdev_api.c             | 76 ++++++++++++++++++++++++++
 doc/guides/rel_notes/release_26_07.rst |  6 ++
 lib/ethdev/rte_flow.c                  | 46 ++++++++++++++--
 lib/ethdev/rte_flow.h                  | 13 +++++
 4 files changed, 135 insertions(+), 6 deletions(-)
app/test/test_ethdev_api.c index renamed from app/test/test_ethdev_api.c +0 -0

76afd0345c..5cae1cdc1d 100644

app/test/test_ethdev_api.c +12 -0
@@ -4,6 +4,7 @@
 
 #include <rte_log.h>
 #include <rte_ethdev.h>
+#include <rte_flow.h>
 
 #include <rte_test.h>
 #include "test.h"
@@ -15,6 +16,80 @@
 #define NUM_MBUF 1024
 #define MBUF_CACHE_SIZE 256
 
+static int32_t
+ethdev_api_flow_conv_pattern_masked(void)
+{
+       const struct rte_flow_item_eth spec = {
+               .hdr.dst_addr.addr_bytes = { 0x01, 0x02, 0x03, 0x04, 0x05,
 0x06 },
+               .hdr.src_addr.addr_bytes = { 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
 0x0f },
+               .hdr.ether_type = RTE_BE16(0x1234),
+       };
+       const struct rte_flow_item_eth last = {
+               .hdr.dst_addr.addr_bytes = { 0x11, 0x12, 0x13, 0x14, 0x15,
 0x16 },
+               .hdr.src_addr.addr_bytes = { 0x1a, 0x1b, 0x1c, 0x1d, 0x1e,

0x1f },

app/test/test_ethdev_api.c +4 -0
+               .hdr.ether_type = RTE_BE16(0x5678),
+       };
+       const struct rte_flow_item_eth mask = {
+               .hdr.dst_addr.addr_bytes = { 0xff, 0xff, 0x00, 0x00, 0xff,

0xff },
+ .hdr.src_addr.addr_bytes = { 0xff, 0x00, 0xff, 0x00, 0xff,
0x00 },

app/test/test_ethdev_api.c +23 -0
+               .hdr.ether_type = RTE_BE16(0xffff),
+       };
+       const struct rte_flow_item pattern[] = {
+               {
+                       .type = RTE_FLOW_ITEM_TYPE_ETH,
+                       .spec = &spec,
+                       .last = &last,
+                       .mask = &mask,
+               },
+               { .type = RTE_FLOW_ITEM_TYPE_END },
+       };
+       union {
+               struct rte_flow_item item;
+               struct rte_flow_item_eth eth;
+               double align;
+               uint8_t raw[256];
+       } dst;
+       const struct rte_flow_item *item;
+       const struct rte_flow_item_eth *conv_spec;
+       const struct rte_flow_item_eth *conv_last;
+       int ret;
+
+       ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN_MASKED, NULL, 0,

pattern, NULL);
+ TEST_ASSERT(ret > 0, "Masked pattern conversion size query
failed");

app/test/test_ethdev_api.c +35 -0
+       TEST_ASSERT((size_t)ret <= sizeof(dst.raw),
+                   "Masked pattern conversion needs too much storage");
+
+       memset(&dst, 0, sizeof(dst));
+       ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN_MASKED, dst.raw,
+                           sizeof(dst.raw), pattern, NULL);
+       TEST_ASSERT(ret > 0, "Masked pattern conversion failed");
+
+       item = (const struct rte_flow_item *)dst.raw;
+       conv_spec = item[0].spec;
+       conv_last = item[0].last;
+       TEST_ASSERT_NOT_NULL(conv_spec, "Converted spec must be set");
+       TEST_ASSERT_NOT_NULL(conv_last, "Converted last must be set");
+
+       TEST_ASSERT_EQUAL(conv_spec->hdr.dst_addr.addr_bytes[0], 0x01,
+                         "Masked spec dst byte 0 mismatch");
+       TEST_ASSERT_EQUAL(conv_spec->hdr.dst_addr.addr_bytes[2], 0x00,
+                         "Masked spec dst byte 2 mismatch");
+       TEST_ASSERT_EQUAL(conv_spec->hdr.src_addr.addr_bytes[1], 0x00,
+                         "Masked spec src byte 1 mismatch");
+       TEST_ASSERT_EQUAL(conv_spec->hdr.ether_type, RTE_BE16(0x1234),
+                         "Masked spec ether type mismatch");
+       TEST_ASSERT_EQUAL(conv_last->hdr.dst_addr.addr_bytes[0], 0x11,
+                         "Masked last dst byte 0 mismatch");
+       TEST_ASSERT_EQUAL(conv_last->hdr.dst_addr.addr_bytes[2], 0x00,
+                         "Masked last dst byte 2 mismatch");
+       TEST_ASSERT_EQUAL(conv_last->hdr.src_addr.addr_bytes[1], 0x00,
+                         "Masked last src byte 1 mismatch");
+       TEST_ASSERT_EQUAL(conv_last->hdr.ether_type, RTE_BE16(0x5678),
+                         "Masked last ether type mismatch");
+
+       return TEST_SUCCESS;
+}
+
 static int32_t
 ethdev_api_queue_status(void)
 {
@@ -167,6 +242,7 @@ static struct unit_test_suite ethdev_api_testsuite = {
        .setup = NULL,
        .teardown = NULL,
        .unit_test_cases = {
+               TEST_CASE(ethdev_api_flow_conv_pattern_masked),
                TEST_CASE(ethdev_api_queue_status),
                /* TODO: Add deferred_start queue status test */
                TEST_CASES_END() /**< NULL terminate unit test array */
doc/guides/rel_notes/release_26_07.rst +6 -0
index b5285af5fe..4f5d21d576 100644
@@ -190,6 +190,12 @@ API Changes
   - ``rte_pmd_mlx5_enable_steering``
   - ``rte_pmd_mlx5_disable_steering``
 
+* ethdev: Added masked pattern conversion.
+
+  Added ``RTE_FLOW_CONV_OP_PATTERN_MASKED`` to ``rte_flow_conv()``  to
+ copy an entire pattern while applying each item's mask to its
+ ``spec`` and ``last`` fields.
+
 
 ABI Changes
 -----------
lib/ethdev/rte_flow.c index renamed from lib/ethdev/rte_flow.c +0 -0

ec0fe08355..c7a94a1194 100644

lib/ethdev/rte_flow.c +7 -0
@@ -178,6 +178,14 @@ static const struct rte_flow_desc_data
 rte_flow_desc_item[] = {
        MK_FLOW_ITEM(COMPARE, sizeof(struct rte_flow_item_compare)),  };
 
+static inline size_t
+rte_flow_conv_item_mask_size(const struct rte_flow_item *item) {
+       if ((int)item->type >= 0)
+               return rte_flow_desc_item[item->type].size;
+       return sizeof(void *);
+}
+
 /** Generate flow_action[] entry. */
 #define MK_FLOW_ACTION(t, s) \
        [RTE_FLOW_ACTION_TYPE_ ## t] = { \ @@ -835,6 +843,8 @@

rte_flow_conv_action_conf(void *buf, const size_t size,
* RTE_FLOW_ITEM_TYPE_END is encountered.
* @param[out] error
* Perform verbose error reporting if not NULL.
+ * @param[in] with_mask
+ * If true, @p src mask will be applied to spec and last.
*
* @return
* A positive value representing the number of bytes needed to store

lib/ethdev/rte_flow.c +27 -3
@@ -847,12 +857,13 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
                      const size_t size,
                      const struct rte_flow_item *src,
                      unsigned int num,
+                     bool with_mask,
                      struct rte_flow_error *error)  {
        uintptr_t data = (uintptr_t)dst;
        size_t off;
        size_t ret;
-       unsigned int i;
+       unsigned int i, j;
 
        for (i = 0, off = 0; !num || i != num; ++i, ++src, ++dst) {
                /**
@@ -876,15 +887,27 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
        src -= num;
        dst -= num;
        do {
+               uint8_t *c_spec = NULL, *c_last = NULL;
+               const uint8_t *mask = src->mask;
+               size_t item_mask_size = mask ?
+ rte_flow_conv_item_mask_size(src) : 0;
+
                if (src->spec) {
                        off = RTE_ALIGN_CEIL(off, sizeof(double));
                        ret = rte_flow_conv_item_spec
                                ((void *)(data + off),
                                 size > off ? size - off : 0, src,
                                 RTE_FLOW_CONV_ITEM_SPEC);
-                       if (size && size >= off + ret)
+                       if (size && size >= off + ret) {
                                dst->spec = (void *)(data + off);
+                               c_spec = (uint8_t *)(data + off);
+                       }
                        off += ret;
+                       if (with_mask && c_spec && mask) {
+                               size_t mask_size = RTE_MIN(ret,
+ item_mask_size);
+
+                               for (j = 0; j < mask_size; j++)
+                                       c_spec[j] &= mask[j];
+                       }
 
                }
                if (src->last) {
@@ -893,9 +916,17 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
                                ((void *)(data + off),
                                 size > off ? size - off : 0, src,
                                 RTE_FLOW_CONV_ITEM_LAST);
-                       if (size && size >= off + ret)
+                       if (size && size >= off + ret) {
                                dst->last = (void *)(data + off);
+                               c_last = (uint8_t *)(data + off);
+                       }
                        off += ret;
+                       if (with_mask && c_last && mask) {
+                               size_t mask_size = RTE_MIN(ret,
+ item_mask_size);
+
+                               for (j = 0; j < mask_size; j++)
+                                       c_last[j] &= mask[j];
+                       }
                }
                if (src->mask) {
                        off = RTE_ALIGN_CEIL(off, sizeof(double)); @@ -

1042,7 +1073,7 @@ rte_flow_conv_rule(struct rte_flow_conv_rule *dst,
off = RTE_ALIGN_CEIL(off, sizeof(double)); ret = rte_flow_conv_pattern((void *)((uintptr_t)dst +
off),
size > off ? size - off : 0,

lib/ethdev/rte_flow.c +2 -1
-                                           src->pattern_ro, 0, error);
+                                           src->pattern_ro, 0, false,
+ error);
                if (ret < 0)
                        return ret;
                if (size && size >= off + (size_t)ret) @@ -1143,7 +1174,7

@@ rte_flow_conv(enum rte_flow_conv_op op,

                ret = sizeof(*attr);
                break;
        case RTE_FLOW_CONV_OP_ITEM:
lib/ethdev/rte_flow.c +6 -3
-               ret = rte_flow_conv_pattern(dst, size, src, 1, error);
+               ret = rte_flow_conv_pattern(dst, size, src, 1, false,
+ error);
                break;
        case RTE_FLOW_CONV_OP_ITEM_MASK:
                item = src;
@@ -1158,7 +1189,7 @@ rte_flow_conv(enum rte_flow_conv_op op,
                ret = rte_flow_conv_actions(dst, size, src, 1, error);
                break;
        case RTE_FLOW_CONV_OP_PATTERN:
-               ret = rte_flow_conv_pattern(dst, size, src, 0, error);
+               ret = rte_flow_conv_pattern(dst, size, src, 0, false,
+ error);
                break;
        case RTE_FLOW_CONV_OP_ACTIONS:
                ret = rte_flow_conv_actions(dst, size, src, 0, error); @@
-1178,6 +1209,9 @@ rte_flow_conv(enum rte_flow_conv_op op,
        case RTE_FLOW_CONV_OP_ACTION_NAME_PTR:
                ret = rte_flow_conv_name(1, 1, dst, size, src, error);
                break;
+       case RTE_FLOW_CONV_OP_PATTERN_MASKED:
+               ret = rte_flow_conv_pattern(dst, size, src, 0, true,

error);

lib/ethdev/rte_flow.c +1 -0
+               break;
        default:
                ret = rte_flow_error_set
                (error, ENOTSUP, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
lib/ethdev/rte_flow.h index renamed from lib/ethdev/rte_flow.h +0 -0

b495409406..959a2f903b 100644

lib/ethdev/rte_flow.h +5 -0
@@ -4556,6 +4556,19 @@ enum rte_flow_conv_op {
         *   @code const char ** @endcode
         */
        RTE_FLOW_CONV_OP_ACTION_NAME_PTR,
+
+       /**
+        * Convert an entire pattern.
+        *
+        * Duplicates all pattern items at once, applying @p mask to @p

spec

lib/ethdev/rte_flow.h +8 -1
+        * and @p last.
+        *
+        * - @p src type:
+        *   @code const struct rte_flow_item * @endcode
+        * - @p dst type:
+        *   @code struct rte_flow_item * @endcode
+        */
+       RTE_FLOW_CONV_OP_PATTERN_MASKED,
 };
 
 /**
--

2.34.1

author
#18 lore raw

RE: [PATCH v5] ethdev: support inline calculating masked item value

Oh, I see, I squashed the changes into the next following mlx5 driver fix but not the proper one, so the code is still the old one. My fault.

Quoted 395 lines

-----Original Message-----
From: Bing Zhao
Sent: Thursday, June 11, 2026 12:55 PM
To: 'Bing Zhao' <[email protected]>; Slava Ovsiienko <[email protected]>; [email protected]; Raslan Darawsheh <[email protected]>; [email protected]
Cc: Ori Kam <[email protected]>; Dariusz Sosnowski <[email protected]>; Suanming Mou <[email protected]>; Matan Azrad <[email protected]>; NBU- Contact-Thomas Monjalon (EXTERNAL) <[email protected]>
Subject: RE: [PATCH v5] ethdev: support inline calculating masked item value

Hi,

In my local code,

lib/ethdev/rte_flow.c index renamed from lib/ethdev/rte_flow.c +0 -0

7cf9f6f6f3..7a2721af00 100644

lib/ethdev/rte_flow.c +0 -0
@@ -181,9 +181,18 @@ static const struct rte_flow_desc_data

rte_flow_desc_item[] = { static inline size_t rte_flow_conv_item_mask_size(const struct rte_flow_item *item) {

lib/ethdev/rte_flow.c +11 -2
-       if ((int)item->type >= 0)
+       if ((int)item->type < 0)
+               return sizeof(void *);
+       switch (item->type) {
+       case RTE_FLOW_ITEM_TYPE_RAW:
+               return offsetof(struct rte_flow_item_raw, pattern);
+       case RTE_FLOW_ITEM_TYPE_GENEVE_OPT:
+               return offsetof(struct rte_flow_item_geneve_opt, data);
+       default:
+               if (rte_flow_desc_item[item->type].desc_fn != NULL)
+                       return 0;
                return rte_flow_desc_item[item->type].size;
-       return sizeof(void *);
+       }
 }

// This is the code before my latest change.

Review

Quoted 7 lines
(quoted diff) +7 -0
+static inline size_t
+rte_flow_conv_item_mask_size(const struct rte_flow_item *item) {
+       if ((int)item->type >= 0)
+               return rte_flow_desc_item[item->type].size;
+       return sizeof(void *);
+}
+

I didn't understand why the patch I sent is still using the old code.

app/test/test_ethdev_api.c static struct unit_test_suite ethdev_api_testsuite =

Quoted 153 lines

-----Original Message-----
From: Bing Zhao <[email protected]>
Sent: Wednesday, June 10, 2026 1:27 PM
To: Slava Ovsiienko <[email protected]>; [email protected]; Raslan Darawsheh <[email protected]>; [email protected]
Cc: Ori Kam <[email protected]>; Dariusz Sosnowski <[email protected]>; Suanming Mou <[email protected]>; Matan Azrad <[email protected]>; NBU- Contact-Thomas Monjalon (EXTERNAL) <[email protected]>
Subject: [PATCH v5] ethdev: support inline calculating masked item value

External email: Use caution opening links or attachments

In the asynchronous API definition and some drivers, the rte_flow_item spec value may not be calculated by the driver due to the reason of speed of light rule insertion rate and sometimes the input parameters will be copied and changed internally.

After copying, the spec and last will be protected by the keyword const and cannot be changed in the code itself. And also the driver needs some extra memory to do the calculation and extra conditions to understand the length of each item spec. This is not efficient.

To solve the issue and support usage of the following fix, a new OP was introduced to calculate the spec and last values after applying the mask inline.

---
v3:
- add test code
- fix the issue found by AI
v4: reabse on top of the main
v5: handle some items separately and add test for them

---
 app/test/test_ethdev_api.c             | 76 ++++++++++++++++++++++++++
 doc/guides/rel_notes/release_26_07.rst |  6 ++
 lib/ethdev/rte_flow.c                  | 46 ++++++++++++++--
 lib/ethdev/rte_flow.h                  | 13 +++++
 4 files changed, 135 insertions(+), 6 deletions(-)
app/test/test_ethdev_api.c +16 -0
index 76afd0345c..5cae1cdc1d 100644
@@ -4,6 +4,7 @@
 
 #include <rte_log.h>
 #include <rte_ethdev.h>
+#include <rte_flow.h>
 
 #include <rte_test.h>
 #include "test.h"
@@ -15,6 +16,80 @@
 #define NUM_MBUF 1024
 #define MBUF_CACHE_SIZE 256
 
+static int32_t
+ethdev_api_flow_conv_pattern_masked(void)
+{
+       const struct rte_flow_item_eth spec = {
+               .hdr.dst_addr.addr_bytes = { 0x01, 0x02, 0x03, 0x04,
+0x05,
 0x06 },
+               .hdr.src_addr.addr_bytes = { 0x0a, 0x0b, 0x0c, 0x0d,
+ 0x0e,
 0x0f },
+               .hdr.ether_type = RTE_BE16(0x1234),
+       };
+       const struct rte_flow_item_eth last = {
+               .hdr.dst_addr.addr_bytes = { 0x11, 0x12, 0x13, 0x14,
+ 0x15,
 0x16 },
+               .hdr.src_addr.addr_bytes = { 0x1a, 0x1b, 0x1c, 0x1d,
+ 0x1e,

0x1f },

app/test/test_ethdev_api.c +5 -0
+               .hdr.ether_type = RTE_BE16(0x5678),
+       };
+       const struct rte_flow_item_eth mask = {
+               .hdr.dst_addr.addr_bytes = { 0xff, 0xff, 0x00, 0x00,
+ 0xff,

0xff },
+ .hdr.src_addr.addr_bytes = { 0xff, 0x00, 0xff, 0x00,
+ 0xff,
0x00 },

app/test/test_ethdev_api.c +23 -0
+               .hdr.ether_type = RTE_BE16(0xffff),
+       };
+       const struct rte_flow_item pattern[] = {
+               {
+                       .type = RTE_FLOW_ITEM_TYPE_ETH,
+                       .spec = &spec,
+                       .last = &last,
+                       .mask = &mask,
+               },
+               { .type = RTE_FLOW_ITEM_TYPE_END },
+       };
+       union {
+               struct rte_flow_item item;
+               struct rte_flow_item_eth eth;
+               double align;
+               uint8_t raw[256];
+       } dst;
+       const struct rte_flow_item *item;
+       const struct rte_flow_item_eth *conv_spec;
+       const struct rte_flow_item_eth *conv_last;
+       int ret;
+
+       ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN_MASKED, NULL, 0,

pattern, NULL);
+ TEST_ASSERT(ret > 0, "Masked pattern conversion size query
failed");

app/test/test_ethdev_api.c +35 -0
+       TEST_ASSERT((size_t)ret <= sizeof(dst.raw),
+                   "Masked pattern conversion needs too much
+ storage");
+
+       memset(&dst, 0, sizeof(dst));
+       ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN_MASKED, dst.raw,
+                           sizeof(dst.raw), pattern, NULL);
+       TEST_ASSERT(ret > 0, "Masked pattern conversion failed");
+
+       item = (const struct rte_flow_item *)dst.raw;
+       conv_spec = item[0].spec;
+       conv_last = item[0].last;
+       TEST_ASSERT_NOT_NULL(conv_spec, "Converted spec must be set");
+       TEST_ASSERT_NOT_NULL(conv_last, "Converted last must be set");
+
+       TEST_ASSERT_EQUAL(conv_spec->hdr.dst_addr.addr_bytes[0], 0x01,
+                         "Masked spec dst byte 0 mismatch");
+       TEST_ASSERT_EQUAL(conv_spec->hdr.dst_addr.addr_bytes[2], 0x00,
+                         "Masked spec dst byte 2 mismatch");
+       TEST_ASSERT_EQUAL(conv_spec->hdr.src_addr.addr_bytes[1], 0x00,
+                         "Masked spec src byte 1 mismatch");
+       TEST_ASSERT_EQUAL(conv_spec->hdr.ether_type, RTE_BE16(0x1234),
+                         "Masked spec ether type mismatch");
+       TEST_ASSERT_EQUAL(conv_last->hdr.dst_addr.addr_bytes[0], 0x11,
+                         "Masked last dst byte 0 mismatch");
+       TEST_ASSERT_EQUAL(conv_last->hdr.dst_addr.addr_bytes[2], 0x00,
+                         "Masked last dst byte 2 mismatch");
+       TEST_ASSERT_EQUAL(conv_last->hdr.src_addr.addr_bytes[1], 0x00,
+                         "Masked last src byte 1 mismatch");
+       TEST_ASSERT_EQUAL(conv_last->hdr.ether_type, RTE_BE16(0x5678),
+                         "Masked last ether type mismatch");
+
+       return TEST_SUCCESS;
+}
+
 static int32_t
 ethdev_api_queue_status(void)
 {
@@ -167,6 +242,7 @@ static struct unit_test_suite ethdev_api_testsuite =

{

Quoted 190 lines
app/test/test_ethdev_api.c +1 -0
        .setup = NULL,
        .teardown = NULL,
        .unit_test_cases = {
+               TEST_CASE(ethdev_api_flow_conv_pattern_masked),
                TEST_CASE(ethdev_api_queue_status),
                /* TODO: Add deferred_start queue status test */
                TEST_CASES_END() /**< NULL terminate unit test array

*/ diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst index b5285af5fe..4f5d21d576 100644

doc/guides/rel_notes/release_26_07.rst +6 -0
@@ -190,6 +190,12 @@ API Changes
   - ``rte_pmd_mlx5_enable_steering``
   - ``rte_pmd_mlx5_disable_steering``
 
+* ethdev: Added masked pattern conversion.
+
+  Added ``RTE_FLOW_CONV_OP_PATTERN_MASKED`` to ``rte_flow_conv()``
+ to copy an entire pattern while applying each item's mask to its
+ ``spec`` and ``last`` fields.
+
 
 ABI Changes
 -----------
lib/ethdev/rte_flow.c index renamed from lib/ethdev/rte_flow.c +0 -0

ec0fe08355..c7a94a1194 100644

lib/ethdev/rte_flow.c +7 -0
@@ -178,6 +178,14 @@ static const struct rte_flow_desc_data
 rte_flow_desc_item[] = {
        MK_FLOW_ITEM(COMPARE, sizeof(struct rte_flow_item_compare)),
 };
 
+static inline size_t
+rte_flow_conv_item_mask_size(const struct rte_flow_item *item) {
+       if ((int)item->type >= 0)
+               return rte_flow_desc_item[item->type].size;
+       return sizeof(void *);
+}
+
 /** Generate flow_action[] entry. */
 #define MK_FLOW_ACTION(t, s) \
        [RTE_FLOW_ACTION_TYPE_ ## t] = { \ @@ -835,6 +843,8 @@

rte_flow_conv_action_conf(void *buf, const size_t size,
* RTE_FLOW_ITEM_TYPE_END is encountered.
* @param[out] error
* Perform verbose error reporting if not NULL.
+ * @param[in] with_mask
+ * If true, @p src mask will be applied to spec and last.
*
* @return
* A positive value representing the number of bytes needed to store

lib/ethdev/rte_flow.c +27 -4
@@ -847,12 +857,13 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
                      const size_t size,
                      const struct rte_flow_item *src,
                      unsigned int num,
+                     bool with_mask,
                      struct rte_flow_error *error)  {
        uintptr_t data = (uintptr_t)dst;
        size_t off;
        size_t ret;
-       unsigned int i;
+       unsigned int i, j;
 
        for (i = 0, off = 0; !num || i != num; ++i, ++src, ++dst) {
                /**
@@ -876,15 +887,27 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
        src -= num;
        dst -= num;
        do {
+               uint8_t *c_spec = NULL, *c_last = NULL;
+               const uint8_t *mask = src->mask;
+               size_t item_mask_size = mask ?
+ rte_flow_conv_item_mask_size(src) : 0;
+
                if (src->spec) {
                        off = RTE_ALIGN_CEIL(off, sizeof(double));
                        ret = rte_flow_conv_item_spec
                                ((void *)(data + off),
                                 size > off ? size - off : 0, src,
                                 RTE_FLOW_CONV_ITEM_SPEC);
-                       if (size && size >= off + ret)
+                       if (size && size >= off + ret) {
                                dst->spec = (void *)(data + off);
+                               c_spec = (uint8_t *)(data + off);
+                       }
                        off += ret;
+                       if (with_mask && c_spec && mask) {
+                               size_t mask_size = RTE_MIN(ret,
+ item_mask_size);
+
+                               for (j = 0; j < mask_size; j++)
+                                       c_spec[j] &= mask[j];
+                       }
 
                }
                if (src->last) {
@@ -893,9 +916,17 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
                                ((void *)(data + off),
                                 size > off ? size - off : 0, src,
                                 RTE_FLOW_CONV_ITEM_LAST);
-                       if (size && size >= off + ret)
+                       if (size && size >= off + ret) {
                                dst->last = (void *)(data + off);
+                               c_last = (uint8_t *)(data + off);
+                       }
                        off += ret;
+                       if (with_mask && c_last && mask) {
+                               size_t mask_size = RTE_MIN(ret,
+ item_mask_size);
+
+                               for (j = 0; j < mask_size; j++)
+                                       c_last[j] &= mask[j];
+                       }
                }
                if (src->mask) {
                        off = RTE_ALIGN_CEIL(off, sizeof(double)); @@
-

1042,7 +1073,7 @@ rte_flow_conv_rule(struct rte_flow_conv_rule *dst,
off = RTE_ALIGN_CEIL(off, sizeof(double)); ret = rte_flow_conv_pattern((void *)((uintptr_t)dst +
off),
size > off ? size - off : 0,

lib/ethdev/rte_flow.c +7 -3
-                                           src->pattern_ro, 0, error);
+                                           src->pattern_ro, 0, false,
+ error);
                if (ret < 0)
                        return ret;
                if (size && size >= off + (size_t)ret) @@ -1143,7
+1174,7 @@ rte_flow_conv(enum rte_flow_conv_op op,
                ret = sizeof(*attr);
                break;
        case RTE_FLOW_CONV_OP_ITEM:
-               ret = rte_flow_conv_pattern(dst, size, src, 1, error);
+               ret = rte_flow_conv_pattern(dst, size, src, 1, false,
+ error);
                break;
        case RTE_FLOW_CONV_OP_ITEM_MASK:
                item = src;
@@ -1158,7 +1189,7 @@ rte_flow_conv(enum rte_flow_conv_op op,
                ret = rte_flow_conv_actions(dst, size, src, 1, error);
                break;
        case RTE_FLOW_CONV_OP_PATTERN:
-               ret = rte_flow_conv_pattern(dst, size, src, 0, error);
+               ret = rte_flow_conv_pattern(dst, size, src, 0, false,
+ error);
                break;
        case RTE_FLOW_CONV_OP_ACTIONS:
                ret = rte_flow_conv_actions(dst, size, src, 0, error);

@@
-1178,6 +1209,9 @@ rte_flow_conv(enum rte_flow_conv_op op,

        case RTE_FLOW_CONV_OP_ACTION_NAME_PTR:
                ret = rte_flow_conv_name(1, 1, dst, size, src, error);
                break;
lib/ethdev/rte_flow.c +2 -0
+       case RTE_FLOW_CONV_OP_PATTERN_MASKED:
+               ret = rte_flow_conv_pattern(dst, size, src, 0, true,

error);
+ break;
default:
ret = rte_flow_error_set
(error, ENOTSUP, RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
NULL, diff --git a/lib/ethdev/rte_flow.h b/lib/ethdev/rte_flow.h index b495409406..959a2f903b 100644

lib/ethdev/rte_flow.h +6 -0
@@ -4556,6 +4556,19 @@ enum rte_flow_conv_op {
         *   @code const char ** @endcode
         */
        RTE_FLOW_CONV_OP_ACTION_NAME_PTR,
+
+       /**
+        * Convert an entire pattern.
+        *
+        * Duplicates all pattern items at once, applying @p mask to
+ @p

spec

lib/ethdev/rte_flow.h +8 -1
+        * and @p last.
+        *
+        * - @p src type:
+        *   @code const struct rte_flow_item * @endcode
+        * - @p dst type:
+        *   @code struct rte_flow_item * @endcode
+        */
+       RTE_FLOW_CONV_OP_PATTERN_MASKED,
 };
 
 /**
--

2.34.1

author
#19 lore raw

PATCH v6 1/1 ethdev: support inline calculating masked item value

In reply to Bing Zhao

In the asynchronous API definition and some drivers, the rte_flow_item spec value may not be calculated by the driver due to the reason of speed of light rule insertion rate and sometimes the input parameters will be copied and changed internally.

After copying, the spec and last will be protected by the keyword const and cannot be changed in the code itself. And also the driver needs some extra memory to do the calculation and extra conditions to understand the length of each item spec. This is not efficient.

To solve the issue and support usage of the following fix, a new OP was introduced to calculate the spec and last values after applying the mask inline.

---
v3:
- add test code
- fix the issue found by AI
v4: reabse on top of the main
v5: handle some items separately and add test for them
v6: resend the correct version instead of v5 with AI defects resolved

---
 app/test/test_ethdev_api.c | 123 +++++++++++++++++++++++++++++++++++++
 lib/ethdev/rte_flow.c      |  55 +++++++++++++++--
 lib/ethdev/rte_flow.h      |  13 ++++
 3 files changed, 185 insertions(+), 6 deletions(-)
app/test/test_ethdev_api.c +123 -0
index 00d6a5c614..ba3e785425 100644
@@ -2,8 +2,11 @@
  * Copyright (C) 2023, Advanced Micro Devices, Inc.
  */
 
+#include <string.h>
+
 #include <rte_log.h>
 #include <rte_ethdev.h>
+#include <rte_flow.h>
 
 #include <rte_test.h>
 #include "test.h"
@@ -15,6 +18,125 @@
 #define NUM_MBUF 1024
 #define MBUF_CACHE_SIZE 256
 
+static int32_t
+ethdev_api_flow_conv_pattern_masked(void)
+{
+	const struct rte_flow_item_eth spec = {
+		.hdr.dst_addr.addr_bytes = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 },
+		.hdr.src_addr.addr_bytes = { 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f },
+		.hdr.ether_type = RTE_BE16(0x1234),
+	};
+	const struct rte_flow_item_eth last = {
+		.hdr.dst_addr.addr_bytes = { 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 },
+		.hdr.src_addr.addr_bytes = { 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f },
+		.hdr.ether_type = RTE_BE16(0x5678),
+	};
+	const struct rte_flow_item_eth mask = {
+		.hdr.dst_addr.addr_bytes = { 0xff, 0xff, 0x00, 0x00, 0xff, 0xff },
+		.hdr.src_addr.addr_bytes = { 0xff, 0x00, 0xff, 0x00, 0xff, 0x00 },
+		.hdr.ether_type = RTE_BE16(0xffff),
+	};
+	const uint8_t raw_pattern[] = { 0xaa, 0xbb, 0xcc, 0xdd };
+	const struct rte_flow_item_raw raw_spec = {
+		.relative = 1,
+		.search = 1,
+		.offset = 8,
+		.limit = 4,
+		.length = sizeof(raw_pattern),
+		.pattern = raw_pattern,
+	};
+	const struct rte_flow_item_raw raw_mask = {
+		.relative = 1,
+		.search = 0,
+		.reserved = 0x3fffffff,
+		.offset = 0xffffffff,
+		.limit = 0xffff,
+		.length = 0xffff,
+		.pattern = NULL,
+	};
+	const struct rte_flow_item pattern[] = {
+		{
+			.type = RTE_FLOW_ITEM_TYPE_ETH,
+			.spec = &spec,
+			.last = &last,
+			.mask = &mask,
+		},
+		{
+			.type = RTE_FLOW_ITEM_TYPE_RAW,
+			.spec = &raw_spec,
+			.mask = &raw_mask,
+		},
+		{ .type = RTE_FLOW_ITEM_TYPE_END },
+	};
+	union {
+		struct rte_flow_item item;
+		struct rte_flow_item_eth eth;
+		struct rte_flow_item_raw raw;
+		double align;
+		uint8_t buf[256];
+	} dst;
+	const struct rte_flow_item *item;
+	const struct rte_flow_item_eth *conv_spec;
+	const struct rte_flow_item_eth *conv_last;
+	const struct rte_flow_item_raw *conv_raw_spec;
+	int ret;
+
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN_MASKED, NULL, 0, pattern, NULL);
+	TEST_ASSERT(ret > 0, "Masked pattern conversion size query failed");
+	TEST_ASSERT((size_t)ret <= sizeof(dst.buf),
+		    "Masked pattern conversion needs too much storage");
+
+	memset(&dst, 0, sizeof(dst));
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN_MASKED, dst.buf,
+			    sizeof(dst.buf), pattern, NULL);
+	TEST_ASSERT(ret > 0, "Masked pattern conversion failed");
+
+	item = (const struct rte_flow_item *)dst.buf;
+	conv_spec = item[0].spec;
+	conv_last = item[0].last;
+	TEST_ASSERT_NOT_NULL(conv_spec, "Converted spec must be set");
+	TEST_ASSERT_NOT_NULL(conv_last, "Converted last must be set");
+
+	TEST_ASSERT_EQUAL(conv_spec->hdr.dst_addr.addr_bytes[0], 0x01,
+			  "Masked spec dst byte 0 mismatch");
+	TEST_ASSERT_EQUAL(conv_spec->hdr.dst_addr.addr_bytes[2], 0x00,
+			  "Masked spec dst byte 2 mismatch");
+	TEST_ASSERT_EQUAL(conv_spec->hdr.src_addr.addr_bytes[1], 0x00,
+			  "Masked spec src byte 1 mismatch");
+	TEST_ASSERT_EQUAL(conv_spec->hdr.ether_type, RTE_BE16(0x1234),
+			  "Masked spec ether type mismatch");
+	TEST_ASSERT_EQUAL(conv_last->hdr.dst_addr.addr_bytes[0], 0x11,
+			  "Masked last dst byte 0 mismatch");
+	TEST_ASSERT_EQUAL(conv_last->hdr.dst_addr.addr_bytes[2], 0x00,
+			  "Masked last dst byte 2 mismatch");
+	TEST_ASSERT_EQUAL(conv_last->hdr.src_addr.addr_bytes[1], 0x00,
+			  "Masked last src byte 1 mismatch");
+	TEST_ASSERT_EQUAL(conv_last->hdr.ether_type, RTE_BE16(0x5678),
+			  "Masked last ether type mismatch");
+
+	conv_raw_spec = item[1].spec;
+	TEST_ASSERT_NOT_NULL(conv_raw_spec, "Converted RAW spec must be set");
+	TEST_ASSERT_NOT_NULL(conv_raw_spec->pattern,
+			     "Converted RAW pattern pointer must be set");
+	TEST_ASSERT(conv_raw_spec->pattern != raw_pattern,
+		    "Converted RAW pattern must be deep-copied");
+	TEST_ASSERT_EQUAL(conv_raw_spec->relative, 1,
+			  "Masked RAW relative mismatch");
+	TEST_ASSERT_EQUAL(conv_raw_spec->search, 0,
+			  "Masked RAW search mismatch");
+	TEST_ASSERT_EQUAL(conv_raw_spec->offset, raw_spec.offset,
+			  "Masked RAW offset mismatch");
+	TEST_ASSERT_EQUAL(conv_raw_spec->limit, raw_spec.limit,
+			  "Masked RAW limit mismatch");
+	TEST_ASSERT_EQUAL(conv_raw_spec->length, raw_spec.length,
+			  "Masked RAW length mismatch");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(conv_raw_spec->pattern, raw_pattern,
+				      sizeof(raw_pattern),
+				      "Converted RAW pattern mismatch");
+
+	return TEST_SUCCESS;
+}
+
 static int32_t
 ethdev_api_queue_status(void)
 {
@@ -167,6 +289,7 @@ static struct unit_test_suite ethdev_api_testsuite = {
 	.setup = NULL,
 	.teardown = NULL,
 	.unit_test_cases = {
+		TEST_CASE(ethdev_api_flow_conv_pattern_masked),
 		TEST_CASE(ethdev_api_queue_status),
 		/* TODO: Add deferred_start queue status test */
 		TEST_CASES_END() /**< NULL terminate unit test array */
lib/ethdev/rte_flow.c +49 -6

124 lines not on this page · raw message

lib/ethdev/rte_flow.h +13 -0
index f864578f80..2261d1ea06 100644
@@ -4544,6 +4544,19 @@ enum rte_flow_conv_op {
 	 *   @code const char ** @endcode
 	 */
 	RTE_FLOW_CONV_OP_ACTION_NAME_PTR,
+
+	/**
+	 * Convert an entire pattern.
+	 *
+	 * Duplicates all pattern items at once, applying each source item mask
+	 * to its copied specification and range.
+	 *
+	 * - @p src type:
+	 *   @code const struct rte_flow_item * @endcode
+	 * - @p dst type:
+	 *   @code struct rte_flow_item * @endcode
+	 */
+	RTE_FLOW_CONV_OP_PATTERN_MASKED,
 };
 
 /**
Signature · 1 line
2.43.0
author
#20 lore raw

RE: [PATCH v6] ethdev: support inline calculating masked item value

No. The three errors in that AI review are not valid, and the warnings do not block this patch.

rte_flow_conv_item_spec() returns size_t, the number of bytes to copy. It does not return a negative errno. In rte_flow_conv_pattern() the local ret is also size_t, so if (ret < 0) is always false and cannot be the missing check the review describes. That copy pattern was already there for spec, last, and mask. This patch only ANDs the mask onto a buffer after that copy has produced a pointer.

The other notes are process nits:

The with_mask comment is already clear.
A release note belongs in the current release file, not release_25_03.rst. This master has no current notes file, and release_26_07.rst is gone, which is why that hunk was dropped. RTE_FLOW_CONV_OP_PATTERN_MASKED is one new enumerator at the end of an existing enum. It cannot be tagged __rte_experimental, and existing callers of rte_flow_conv() are unchanged.

https://mails.dpdk.org/archives/test-report/2026-October/1057123.html

@[email protected], FYI

Quoted 371 lines

-----Original Message-----
From: Bing Zhao <[email protected]>
Sent: Thursday, October 8, 2026 12:58 AM
To: Slava Ovsiienko <[email protected]>; [email protected]; Raslan Darawsheh <[email protected]>; [email protected]
Cc: Ori Kam <[email protected]>; Dariusz Sosnowski <[email protected]>; Suanming Mou <[email protected]>; Matan Azrad <[email protected]>; NBU- Contact-Thomas Monjalon (EXTERNAL) <[email protected]>
Subject: [PATCH v6] ethdev: support inline calculating masked item value

External email: Use caution opening links or attachments

In the asynchronous API definition and some drivers, the rte_flow_item spec value may not be calculated by the driver due to the reason of speed of light rule insertion rate and sometimes the input parameters will be copied and changed internally.

After copying, the spec and last will be protected by the keyword const and cannot be changed in the code itself. And also the driver needs some extra memory to do the calculation and extra conditions to understand the length of each item spec. This is not efficient.

To solve the issue and support usage of the following fix, a new OP was introduced to calculate the spec and last values after applying the mask inline.

---
v3:
- add test code
- fix the issue found by AI
v4: reabse on top of the main
v5: handle some items separately and add test for them
v6: resend the correct version instead of v5 with AI defects resolved

---
 app/test/test_ethdev_api.c | 123 +++++++++++++++++++++++++++++++++++++
 lib/ethdev/rte_flow.c      |  55 +++++++++++++++--
 lib/ethdev/rte_flow.h      |  13 ++++
 3 files changed, 185 insertions(+), 6 deletions(-)
app/test/test_ethdev_api.c index renamed from app/test/test_ethdev_api.c +0 -0

00d6a5c614..ba3e785425 100644

app/test/test_ethdev_api.c +14 -0
@@ -2,8 +2,11 @@
  * Copyright (C) 2023, Advanced Micro Devices, Inc.
  */
 
+#include <string.h>
+
 #include <rte_log.h>
 #include <rte_ethdev.h>
+#include <rte_flow.h>
 
 #include <rte_test.h>
 #include "test.h"
@@ -15,6 +18,125 @@
 #define NUM_MBUF 1024
 #define MBUF_CACHE_SIZE 256
 
+static int32_t
+ethdev_api_flow_conv_pattern_masked(void)
+{
+       const struct rte_flow_item_eth spec = {
+               .hdr.dst_addr.addr_bytes = { 0x01, 0x02, 0x03, 0x04, 0x05,
 0x06 },
+               .hdr.src_addr.addr_bytes = { 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
 0x0f },
+               .hdr.ether_type = RTE_BE16(0x1234),
+       };
+       const struct rte_flow_item_eth last = {
+               .hdr.dst_addr.addr_bytes = { 0x11, 0x12, 0x13, 0x14, 0x15,
 0x16 },
+               .hdr.src_addr.addr_bytes = { 0x1a, 0x1b, 0x1c, 0x1d, 0x1e,

0x1f },

app/test/test_ethdev_api.c +4 -0
+               .hdr.ether_type = RTE_BE16(0x5678),
+       };
+       const struct rte_flow_item_eth mask = {
+               .hdr.dst_addr.addr_bytes = { 0xff, 0xff, 0x00, 0x00, 0xff,

0xff },
+ .hdr.src_addr.addr_bytes = { 0xff, 0x00, 0xff, 0x00, 0xff,
0x00 },

app/test/test_ethdev_api.c +48 -0
+               .hdr.ether_type = RTE_BE16(0xffff),
+       };
+       const uint8_t raw_pattern[] = { 0xaa, 0xbb, 0xcc, 0xdd };
+       const struct rte_flow_item_raw raw_spec = {
+               .relative = 1,
+               .search = 1,
+               .offset = 8,
+               .limit = 4,
+               .length = sizeof(raw_pattern),
+               .pattern = raw_pattern,
+       };
+       const struct rte_flow_item_raw raw_mask = {
+               .relative = 1,
+               .search = 0,
+               .reserved = 0x3fffffff,
+               .offset = 0xffffffff,
+               .limit = 0xffff,
+               .length = 0xffff,
+               .pattern = NULL,
+       };
+       const struct rte_flow_item pattern[] = {
+               {
+                       .type = RTE_FLOW_ITEM_TYPE_ETH,
+                       .spec = &spec,
+                       .last = &last,
+                       .mask = &mask,
+               },
+               {
+                       .type = RTE_FLOW_ITEM_TYPE_RAW,
+                       .spec = &raw_spec,
+                       .mask = &raw_mask,
+               },
+               { .type = RTE_FLOW_ITEM_TYPE_END },
+       };
+       union {
+               struct rte_flow_item item;
+               struct rte_flow_item_eth eth;
+               struct rte_flow_item_raw raw;
+               double align;
+               uint8_t buf[256];
+       } dst;
+       const struct rte_flow_item *item;
+       const struct rte_flow_item_eth *conv_spec;
+       const struct rte_flow_item_eth *conv_last;
+       const struct rte_flow_item_raw *conv_raw_spec;
+       int ret;
+
+       ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN_MASKED, NULL, 0,

pattern, NULL);
+ TEST_ASSERT(ret > 0, "Masked pattern conversion size query
failed");

app/test/test_ethdev_api.c +33 -0
+       TEST_ASSERT((size_t)ret <= sizeof(dst.buf),
+                   "Masked pattern conversion needs too much storage");
+
+       memset(&dst, 0, sizeof(dst));
+       ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN_MASKED, dst.buf,
+                           sizeof(dst.buf), pattern, NULL);
+       TEST_ASSERT(ret > 0, "Masked pattern conversion failed");
+
+       item = (const struct rte_flow_item *)dst.buf;
+       conv_spec = item[0].spec;
+       conv_last = item[0].last;
+       TEST_ASSERT_NOT_NULL(conv_spec, "Converted spec must be set");
+       TEST_ASSERT_NOT_NULL(conv_last, "Converted last must be set");
+
+       TEST_ASSERT_EQUAL(conv_spec->hdr.dst_addr.addr_bytes[0], 0x01,
+                         "Masked spec dst byte 0 mismatch");
+       TEST_ASSERT_EQUAL(conv_spec->hdr.dst_addr.addr_bytes[2], 0x00,
+                         "Masked spec dst byte 2 mismatch");
+       TEST_ASSERT_EQUAL(conv_spec->hdr.src_addr.addr_bytes[1], 0x00,
+                         "Masked spec src byte 1 mismatch");
+       TEST_ASSERT_EQUAL(conv_spec->hdr.ether_type, RTE_BE16(0x1234),
+                         "Masked spec ether type mismatch");
+       TEST_ASSERT_EQUAL(conv_last->hdr.dst_addr.addr_bytes[0], 0x11,
+                         "Masked last dst byte 0 mismatch");
+       TEST_ASSERT_EQUAL(conv_last->hdr.dst_addr.addr_bytes[2], 0x00,
+                         "Masked last dst byte 2 mismatch");
+       TEST_ASSERT_EQUAL(conv_last->hdr.src_addr.addr_bytes[1], 0x00,
+                         "Masked last src byte 1 mismatch");
+       TEST_ASSERT_EQUAL(conv_last->hdr.ether_type, RTE_BE16(0x5678),
+                         "Masked last ether type mismatch");
+
+       conv_raw_spec = item[1].spec;
+       TEST_ASSERT_NOT_NULL(conv_raw_spec, "Converted RAW spec must be

set");

app/test/test_ethdev_api.c +22 -0
+       TEST_ASSERT_NOT_NULL(conv_raw_spec->pattern,
+                            "Converted RAW pattern pointer must be set");
+       TEST_ASSERT(conv_raw_spec->pattern != raw_pattern,
+                   "Converted RAW pattern must be deep-copied");
+       TEST_ASSERT_EQUAL(conv_raw_spec->relative, 1,
+                         "Masked RAW relative mismatch");
+       TEST_ASSERT_EQUAL(conv_raw_spec->search, 0,
+                         "Masked RAW search mismatch");
+       TEST_ASSERT_EQUAL(conv_raw_spec->offset, raw_spec.offset,
+                         "Masked RAW offset mismatch");
+       TEST_ASSERT_EQUAL(conv_raw_spec->limit, raw_spec.limit,
+                         "Masked RAW limit mismatch");
+       TEST_ASSERT_EQUAL(conv_raw_spec->length, raw_spec.length,
+                         "Masked RAW length mismatch");
+       TEST_ASSERT_BUFFERS_ARE_EQUAL(conv_raw_spec->pattern, raw_pattern,
+                                     sizeof(raw_pattern),
+                                     "Converted RAW pattern mismatch");
+
+       return TEST_SUCCESS;
+}
+
 static int32_t
 ethdev_api_queue_status(void)
 {
@@ -167,6 +289,7 @@ static struct unit_test_suite ethdev_api_testsuite = {
        .setup = NULL,
        .teardown = NULL,
        .unit_test_cases = {
+               TEST_CASE(ethdev_api_flow_conv_pattern_masked),
                TEST_CASE(ethdev_api_queue_status),
                /* TODO: Add deferred_start queue status test */
                TEST_CASES_END() /**< NULL terminate unit test array */
lib/ethdev/rte_flow.c index renamed from lib/ethdev/rte_flow.c +0 -0

ca2f85c3fa..e4efcbf10f 100644

lib/ethdev/rte_flow.c +16 -0
@@ -175,6 +175,23 @@ static const struct rte_flow_desc_data
 rte_flow_desc_item[] = {
        MK_FLOW_ITEM(COMPARE, sizeof(struct rte_flow_item_compare)),  };
 
+static inline size_t
+rte_flow_conv_item_mask_size(const struct rte_flow_item *item) {
+       if ((int)item->type < 0)
+               return sizeof(void *);
+       switch (item->type) {
+       case RTE_FLOW_ITEM_TYPE_RAW:
+               return offsetof(struct rte_flow_item_raw, pattern);
+       case RTE_FLOW_ITEM_TYPE_GENEVE_OPT:
+               return offsetof(struct rte_flow_item_geneve_opt, data);
+       default:
+               if (rte_flow_desc_item[item->type].desc_fn != NULL)
+                       return 0;
+               return rte_flow_desc_item[item->type].size;
+       }
+}
+
 /** Generate flow_action[] entry. */
 #define MK_FLOW_ACTION(t, s) \
        [RTE_FLOW_ACTION_TYPE_ ## t] = { \ @@ -797,6 +814,8 @@

rte_flow_conv_action_conf(void *buf, const size_t size,
* RTE_FLOW_ITEM_TYPE_END is encountered.
* @param[out] error
* Perform verbose error reporting if not NULL.
+ * @param[in] with_mask
+ * If true, @p src mask will be applied to spec and last.
*
* @return
* A positive value representing the number of bytes needed to store

lib/ethdev/rte_flow.c +27 -3
@@ -809,12 +828,13 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
                      const size_t size,
                      const struct rte_flow_item *src,
                      unsigned int num,
+                     bool with_mask,
                      struct rte_flow_error *error)  {
        uintptr_t data = (uintptr_t)dst;
        size_t off;
        size_t ret;
-       unsigned int i;
+       unsigned int i, j;
 
        for (i = 0, off = 0; !num || i != num; ++i, ++src, ++dst) {
                /**
@@ -838,15 +858,27 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
        src -= num;
        dst -= num;
        do {
+               uint8_t *c_spec = NULL, *c_last = NULL;
+               const uint8_t *mask = src->mask;
+               size_t item_mask_size = mask ?
+ rte_flow_conv_item_mask_size(src) : 0;
+
                if (src->spec) {
                        off = RTE_ALIGN_CEIL(off, sizeof(double));
                        ret = rte_flow_conv_item_spec
                                ((void *)(data + off),
                                 size > off ? size - off : 0, src,
                                 RTE_FLOW_CONV_ITEM_SPEC);
-                       if (size && size >= off + ret)
+                       if (size && size >= off + ret) {
                                dst->spec = (void *)(data + off);
+                               c_spec = (uint8_t *)(data + off);
+                       }
                        off += ret;
+                       if (with_mask && c_spec && mask) {
+                               size_t mask_size = RTE_MIN(ret,
+ item_mask_size);
+
+                               for (j = 0; j < mask_size; j++)
+                                       c_spec[j] &= mask[j];
+                       }
 
                }
                if (src->last) {
@@ -855,9 +887,17 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
                                ((void *)(data + off),
                                 size > off ? size - off : 0, src,
                                 RTE_FLOW_CONV_ITEM_LAST);
-                       if (size && size >= off + ret)
+                       if (size && size >= off + ret) {
                                dst->last = (void *)(data + off);
+                               c_last = (uint8_t *)(data + off);
+                       }
                        off += ret;
+                       if (with_mask && c_last && mask) {
+                               size_t mask_size = RTE_MIN(ret,
+ item_mask_size);
+
+                               for (j = 0; j < mask_size; j++)
+                                       c_last[j] &= mask[j];
+                       }
                }
                if (src->mask) {
                        off = RTE_ALIGN_CEIL(off, sizeof(double)); @@ -

1004,7 +1044,7 @@ rte_flow_conv_rule(struct rte_flow_conv_rule *dst,
off = RTE_ALIGN_CEIL(off, sizeof(double)); ret = rte_flow_conv_pattern((void *)((uintptr_t)dst +
off),
size > off ? size - off : 0,

lib/ethdev/rte_flow.c +2 -1
-                                           src->pattern_ro, 0, error);
+                                           src->pattern_ro, 0, false,
+ error);
                if (ret < 0)
                        return ret;
                if (size && size >= off + (size_t)ret) @@ -1104,7 +1144,7

@@ rte_flow_conv(enum rte_flow_conv_op op,

                ret = sizeof(*attr);
                break;
        case RTE_FLOW_CONV_OP_ITEM:
lib/ethdev/rte_flow.c +6 -3
-               ret = rte_flow_conv_pattern(dst, size, src, 1, error);
+               ret = rte_flow_conv_pattern(dst, size, src, 1, false,
+ error);
                break;
        case RTE_FLOW_CONV_OP_ITEM_MASK:
                item = src;
@@ -1119,7 +1159,7 @@ rte_flow_conv(enum rte_flow_conv_op op,
                ret = rte_flow_conv_actions(dst, size, src, 1, error);
                break;
        case RTE_FLOW_CONV_OP_PATTERN:
-               ret = rte_flow_conv_pattern(dst, size, src, 0, error);
+               ret = rte_flow_conv_pattern(dst, size, src, 0, false,
+ error);
                break;
        case RTE_FLOW_CONV_OP_ACTIONS:
                ret = rte_flow_conv_actions(dst, size, src, 0, error); @@
-1139,6 +1179,9 @@ rte_flow_conv(enum rte_flow_conv_op op,
        case RTE_FLOW_CONV_OP_ACTION_NAME_PTR:
                ret = rte_flow_conv_name(1, 1, dst, size, src, error);
                break;
+       case RTE_FLOW_CONV_OP_PATTERN_MASKED:
+               ret = rte_flow_conv_pattern(dst, size, src, 0, true,

error);

lib/ethdev/rte_flow.c +1 -0
+               break;
        default:
                ret = rte_flow_error_set
                (error, ENOTSUP, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
lib/ethdev/rte_flow.h index renamed from lib/ethdev/rte_flow.h +0 -0

f864578f80..2261d1ea06 100644

lib/ethdev/rte_flow.h +5 -0
@@ -4544,6 +4544,19 @@ enum rte_flow_conv_op {
         *   @code const char ** @endcode
         */
        RTE_FLOW_CONV_OP_ACTION_NAME_PTR,
+
+       /**
+        * Convert an entire pattern.
+        *
+        * Duplicates all pattern items at once, applying each source item

mask

lib/ethdev/rte_flow.h +8 -1
+        * to its copied specification and range.
+        *
+        * - @p src type:
+        *   @code const struct rte_flow_item * @endcode
+        * - @p dst type:
+        *   @code struct rte_flow_item * @endcode
+        */
+       RTE_FLOW_CONV_OP_PATTERN_MASKED,
 };
 
 /**
--

2.43.0

#21 lore raw

Re: [PATCH v6] ethdev: support inline calculating masked item value

In reply to Bing Zhao
Quoted 10 lines · On Wed, 7 Oct 2026 19:57:44 +0300 Bing Zhao <[email protected]> wrote:

In the asynchronous API definition and some drivers, the rte_flow_item spec value may not be calculated by the driver due to the reason of speed of light rule insertion rate and sometimes the input parameters will be copied and changed internally.

After copying, the spec and last will be protected by the keyword const and cannot be changed in the code itself. And also the driver needs some extra memory to do the calculation and extra conditions to understand the length of each item spec. This is not efficient.

Quoted 7 lines

To solve the issue and support usage of the following fix, a new OP was introduced to calculate the spec and last values after applying the mask inline.

---

Existing AI review done by CI is useless at this point...

Running a more detailed review does find some things.

Review: [PATCH v6] ethdev: support inline calculating masked item value

Error
-----

1. NULL mask leaves spec/last unmasked.

rte_flow semantics: a NULL item mask means the default mask (rte_flow_item_<name>_mask) applies, not "match all bits". Here item_mask_size is 0 when src->mask is NULL, so spec and last are copied raw. For ETH the default mask is all ones so nothing shows, but for IPV4 (default mask covers only src/dst addr) tos, ttl, etc. come back unmasked. A driver relying on the new op to get "masked values" gets wrong ones.

rte_flow.c has no type -> default mask table, so either add one to rte_flow_desc_data, or document that a NULL mask is not applied and the caller must handle it (see doc text in Warning 3).

Warning
-------

1. PMD private items get their opaque data ANDed.

rte_flow_conv_item_mask_size() returns sizeof(void *) for (int)type < 0, so the first pointer-size bytes of a private spec are ANDed with the first bytes of its mask. ethdev has no idea what those bytes mean. Leave them alone:

	if ((int)item->type < 0)
		return 0;

2. Variable part of RAW and GENEVE_OPT is not masked.

item_mask_size stops at offsetof(pattern) / offsetof(data), so the copied pattern bytes and option data are never ANDed with mask->pattern / mask->data. The same holds for FLEX (desc_fn returns 0). Either mask them or document it; the current comment says the mask is applied to the spec, which is not true for these.

3. Doxygen for RTE_FLOW_CONV_OP_PATTERN_MASKED is a copy of OP_PATTERN and does not describe the limits above. Suggest:

/**
* Convert an entire pattern, applying item masks. *
* Same as RTE_FLOW_CONV_OP_PATTERN, except each copied spec
* and last is ANDed with the item mask. Not masked:
* - items with a NULL mask (default mask is not applied);
* - RAW pattern and GENEVE_OPT data contents;
* - FLEX and PMD private items.
*
* - @p src type:
* @code const struct rte_flow_item * @endcode
* - @p dst type:
* @code struct rte_flow_item * @endcode
*/

4. Missing release notes. New rte_flow_conv() op is a new API and needs an entry in doc/guides/rel_notes/release_26_11.rst.

5. Commit message is hard to parse ("due to the reason of speed of light rule insertion rate", "support usage of the following fix" refers to a patch not in this series). Suggested body:

Add RTE_FLOW_CONV_OP_PATTERN_MASKED to rte_flow_conv(). It copies a pattern like RTE_FLOW_CONV_OP_PATTERN and ANDs each item spec and last with the item mask.

Drivers using the async flow API copy patterns on the rule insertion path and need masked values. The copied spec and last are const, so masking them in the driver needs extra memory and per-item length handling. Doing it during the conversion avoids both.

Info
----

1. No in-tree user. Please send the mlx5 consumer in the same series so the API is exercised and reviewed in context.

2. Spec and last masking loops are duplicated, and item_mask_size is computed even when with_mask is false. Factor out:

	static void
	rte_flow_conv_apply_mask(uint8_t *buf, const uint8_t *mask,
				 size_t len)
	{
		size_t i;

		for (i = 0; i < len; i++)
			buf[i] &= mask[i];
	}

and compute the size only under with_mask && mask.

3. Test does not cover a NULL-mask item or an item with a non-trivial default mask (e.g. IPV4). Add one once Error 1 is settled so the chosen behaviour is locked in.

author
#22 lore raw

PATCH v7 1/1 ethdev: support inline calculating masked item value

In reply to Bing Zhao

In the asynchronous API definition and some drivers, the rte_flow_item spec value may not be calculated by the driver due to the reason of speed of light rule insertion rate and sometimes the input parameters will be copied and changed internally.

After copying, the spec and last will be protected by the keyword const and cannot be changed in the code itself. And also the driver needs some extra memory to do the calculation and extra conditions to understand the length of each item spec. This is not efficient.

To solve the issue and support usage of the following fix, a new OP was introduced to calculate the spec and last values after applying the mask inline.

---
v2: add review comments from Dariusz Sosnowski
v3:
- add test code
- fix the issue found by AI
v4: reabse on top of the main
v5: handle some items separately and add test for them
v6: resend the correct version instead of v5 with AI defects resolved
v7: address the review comments from AI

---
 app/test/test_ethdev_api.c             | 338 +++++++++++++++++++++++++
 doc/guides/rel_notes/release_26_11.rst |   6 +
 lib/ethdev/rte_flow.c                  |  76 +++++-
 lib/ethdev/rte_flow.h                  |  16 ++
 4 files changed, 430 insertions(+), 6 deletions(-)
app/test/test_ethdev_api.c +338 -0
index 76afd0345c..ef4c318b7d 100644
@@ -2,8 +2,11 @@
  * Copyright (C) 2023, Advanced Micro Devices, Inc.
  */
 
+#include <string.h>
+
 #include <rte_log.h>
 #include <rte_ethdev.h>
+#include <rte_flow.h>
 
 #include <rte_test.h>
 #include "test.h"
@@ -15,6 +18,323 @@
 #define NUM_MBUF 1024
 #define MBUF_CACHE_SIZE 256
 
+static int32_t
+ethdev_api_flow_conv_pattern_masked(void)
+{
+	const struct rte_flow_item_eth spec = {
+		.hdr.dst_addr.addr_bytes = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 },
+		.hdr.src_addr.addr_bytes = { 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f },
+		.hdr.ether_type = RTE_BE16(0x1234),
+	};
+	const struct rte_flow_item_eth last = {
+		.hdr.dst_addr.addr_bytes = { 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 },
+		.hdr.src_addr.addr_bytes = { 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f },
+		.hdr.ether_type = RTE_BE16(0x5678),
+	};
+	const struct rte_flow_item_eth mask = {
+		.hdr.dst_addr.addr_bytes = { 0xff, 0xff, 0x00, 0x00, 0xff, 0xff },
+		.hdr.src_addr.addr_bytes = { 0xff, 0x00, 0xff, 0x00, 0xff, 0x00 },
+		.hdr.ether_type = RTE_BE16(0xffff),
+	};
+	const uint8_t raw_pattern[] = { 0xaa, 0xbb, 0xcc, 0xdd };
+	const uint8_t raw_pattern_mask[] = { 0x0f, 0xf0, 0x00, 0xff };
+	const struct rte_flow_item_raw raw_spec = {
+		.relative = 1,
+		.search = 1,
+		.offset = 8,
+		.limit = 4,
+		.length = sizeof(raw_pattern),
+		.pattern = raw_pattern,
+	};
+	const struct rte_flow_item_raw raw_mask = {
+		.relative = 1,
+		.search = 0,
+		.reserved = 0x3fffffff,
+		.offset = 0xffffffff,
+		.limit = 0xffff,
+		.length = 0xffff,
+		.pattern = raw_pattern_mask,
+	};
+	const struct rte_flow_item pattern[] = {
+		{
+			.type = RTE_FLOW_ITEM_TYPE_ETH,
+			.spec = &spec,
+			.last = &last,
+			.mask = &mask,
+		},
+		{
+			.type = RTE_FLOW_ITEM_TYPE_RAW,
+			.spec = &raw_spec,
+			.mask = &raw_mask,
+		},
+		{ .type = RTE_FLOW_ITEM_TYPE_END },
+	};
+	union {
+		struct rte_flow_item item;
+		struct rte_flow_item_eth eth;
+		struct rte_flow_item_raw raw;
+		double align;
+		uint8_t buf[256];
+	} dst;
+	const struct rte_flow_item *item;
+	const struct rte_flow_item_eth *conv_spec;
+	const struct rte_flow_item_eth *conv_last;
+	const struct rte_flow_item_raw *conv_raw_spec;
+	int ret;
+
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN_MASKED, NULL, 0, pattern, NULL);
+	TEST_ASSERT(ret > 0, "Masked pattern conversion size query failed");
+	TEST_ASSERT((size_t)ret <= sizeof(dst.buf),
+		    "Masked pattern conversion needs too much storage");
+
+	memset(&dst, 0, sizeof(dst));
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN_MASKED, dst.buf,
+			    sizeof(dst.buf), pattern, NULL);
+	TEST_ASSERT(ret > 0, "Masked pattern conversion failed");
+
+	item = (const struct rte_flow_item *)dst.buf;
+	conv_spec = item[0].spec;
+	conv_last = item[0].last;
+	TEST_ASSERT_NOT_NULL(conv_spec, "Converted spec must be set");
+	TEST_ASSERT_NOT_NULL(conv_last, "Converted last must be set");
+
+	TEST_ASSERT_EQUAL(conv_spec->hdr.dst_addr.addr_bytes[0], 0x01,
+			  "Masked spec dst byte 0 mismatch");
+	TEST_ASSERT_EQUAL(conv_spec->hdr.dst_addr.addr_bytes[2], 0x00,
+			  "Masked spec dst byte 2 mismatch");
+	TEST_ASSERT_EQUAL(conv_spec->hdr.src_addr.addr_bytes[1], 0x00,
+			  "Masked spec src byte 1 mismatch");
+	TEST_ASSERT_EQUAL(conv_spec->hdr.ether_type, RTE_BE16(0x1234),
+			  "Masked spec ether type mismatch");
+	TEST_ASSERT_EQUAL(conv_last->hdr.dst_addr.addr_bytes[0], 0x11,
+			  "Masked last dst byte 0 mismatch");
+	TEST_ASSERT_EQUAL(conv_last->hdr.dst_addr.addr_bytes[2], 0x00,
+			  "Masked last dst byte 2 mismatch");
+	TEST_ASSERT_EQUAL(conv_last->hdr.src_addr.addr_bytes[1], 0x00,
+			  "Masked last src byte 1 mismatch");
+	TEST_ASSERT_EQUAL(conv_last->hdr.ether_type, RTE_BE16(0x5678),
+			  "Masked last ether type mismatch");
+
+	conv_raw_spec = item[1].spec;
+	TEST_ASSERT_NOT_NULL(conv_raw_spec, "Converted RAW spec must be set");
+	TEST_ASSERT_NOT_NULL(conv_raw_spec->pattern,
+			     "Converted RAW pattern pointer must be set");
+	TEST_ASSERT(conv_raw_spec->pattern != raw_pattern,
+		    "Converted RAW pattern must be deep-copied");
+	TEST_ASSERT_EQUAL(conv_raw_spec->relative, 1,
+			  "Masked RAW relative mismatch");
+	TEST_ASSERT_EQUAL(conv_raw_spec->search, 0,
+			  "Masked RAW search mismatch");
+	TEST_ASSERT_EQUAL(conv_raw_spec->offset, raw_spec.offset,
+			  "Masked RAW offset mismatch");
+	TEST_ASSERT_EQUAL(conv_raw_spec->limit, raw_spec.limit,
+			  "Masked RAW limit mismatch");
+	TEST_ASSERT_EQUAL(conv_raw_spec->length, raw_spec.length,
+			  "Masked RAW length mismatch");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(conv_raw_spec->pattern, raw_pattern,
+				      sizeof(raw_pattern),
+				      "RAW pattern bytes must not be masked");
+
+	/*
+	 * NULL mask keeps spec and last unchanged: the IPv4 default mask
+	 * (addresses only) is not substituted. An explicit mask is applied
+	 * to both spec and last. Private items, GENEVE_OPT data and COMPARE
+	 * items are not.
+	 */
+	{
+		const struct rte_flow_item_ipv4 ipv4_open = {
+			.hdr = {
+				.type_of_service = 0xab,
+				.time_to_live = 0x40,
+				.src_addr = RTE_BE32(0x0a010203),
+				.dst_addr = RTE_BE32(0xc0a80001),
+			},
+		};
+		const struct rte_flow_item_ipv4 ipv4_spec = {
+			.hdr = {
+				.type_of_service = 0xab,
+				.time_to_live = 0x40,
+				.src_addr = RTE_BE32(0x0a000001),
+				.dst_addr = RTE_BE32(0x0a000002),
+			},
+		};
+		const struct rte_flow_item_ipv4 ipv4_last = {
+			.hdr = {
+				.type_of_service = 0xcd,
+				.time_to_live = 0x20,
+				.src_addr = RTE_BE32(0x0a0000ff),
+				.dst_addr = RTE_BE32(0x0a00ffff),
+			},
+		};
+		const struct rte_flow_item_ipv4 ipv4_mask = {
+			.hdr = {
+				.type_of_service = 0x0f,
+				.src_addr = RTE_BE32(0xffffffff),
+				.dst_addr = RTE_BE32(0x0000ffff),
+			},
+		};
+		const struct {
+			uint64_t value;
+			uint32_t tail;
+		} priv_spec = {
+			.value = 0x1122334455667788ULL,
+			.tail = 0xaabbccdd,
+		};
+		const struct {
+			uint64_t value;
+			uint32_t tail;
+		} priv_mask = {
+			.value = 0,
+			.tail = 0,
+		};
+		uint32_t opt_data[2] = { 0xffffffff, 0x11111111 };
+		uint32_t opt_data_mask[2] = { 0, 0 };
+		const struct rte_flow_item_geneve_opt opt_spec = {
+			.option_class = RTE_BE16(0x1234),
+			.option_type = 0xaa,
+			.option_len = 2,
+			.data = opt_data,
+		};
+		const struct rte_flow_item_geneve_opt opt_mask = {
+			.option_type = 0xff,
+			.data = opt_data_mask,
+		};
+		uint32_t cmp_value = 0x12345678;
+		const struct rte_flow_item_compare cmp_spec = {
+			.operation = RTE_FLOW_ITEM_COMPARE_GT,
+			.a = {
+				.field = RTE_FLOW_FIELD_TAG,
+				.tag_index = 1,
+			},
+			.b = {
+				.field = RTE_FLOW_FIELD_POINTER,
+				.pvalue = &cmp_value,
+			},
+			.width = 32,
+		};
+		const struct rte_flow_item_compare cmp_mask = {
+			.width = UINT32_MAX,
+		};
+		const struct rte_flow_item limits[] = {
+			{
+				.type = RTE_FLOW_ITEM_TYPE_IPV4,
+				.spec = &ipv4_open,
+				.last = &ipv4_open,
+			},
+			{
+				.type = RTE_FLOW_ITEM_TYPE_IPV4,
+				.spec = &ipv4_spec,
+				.last = &ipv4_last,
+				.mask = &ipv4_mask,
+			},
+			{
+				.type = (enum rte_flow_item_type)-1,
+				.spec = &priv_spec,
+				.mask = &priv_mask,
+			},
+			{
+				.type = RTE_FLOW_ITEM_TYPE_GENEVE_OPT,
+				.spec = &opt_spec,
+				.mask = &opt_mask,
+			},
+			{
+				.type = RTE_FLOW_ITEM_TYPE_COMPARE,
+				.spec = &cmp_spec,
+				.mask = &cmp_mask,
+			},
+			{ .type = RTE_FLOW_ITEM_TYPE_END },
+		};
+		union {
+			struct rte_flow_item item;
+			uint8_t buf[1024];
+		} conv;
+		const struct rte_flow_item *out;
+		const struct rte_flow_item_ipv4 *ipv4;
+		const struct rte_flow_item_geneve_opt *opt;
+		const struct rte_flow_item_compare *cmp;
+
+		memset(&conv, 0, sizeof(conv));
+		ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN_MASKED, conv.buf,
+				    sizeof(conv.buf), limits, NULL);
+		TEST_ASSERT(ret > 0, "Limit pattern conversion failed");
+		TEST_ASSERT((size_t)ret <= sizeof(conv.buf),
+			    "Limit pattern conversion needs too much storage");
+
+		out = (const struct rte_flow_item *)conv.buf;
+		TEST_ASSERT_NULL(out[0].mask, "NULL mask must stay NULL");
+		ipv4 = out[0].spec;
+		TEST_ASSERT_NOT_NULL(ipv4, "Unmasked IPv4 spec must be set");
+		TEST_ASSERT_EQUAL(ipv4->hdr.type_of_service, 0xab,
+				  "NULL mask must not clear IPv4 TOS");
+		TEST_ASSERT_EQUAL(ipv4->hdr.time_to_live, 0x40,
+				  "NULL mask must not clear IPv4 TTL");
+		TEST_ASSERT_EQUAL(ipv4->hdr.src_addr, ipv4_open.hdr.src_addr,
+				  "NULL mask must keep IPv4 source address");
+		TEST_ASSERT_EQUAL(ipv4->hdr.dst_addr, ipv4_open.hdr.dst_addr,
+				  "NULL mask must keep IPv4 destination address");
+		ipv4 = out[0].last;
+		TEST_ASSERT_NOT_NULL(ipv4, "Unmasked IPv4 last must be set");
+		TEST_ASSERT_EQUAL(ipv4->hdr.type_of_service, 0xab,
+				  "NULL mask must not clear IPv4 last TOS");
+		TEST_ASSERT_EQUAL(ipv4->hdr.time_to_live, 0x40,
+				  "NULL mask must not clear IPv4 last TTL");
+
+		ipv4 = out[1].spec;
+		TEST_ASSERT_NOT_NULL(ipv4, "Masked IPv4 spec must be set");
+		TEST_ASSERT_EQUAL(ipv4->hdr.type_of_service, 0x0b,
+				  "IPv4 spec TOS mask mismatch");
+		TEST_ASSERT_EQUAL(ipv4->hdr.time_to_live, 0,
+				  "IPv4 spec TTL must be cleared by the mask");
+		TEST_ASSERT_EQUAL(ipv4->hdr.src_addr, ipv4_spec.hdr.src_addr,
+				  "IPv4 spec source address mismatch");
+		TEST_ASSERT_EQUAL(ipv4->hdr.dst_addr, RTE_BE32(0x00000002),
+				  "IPv4 spec destination address mismatch");
+		ipv4 = out[1].last;
+		TEST_ASSERT_NOT_NULL(ipv4, "Masked IPv4 last must be set");
+		TEST_ASSERT_EQUAL(ipv4->hdr.type_of_service, 0x0d,
+				  "IPv4 last TOS mask mismatch");
+		TEST_ASSERT_EQUAL(ipv4->hdr.time_to_live, 0,
+				  "IPv4 last TTL must be cleared by the mask");
+		TEST_ASSERT_EQUAL(ipv4->hdr.src_addr, ipv4_last.hdr.src_addr,
+				  "IPv4 last source address mismatch");
+		TEST_ASSERT_EQUAL(ipv4->hdr.dst_addr, RTE_BE32(0x0000ffff),
+				  "IPv4 last destination address mismatch");
+
+		TEST_ASSERT_NOT_NULL(out[2].spec, "Private spec must be set");
+		TEST_ASSERT_BUFFERS_ARE_EQUAL(out[2].spec, &priv_spec,
+					      sizeof(void *),
+					      "PMD private spec must not be masked");
+
+		opt = out[3].spec;
+		TEST_ASSERT_NOT_NULL(opt, "GENEVE_OPT spec must be set");
+		TEST_ASSERT_EQUAL(opt->option_class, 0,
+				  "GENEVE_OPT class must be masked");
+		TEST_ASSERT_EQUAL(opt->option_type, 0xaa,
+				  "GENEVE_OPT type mask mismatch");
+		TEST_ASSERT_EQUAL(opt->option_len, 0,
+				  "GENEVE_OPT length must be masked");
+		TEST_ASSERT(opt->data != opt_data,
+			    "GENEVE_OPT data must be deep-copied");
+		TEST_ASSERT_BUFFERS_ARE_EQUAL(opt->data, opt_data, sizeof(opt_data),
+					      "GENEVE_OPT data must not be masked");
+
+		cmp = out[4].spec;
+		TEST_ASSERT_NOT_NULL(cmp, "COMPARE spec must be set");
+		TEST_ASSERT_EQUAL(cmp->operation, RTE_FLOW_ITEM_COMPARE_GT,
+				  "COMPARE operation must not be masked");
+		TEST_ASSERT_EQUAL(cmp->a.field, RTE_FLOW_FIELD_TAG,
+				  "COMPARE field a must not be masked");
+		TEST_ASSERT_EQUAL(cmp->a.tag_index, 1,
+				  "COMPARE field a tag index must not be masked");
+		TEST_ASSERT_EQUAL(cmp->b.field, RTE_FLOW_FIELD_POINTER,
+				  "COMPARE field b must not be masked");
+		TEST_ASSERT(cmp->b.pvalue == &cmp_value,
+			    "COMPARE pointer must not be masked");
+	}
+
+	return TEST_SUCCESS;
+}
+
 static int32_t
 ethdev_api_queue_status(void)
 {
@@ -162,6 +482,24 @@ ethdev_api_queue_status(void)
 	return TEST_SUCCESS;
 }
 
+static struct unit_test_suite ethdev_flow_conv_testsuite = {
+	.suite_name = "ethdev flow conversion tests",
+	.setup = NULL,
+	.teardown = NULL,
+	.unit_test_cases = {
+		TEST_CASE(ethdev_api_flow_conv_pattern_masked),
+		TEST_CASES_END()
+	}
+};
+
+static int
+test_ethdev_flow_conv(void)
+{
+	return unit_test_suite_runner(&ethdev_flow_conv_testsuite);
+}
+
+REGISTER_FAST_TEST(ethdev_flow_conv, NOHUGE_OK, ASAN_OK, test_ethdev_flow_conv);
+
 static struct unit_test_suite ethdev_api_testsuite = {
 	.suite_name = "ethdev API tests",
 	.setup = NULL,
doc/guides/rel_notes/release_26_11.rst +6 -0
index a6df347411..31c909dad9 100644
@@ -82,6 +82,12 @@ New Features
   Added support for the VFIO character device (cdev) API, also known as IOMMUFD.
   DPDK now supports both the group mode and the new cdev mode.
 
+* **Added masked pattern conversion to flow API.**
+
+  Added ``RTE_FLOW_CONV_OP_PATTERN_MASKED`` to ``rte_flow_conv()``.
+  The new operation copies a pattern like ``RTE_FLOW_CONV_OP_PATTERN`` and
+  ANDs each item ``spec`` and ``last`` with the item mask.
+
 * **Updated AF_XDP driver.**
 
   * Changed the default device plugin endpoint path used when
lib/ethdev/rte_flow.c +70 -6

146 lines not on this page · raw message

lib/ethdev/rte_flow.h +16 -0
index 1bfe1b3f61..428e5f4fd4 100644
@@ -4411,6 +4411,22 @@ enum rte_flow_conv_op {
 	 *   @code const char ** @endcode
 	 */
 	RTE_FLOW_CONV_OP_ACTION_NAME_PTR,
+
+	/**
+	 * Convert an entire pattern, applying item masks.
+	 *
+	 * Same as RTE_FLOW_CONV_OP_PATTERN, except each copied spec
+	 * and last is ANDed with the item mask. Not masked:
+	 * - items with a NULL mask (default mask is not applied);
+	 * - RAW pattern and GENEVE_OPT data contents;
+	 * - FLEX, COMPARE and PMD private items.
+	 *
+	 * - @p src type:
+	 *   @code const struct rte_flow_item * @endcode
+	 * - @p dst type:
+	 *   @code struct rte_flow_item * @endcode
+	 */
+	RTE_FLOW_CONV_OP_PATTERN_MASKED,
 };
 
 /**
Signature · 1 line
2.43.0
#23 lore raw

Re: [PATCH v7] ethdev: support inline calculating masked item value

Quoted 10 lines · On Tue, 13 Oct 2026 16:49:50 +0300 Bing Zhao <[email protected]> wrote:

In the asynchronous API definition and some drivers, the rte_flow_item spec value may not be calculated by the driver due to the reason of speed of light rule insertion rate and sometimes the input parameters will be copied and changed internally.

After copying, the spec and last will be protected by the keyword const and cannot be changed in the code itself. And also the driver needs some extra memory to do the calculation and extra conditions to understand the length of each item spec. This is not efficient.

Quoted 7 lines

To solve the issue and support usage of the following fix, a new OP was introduced to calculate the spec and last values after applying the mask inline.

---

Semi-automated AI review had some feedback.
I assume mlx5 will come as followon so complaints about just this patch are not really valid. And not concerned about its complaints about going over flow twice when copying.

[PATCH v7] ethdev: support inline calculating masked item value

Applies to main, builds with -Dwerror=true.

Warning

1. GENEVE_OPT option_len is masked, data length is not.

rte_flow_conv_item_mask_size() returns offsetof(..., data), so option_len is ANDed with the mask. The default rte_flow_item_geneve_opt_mask has option_len = 0, while the deep copy of data is sized from the unmasked spec->option_len. The output is a spec with option_len == 0 pointing at option_len * 4 bytes of data; the new test asserts exactly that. mlx5 sizes the option data from spec->option_len (mlx5_flow.c, data_len = spec->option_len * 4), so a driver switched to this op loses the option length.

option_len is a length, not a match value. Leave it unmasked:

	case RTE_FLOW_ITEM_TYPE_GENEVE_OPT:
		return offsetof(struct rte_flow_item_geneve_opt,
				option_len);

and change the test to assert option_len is preserved.

2. TAG index is a selector, same reasoning as COMPARE.

rte_flow_item_tag.index picks the register, it is not matched against packet data. mlx5 takes it from the unmasked spec on purpose (mlx5_flow_dv.c: "When set mask, the index should be from spec"). With a template mask that has index == 0, this op returns index 0, i.e. a different tag. Exclude it:

	case RTE_FLOW_ITEM_TYPE_TAG:
		return offsetof(struct rte_flow_item_tag, index);

Update the "Not masked" list in rte_flow.h, the comment above rte_flow_conv_item_mask_size() and the rte_flow_conv_pattern() doxygen to match, and add a TAG case to the test.

3. No user of the new op.

The commit message refers to "the following fix", but nothing in this submission or in tree uses RTE_FLOW_CONV_OP_PATTERN_MASKED. Send it as a series with the driver change that consumes it, so the semantics above can be checked against a real caller.

4. Commit message needs rewriting. Suggested body:

Drivers translating async flow rules get const spec/last and must copy each item to apply the mask, which needs per-item knowledge of the spec size.

Add RTE_FLOW_CONV_OP_PATTERN_MASKED to rte_flow_conv(). It copies the pattern like RTE_FLOW_CONV_OP_PATTERN and ANDs each copied spec and last with the item mask. Pointed-to data (RAW pattern, GENEVE_OPT data), selector fields, FLEX, COMPARE and PMD private items are not masked.

Info

5. RAW pattern bytes are deep-copied into the writable output buffer and spec.length is already masked, so masking the copied pattern with mask->pattern for that length is straightforward. If it is left out on purpose, say why in the commit message.

6. The insertion-rate argument needs numbers. rte_flow_conv() walks the pattern twice (validate, copy) and masks byte by byte; show it is not slower than the driver-local copy it replaces, or drop the claim.

author
#24 lore raw

PATCH v8 1/1 ethdev: support inline calculating masked item value

In reply to Bing Zhao

Drivers implementing synchronous flow rules through the asynchronous template API need to apply item masks before creating a rule. The spec and last pointers are const, so this requires writable copies and per-item knowledge of their sizes.

Add RTE_FLOW_CONV_OP_PATTERN_MASKED to rte_flow_conv(). It copies the pattern like RTE_FLOW_CONV_OP_PATTERN and ANDs the inline fields of each copied spec and last with an explicit item mask. A NULL mask leaves the values unchanged; default masks are not substituted.

Preserve GENEVE_OPT.option_len and TAG.index because they describe the pointed-to data length and select the tag register, respectively. RAW pattern and GENEVE_OPT data are copied without applying their payload masks. Limit this operation to inline fields, leaving variable-length payload masking to the caller. FLEX, COMPARE and PMD private items are not masked.

---
v2: add review comments from Dariusz Sosnowski
v3:
- add test code
- fix the issue found by AI
v4: reabse on top of the main
v5: handle some items separately and add test for them
v6: resend the correct version instead of v5 with AI defects resolved
v7: address the review comments from AI
v8:
- preserve GENEVE_OPT option_len and TAG index
- test spec and last preservation, TAG data masking and unchanged masks
- document the masking exceptions and explicit-mask behavior
- clarify the inline-only scope without insertion-rate claims
- rebase on the latest main, retaining the default-mask lookup operation

---
 app/test/test_ethdev_api.c             | 389 +++++++++++++++++++++++++
 doc/guides/rel_notes/release_26_11.rst |   9 +
 lib/ethdev/rte_flow.c                  |  80 ++++-
 lib/ethdev/rte_flow.h                  |  17 ++
 4 files changed, 489 insertions(+), 6 deletions(-)
app/test/test_ethdev_api.c +389 -0
index 76afd0345c..69cb38a0a1 100644
@@ -2,8 +2,11 @@
  * Copyright (C) 2023, Advanced Micro Devices, Inc.
  */
 
+#include <string.h>
+
 #include <rte_log.h>
 #include <rte_ethdev.h>
+#include <rte_flow.h>
 
 #include <rte_test.h>
 #include "test.h"
@@ -15,6 +18,374 @@
 #define NUM_MBUF 1024
 #define MBUF_CACHE_SIZE 256
 
+static int32_t
+ethdev_api_flow_conv_pattern_masked(void)
+{
+	const struct rte_flow_item_eth spec = {
+		.hdr.dst_addr.addr_bytes = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 },
+		.hdr.src_addr.addr_bytes = { 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f },
+		.hdr.ether_type = RTE_BE16(0x1234),
+	};
+	const struct rte_flow_item_eth last = {
+		.hdr.dst_addr.addr_bytes = { 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 },
+		.hdr.src_addr.addr_bytes = { 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f },
+		.hdr.ether_type = RTE_BE16(0x5678),
+	};
+	const struct rte_flow_item_eth mask = {
+		.hdr.dst_addr.addr_bytes = { 0xff, 0xff, 0x00, 0x00, 0xff, 0xff },
+		.hdr.src_addr.addr_bytes = { 0xff, 0x00, 0xff, 0x00, 0xff, 0x00 },
+		.hdr.ether_type = RTE_BE16(0xffff),
+	};
+	const uint8_t raw_pattern[] = { 0xaa, 0xbb, 0xcc, 0xdd };
+	const uint8_t raw_pattern_mask[] = { 0x0f, 0xf0, 0x00, 0xff };
+	const struct rte_flow_item_raw raw_spec = {
+		.relative = 1,
+		.search = 1,
+		.offset = 8,
+		.limit = 4,
+		.length = sizeof(raw_pattern),
+		.pattern = raw_pattern,
+	};
+	const struct rte_flow_item_raw raw_mask = {
+		.relative = 1,
+		.search = 0,
+		.reserved = 0x3fffffff,
+		.offset = 0xffffffff,
+		.limit = 0xffff,
+		.length = 0xffff,
+		.pattern = raw_pattern_mask,
+	};
+	const struct rte_flow_item pattern[] = {
+		{
+			.type = RTE_FLOW_ITEM_TYPE_ETH,
+			.spec = &spec,
+			.last = &last,
+			.mask = &mask,
+		},
+		{
+			.type = RTE_FLOW_ITEM_TYPE_RAW,
+			.spec = &raw_spec,
+			.mask = &raw_mask,
+		},
+		{ .type = RTE_FLOW_ITEM_TYPE_END },
+	};
+	union {
+		struct rte_flow_item item;
+		struct rte_flow_item_eth eth;
+		struct rte_flow_item_raw raw;
+		double align;
+		uint8_t buf[256];
+	} dst;
+	const struct rte_flow_item *item;
+	const struct rte_flow_item_eth *conv_spec;
+	const struct rte_flow_item_eth *conv_last;
+	const struct rte_flow_item_raw *conv_raw_spec;
+	int ret;
+
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN_MASKED, NULL, 0, pattern, NULL);
+	TEST_ASSERT(ret > 0, "Masked pattern conversion size query failed");
+	TEST_ASSERT((size_t)ret <= sizeof(dst.buf),
+		    "Masked pattern conversion needs too much storage");
+
+	memset(&dst, 0, sizeof(dst));
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN_MASKED, dst.buf,
+			    sizeof(dst.buf), pattern, NULL);
+	TEST_ASSERT(ret > 0, "Masked pattern conversion failed");
+
+	item = (const struct rte_flow_item *)dst.buf;
+	conv_spec = item[0].spec;
+	conv_last = item[0].last;
+	TEST_ASSERT_NOT_NULL(conv_spec, "Converted spec must be set");
+	TEST_ASSERT_NOT_NULL(conv_last, "Converted last must be set");
+
+	TEST_ASSERT_EQUAL(conv_spec->hdr.dst_addr.addr_bytes[0], 0x01,
+			  "Masked spec dst byte 0 mismatch");
+	TEST_ASSERT_EQUAL(conv_spec->hdr.dst_addr.addr_bytes[2], 0x00,
+			  "Masked spec dst byte 2 mismatch");
+	TEST_ASSERT_EQUAL(conv_spec->hdr.src_addr.addr_bytes[1], 0x00,
+			  "Masked spec src byte 1 mismatch");
+	TEST_ASSERT_EQUAL(conv_spec->hdr.ether_type, RTE_BE16(0x1234),
+			  "Masked spec ether type mismatch");
+	TEST_ASSERT_EQUAL(conv_last->hdr.dst_addr.addr_bytes[0], 0x11,
+			  "Masked last dst byte 0 mismatch");
+	TEST_ASSERT_EQUAL(conv_last->hdr.dst_addr.addr_bytes[2], 0x00,
+			  "Masked last dst byte 2 mismatch");
+	TEST_ASSERT_EQUAL(conv_last->hdr.src_addr.addr_bytes[1], 0x00,
+			  "Masked last src byte 1 mismatch");
+	TEST_ASSERT_EQUAL(conv_last->hdr.ether_type, RTE_BE16(0x5678),
+			  "Masked last ether type mismatch");
+
+	conv_raw_spec = item[1].spec;
+	TEST_ASSERT_NOT_NULL(conv_raw_spec, "Converted RAW spec must be set");
+	TEST_ASSERT_NOT_NULL(conv_raw_spec->pattern,
+			     "Converted RAW pattern pointer must be set");
+	TEST_ASSERT(conv_raw_spec->pattern != raw_pattern,
+		    "Converted RAW pattern must be deep-copied");
+	TEST_ASSERT_EQUAL(conv_raw_spec->relative, 1,
+			  "Masked RAW relative mismatch");
+	TEST_ASSERT_EQUAL(conv_raw_spec->search, 0,
+			  "Masked RAW search mismatch");
+	TEST_ASSERT_EQUAL(conv_raw_spec->offset, raw_spec.offset,
+			  "Masked RAW offset mismatch");
+	TEST_ASSERT_EQUAL(conv_raw_spec->limit, raw_spec.limit,
+			  "Masked RAW limit mismatch");
+	TEST_ASSERT_EQUAL(conv_raw_spec->length, raw_spec.length,
+			  "Masked RAW length mismatch");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(conv_raw_spec->pattern, raw_pattern,
+				      sizeof(raw_pattern),
+				      "RAW pattern bytes must not be masked");
+
+	/*
+	 * NULL mask keeps spec and last unchanged: the IPv4 default mask
+	 * (addresses only) is not substituted. An explicit mask is applied
+	 * to both spec and last. Private items, GENEVE_OPT length and data,
+	 * TAG index and COMPARE items are not masked.
+	 */
+	{
+		const struct rte_flow_item_ipv4 ipv4_open = {
+			.hdr = {
+				.type_of_service = 0xab,
+				.time_to_live = 0x40,
+				.src_addr = RTE_BE32(0x0a010203),
+				.dst_addr = RTE_BE32(0xc0a80001),
+			},
+		};
+		const struct rte_flow_item_ipv4 ipv4_spec = {
+			.hdr = {
+				.type_of_service = 0xab,
+				.time_to_live = 0x40,
+				.src_addr = RTE_BE32(0x0a000001),
+				.dst_addr = RTE_BE32(0x0a000002),
+			},
+		};
+		const struct rte_flow_item_ipv4 ipv4_last = {
+			.hdr = {
+				.type_of_service = 0xcd,
+				.time_to_live = 0x20,
+				.src_addr = RTE_BE32(0x0a0000ff),
+				.dst_addr = RTE_BE32(0x0a00ffff),
+			},
+		};
+		const struct rte_flow_item_ipv4 ipv4_mask = {
+			.hdr = {
+				.type_of_service = 0x0f,
+				.src_addr = RTE_BE32(0xffffffff),
+				.dst_addr = RTE_BE32(0x0000ffff),
+			},
+		};
+		const struct {
+			uint64_t value;
+			uint32_t tail;
+		} priv_spec = {
+			.value = 0x1122334455667788ULL,
+			.tail = 0xaabbccdd,
+		};
+		const struct {
+			uint64_t value;
+			uint32_t tail;
+		} priv_mask = {
+			.value = 0,
+			.tail = 0,
+		};
+		uint32_t opt_data[2] = { 0xffffffff, 0x11111111 };
+		uint32_t opt_data_mask[2] = { 0, 0 };
+		const struct rte_flow_item_geneve_opt opt_spec = {
+			.option_class = RTE_BE16(0x1234),
+			.option_type = 0xaa,
+			.option_len = 2,
+			.data = opt_data,
+		};
+		const struct rte_flow_item_geneve_opt opt_mask = {
+			.option_type = 0xff,
+			.data = opt_data_mask,
+		};
+		uint32_t cmp_value = 0x12345678;
+		const struct rte_flow_item_compare cmp_spec = {
+			.operation = RTE_FLOW_ITEM_COMPARE_GT,
+			.a = {
+				.field = RTE_FLOW_FIELD_TAG,
+				.tag_index = 1,
+			},
+			.b = {
+				.field = RTE_FLOW_FIELD_POINTER,
+				.pvalue = &cmp_value,
+			},
+			.width = 32,
+		};
+		const struct rte_flow_item_compare cmp_mask = {
+			.width = UINT32_MAX,
+		};
+		const struct rte_flow_item_tag tag_spec = {
+			.data = 0x12345678,
+			.index = 3,
+		};
+		const struct rte_flow_item_tag tag_last = {
+			.data = 0xabcdef01,
+			.index = 3,
+		};
+		const struct rte_flow_item_tag tag_mask = {
+			.data = 0x00ff00ff,
+			.index = 0,
+		};
+		const struct rte_flow_item limits[] = {
+			{
+				.type = RTE_FLOW_ITEM_TYPE_IPV4,
+				.spec = &ipv4_open,
+				.last = &ipv4_open,
+			},
+			{
+				.type = RTE_FLOW_ITEM_TYPE_IPV4,
+				.spec = &ipv4_spec,
+				.last = &ipv4_last,
+				.mask = &ipv4_mask,
+			},
+			{
+				.type = (enum rte_flow_item_type)-1,
+				.spec = &priv_spec,
+				.mask = &priv_mask,
+			},
+			{
+				.type = RTE_FLOW_ITEM_TYPE_GENEVE_OPT,
+				.spec = &opt_spec,
+				.last = &opt_spec,
+				.mask = &opt_mask,
+			},
+			{
+				.type = RTE_FLOW_ITEM_TYPE_COMPARE,
+				.spec = &cmp_spec,
+				.mask = &cmp_mask,
+			},
+			{
+				.type = RTE_FLOW_ITEM_TYPE_TAG,
+				.spec = &tag_spec,
+				.last = &tag_last,
+				.mask = &tag_mask,
+			},
+			{ .type = RTE_FLOW_ITEM_TYPE_END },
+		};
+		union {
+			struct rte_flow_item item;
+			uint8_t buf[1024];
+		} conv;
+		const struct rte_flow_item *out;
+		const struct rte_flow_item_ipv4 *ipv4;
+		const struct rte_flow_item_geneve_opt *opt;
+		const struct rte_flow_item_compare *cmp;
+		const struct rte_flow_item_tag *tag;
+
+		memset(&conv, 0, sizeof(conv));
+		ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN_MASKED, conv.buf,
+				    sizeof(conv.buf), limits, NULL);
+		TEST_ASSERT(ret > 0, "Limit pattern conversion failed");
+		TEST_ASSERT((size_t)ret <= sizeof(conv.buf),
+			    "Limit pattern conversion needs too much storage");
+
+		out = (const struct rte_flow_item *)conv.buf;
+		TEST_ASSERT_NULL(out[0].mask, "NULL mask must stay NULL");
+		ipv4 = out[0].spec;
+		TEST_ASSERT_NOT_NULL(ipv4, "Unmasked IPv4 spec must be set");
+		TEST_ASSERT_EQUAL(ipv4->hdr.type_of_service, 0xab,
+				  "NULL mask must not clear IPv4 TOS");
+		TEST_ASSERT_EQUAL(ipv4->hdr.time_to_live, 0x40,
+				  "NULL mask must not clear IPv4 TTL");
+		TEST_ASSERT_EQUAL(ipv4->hdr.src_addr, ipv4_open.hdr.src_addr,
+				  "NULL mask must keep IPv4 source address");
+		TEST_ASSERT_EQUAL(ipv4->hdr.dst_addr, ipv4_open.hdr.dst_addr,
+				  "NULL mask must keep IPv4 destination address");
+		ipv4 = out[0].last;
+		TEST_ASSERT_NOT_NULL(ipv4, "Unmasked IPv4 last must be set");
+		TEST_ASSERT_EQUAL(ipv4->hdr.type_of_service, 0xab,
+				  "NULL mask must not clear IPv4 last TOS");
+		TEST_ASSERT_EQUAL(ipv4->hdr.time_to_live, 0x40,
+				  "NULL mask must not clear IPv4 last TTL");
+
+		ipv4 = out[1].spec;
+		TEST_ASSERT_NOT_NULL(ipv4, "Masked IPv4 spec must be set");
+		TEST_ASSERT_EQUAL(ipv4->hdr.type_of_service, 0x0b,
+				  "IPv4 spec TOS mask mismatch");
+		TEST_ASSERT_EQUAL(ipv4->hdr.time_to_live, 0,
+				  "IPv4 spec TTL must be cleared by the mask");
+		TEST_ASSERT_EQUAL(ipv4->hdr.src_addr, ipv4_spec.hdr.src_addr,
+				  "IPv4 spec source address mismatch");
+		TEST_ASSERT_EQUAL(ipv4->hdr.dst_addr, RTE_BE32(0x00000002),
+				  "IPv4 spec destination address mismatch");
+		ipv4 = out[1].last;
+		TEST_ASSERT_NOT_NULL(ipv4, "Masked IPv4 last must be set");
+		TEST_ASSERT_EQUAL(ipv4->hdr.type_of_service, 0x0d,
+				  "IPv4 last TOS mask mismatch");
+		TEST_ASSERT_EQUAL(ipv4->hdr.time_to_live, 0,
+				  "IPv4 last TTL must be cleared by the mask");
+		TEST_ASSERT_EQUAL(ipv4->hdr.src_addr, ipv4_last.hdr.src_addr,
+				  "IPv4 last source address mismatch");
+		TEST_ASSERT_EQUAL(ipv4->hdr.dst_addr, RTE_BE32(0x0000ffff),
+				  "IPv4 last destination address mismatch");
+
+		TEST_ASSERT_NOT_NULL(out[2].spec, "Private spec must be set");
+		TEST_ASSERT_BUFFERS_ARE_EQUAL(out[2].spec, &priv_spec,
+					      sizeof(void *),
+					      "PMD private spec must not be masked");
+
+		opt = out[3].spec;
+		TEST_ASSERT_NOT_NULL(opt, "GENEVE_OPT spec must be set");
+		TEST_ASSERT_EQUAL(opt->option_class, 0,
+				  "GENEVE_OPT class must be masked");
+		TEST_ASSERT_EQUAL(opt->option_type, 0xaa,
+				  "GENEVE_OPT type mask mismatch");
+		TEST_ASSERT_EQUAL(opt->option_len, opt_spec.option_len,
+				  "GENEVE_OPT length must not be masked");
+		TEST_ASSERT(opt->data != opt_data,
+			    "GENEVE_OPT data must be deep-copied");
+		TEST_ASSERT_BUFFERS_ARE_EQUAL(opt->data, opt_data, sizeof(opt_data),
+					      "GENEVE_OPT data must not be masked");
+		opt = out[3].last;
+		TEST_ASSERT_NOT_NULL(opt, "GENEVE_OPT last must be set");
+		TEST_ASSERT_EQUAL(opt->option_class, 0,
+				  "GENEVE_OPT last class must be masked");
+		TEST_ASSERT_EQUAL(opt->option_type, 0xaa,
+				  "GENEVE_OPT last type mask mismatch");
+		TEST_ASSERT_EQUAL(opt->option_len, opt_spec.option_len,
+				  "GENEVE_OPT last length must not be masked");
+		TEST_ASSERT(opt->data != opt_data,
+			    "GENEVE_OPT last data must be deep-copied");
+		TEST_ASSERT_BUFFERS_ARE_EQUAL(opt->data, opt_data, sizeof(opt_data),
+					      "GENEVE_OPT last data must not be masked");
+
+		cmp = out[4].spec;
+		TEST_ASSERT_NOT_NULL(cmp, "COMPARE spec must be set");
+		TEST_ASSERT_EQUAL(cmp->operation, RTE_FLOW_ITEM_COMPARE_GT,
+				  "COMPARE operation must not be masked");
+		TEST_ASSERT_EQUAL(cmp->a.field, RTE_FLOW_FIELD_TAG,
+				  "COMPARE field a must not be masked");
+		TEST_ASSERT_EQUAL(cmp->a.tag_index, 1,
+				  "COMPARE field a tag index must not be masked");
+		TEST_ASSERT_EQUAL(cmp->b.field, RTE_FLOW_FIELD_POINTER,
+				  "COMPARE field b must not be masked");
+		TEST_ASSERT(cmp->b.pvalue == &cmp_value,
+			    "COMPARE pointer must not be masked");
+
+		tag = out[5].spec;
+		TEST_ASSERT_NOT_NULL(tag, "TAG spec must be set");
+		TEST_ASSERT_EQUAL(tag->data, 0x00340078,
+				  "TAG spec data mask mismatch");
+		TEST_ASSERT_EQUAL(tag->index, tag_spec.index,
+				  "TAG spec index must not be masked");
+		tag = out[5].last;
+		TEST_ASSERT_NOT_NULL(tag, "TAG last must be set");
+		TEST_ASSERT_EQUAL(tag->data, 0x00cd0001,
+				  "TAG last data mask mismatch");
+		TEST_ASSERT_EQUAL(tag->index, tag_last.index,
+				  "TAG last index must not be masked");
+		tag = out[5].mask;
+		TEST_ASSERT_NOT_NULL(tag, "TAG mask must be set");
+		TEST_ASSERT_EQUAL(tag->data, tag_mask.data,
+				  "TAG data mask must not change");
+		TEST_ASSERT_EQUAL(tag->index, tag_mask.index,
+				  "TAG index mask must not change");
+	}
+
+	return TEST_SUCCESS;
+}
+
 static int32_t
 ethdev_api_queue_status(void)
 {
@@ -162,6 +533,24 @@ ethdev_api_queue_status(void)
 	return TEST_SUCCESS;
 }
 
+static struct unit_test_suite ethdev_flow_conv_testsuite = {
+	.suite_name = "ethdev flow conversion tests",
+	.setup = NULL,
+	.teardown = NULL,
+	.unit_test_cases = {
+		TEST_CASE(ethdev_api_flow_conv_pattern_masked),
+		TEST_CASES_END()
+	}
+};
+
+static int
+test_ethdev_flow_conv(void)
+{
+	return unit_test_suite_runner(&ethdev_flow_conv_testsuite);
+}
+
+REGISTER_FAST_TEST(ethdev_flow_conv, NOHUGE_OK, ASAN_OK, test_ethdev_flow_conv);
+
 static struct unit_test_suite ethdev_api_testsuite = {
 	.suite_name = "ethdev API tests",
 	.setup = NULL,
doc/guides/rel_notes/release_26_11.rst +9 -0
index be758a0c02..c318e961ea 100644
@@ -123,6 +123,15 @@ New Features
   Added internal ``flow_graph`` API in ethdev
   for PMD drivers to build graph-based flow pattern parsers.
 
+* **Added masked pattern conversion to flow API.**
+
+  Added ``RTE_FLOW_CONV_OP_PATTERN_MASKED`` to ``rte_flow_conv()``.
+  The new operation copies a pattern like ``RTE_FLOW_CONV_OP_PATTERN`` and
+  ANDs inline fields of each item ``spec`` and ``last`` with an explicit mask.
+  It preserves ``GENEVE_OPT.option_len`` and ``TAG.index``.
+  RAW pattern bytes, GENEVE_OPT data, FLEX, COMPARE and PMD private items
+  are not masked. Items with a NULL mask remain unchanged.
+
 * **Updated AF_XDP driver.**
 
   * Changed the default device plugin endpoint path used when
lib/ethdev/rte_flow.c +74 -6

150 lines not on this page · raw message

lib/ethdev/rte_flow.h +17 -0
index df8101e439..3cbe27bac4 100644
@@ -4426,6 +4426,23 @@ enum rte_flow_conv_op {
 	 *   @code const void ** @endcode
 	 */
 	RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR,
+
+	/**
+	 * Convert an entire pattern, applying item masks.
+	 *
+	 * Same as RTE_FLOW_CONV_OP_PATTERN, except each copied spec
+	 * and last is ANDed with the item mask. Not masked:
+	 * - items with a NULL mask (default mask is not applied);
+	 * - RAW pattern and GENEVE_OPT data contents;
+	 * - GENEVE_OPT option_len and TAG index;
+	 * - FLEX, COMPARE and PMD private items.
+	 *
+	 * - @p src type:
+	 *   @code const struct rte_flow_item * @endcode
+	 * - @p dst type:
+	 *   @code struct rte_flow_item * @endcode
+	 */
+	RTE_FLOW_CONV_OP_PATTERN_MASKED,
 };
 
 /**
Signature · 1 line
2.43.0
#25 lore raw

Re: [PATCH v8] ethdev: support inline calculating masked item value

Quoted 17 lines · On Fri, 16 Oct 2026 08:26:45 +0300 Bing Zhao <[email protected]> wrote:

Drivers implementing synchronous flow rules through the asynchronous template API need to apply item masks before creating a rule. The spec and last pointers are const, so this requires writable copies and per-item knowledge of their sizes.

Add RTE_FLOW_CONV_OP_PATTERN_MASKED to rte_flow_conv(). It copies the pattern like RTE_FLOW_CONV_OP_PATTERN and ANDs the inline fields of each copied spec and last with an explicit item mask. A NULL mask leaves the values unchanged; default masks are not substituted.

Preserve GENEVE_OPT.option_len and TAG.index because they describe the pointed-to data length and select the tag register, respectively. RAW pattern and GENEVE_OPT data are copied without applying their payload masks. Limit this operation to inline fields, leaving variable-length payload masking to the caller. FLEX, COMPARE and PMD private items are not masked.

Quoted 3 lines

---

Before merging:
1. Need this warning fixed.
2. Need a driver to support.
It doesn't make sense to have things in ethdev until a driver uses it. (YAGNI)

Make a patch series for mlx5 with this as the first patch.

[PATCH v8] ethdev: support inline calculating masked item value

v7 comments on GENEVE_OPT option_len, TAG index and the commit message are addressed. Applies to main (26.11-rc1), builds with -Dwerror=true.

Warning

1. INTEGRITY level is a selector and is still masked.

rte_flow_item_integrity.level picks the encapsulation level, same class as TAG index. rte_flow_item_integrity_mask has level = 0, so a rule passing the default mask explicitly gets level zeroed, turning an inner integrity match into an outer one. mlx5 SWS reads the unmasked spec->level (flow_dv_translate_item_integrity(), "if (spec->level > 1)").

level is the first member, so an offsetof() end limit does not work. Return a range instead:

	static inline size_t
	rte_flow_conv_item_mask_range(const struct rte_flow_item *item,
				      size_t *start)
	{
		*start = 0;
		...
		case RTE_FLOW_ITEM_TYPE_INTEGRITY:
			*start = offsetof(struct rte_flow_item_integrity,
					  value);
			return sizeof(struct rte_flow_item_integrity);
		...
	}

and in rte_flow_conv_pattern():

	end = RTE_MIN(ret, item_mask_end);
	if (c_spec && mask && end > start)
		rte_flow_conv_apply_mask(c_spec + start, mask + start,
					 end - start);

Add INTEGRITY to the exception lists (rte_flow.h, helper comment, release notes) and a test case.

2. Still no user of the new op.

Nothing in tree calls RTE_FLOW_CONV_OP_PATTERN_MASKED. Send it in a series with the mlx5 change that uses it.

Info

3. NULL mask is not replaced by the default mask.

flow_offload.rst says spec without mask means the default mask. The default masks are now in rte_flow_desc_item[].default_mask, so the op could do it:

	mask = src->mask;
	if (mask == NULL && (int)src->type >= 0)
		mask = rte_flow_desc_item[src->type].default_mask;

As written, every caller has to handle the NULL case itself. If leaving it out is intended, say why in the commit message.