* [PATCH] xsk: Fix race condition in AF_XDP generic RX path
[not found] <CGME20250409125216eucas1p150b189cd13807197a233718302103a02@eucas1p1.samsung.com>
@ 2025-04-09 12:49 ` e.kubanski
2025-04-09 13:21 ` Magnus Karlsson
2025-04-09 14:20 ` e.kubanski
1 sibling, 1 reply; 4+ messages in thread
From: e.kubanski @ 2025-04-09 12:49 UTC (permalink / raw)
To: linux-kernel, netdev
Cc: bjorn, magnus.karlsson, maciej.fijalkowski, jonathan.lemon, e.kubanski
rx_lock moved from xsk_socket to xsk_buff_pool.
Previous synchronization didn't take care of
shared umem mode in generic RX path where sockets
share the same xsk_buff_pool.
RX queue is exclusive to xsk_socket, while FILL
queue can be shared between multiple sockets.
This could result in race condition where two
CPU cores access RX path of two different sockets
sharing the same umem.
Now both queues are protected by acquiring spinlock
in shared xsk_buff_pool.
Lock contention may be minimized in the future by some
per-thread FQ buffering.
It's safe and necessary to move spin_lock_bh(rx_lock)
after xsk_rcv_check():
* xs->pool and spinlock_init is synchronized by
xsk_bind() -> xsk_is_bound() memory barriers.
* xsk_rcv_check() may return true at the moment
of xsk_release() or xsk_unbind_dev(),
however this will not cause any data races or
race conditions. xsk_unbind_dev() removes xdp
socket from all maps and waits for completion
of all outstanding rx operations. Packets in
RX path will either complete safely or drop.
Signed-off-by: Eryk Kubanski <e.kubanski@partner.samsung.com>
---
include/net/xdp_sock.h | 3 ---
include/net/xsk_buff_pool.h | 2 ++
net/xdp/xsk.c | 6 +++---
net/xdp/xsk_buff_pool.c | 1 +
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/include/net/xdp_sock.h b/include/net/xdp_sock.h
index bfe625b55d55..df3f5f07bc7c 100644
--- a/include/net/xdp_sock.h
+++ b/include/net/xdp_sock.h
@@ -71,9 +71,6 @@ struct xdp_sock {
*/
u32 tx_budget_spent;
- /* Protects generic receive. */
- spinlock_t rx_lock;
-
/* Statistics */
u64 rx_dropped;
u64 rx_queue_full;
diff --git a/include/net/xsk_buff_pool.h b/include/net/xsk_buff_pool.h
index 50779406bc2d..7f0a75d6563d 100644
--- a/include/net/xsk_buff_pool.h
+++ b/include/net/xsk_buff_pool.h
@@ -53,6 +53,8 @@ struct xsk_buff_pool {
refcount_t users;
struct xdp_umem *umem;
struct work_struct work;
+ /* Protects generic receive in shared and non-shared umem mode. */
+ spinlock_t rx_lock;
struct list_head free_list;
struct list_head xskb_list;
u32 heads_cnt;
diff --git a/net/xdp/xsk.c b/net/xdp/xsk.c
index 89d2bef96469..e2a75f3be237 100644
--- a/net/xdp/xsk.c
+++ b/net/xdp/xsk.c
@@ -337,13 +337,14 @@ int xsk_generic_rcv(struct xdp_sock *xs, struct xdp_buff *xdp)
u32 len = xdp_get_buff_len(xdp);
int err;
- spin_lock_bh(&xs->rx_lock);
err = xsk_rcv_check(xs, xdp, len);
if (!err) {
+ spin_lock_bh(&xs->pool->rx_lock);
err = __xsk_rcv(xs, xdp, len);
xsk_flush(xs);
+ spin_unlock_bh(&xs->pool->rx_lock);
}
- spin_unlock_bh(&xs->rx_lock);
+
return err;
}
@@ -1724,7 +1725,6 @@ static int xsk_create(struct net *net, struct socket *sock, int protocol,
xs = xdp_sk(sk);
xs->state = XSK_READY;
mutex_init(&xs->mutex);
- spin_lock_init(&xs->rx_lock);
INIT_LIST_HEAD(&xs->map_list);
spin_lock_init(&xs->map_list_lock);
diff --git a/net/xdp/xsk_buff_pool.c b/net/xdp/xsk_buff_pool.c
index 1f7975b49657..3a5f16f53178 100644
--- a/net/xdp/xsk_buff_pool.c
+++ b/net/xdp/xsk_buff_pool.c
@@ -87,6 +87,7 @@ struct xsk_buff_pool *xp_create_and_assign_umem(struct xdp_sock *xs,
pool->addrs = umem->addrs;
pool->tx_metadata_len = umem->tx_metadata_len;
pool->tx_sw_csum = umem->flags & XDP_UMEM_TX_SW_CSUM;
+ spin_lock_init(&pool->rx_lock);
INIT_LIST_HEAD(&pool->free_list);
INIT_LIST_HEAD(&pool->xskb_list);
INIT_LIST_HEAD(&pool->xsk_tx_list);
--
2.34.1
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] xsk: Fix race condition in AF_XDP generic RX path
2025-04-09 12:49 ` [PATCH] xsk: Fix race condition in AF_XDP generic RX path e.kubanski
@ 2025-04-09 13:21 ` Magnus Karlsson
0 siblings, 0 replies; 4+ messages in thread
From: Magnus Karlsson @ 2025-04-09 13:21 UTC (permalink / raw)
To: e.kubanski
Cc: linux-kernel, netdev, bjorn, magnus.karlsson, maciej.fijalkowski,
jonathan.lemon
On Wed, 9 Apr 2025 at 14:56, e.kubanski <e.kubanski@partner.samsung.com> wrote:
>
> rx_lock moved from xsk_socket to xsk_buff_pool.
> Previous synchronization didn't take care of
> shared umem mode in generic RX path where sockets
> share the same xsk_buff_pool.
>
> RX queue is exclusive to xsk_socket, while FILL
> queue can be shared between multiple sockets.
> This could result in race condition where two
> CPU cores access RX path of two different sockets
> sharing the same umem.
I do not fully understand what you are doing in user space. Could you
please provide a user-space code example that will trigger this
problem?
Please note that if you share an Rx ring or the fill ring between
processes/threads, then you have to take care about mutual exclusion
in user space. If you really want to do this, it is usually a better
idea to use the other shared umem mode in which each process gets its
own rx and fill ring, removing the need for mutual exclusion.
> Now both queues are protected by acquiring spinlock
> in shared xsk_buff_pool.
>
> Lock contention may be minimized in the future by some
> per-thread FQ buffering.
>
> It's safe and necessary to move spin_lock_bh(rx_lock)
> after xsk_rcv_check():
> * xs->pool and spinlock_init is synchronized by
> xsk_bind() -> xsk_is_bound() memory barriers.
> * xsk_rcv_check() may return true at the moment
> of xsk_release() or xsk_unbind_dev(),
> however this will not cause any data races or
> race conditions. xsk_unbind_dev() removes xdp
> socket from all maps and waits for completion
> of all outstanding rx operations. Packets in
> RX path will either complete safely or drop.
>
> Signed-off-by: Eryk Kubanski <e.kubanski@partner.samsung.com>
> ---
> include/net/xdp_sock.h | 3 ---
> include/net/xsk_buff_pool.h | 2 ++
> net/xdp/xsk.c | 6 +++---
> net/xdp/xsk_buff_pool.c | 1 +
> 4 files changed, 6 insertions(+), 6 deletions(-)
>
> diff --git a/include/net/xdp_sock.h b/include/net/xdp_sock.h
> index bfe625b55d55..df3f5f07bc7c 100644
> --- a/include/net/xdp_sock.h
> +++ b/include/net/xdp_sock.h
> @@ -71,9 +71,6 @@ struct xdp_sock {
> */
> u32 tx_budget_spent;
>
> - /* Protects generic receive. */
> - spinlock_t rx_lock;
> -
> /* Statistics */
> u64 rx_dropped;
> u64 rx_queue_full;
> diff --git a/include/net/xsk_buff_pool.h b/include/net/xsk_buff_pool.h
> index 50779406bc2d..7f0a75d6563d 100644
> --- a/include/net/xsk_buff_pool.h
> +++ b/include/net/xsk_buff_pool.h
> @@ -53,6 +53,8 @@ struct xsk_buff_pool {
> refcount_t users;
> struct xdp_umem *umem;
> struct work_struct work;
> + /* Protects generic receive in shared and non-shared umem mode. */
> + spinlock_t rx_lock;
> struct list_head free_list;
> struct list_head xskb_list;
> u32 heads_cnt;
> diff --git a/net/xdp/xsk.c b/net/xdp/xsk.c
> index 89d2bef96469..e2a75f3be237 100644
> --- a/net/xdp/xsk.c
> +++ b/net/xdp/xsk.c
> @@ -337,13 +337,14 @@ int xsk_generic_rcv(struct xdp_sock *xs, struct xdp_buff *xdp)
> u32 len = xdp_get_buff_len(xdp);
> int err;
>
> - spin_lock_bh(&xs->rx_lock);
> err = xsk_rcv_check(xs, xdp, len);
> if (!err) {
> + spin_lock_bh(&xs->pool->rx_lock);
> err = __xsk_rcv(xs, xdp, len);
> xsk_flush(xs);
> + spin_unlock_bh(&xs->pool->rx_lock);
> }
> - spin_unlock_bh(&xs->rx_lock);
> +
> return err;
> }
>
> @@ -1724,7 +1725,6 @@ static int xsk_create(struct net *net, struct socket *sock, int protocol,
> xs = xdp_sk(sk);
> xs->state = XSK_READY;
> mutex_init(&xs->mutex);
> - spin_lock_init(&xs->rx_lock);
>
> INIT_LIST_HEAD(&xs->map_list);
> spin_lock_init(&xs->map_list_lock);
> diff --git a/net/xdp/xsk_buff_pool.c b/net/xdp/xsk_buff_pool.c
> index 1f7975b49657..3a5f16f53178 100644
> --- a/net/xdp/xsk_buff_pool.c
> +++ b/net/xdp/xsk_buff_pool.c
> @@ -87,6 +87,7 @@ struct xsk_buff_pool *xp_create_and_assign_umem(struct xdp_sock *xs,
> pool->addrs = umem->addrs;
> pool->tx_metadata_len = umem->tx_metadata_len;
> pool->tx_sw_csum = umem->flags & XDP_UMEM_TX_SW_CSUM;
> + spin_lock_init(&pool->rx_lock);
> INIT_LIST_HEAD(&pool->free_list);
> INIT_LIST_HEAD(&pool->xskb_list);
> INIT_LIST_HEAD(&pool->xsk_tx_list);
> --
> 2.34.1
>
>
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: Re: [PATCH] xsk: Fix race condition in AF_XDP generic RX path
[not found] <CGME20250409125216eucas1p150b189cd13807197a233718302103a02@eucas1p1.samsung.com>
2025-04-09 12:49 ` [PATCH] xsk: Fix race condition in AF_XDP generic RX path e.kubanski
@ 2025-04-09 14:20 ` e.kubanski
2025-04-10 7:52 ` Magnus Karlsson
1 sibling, 1 reply; 4+ messages in thread
From: e.kubanski @ 2025-04-09 14:20 UTC (permalink / raw)
To: magnus.karlsson
Cc: linux-kernel, netdev, bjorn, maciej.fijalkowski, jonathan.lemon
> I do not fully understand what you are doing in user space. Could you
> please provide a user-space code example that will trigger this
> problem?
We want to scale single hardware queue AF_XDP setup to
receive packets on multiple threads through RPS mechanisms.
The problem arises when RPS is enabled in the kernel.
In this situation single hardware queue flow can scale across
multiple CPU cores. Then we perform XDP/eBPF load-balancing
to multiple sockets, by using CPU_ID of issued XDP call.
Every socket is binded to queue number 0, device has single queue.
User-space socket setup looks more-or-less like that (with libxdp):
```
xsk_ring_prod fq{};
xsk_ring_cons cq{};
xsk_umem_config umem_cfg{ ... };
xsk_umem* umem;
auto result = xsk_umem__create(&umem, umem_memory, pool_size_bytes, &fq, &cq, &umem_cfg);
...
xsk_socket_config xsk_cfg{
...
.xdp_flags = XDP_FLAGS_SKB_MODE,
...
};
xsk_socket* sock1{nullptr};
xsk_ring_cons rq1{};
xsk_ring_prod tq1{};
auto result = xsk_socket__create_shared(
&sock1,
device_name,
0,
&rq1,
&tq1,
&fq,
&cq,
&cfg
);
xsk_socket* sock2{nullptr};
xsk_ring_cons rq2{};
xsk_ring_prod tq2{};
auto result = xsk_socket__create_shared(
&sock2,
device_name,
0,
&rq2,
&tq2,
&fq,
&cq,
&cfg
);
...
```
We're working on cloud native deploymetns, where
it's not possible to scale RX through RSS mechanism only.
That's why we wanted to use RPS to scale not only
user-space processing but also XDP processing.
This patch effectively allows us to use RPS to scale XDP
in Generic mode.
The same goes for RPS disabled, where we use MACVLAN
child device attached to parent device with multiple queues.
In this situation MACVLAN allows for multi-core kernel-side
processing, but xsk_buff_pool isn't protected.
We can't do any passthrough in this situation, we must rely
on MACVLAN with single RX/TX queue pair.
Of course this is not a problem in situation where every device
packet is processed on single core.
> Please note that if you share an Rx ring or the fill ring between
> processes/threads, then you have to take care about mutual exclusion
> in user space.
Of course, RX/TX/FILL/COMP are SPSC queues, we included mutual
exclusion for FILL/COMP because RX/TX are accessed by single thread.
Im doing single process deployment with multiple threads, where every
thread has it's own AF_XDP socket and pool is shared across threads.
> If you really want to do this, it is usually a better
> idea to use the other shared umem mode in which each process gets its
> own rx and fill ring, removing the need for mutual exclusion.
If I understand AF_XDP architecture correctly it's not possible for single
queue deployment, or maybe Im missing something? We need to maintain
single FILL/COMP pair per device queue.
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: Re: [PATCH] xsk: Fix race condition in AF_XDP generic RX path
2025-04-09 14:20 ` e.kubanski
@ 2025-04-10 7:52 ` Magnus Karlsson
0 siblings, 0 replies; 4+ messages in thread
From: Magnus Karlsson @ 2025-04-10 7:52 UTC (permalink / raw)
To: e.kubanski
Cc: magnus.karlsson, linux-kernel, netdev, bjorn, maciej.fijalkowski,
jonathan.lemon
On Wed, 9 Apr 2025 at 16:21, e.kubanski <e.kubanski@partner.samsung.com> wrote:
>
> > I do not fully understand what you are doing in user space. Could you
> > please provide a user-space code example that will trigger this
> > problem?
>
> We want to scale single hardware queue AF_XDP setup to
> receive packets on multiple threads through RPS mechanisms.
> The problem arises when RPS is enabled in the kernel.
> In this situation single hardware queue flow can scale across
> multiple CPU cores. Then we perform XDP/eBPF load-balancing
> to multiple sockets, by using CPU_ID of issued XDP call.
>
> Every socket is binded to queue number 0, device has single queue.
>
> User-space socket setup looks more-or-less like that (with libxdp):
> ```
> xsk_ring_prod fq{};
> xsk_ring_cons cq{};
>
> xsk_umem_config umem_cfg{ ... };
> xsk_umem* umem;
> auto result = xsk_umem__create(&umem, umem_memory, pool_size_bytes, &fq, &cq, &umem_cfg);
>
> ...
>
> xsk_socket_config xsk_cfg{
> ...
> .xdp_flags = XDP_FLAGS_SKB_MODE,
> ...
> };
>
> xsk_socket* sock1{nullptr};
> xsk_ring_cons rq1{};
> xsk_ring_prod tq1{};
> auto result = xsk_socket__create_shared(
> &sock1,
> device_name,
> 0,
> &rq1,
> &tq1,
> &fq,
> &cq,
> &cfg
> );
>
> xsk_socket* sock2{nullptr};
> xsk_ring_cons rq2{};
> xsk_ring_prod tq2{};
> auto result = xsk_socket__create_shared(
> &sock2,
> device_name,
> 0,
> &rq2,
> &tq2,
> &fq,
> &cq,
> &cfg
> );
>
> ...
> ```
>
> We're working on cloud native deploymetns, where
> it's not possible to scale RX through RSS mechanism only.
>
> That's why we wanted to use RPS to scale not only
> user-space processing but also XDP processing.
>
> This patch effectively allows us to use RPS to scale XDP
> in Generic mode.
>
> The same goes for RPS disabled, where we use MACVLAN
>
> child device attached to parent device with multiple queues.
> In this situation MACVLAN allows for multi-core kernel-side
> processing, but xsk_buff_pool isn't protected.
>
> We can't do any passthrough in this situation, we must rely
> on MACVLAN with single RX/TX queue pair.
>
> Of course this is not a problem in situation where every device
> packet is processed on single core.
Thanks, this really helped. You are correct that there is a race and
that the previous fix (6 years ago) did not take into account this
shared umem case. I am fine with your fix, just a few things you need
to add to the patch and resubmit a v2.
* Write [PATCH bpf] in your subject and base your code on the bpf
tree, if you did not do that already.
* You need to add a Fixes tag.
And yes, let us worry about performance at some later stage, if it
needs addressing at all. The important thing for this patch is to make
the code solid.
Thank you for spotting this. Highly appreciated.
> > Please note that if you share an Rx ring or the fill ring between
> > processes/threads, then you have to take care about mutual exclusion
> > in user space.
>
> Of course, RX/TX/FILL/COMP are SPSC queues, we included mutual
> exclusion for FILL/COMP because RX/TX are accessed by single thread.
> Im doing single process deployment with multiple threads, where every
> thread has it's own AF_XDP socket and pool is shared across threads.
Good. Just wanted to make sure.
> > If you really want to do this, it is usually a better
> > idea to use the other shared umem mode in which each process gets its
> > own rx and fill ring, removing the need for mutual exclusion.
>
> If I understand AF_XDP architecture correctly it's not possible for single
> queue deployment, or maybe Im missing something? We need to maintain
> single FILL/COMP pair per device queue.
This is correct. You cannot use that mode in your setup.
>
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2025-04-10 7:52 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
[not found] <CGME20250409125216eucas1p150b189cd13807197a233718302103a02@eucas1p1.samsung.com>
2025-04-09 12:49 ` [PATCH] xsk: Fix race condition in AF_XDP generic RX path e.kubanski
2025-04-09 13:21 ` Magnus Karlsson
2025-04-09 14:20 ` e.kubanski
2025-04-10 7:52 ` Magnus Karlsson
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®