[v4] ethdev: add API to query proxy port to manage transfer flows

Message ID 20211014032146.23234-1-ivan.malov@oktetlabs.ru (mailing list archive)
State Accepted, archived
Delegated to: Ferruh Yigit
Headers
Series [v4] ethdev: add API to query proxy port to manage transfer flows |

Checks

Context Check Description
ci/checkpatch success coding style OK
ci/iol-testing warning apply patch failure

Commit Message

Ivan Malov Oct. 14, 2021, 3:21 a.m. UTC
  Not all DPDK ports in a given switching domain may have the
privilege to manage "transfer" flows. Add an API to find a
port with sufficient privileges by any port in the domain.

Signed-off-by: Ivan Malov <ivan.malov@oktetlabs.ru>
Reviewed-by: Andrew Rybchenko <andrew.rybchenko@oktetlabs.ru>
Acked-by: Ori Kam <orika@nvidia.com>
---
Patch series [1] has reworked support for "transfer" flows.
This allows to elaborate on the idea which first appeared
in RFC [2]. Hence the patch in question.

net/sfc driver is going to support the new API. The
corresponding patch is already in progress and will
be provided in the course of this release cycle.

[1] https://patches.dpdk.org/project/dpdk/list/?series=19608
[2] https://patches.dpdk.org/project/dpdk/list/?series=18737

Changes in v2:
* The patch has review notes from Thomas Monjalon applied
* The patch has the ack from Ori Kam added

Changes in v3:
* The patch has review notes from Andrew Rybchenko applied

Changes in v4:
* Rebase on top of the most recent dpdk-next-net/main
---
 app/test-pmd/config.c                  | 106 ++++++++++++++++++++++++-
 app/test-pmd/testpmd.c                 |  20 +++++
 app/test-pmd/testpmd.h                 |   4 +
 app/test-pmd/util.c                    |   5 +-
 doc/guides/rel_notes/release_21_11.rst |   3 +
 lib/ethdev/rte_flow.c                  |  22 +++++
 lib/ethdev/rte_flow.h                  |  36 +++++++++
 lib/ethdev/rte_flow_driver.h           |   5 ++
 lib/ethdev/version.map                 |   1 +
 9 files changed, 198 insertions(+), 4 deletions(-)
  

Comments

Ferruh Yigit Oct. 14, 2021, 11:45 a.m. UTC | #1
On 10/14/2021 4:21 AM, Ivan Malov wrote:
> Not all DPDK ports in a given switching domain may have the
> privilege to manage "transfer" flows. Add an API to find a
> port with sufficient privileges by any port in the domain.
> 
> Signed-off-by: Ivan Malov <ivan.malov@oktetlabs.ru>
> Reviewed-by: Andrew Rybchenko <andrew.rybchenko@oktetlabs.ru>
> Acked-by: Ori Kam <orika@nvidia.com>

Applied to dpdk-next-net/main, thanks.
  
David Marchand Oct. 26, 2021, 2:46 p.m. UTC | #2
On Thu, Oct 14, 2021 at 5:22 AM Ivan Malov <ivan.malov@oktetlabs.ru> wrote:
>
> Not all DPDK ports in a given switching domain may have the
> privilege to manage "transfer" flows. Add an API to find a
> port with sufficient privileges by any port in the domain.
>
> Signed-off-by: Ivan Malov <ivan.malov@oktetlabs.ru>
> Reviewed-by: Andrew Rybchenko <andrew.rybchenko@oktetlabs.ru>
> Acked-by: Ori Kam <orika@nvidia.com>
> ---
> Patch series [1] has reworked support for "transfer" flows.
> This allows to elaborate on the idea which first appeared
> in RFC [2]. Hence the patch in question.
>
> net/sfc driver is going to support the new API. The
> corresponding patch is already in progress and will
> be provided in the course of this release cycle.
>
> [1] https://patches.dpdk.org/project/dpdk/list/?series=19608
> [2] https://patches.dpdk.org/project/dpdk/list/?series=18737
>

[snip]

> @@ -568,6 +568,25 @@ eth_rx_metadata_negotiate_mp(uint16_t port_id)
>         }
>  }
>
> +static void
> +flow_pick_transfer_proxy_mp(uint16_t port_id)
> +{
> +       struct rte_port *port = &ports[port_id];
> +       int ret;
> +
> +       port->flow_transfer_proxy = port_id;
> +
> +       if (!is_proc_primary())
> +               return;
> +
> +       ret = rte_flow_pick_transfer_proxy(port_id, &port->flow_transfer_proxy,
> +                                          NULL);
> +       if (ret != 0) {
> +               fprintf(stderr, "Error picking flow transfer proxy for port %u: %s - ignore\n",
> +                       port_id, rte_strerror(-ret));
> +       }
> +}

I did not follow this subject, but I find it odd to get a warning
regardless of what is done in testpmd.
Like simply running a forwarding test with null pmd, I get:

$ ./devtools/test-null.sh
EAL: Detected CPU lcores: 8
EAL: Detected NUMA nodes: 1
EAL: Detected static linkage of DPDK
EAL: Multi-process socket /run/user/114840/dpdk/rte/mp_socket
EAL: Selected IOVA mode 'VA'
Interactive-mode selected
Auto-start selected

vvvv
Error picking flow transfer proxy for port 0: Function not implemented - ignore
Error picking flow transfer proxy for port 1: Function not implemented - ignore
^^^^

testpmd: create a new mbuf pool <mb_pool_0>: n=2048, size=2176, socket=0
testpmd: preferred mempool ops selected: ring_mp_mc
Configuring Port 0 (socket 0)
Port 0: 12:C9:63:52:5A:8B
Configuring Port 1 (socket 0)
Port 1: 92:69:E3:1E:31:82
Checking link statuses...
  
Ivan Malov Oct. 26, 2021, 3:47 p.m. UTC | #3
Hi David,

On 26/10/2021 17:46, David Marchand wrote:
> On Thu, Oct 14, 2021 at 5:22 AM Ivan Malov <ivan.malov@oktetlabs.ru> wrote:
>>
>> Not all DPDK ports in a given switching domain may have the
>> privilege to manage "transfer" flows. Add an API to find a
>> port with sufficient privileges by any port in the domain.
>>
>> Signed-off-by: Ivan Malov <ivan.malov@oktetlabs.ru>
>> Reviewed-by: Andrew Rybchenko <andrew.rybchenko@oktetlabs.ru>
>> Acked-by: Ori Kam <orika@nvidia.com>
>> ---
>> Patch series [1] has reworked support for "transfer" flows.
>> This allows to elaborate on the idea which first appeared
>> in RFC [2]. Hence the patch in question.
>>
>> net/sfc driver is going to support the new API. The
>> corresponding patch is already in progress and will
>> be provided in the course of this release cycle.
>>
>> [1] https://patches.dpdk.org/project/dpdk/list/?series=19608
>> [2] https://patches.dpdk.org/project/dpdk/list/?series=18737
>>
> 
> [snip]
> 
>> @@ -568,6 +568,25 @@ eth_rx_metadata_negotiate_mp(uint16_t port_id)
>>          }
>>   }
>>
>> +static void
>> +flow_pick_transfer_proxy_mp(uint16_t port_id)
>> +{
>> +       struct rte_port *port = &ports[port_id];
>> +       int ret;
>> +
>> +       port->flow_transfer_proxy = port_id;
>> +
>> +       if (!is_proc_primary())
>> +               return;
>> +
>> +       ret = rte_flow_pick_transfer_proxy(port_id, &port->flow_transfer_proxy,
>> +                                          NULL);
>> +       if (ret != 0) {
>> +               fprintf(stderr, "Error picking flow transfer proxy for port %u: %s - ignore\n",
>> +                       port_id, rte_strerror(-ret));
>> +       }
>> +}
> 
> I did not follow this subject, but I find it odd to get a warning
> regardless of what is done in testpmd.
> Like simply running a forwarding test with null pmd, I get:

The API is meant to avoid returning error when the PMD does not support
the corresponding method. However, it has to return error when the PMD
does not support flow API at all (rte_flow_ops_get() failure).

I guess, this is exactly what happens in your case. net/null does not
support "flow_ops_get", and the API in question forwards error
from rte_flow_ops_get().

Non-dummy PMDs should not trigger the error you see.

> 
> $ ./devtools/test-null.sh
> EAL: Detected CPU lcores: 8
> EAL: Detected NUMA nodes: 1
> EAL: Detected static linkage of DPDK
> EAL: Multi-process socket /run/user/114840/dpdk/rte/mp_socket
> EAL: Selected IOVA mode 'VA'
> Interactive-mode selected
> Auto-start selected
> 
> vvvv
> Error picking flow transfer proxy for port 0: Function not implemented - ignore
> Error picking flow transfer proxy for port 1: Function not implemented - ignore
> ^^^^
> 
> testpmd: create a new mbuf pool <mb_pool_0>: n=2048, size=2176, socket=0
> testpmd: preferred mempool ops selected: ring_mp_mc
> Configuring Port 0 (socket 0)
> Port 0: 12:C9:63:52:5A:8B
> Configuring Port 1 (socket 0)
> Port 1: 92:69:E3:1E:31:82
> Checking link statuses...
> 
>
  
Thomas Monjalon Oct. 26, 2021, 3:58 p.m. UTC | #4
26/10/2021 17:47, Ivan Malov:
> Hi David,
> 
> On 26/10/2021 17:46, David Marchand wrote:
> > On Thu, Oct 14, 2021 at 5:22 AM Ivan Malov <ivan.malov@oktetlabs.ru> wrote:
> >>
> >> Not all DPDK ports in a given switching domain may have the
> >> privilege to manage "transfer" flows. Add an API to find a
> >> port with sufficient privileges by any port in the domain.
> >>
> >> Signed-off-by: Ivan Malov <ivan.malov@oktetlabs.ru>
> >> Reviewed-by: Andrew Rybchenko <andrew.rybchenko@oktetlabs.ru>
> >> Acked-by: Ori Kam <orika@nvidia.com>
> >> ---
> >> Patch series [1] has reworked support for "transfer" flows.
> >> This allows to elaborate on the idea which first appeared
> >> in RFC [2]. Hence the patch in question.
> >>
> >> net/sfc driver is going to support the new API. The
> >> corresponding patch is already in progress and will
> >> be provided in the course of this release cycle.
> >>
> >> [1] https://patches.dpdk.org/project/dpdk/list/?series=19608
> >> [2] https://patches.dpdk.org/project/dpdk/list/?series=18737
> >>
> > 
> > [snip]
> > 
> >> @@ -568,6 +568,25 @@ eth_rx_metadata_negotiate_mp(uint16_t port_id)
> >>          }
> >>   }
> >>
> >> +static void
> >> +flow_pick_transfer_proxy_mp(uint16_t port_id)
> >> +{
> >> +       struct rte_port *port = &ports[port_id];
> >> +       int ret;
> >> +
> >> +       port->flow_transfer_proxy = port_id;
> >> +
> >> +       if (!is_proc_primary())
> >> +               return;
> >> +
> >> +       ret = rte_flow_pick_transfer_proxy(port_id, &port->flow_transfer_proxy,
> >> +                                          NULL);
> >> +       if (ret != 0) {
> >> +               fprintf(stderr, "Error picking flow transfer proxy for port %u: %s - ignore\n",
> >> +                       port_id, rte_strerror(-ret));
> >> +       }
> >> +}
> > 
> > I did not follow this subject, but I find it odd to get a warning
> > regardless of what is done in testpmd.
> > Like simply running a forwarding test with null pmd, I get:
> 
> The API is meant to avoid returning error when the PMD does not support
> the corresponding method. However, it has to return error when the PMD
> does not support flow API at all (rte_flow_ops_get() failure).
> 
> I guess, this is exactly what happens in your case. net/null does not
> support "flow_ops_get", and the API in question forwards error
> from rte_flow_ops_get().
> 
> Non-dummy PMDs should not trigger the error you see.
> 
> > 
> > $ ./devtools/test-null.sh
> > EAL: Detected CPU lcores: 8
> > EAL: Detected NUMA nodes: 1
> > EAL: Detected static linkage of DPDK
> > EAL: Multi-process socket /run/user/114840/dpdk/rte/mp_socket
> > EAL: Selected IOVA mode 'VA'
> > Interactive-mode selected
> > Auto-start selected
> > 
> > vvvv
> > Error picking flow transfer proxy for port 0: Function not implemented - ignore
> > Error picking flow transfer proxy for port 1: Function not implemented - ignore
> > ^^^^

We should avoid such meaningless error message.
Please Ivan what do you suggest?
  
Ferruh Yigit Oct. 26, 2021, 4:15 p.m. UTC | #5
On 10/26/2021 4:58 PM, Thomas Monjalon wrote:
> 26/10/2021 17:47, Ivan Malov:
>> Hi David,
>>
>> On 26/10/2021 17:46, David Marchand wrote:
>>> On Thu, Oct 14, 2021 at 5:22 AM Ivan Malov <ivan.malov@oktetlabs.ru> wrote:
>>>>
>>>> Not all DPDK ports in a given switching domain may have the
>>>> privilege to manage "transfer" flows. Add an API to find a
>>>> port with sufficient privileges by any port in the domain.
>>>>
>>>> Signed-off-by: Ivan Malov <ivan.malov@oktetlabs.ru>
>>>> Reviewed-by: Andrew Rybchenko <andrew.rybchenko@oktetlabs.ru>
>>>> Acked-by: Ori Kam <orika@nvidia.com>
>>>> ---
>>>> Patch series [1] has reworked support for "transfer" flows.
>>>> This allows to elaborate on the idea which first appeared
>>>> in RFC [2]. Hence the patch in question.
>>>>
>>>> net/sfc driver is going to support the new API. The
>>>> corresponding patch is already in progress and will
>>>> be provided in the course of this release cycle.
>>>>
>>>> [1] https://patches.dpdk.org/project/dpdk/list/?series=19608
>>>> [2] https://patches.dpdk.org/project/dpdk/list/?series=18737
>>>>
>>>
>>> [snip]
>>>
>>>> @@ -568,6 +568,25 @@ eth_rx_metadata_negotiate_mp(uint16_t port_id)
>>>>           }
>>>>    }
>>>>
>>>> +static void
>>>> +flow_pick_transfer_proxy_mp(uint16_t port_id)
>>>> +{
>>>> +       struct rte_port *port = &ports[port_id];
>>>> +       int ret;
>>>> +
>>>> +       port->flow_transfer_proxy = port_id;
>>>> +
>>>> +       if (!is_proc_primary())
>>>> +               return;
>>>> +
>>>> +       ret = rte_flow_pick_transfer_proxy(port_id, &port->flow_transfer_proxy,
>>>> +                                          NULL);
>>>> +       if (ret != 0) {
>>>> +               fprintf(stderr, "Error picking flow transfer proxy for port %u: %s - ignore\n",
>>>> +                       port_id, rte_strerror(-ret));
>>>> +       }
>>>> +}
>>>
>>> I did not follow this subject, but I find it odd to get a warning
>>> regardless of what is done in testpmd.
>>> Like simply running a forwarding test with null pmd, I get:
>>
>> The API is meant to avoid returning error when the PMD does not support
>> the corresponding method. However, it has to return error when the PMD
>> does not support flow API at all (rte_flow_ops_get() failure).
>>
>> I guess, this is exactly what happens in your case. net/null does not
>> support "flow_ops_get", and the API in question forwards error
>> from rte_flow_ops_get().
>>
>> Non-dummy PMDs should not trigger the error you see.
>>
>>>
>>> $ ./devtools/test-null.sh
>>> EAL: Detected CPU lcores: 8
>>> EAL: Detected NUMA nodes: 1
>>> EAL: Detected static linkage of DPDK
>>> EAL: Multi-process socket /run/user/114840/dpdk/rte/mp_socket
>>> EAL: Selected IOVA mode 'VA'
>>> Interactive-mode selected
>>> Auto-start selected
>>>
>>> vvvv
>>> Error picking flow transfer proxy for port 0: Function not implemented - ignore
>>> Error picking flow transfer proxy for port 1: Function not implemented - ignore
>>> ^^^^
> 
> We should avoid such meaningless error message.

+1

> Please Ivan what do you suggest?
> 
>
  

Patch

diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c
index 7221644230..6ea8630fca 100644
--- a/app/test-pmd/config.c
+++ b/app/test-pmd/config.c
@@ -1505,10 +1505,25 @@  port_action_handle_create(portid_t port_id, uint32_t id,
 	struct port_indirect_action *pia;
 	int ret;
 	struct rte_flow_error error;
+	struct rte_port *port;
+
+	if (port_id_is_invalid(port_id, ENABLED_WARN) ||
+	    port_id == (portid_t)RTE_PORT_ALL)
+		return -EINVAL;
 
 	ret = action_alloc(port_id, id, &pia);
 	if (ret)
 		return ret;
+
+	port = &ports[port_id];
+
+	if (conf->transfer)
+		port_id = port->flow_transfer_proxy;
+
+	if (port_id_is_invalid(port_id, ENABLED_WARN) ||
+	    port_id == (portid_t)RTE_PORT_ALL)
+		return -EINVAL;
+
 	if (action->type == RTE_FLOW_ACTION_TYPE_AGE) {
 		struct rte_flow_action_age *age =
 			(struct rte_flow_action_age *)(uintptr_t)(action->conf);
@@ -1531,6 +1546,7 @@  port_action_handle_create(portid_t port_id, uint32_t id,
 		return port_flow_complain(&error);
 	}
 	pia->type = action->type;
+	pia->transfer = conf->transfer;
 	printf("Indirect action #%u created\n", pia->id);
 	return 0;
 }
@@ -1557,9 +1573,18 @@  port_action_handle_destroy(portid_t port_id,
 		for (i = 0; i != n; ++i) {
 			struct rte_flow_error error;
 			struct port_indirect_action *pia = *tmp;
+			portid_t port_id_eff = port_id;
 
 			if (actions[i] != pia->id)
 				continue;
+
+			if (pia->transfer)
+				port_id_eff = port->flow_transfer_proxy;
+
+			if (port_id_is_invalid(port_id_eff, ENABLED_WARN) ||
+			    port_id_eff == (portid_t)RTE_PORT_ALL)
+				return -EINVAL;
+
 			/*
 			 * Poisoning to make sure PMDs update it in case
 			 * of error.
@@ -1567,7 +1592,7 @@  port_action_handle_destroy(portid_t port_id,
 			memset(&error, 0x33, sizeof(error));
 
 			if (pia->handle && rte_flow_action_handle_destroy(
-					port_id, pia->handle, &error)) {
+					port_id_eff, pia->handle, &error)) {
 				ret = port_flow_complain(&error);
 				continue;
 			}
@@ -1602,8 +1627,15 @@  port_action_handle_update(portid_t port_id, uint32_t id,
 	struct rte_flow_error error;
 	struct rte_flow_action_handle *action_handle;
 	struct port_indirect_action *pia;
+	struct rte_port *port;
 	const void *update;
 
+	if (port_id_is_invalid(port_id, ENABLED_WARN) ||
+	    port_id == (portid_t)RTE_PORT_ALL)
+		return -EINVAL;
+
+	port = &ports[port_id];
+
 	action_handle = port_action_handle_get_by_id(port_id, id);
 	if (!action_handle)
 		return -EINVAL;
@@ -1618,6 +1650,14 @@  port_action_handle_update(portid_t port_id, uint32_t id,
 		update = action;
 		break;
 	}
+
+	if (pia->transfer)
+		port_id = port->flow_transfer_proxy;
+
+	if (port_id_is_invalid(port_id, ENABLED_WARN) ||
+	    port_id == (portid_t)RTE_PORT_ALL)
+		return -EINVAL;
+
 	if (rte_flow_action_handle_update(port_id, action_handle, update,
 					  &error)) {
 		return port_flow_complain(&error);
@@ -1636,6 +1676,14 @@  port_action_handle_query(portid_t port_id, uint32_t id)
 		struct rte_flow_query_age age;
 		struct rte_flow_action_conntrack ct;
 	} query;
+	portid_t port_id_eff = port_id;
+	struct rte_port *port;
+
+	if (port_id_is_invalid(port_id, ENABLED_WARN) ||
+	    port_id == (portid_t)RTE_PORT_ALL)
+		return -EINVAL;
+
+	port = &ports[port_id];
 
 	pia = action_get_by_id(port_id, id);
 	if (!pia)
@@ -1650,10 +1698,19 @@  port_action_handle_query(portid_t port_id, uint32_t id)
 			id, pia->type, port_id);
 		return -ENOTSUP;
 	}
+
+	if (pia->transfer)
+		port_id_eff = port->flow_transfer_proxy;
+
+	if (port_id_is_invalid(port_id_eff, ENABLED_WARN) ||
+	    port_id_eff == (portid_t)RTE_PORT_ALL)
+		return -EINVAL;
+
 	/* Poisoning to make sure PMDs update it in case of error. */
 	memset(&error, 0x55, sizeof(error));
 	memset(&query, 0, sizeof(query));
-	if (rte_flow_action_handle_query(port_id, pia->handle, &query, &error))
+	if (rte_flow_action_handle_query(port_id_eff, pia->handle, &query,
+					 &error))
 		return port_flow_complain(&error);
 	switch (pia->type) {
 	case RTE_FLOW_ACTION_TYPE_AGE:
@@ -1872,6 +1929,20 @@  port_flow_validate(portid_t port_id,
 {
 	struct rte_flow_error error;
 	struct port_flow_tunnel *pft = NULL;
+	struct rte_port *port;
+
+	if (port_id_is_invalid(port_id, ENABLED_WARN) ||
+	    port_id == (portid_t)RTE_PORT_ALL)
+		return -EINVAL;
+
+	port = &ports[port_id];
+
+	if (attr->transfer)
+		port_id = port->flow_transfer_proxy;
+
+	if (port_id_is_invalid(port_id, ENABLED_WARN) ||
+	    port_id == (portid_t)RTE_PORT_ALL)
+		return -EINVAL;
 
 	/* Poisoning to make sure PMDs update it in case of error. */
 	memset(&error, 0x11, sizeof(error));
@@ -1925,7 +1996,19 @@  port_flow_create(portid_t port_id,
 	struct port_flow_tunnel *pft = NULL;
 	struct rte_flow_action_age *age = age_action_get(actions);
 
+	if (port_id_is_invalid(port_id, ENABLED_WARN) ||
+	    port_id == (portid_t)RTE_PORT_ALL)
+		return -EINVAL;
+
 	port = &ports[port_id];
+
+	if (attr->transfer)
+		port_id = port->flow_transfer_proxy;
+
+	if (port_id_is_invalid(port_id, ENABLED_WARN) ||
+	    port_id == (portid_t)RTE_PORT_ALL)
+		return -EINVAL;
+
 	if (port->flow_list) {
 		if (port->flow_list->id == UINT32_MAX) {
 			fprintf(stderr,
@@ -1989,6 +2072,7 @@  port_flow_destroy(portid_t port_id, uint32_t n, const uint32_t *rule)
 		uint32_t i;
 
 		for (i = 0; i != n; ++i) {
+			portid_t port_id_eff = port_id;
 			struct rte_flow_error error;
 			struct port_flow *pf = *tmp;
 
@@ -1999,7 +2083,15 @@  port_flow_destroy(portid_t port_id, uint32_t n, const uint32_t *rule)
 			 * of error.
 			 */
 			memset(&error, 0x33, sizeof(error));
-			if (rte_flow_destroy(port_id, pf->flow, &error)) {
+
+			if (pf->rule.attr->transfer)
+				port_id_eff = port->flow_transfer_proxy;
+
+			if (port_id_is_invalid(port_id_eff, ENABLED_WARN) ||
+			    port_id_eff == (portid_t)RTE_PORT_ALL)
+				return -EINVAL;
+
+			if (rte_flow_destroy(port_id_eff, pf->flow, &error)) {
 				ret = port_flow_complain(&error);
 				continue;
 			}
@@ -2133,6 +2225,14 @@  port_flow_query(portid_t port_id, uint32_t rule,
 		fprintf(stderr, "Flow rule #%u not found\n", rule);
 		return -ENOENT;
 	}
+
+	if (pf->rule.attr->transfer)
+		port_id = port->flow_transfer_proxy;
+
+	if (port_id_is_invalid(port_id, ENABLED_WARN) ||
+	    port_id == (portid_t)RTE_PORT_ALL)
+		return -EINVAL;
+
 	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ACTION_NAME_PTR,
 			    &name, sizeof(name),
 			    (void *)(uintptr_t)action->type, &error);
diff --git a/app/test-pmd/testpmd.c b/app/test-pmd/testpmd.c
index a7841c557f..38d7ce8eb4 100644
--- a/app/test-pmd/testpmd.c
+++ b/app/test-pmd/testpmd.c
@@ -568,6 +568,25 @@  eth_rx_metadata_negotiate_mp(uint16_t port_id)
 	}
 }
 
+static void
+flow_pick_transfer_proxy_mp(uint16_t port_id)
+{
+	struct rte_port *port = &ports[port_id];
+	int ret;
+
+	port->flow_transfer_proxy = port_id;
+
+	if (!is_proc_primary())
+		return;
+
+	ret = rte_flow_pick_transfer_proxy(port_id, &port->flow_transfer_proxy,
+					   NULL);
+	if (ret != 0) {
+		fprintf(stderr, "Error picking flow transfer proxy for port %u: %s - ignore\n",
+			port_id, rte_strerror(-ret));
+	}
+}
+
 static int
 eth_dev_configure_mp(uint16_t port_id, uint16_t nb_rx_q, uint16_t nb_tx_q,
 		      const struct rte_eth_conf *dev_conf)
@@ -1525,6 +1544,7 @@  init_config_port_offloads(portid_t pid, uint32_t socket_id)
 	int i;
 
 	eth_rx_metadata_negotiate_mp(pid);
+	flow_pick_transfer_proxy_mp(pid);
 
 	port->dev_conf.txmode = tx_mode;
 	port->dev_conf.rxmode = rx_mode;
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
index e9d9db06ce..786b7c707d 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -173,6 +173,8 @@  struct port_indirect_action {
 	enum rte_flow_action_type type; /**< Action type. */
 	struct rte_flow_action_handle *handle;	/**< Indirect action handle. */
 	enum age_action_context_type age_type; /**< Age action context type. */
+	/** If true, the action applies to "transfer" flows, and vice versa */
+	bool transfer;
 };
 
 struct port_flow_tunnel {
@@ -234,6 +236,8 @@  struct rte_port {
 	/**< dynamic flags. */
 	uint64_t		mbuf_dynf;
 	const struct rte_eth_rxtx_callback *tx_set_dynf_cb[RTE_MAX_QUEUES_PER_PORT+1];
+	/** Associated port which is supposed to handle "transfer" flows */
+	portid_t		flow_transfer_proxy;
 };
 
 /**
diff --git a/app/test-pmd/util.c b/app/test-pmd/util.c
index 51506e4940..480cc1144a 100644
--- a/app/test-pmd/util.c
+++ b/app/test-pmd/util.c
@@ -98,13 +98,16 @@  dump_pkt_burst(uint16_t port_id, uint16_t queue, struct rte_mbuf *pkts[],
 		int ret;
 		struct rte_flow_error error;
 		struct rte_flow_restore_info info = { 0, };
+		struct rte_port *port = &ports[port_id];
 
 		mb = pkts[i];
 		eth_hdr = rte_pktmbuf_read(mb, 0, sizeof(_eth_hdr), &_eth_hdr);
 		eth_type = RTE_BE_TO_CPU_16(eth_hdr->ether_type);
 		packet_type = mb->packet_type;
 		is_encapsulation = RTE_ETH_IS_TUNNEL_PKT(packet_type);
-		ret = rte_flow_get_restore_info(port_id, mb, &info, &error);
+
+		ret = rte_flow_get_restore_info(port->flow_transfer_proxy,
+						mb, &info, &error);
 		if (!ret) {
 			MKDUMPSTR(print_buf, buf_size, cur_len,
 				  "restore info:");
diff --git a/doc/guides/rel_notes/release_21_11.rst b/doc/guides/rel_notes/release_21_11.rst
index 7f07f2a184..4dc05946ad 100644
--- a/doc/guides/rel_notes/release_21_11.rst
+++ b/doc/guides/rel_notes/release_21_11.rst
@@ -81,6 +81,9 @@  New Features
   * Added ``rte_eth_macaddrs_get`` to allow user to retrieve all Ethernet
     addresses assigned to given ethernet port.
 
+* **Added an API to get a proxy port to manage "transfer" flows**
+  A new API, ``rte_flow_pick_transfer_proxy()``, was added.
+
 * **Updated af_packet ethdev driver.**
 
   * Default VLAN strip behavior was changed. VLAN tag won't be stripped
diff --git a/lib/ethdev/rte_flow.c b/lib/ethdev/rte_flow.c
index 542e40e496..29f2b0e954 100644
--- a/lib/ethdev/rte_flow.c
+++ b/lib/ethdev/rte_flow.c
@@ -1270,3 +1270,25 @@  rte_flow_tunnel_item_release(uint16_t port_id,
 				  RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
 				  NULL, rte_strerror(ENOTSUP));
 }
+
+int
+rte_flow_pick_transfer_proxy(uint16_t port_id, uint16_t *proxy_port_id,
+			     struct rte_flow_error *error)
+{
+	const struct rte_flow_ops *ops = rte_flow_ops_get(port_id, error);
+	struct rte_eth_dev *dev;
+
+	if (unlikely(ops == NULL))
+		return -rte_errno;
+
+	if (ops->pick_transfer_proxy == NULL) {
+		*proxy_port_id = port_id;
+		return 0;
+	}
+
+	dev = &rte_eth_devices[port_id];
+
+	return flow_err(port_id,
+			ops->pick_transfer_proxy(dev, proxy_port_id, error),
+			error);
+}
diff --git a/lib/ethdev/rte_flow.h b/lib/ethdev/rte_flow.h
index 7e7b4bce5b..ba36edb9f9 100644
--- a/lib/ethdev/rte_flow.h
+++ b/lib/ethdev/rte_flow.h
@@ -122,6 +122,9 @@  struct rte_flow_attr {
 	 *
 	 * The application should match traffic originating from precise
 	 * locations. See items PORT_REPRESENTOR and REPRESENTED_PORT.
+	 *
+	 * Managing "transfer" flows requires that the user communicate them
+	 * through a suitable port. @see rte_flow_pick_transfer_proxy().
 	 */
 	uint32_t transfer:1;
 	uint32_t reserved:29; /**< Reserved, must be zero. */
@@ -4430,6 +4433,39 @@  rte_flow_tunnel_item_release(uint16_t port_id,
 			     struct rte_flow_item *items,
 			     uint32_t num_of_items,
 			     struct rte_flow_error *error);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Get a proxy port to manage "transfer" flows.
+ *
+ * Managing "transfer" flows requires that the user communicate them
+ * via a port which has the privilege to control the embedded switch.
+ * For some vendors, all ports in a given switching domain have
+ * this privilege. For other vendors, it's only one port.
+ *
+ * This API indicates such a privileged port (a "proxy")
+ * for a given port in the same switching domain.
+ *
+ * @note
+ *   If the PMD serving @p port_id doesn't have the corresponding method
+ *   implemented, the API will return @p port_id via @p proxy_port_id.
+ *
+ * @param port_id
+ *   Indicates the port to get a "proxy" for
+ * @param[out] proxy_port_id
+ *   Indicates the "proxy" port
+ * @param[out] error
+ *   If not NULL, allows the PMD to provide verbose report in case of error
+ *
+ * @return
+ *   0 on success, a negative error code otherwise
+ */
+__rte_experimental
+int
+rte_flow_pick_transfer_proxy(uint16_t port_id, uint16_t *proxy_port_id,
+			     struct rte_flow_error *error);
 #ifdef __cplusplus
 }
 #endif
diff --git a/lib/ethdev/rte_flow_driver.h b/lib/ethdev/rte_flow_driver.h
index 46f62c2ec2..ed52e59a0a 100644
--- a/lib/ethdev/rte_flow_driver.h
+++ b/lib/ethdev/rte_flow_driver.h
@@ -139,6 +139,11 @@  struct rte_flow_ops {
 		 struct rte_flow_item *pmd_items,
 		 uint32_t num_of_items,
 		 struct rte_flow_error *err);
+	/** See rte_flow_pick_transfer_proxy() */
+	int (*pick_transfer_proxy)
+		(struct rte_eth_dev *dev,
+		 uint16_t *proxy_port_id,
+		 struct rte_flow_error *error);
 };
 
 /**
diff --git a/lib/ethdev/version.map b/lib/ethdev/version.map
index 96ac8abb6b..6c68d97c9c 100644
--- a/lib/ethdev/version.map
+++ b/lib/ethdev/version.map
@@ -251,6 +251,7 @@  EXPERIMENTAL {
 	# added in 21.11
 	rte_eth_macaddrs_get;
 	rte_eth_rx_metadata_negotiate;
+	rte_flow_pick_transfer_proxy;
 };
 
 INTERNAL {