bpf, sockmap: Fix data loss and panic issues
Subsystems (6)
Jiayuan Chen
maintainercover bpf, sockmap: Fix data loss and panic issues
I was writing a benchmark based on sockmap + TCP and discovered several issues:
1. When EAGAIN occurs, the direction of skb is incorrect, causing data loss when retry.
2. When sending partial data, the offset is not recorded, leading to duplicate data being sent when retry.
3. An unexpected BUG_ON() judgment in skb_linearize is triggered.
4. The memory of psock->ingress_skb is not limited by the socket buffer and memcg.
Issues 1, 2, and 3 are described in each patch's commit message.
Regarding issue 4, this patchset does not cover it as it is difficult to handle in practice, and I am still working on it.
Here is a brief description of the issue:
When using sockmap to skb/stream redirect, if the receiving end does not perform read operations, all data will be buffered in ingress_skb.
For example:
'''
// set memory limit to 50G
cgcreate -g memory:myGroup
cgset -r memory.max="5000M" myGroup
// start benchmark and disable consumer from reading
cgexec -g "memory:myGroup" ./bench sockmap -c 2 -p 1 -a --rx-verdict-ingress --delay-consumer=-1 -d 100
Iter 0 ( 29.179us): Send Speed 2668.548 MB/s (20360.406 calls/s), ... Rcv Speed 0.000 MB/s ( 0.000 calls/s)
Iter 1 ( -7.237us): Send Speed 2694.467 MB/s (20557.149 calls/s), ... Rcv Speed 0.000 MB/s ( 0.000 calls/s)
Iter 2 ( -1.918us): Send Speed 2693.404 MB/s (20548.039 calls/s), ... Rcv Speed 0.000 MB/s ( 0.000 calls/s)
Iter 3 ( -0.684us): Send Speed 2693.138 MB/s (20548.014 calls/s), ... Rcv Speed 0.000 MB/s ( 0.000 calls/s)
Iter 4 ( 7.879us): Send Speed 2698.620 MB/s (20588.838 calls/s), ... Rcv Speed 0.000 MB/s ( 0.000 calls/s)
Iter 5 ( -3.224us): Send Speed 2696.553 MB/s (20573.066 calls/s), ... Rcv Speed 0.000 MB/s ( 0.000 calls/s)
Iter 6 ( -5.409us): Send Speed 2699.705 MB/s (20597.111 calls/s), ... Rcv Speed 0.000 MB/s ( 0.000 calls/s)
Iter 7 ( -0.439us): Send Speed 2699.691 MB/s (20597.009 calls/s), ... Rcv Speed 0.000 MB/s ( 0.000 calls/s)
...
// memory usage are not limited
cat /proc/slabinfo | grep skb
skbuff_small_head 11824024 11824024 704 46 8 : tunables 0 0 0 : slabdata 257044 257044 0 skbuff_fclone_cache 11822080 11822080 512 32 4 : tunables 0 0 0 : slabdata 369440 369440 0
'''
Thus, a simple socket in a large file upload/download model can eat the entire OS memory.
We must charge the skb memory to psock->sk, and if we do not want losing skb, we need to feedback the error info to read_sock/read_skb when the enqueue operation of psock->ingress_skb fails.
---
My another patch related to stability also requires maintainers to spare some time from their busy schedules for review.
https://lore.kernel.org/bpf/[email protected]/T/#t
Jiayuan Chen (4):
bpf, sockmap: Fix data lost during EAGAIN retries bpf, sockmap: fix duplicated data transmission bpf, sockmap: Fix panic when calling skb_linearize selftest/bpf/benchs: Add benchmark for sockmap usage
net/core/skmsg.c | 48 +- tools/testing/selftests/bpf/Makefile | 2 + tools/testing/selftests/bpf/bench.c | 4 + .../selftests/bpf/benchs/bench_sockmap.c | 599 ++++++++++++++++++ .../selftests/bpf/progs/bench_sockmap_prog.c | 65 ++ 5 files changed, 697 insertions(+), 21 deletions(-) create mode 100644 tools/testing/selftests/bpf/benchs/bench_sockmap.c create mode 100644 tools/testing/selftests/bpf/progs/bench_sockmap_prog.c
Signature · 1 line
2.47.1
Jiayuan Chen
maintainerPATCH 1/4 bpf, sockmap: Fix data lost during EAGAIN retries
We call skb_bpf_redirect_clear() to clean _sk_redir before handling skb in backlog, but when sk_psock_handle_skb() return EAGAIN due to sk_rcvbuf limit, the redirect info in _sk_redir is not recovered.
Fix skb redir loss during EAGAIN retries by restoring _sk_redir information using skb_bpf_set_redir().
Before this patch:
'''
./bench sockmap -c 2 -p 1 -a --rx-verdict-ingress
Setting up benchmark 'sockmap'...
create socket fd c1:13 p1:14 c2:15 p2:16
Benchmark 'sockmap' started.
Send Speed 1343.172 MB/s, BPF Speed 1343.238 MB/s, Rcv Speed 65.271 MB/s Send Speed 1352.022 MB/s, BPF Speed 1352.088 MB/s, Rcv Speed 0 MB/s Send Speed 1354.105 MB/s, BPF Speed 1354.105 MB/s, Rcv Speed 0 MB/s Send Speed 1355.018 MB/s, BPF Speed 1354.887 MB/s, Rcv Speed 0 MB/s
'''
Due to the high send rate, the RX processing path may frequently hit the sk_rcvbuf limit. Once triggered, incorrect _sk_redir will cause the flow to mistakenly enter the "!ingress" path, leading to send failures. (The Rcv speed depends on tcp_rmem).
After this patch:
'''
./bench sockmap -c 2 -p 1 -a --rx-verdict-ingress
Setting up benchmark 'sockmap'...
create socket fd c1:13 p1:14 c2:15 p2:16
Benchmark 'sockmap' started.
Send Speed 1347.236 MB/s, BPF Speed 1347.367 MB/s, Rcv Speed 65.402 MB/s Send Speed 1353.320 MB/s, BPF Speed 1353.320 MB/s, Rcv Speed 65.536 MB/s Send Speed 1353.186 MB/s, BPF Speed 1353.121 MB/s, Rcv Speed 65.536 MB/s
'''
--- net/core/skmsg.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
net/core/skmsg.c +2 -1
@@ -680,7 +680,8 @@ static void sk_psock_backlog(struct work_struct *work)if (ret <=0 ) {if (ret == -EAGAIN ) {sk_psock_skb_state (psock, state, len, off); - +/* Restore redir info we cleared before */ +skb_bpf_set_redir (skb, psock->sk, ingress);/* Delay slightly to prioritize any * other work that might be here. */
Signature · 1 line
2.47.1
Jiayuan Chen
maintainerPATCH 2/4 bpf, sockmap: fix duplicated data transmission
In reply to Jiayuan ChenIn the !ingress path under sk_psock_handle_skb(), when sending data to the remote under snd_buf limitations, partial skb data might be transmitted.
Although we preserved the partial transmission state (offset/length), the state wasn't properly consumed during retries. This caused the retry path to resend the entire skb data instead of continuing from the previous offset, resulting in data overlap at the receiver side.
--- net/core/skmsg.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-)
net/core/skmsg.c +9 -5
@@ -656,11 +656,6 @@ static void sk_psock_backlog(struct work_struct *work)int ret;mutex_lock (&psock->work_mutex); -if (unlikely (state->len)) { - len = state->len; - off = state->off; - } -while ((skb =skb_peek (&psock->ingress_skb))) { len = skb->len; off =0 ;
@@ -670,6 +665,13 @@ static void sk_psock_backlog(struct work_struct *work) off = stm->offset; len = stm->full_len; } + +/* Resume processing from previous partial state */ +if (unlikely (state->len)) { + len = state->len; + off = state->off; + } + ingress =skb_bpf_ingress (skb);skb_bpf_redirect_clear (skb); do {
@@ -698,6 +700,8 @@ static void sk_psock_backlog(struct work_struct *work) len -= ret; }while (len); +/* The entire skb sent, clear state */ +sk_psock_skb_state (psock, state,0 ,0 ); skb =skb_dequeue (&psock->ingress_skb);kfree_skb (skb); }
Signature · 1 line
2.47.1
Jiayuan Chen
maintainerPATCH 3/4 bpf, sockmap: Fix panic when calling skb_linearize
In reply to Jiayuan ChenThe panic can be reproduced by executing the command:
./bench sockmap -c 2 -p 1 -a --rx-verdict-ingress --rx-strp 100000
Then a kernel panic was captured:
'''
[ 657.460555] kernel BUG at net/core/skbuff.c:2178! [ 657.462680] Tainted: [W]=WARN [ 657.463287] Workqueue: events sk_psock_backlog ... [ 657.469610] <TASK> [ 657.469738] ? die+0x36/0x90 [ 657.469916] ? do_trap+0x1d0/0x270 [ 657.470118] ? pskb_expand_head+0x612/0xf40 [ 657.470376] ? pskb_expand_head+0x612/0xf40 [ 657.470620] ? do_error_trap+0xa3/0x170 [ 657.470846] ? pskb_expand_head+0x612/0xf40 [ 657.471092] ? handle_invalid_op+0x2c/0x40 [ 657.471335] ? pskb_expand_head+0x612/0xf40 [ 657.471579] ? exc_invalid_op+0x2d/0x40 [ 657.471805] ? asm_exc_invalid_op+0x1a/0x20 [ 657.472052] ? pskb_expand_head+0xd1/0xf40 [ 657.472292] ? pskb_expand_head+0x612/0xf40 [ 657.472540] ? lock_acquire+0x18f/0x4e0 [ 657.472766] ? find_held_lock+0x2d/0x110 [ 657.472999] ? __pfx_pskb_expand_head+0x10/0x10 [ 657.473263] ? __kmalloc_cache_noprof+0x5b/0x470 [ 657.473537] ? __pfx___lock_release.isra.0+0x10/0x10 [ 657.473826] __pskb_pull_tail+0xfd/0x1d20 [ 657.474062] ? __kasan_slab_alloc+0x4e/0x90 [ 657.474707] sk_psock_skb_ingress_enqueue+0x3bf/0x510 [ 657.475392] ? __kasan_kmalloc+0xaa/0xb0 [ 657.476010] sk_psock_backlog+0x5cf/0xd70 [ 657.476637] process_one_work+0x858/0x1a20
'''
The panic originates from the assertion BUG_ON(skb_shared(skb)) in skb_linearize(). A previous commit(see Fixes tag) introduced skb_get() to avoid race conditions between skb operations in the backlog and skb release in the recvmsg path. However, this caused the panic to always occur when skb_linearize is executed.
The "--rx-strp 100000" parameter forces the RX path to use the strparser module which aggregates data until it reaches 100KB before calling sockmap logic. The 100KB payload exceeds MAX_MSG_FRAGS, triggering skb_linearize.
To fix this issue, just move skb_get into sk_psock_skb_ingress_enqueue.
'''sk_psock_backlog:
sk_psock_handle_skb
skb_get(skb) <== we move it into 'sk_psock_skb_ingress_enqueue'
sk_psock_skb_ingress____________
↓
|
| → sk_psock_skb_ingress_self
| sk_psock_skb_ingress_enqueuesk_psock_verdict_apply_________________↑ skb_linearize
'''
Note that for verdict_apply path, the skb_get operation is unnecessary so we add 'take_ref' param to control it's behavior.
--- net/core/skmsg.c | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-)
net/core/skmsg.c +16 -15
@@ -530,16 +530,22 @@ static int sk_psock_skb_ingress_enqueue(struct sk_buff *skb, u32 off, u32 len, struct sk_psock *psock, struct sock *sk, - struct sk_msg *msg) + struct sk_msg *msg, + bool take_ref) { int num_sge, copied; +/* skb_to_sgvec will fail when the total number of fragments in +* frag_list and frags exceeds MAX_MSG_FRAGS. For example, the +* caller may aggregate multiple skbs. +*/ num_sge =skb_to_sgvec (skb, msg->sg.data, off, len);if (num_sge <0 ) {/* skb linearize may fail with ENOMEM, but lets simply try again * later if this happens. Under memory pressure we don't want to * drop the skb. We need to linearize the skb so that the mapping * in skb_to_sgvec can not error. +* Note that skb_linearize requires the skb not to be shared. */ if (skb_linearize (skb))return -EAGAIN ;
@@ -556,7 +562,7 @@ static int sk_psock_skb_ingress_enqueue(struct sk_buff *skb, msg->sg.start =0 ; msg->sg.size = copied; msg->sg.end = num_sge; - msg->skb = skb; + msg->skb = take_ref ?skb_get (skb) : skb;sk_psock_queue_msg (psock, msg);sk_psock_data_ready (sk, psock);
@@ -564,7 +570,7 @@ static int sk_psock_skb_ingress_enqueue(struct sk_buff *skb, }static int sk_psock_skb_ingress_self (struct sk_psock *psock,struct sk_buff *skb, -u32 off,u32 len); +u32 off,u32 len,bool take_ref);static int sk_psock_skb_ingress (struct sk_psock *psock,struct sk_buff *skb,u32 off,u32 len)
@@ -578,7 +584,7 @@ static int sk_psock_skb_ingress(struct sk_psock *psock, struct sk_buff *skb,* correctly. */ if (unlikely (skb->sk == sk)) -return sk_psock_skb_ingress_self (psock, skb, off, len); +return sk_psock_skb_ingress_self (psock, skb, off, len, true); msg =sk_psock_create_ingress_msg (sk, skb);if (!msg)return -EAGAIN ;
@@ -590,7 +596,7 @@ static int sk_psock_skb_ingress(struct sk_psock *psock, struct sk_buff *skb,* into user buffers. */ skb_set_owner_r (skb, sk); - err =sk_psock_skb_ingress_enqueue (skb, off, len, psock, sk, msg); + err =sk_psock_skb_ingress_enqueue (skb, off, len, psock, sk, msg, true);if (err <0 )kfree (msg);return err;
@@ -601,7 +607,7 @@ static int sk_psock_skb_ingress(struct sk_psock *psock, struct sk_buff *skb,* because the skb is already accounted for here. */ static int sk_psock_skb_ingress_self (struct sk_psock *psock,struct sk_buff *skb, -u32 off,u32 len) +u32 off,u32 len,bool take_ref) {struct sk_msg *msg =alloc_sk_msg (GFP_ATOMIC );struct sock *sk = psock->sk;
@@ -610,7 +616,7 @@ static int sk_psock_skb_ingress_self(struct sk_psock *psock, struct sk_buff *skbif (unlikely (!msg))return -EAGAIN ;skb_set_owner_r (skb, sk); - err =sk_psock_skb_ingress_enqueue (skb, off, len, psock, sk, msg); + err =sk_psock_skb_ingress_enqueue (skb, off, len, psock, sk, msg, take_ref);if (err <0 )kfree (msg);return err;
@@ -619,18 +625,13 @@ static int sk_psock_skb_ingress_self(struct sk_psock *psock, struct sk_buff *skbstatic int sk_psock_handle_skb (struct sk_psock *psock,struct sk_buff *skb,u32 off,u32 len,bool ingress) { -int err =0 ; -if (!ingress) {if (!sock_writeable (psock->sk))return -EAGAIN ;return skb_send_sock (psock->sk, skb, off, len); } -skb_get (skb); - err =sk_psock_skb_ingress (psock, skb, off, len); -if (err <0 ) -kfree_skb (skb); -return err; + +return sk_psock_skb_ingress (psock, skb, off, len); } static void sk_psock_skb_state(struct sk_psock *psock,
@@ -1019,7 +1020,7 @@ static int sk_psock_verdict_apply(struct sk_psock *psock, struct sk_buff *skb, off = stm->offset; len = stm->full_len; } - err =sk_psock_skb_ingress_self (psock, skb, off, len); + err =sk_psock_skb_ingress_self (psock, skb, off, len, false); }if (err <0 ) {spin_lock_bh (&psock->ingress_lock);
Signature · 1 line
2.47.1
Jiayuan Chen
maintainerPATCH 4/4 selftest/bpf/benchs: Add benchmark for sockmap usage
In reply to Jiayuan ChenAdd TCP+sockmap-based benchmark.
Since sockmap's own update and delete operations are generally less critical, the performance of the fast forwarding framework built upon it is the key aspect.
Also with cgset/cgexec, we can observe the behavior of sockmap under memory pressure.
The benchmark can be run with:
'''
./bench sockmap -c 2 -p 1 -a --rx-verdict-ingress
'''
In the future, we plan to move socket_helpers.h out of the prog_tests directory to make it accessible for the benchmark. This will enable better support for various socket types.
--- tools/testing/selftests/bpf/Makefile | 2 + tools/testing/selftests/bpf/bench.c | 4 + .../selftests/bpf/benchs/bench_sockmap.c | 599 ++++++++++++++++++ .../selftests/bpf/progs/bench_sockmap_prog.c | 65 ++ 4 files changed, 670 insertions(+) create mode 100644 tools/testing/selftests/bpf/benchs/bench_sockmap.c create mode 100644 tools/testing/selftests/bpf/progs/bench_sockmap_prog.c
tools/testing/selftests/bpf/Makefile +2 -0
@@ -815,6 +815,7 @@ $(OUTPUT)/bench_local_storage_create.o: $(OUTPUT)/bench_local_storage_create.ske $(OUTPUT)/bench_bpf_hashmap_lookup.o: $(OUTPUT)/bpf_hashmap_lookup.skel.h $(OUTPUT)/bench_htab_mem.o: $(OUTPUT)/htab_mem_bench.skel.h $(OUTPUT)/bench_bpf_crypto.o: $(OUTPUT)/crypto_bench.skel.h +$(OUTPUT)/bench_sockmap.o: $(OUTPUT)/bench_sockmap_prog.skel.h $(OUTPUT)/bench.o: bench.h testing_helpers.h $(BPFOBJ) $(OUTPUT)/bench: LDLIBS += -lm $(OUTPUT)/bench: $(OUTPUT)/bench.o \
@@ -835,6 +836,7 @@ $(OUTPUT)/bench: $(OUTPUT)/bench.o \ $(OUTPUT)/bench_local_storage_create.o \ $(OUTPUT)/bench_htab_mem.o \ $(OUTPUT)/bench_bpf_crypto.o \ + $(OUTPUT)/bench_sockmap.o \ # $(call msg,BINARY,,$@) $(Q)$(CC) $(CFLAGS) $(LDFLAGS) $(filter %.a %.o,$^) $(LDLIBS) -o $@
tools/testing/selftests/bpf/bench.c +4 -0
@@ -283,6 +283,7 @@ extern struct argp bench_local_storage_create_argp;extern struct argp bench_htab_mem_argp;extern struct argp bench_trigger_batch_argp;extern struct argp bench_crypto_argp; +extern struct argp bench_sockmap_argp;static const struct argp_child bench_parsers[] = { { &bench_ringbufs_argp,0 ,"Ring buffers benchmark" ,0 },
@@ -297,6 +298,7 @@ static const struct argp_child bench_parsers[] = { { &bench_htab_mem_argp, 0, "hash map memory benchmark", 0 }, { &bench_trigger_batch_argp, 0, "BPF triggering benchmark", 0 }, { &bench_crypto_argp, 0, "bpf crypto benchmark", 0 }, + { &bench_sockmap_argp, 0, "bpf sockmap benchmark", 0 }, {}, };
@@ -549,6 +551,7 @@ extern const struct bench bench_local_storage_create;extern const struct bench bench_htab_mem;extern const struct bench bench_crypto_encrypt;extern const struct bench bench_crypto_decrypt; +extern const struct bench bench_sockmap;static const struct bench *benchs[] = { &bench_count_global,
@@ -609,6 +612,7 @@ static const struct bench *benchs[] = { &bench_htab_mem, &bench_crypto_encrypt, &bench_crypto_decrypt, + &bench_sockmap, };static void find_benchmark (void )
tools/testing/selftests/bpf/benchs/bench_sockmap.c new file +599 -0
599 lines not on this page · raw message
tools/testing/selftests/bpf/progs/bench_sockmap_prog.c new file +65 -0
65 lines not on this page · raw message
Signature · 1 line
2.47.1
This series was applied to bpf/bpf-next.git (master) by Alexei Starovoitov <[email protected]>:
Hello:
This series was applied to bpf/bpf-next.git (master) by Alexei Starovoitov <[email protected]>:
Quoted 11 lines · On Mon, 7 Apr 2025 22:21:19 +0800 you wrote:
I was writing a benchmark based on sockmap + TCP and discovered several issues:
1. When EAGAIN occurs, the direction of skb is incorrect, causing data loss when retry.
2. When sending partial data, the offset is not recorded, leading to duplicate data being sent when retry.
3. An unexpectedBUG_ON()judgment inskb_linearizeis triggered.
4. The memory ofpsock->ingress_skbis not limited by the socket buffer and memcg.
Quoted 1 line
[...]
Here is the summary with links:
- [bpf-next,v1,1/4] bpf, sockmap: Fix data lost during EAGAIN retries
https://git.kernel.org/bpf/bpf-next/c/7683167196bd - [bpf-next,v1,2/4] bpf, sockmap: fix duplicated data transmission https://git.kernel.org/bpf/bpf-next/c/3b4f14b79428 - [bpf-next,v1,3/4] bpf, sockmap: Fix panic when calling skb_linearize https://git.kernel.org/bpf/bpf-next/c/5ca2e29f6834 - [bpf-next,v1,4/4] selftest/bpf/benchs: Add benchmark for sockmap usage https://git.kernel.org/bpf/bpf-next/c/7b2fa44de5e7
You are awesome, thank you!
Signature · 2 lines
Deet-doot-dot, I am a bot. https://korg.docs.kernel.org/patchwork/pwbot.html
John Fastabend
maintainerQuoted 18 lines · On 2025-04-10 03:10:37, [email protected] wrote:
Hello:
This series was applied to bpf/bpf-next.git (master) by Alexei Starovoitov <[email protected]>:
Quoted 12 lines · On Mon, 7 Apr 2025 22:21:19 +0800 you wrote:
I was writing a benchmark based on sockmap + TCP and discovered several issues:
1. When EAGAIN occurs, the direction of skb is incorrect, causing data loss when retry.
2. When sending partial data, the offset is not recorded, leading to duplicate data being sent when retry.
3. An unexpectedBUG_ON()judgment inskb_linearizeis triggered.
4. The memory ofpsock->ingress_skbis not limited by the socket buffer and memcg.[...]
LGTM thanks for the fixes Jiayuan. Good to see someone working through all the cases.
already merged but ACK for me.
Quoted 17 lines
Here is the summary with links:
- [bpf-next,v1,1/4] bpf, sockmap: Fix data lost during EAGAIN retrieshttps://git.kernel.org/bpf/bpf-next/c/7683167196bd - [bpf-next,v1,2/4] bpf, sockmap: fix duplicated data transmission https://git.kernel.org/bpf/bpf-next/c/3b4f14b79428 - [bpf-next,v1,3/4] bpf, sockmap: Fix panic when calling skb_linearize https://git.kernel.org/bpf/bpf-next/c/5ca2e29f6834 - [bpf-next,v1,4/4] selftest/bpf/benchs: Add benchmark for sockmap usage https://git.kernel.org/bpf/bpf-next/c/7b2fa44de5e7You are awesome, thank you!
Signature · 2 lines
Deet-doot-dot, I am a bot. https://korg.docs.kernel.org/patchwork/pwbot.html