* [PATCH v7 0/2] bpf, sockmap: handle spurious tcp_msg_wait_data() wakeup @ 2026-07-21 22:38 Nnamdi Onyeyiri 2026-07-21 22:38 ` [PATCH v7 1/2] " Nnamdi Onyeyiri 2026-07-21 22:38 ` [PATCH v7 2/2] selftests/bpf: add sockmap recvfrom EAGAIN selftest Nnamdi Onyeyiri 0 siblings, 2 replies; 5+ messages in thread From: Nnamdi Onyeyiri @ 2026-07-21 22:38 UTC (permalink / raw) To: nnamdio Cc: bpf, davem, edumazet, horms, jakub, jiayuan.chen, john.fastabend, kuba, kuniyu, ncardwell, netdev, pabeni, sashiko-reviews, linux-kernel, emil Spurious wakeups in tcp_msg_wait_data() aren't being handled by tcp_bpf_recvmsg() and tcp_bpf_recvmsg_parser(), leading to unexpected EAGAIN errors returned by recvfrom()/recv(). Adding handling for the wakeup and a selftest. This issue was first discovered in an application that adds sockets to a sockmap as a way to view the received data. No redirects or any other operations are performed. Sashiko has noted a few other pre-existing issues in the same area (https://patch.msgid.link/20260714205118.17DB11F000E9@smtp.kernel.org) that can lead to EAGAIN in tcp_bpf_recvmsg() and tcp_bpf_recvmsg_parser(). A local run of Sashiko also identified a potential issue in tcp_bpf_recvmsg() handling zero-data FIN packets when the MSG_PEEK flag is set. To prevent this patchset from growing too large, I intend to submit follow up patches to address these once this one has been accepted. Changes in v7: - In tcp_bpf_recvmsg check for data in the TCP_CLOSE, !timeo and signal_pending branches. - Refactored changes to tcp_bpf_recvmsg to reduce duplication. - Increase the number of iterations in the selftests. - Documented selftests numeric constants. - Link to v6: https://patch.msgid.link/20260720171535.67867-1-nnamdio@gmail.com Changes in v6: - Closing the file descriptor in the selftest worker thread. - Ensuring the selftest loop breaks early for an error in the worker thread. - Added comments to selftest regarding focus on EAGAIN error. - Updated selftest commit message to imperative mood. - Link to v5: https://patch.msgid.link/20260717155348.54975-1-nnamdio@gmail.com Changes in v5: - Move selftest from net into bpf/prog_tests/sockmap_basic.c - Link to v4: https://patch.msgid.link/20260715213538.37229-1-nnamdio@gmail.com Changes in v4: - Fix potential data loss in tcp_bpf_recvmsg() when a FIN or RST has been received. - Check the return code of pthread_create() in the selftest. - Fix race caused by using EXPECT macros in a thread in selftest. - Link to v3: https://patch.msgid.link/20260714203927.32289-1-nnamdio@gmail.com Changes in v3: - Added the sockmap_recvfrom selftest. - Link to v2: https://patch.msgid.link/alFRK66z45eDNZA7@localhost.localdomain Changes in v2: - In tcp_bpf_recvmsg, handle signals and the socket closing in the loop. - Fix spurious wakeups when SO_RCVTIMEO has been set on the socket. - Link to v1: https://patch.msgid.link/ak_rR-Skd8Mvn4mH@localhost.localdomain Signed-off-by: Nnamdi Onyeyiri <nnamdio@gmail.com> --- Nnamdi Onyeyiri (2): bpf, sockmap: handle spurious tcp_msg_wait_data() wakeup selftests/bpf: add sockmap recvfrom EAGAIN selftest net/ipv4/tcp_bpf.c | 72 ++++++++-- .../selftests/bpf/prog_tests/sockmap_basic.c | 136 ++++++++++++++++++ 2 files changed, 199 insertions(+), 9 deletions(-) -- 2.52.0 ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v7 1/2] bpf, sockmap: handle spurious tcp_msg_wait_data() wakeup 2026-07-21 22:38 [PATCH v7 0/2] bpf, sockmap: handle spurious tcp_msg_wait_data() wakeup Nnamdi Onyeyiri @ 2026-07-21 22:38 ` Nnamdi Onyeyiri 2026-07-22 11:01 ` Jakub Sitnicki 2026-07-21 22:38 ` [PATCH v7 2/2] selftests/bpf: add sockmap recvfrom EAGAIN selftest Nnamdi Onyeyiri 1 sibling, 1 reply; 5+ messages in thread From: Nnamdi Onyeyiri @ 2026-07-21 22:38 UTC (permalink / raw) To: nnamdio Cc: bpf, davem, edumazet, horms, jakub, jiayuan.chen, john.fastabend, kuba, kuniyu, ncardwell, netdev, pabeni, sashiko-reviews, linux-kernel, emil recvfrom()/recv() are documented as only returning EAGAIN for blocking sockets when they have a receive timeout configured. However, adding a blocking ipv4 tcp socket without a receive timeout to a sockmap will cause EAGAIN errors sporadically. A socket with a receive timeout may return EAGAIN before the timeout expires. There are 2 code paths affected by this: 1. tcp_bpf_recvmsg() - Used when the socket has been added to a sockmap that has no verdict program attached. 2. tcp_bpf_recvmsg_parser() - Used when the socket has been added to a sockmap that has a verdict program. To reproduce this issue, it is enough for the verdict program to do nothing but return SK_PASS. In both cases this happens when tcp_msg_wait_data() wakes spuriously (returning 0). To fix it, we now loop back to msg_bytes_ready instead of returning -EAGAIN on spurious wakeup. To ensure the looping does not cause sockets with a SO_RCVTIMEO set to wait excessively long, tcp_msg_wait_data() now takes a pointer to timeo, allowing sk_wait_event() to update it as appropriate. The logic in tcp_bpf_recvmsg_parser() that allow it to handle signals, socket errors and closuers in its loop was also added to tcp_bpf_recvmsg(). Signed-off-by: Nnamdi Onyeyiri <nnamdio@gmail.com> --- net/ipv4/tcp_bpf.c | 72 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 63 insertions(+), 9 deletions(-) diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c index cc0bd73f36b6..1755fcf726fc 100644 --- a/net/ipv4/tcp_bpf.c +++ b/net/ipv4/tcp_bpf.c @@ -179,7 +179,7 @@ EXPORT_SYMBOL_GPL(tcp_bpf_sendmsg_redir); #ifdef CONFIG_BPF_SYSCALL static int tcp_msg_wait_data(struct sock *sk, struct sk_psock *psock, - long timeo) + long *timeo) { DEFINE_WAIT_FUNC(wait, woken_wake_function); int ret = 0; @@ -187,12 +187,12 @@ static int tcp_msg_wait_data(struct sock *sk, struct sk_psock *psock, if (sk->sk_shutdown & RCV_SHUTDOWN) return 1; - if (!timeo) + if (!*timeo) return ret; add_wait_queue(sk_sleep(sk), &wait); sk_set_bit(SOCKWQ_ASYNC_WAITDATA, sk); - ret = sk_wait_event(sk, &timeo, + ret = sk_wait_event(sk, timeo, !list_empty(&psock->ingress_msg) || !skb_queue_empty_lockless(&sk->sk_receive_queue), &wait); sk_clear_bit(SOCKWQ_ASYNC_WAITDATA, sk); @@ -229,6 +229,7 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, int copied_from_self = 0; int copied = 0; u32 seq; + long timeo; if (unlikely(flags & MSG_ERRQUEUE)) return inet_recv_error(sk, msg, len); @@ -262,6 +263,8 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, } } + timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); + msg_bytes_ready: copied = __sk_msg_recvmsg(sk, psock, msg, len, flags, &copied_from_self); /* The typical case for EFAULT is the socket was gracefully @@ -280,7 +283,6 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, } seq += copied_from_self; if (!copied) { - long timeo; int data; if (sock_flag(sk, SOCK_DONE)) @@ -299,7 +301,6 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, goto out; } - timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); if (!timeo) { copied = -EAGAIN; goto out; @@ -310,13 +311,15 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, goto out; } - data = tcp_msg_wait_data(sk, psock, timeo); + data = tcp_msg_wait_data(sk, psock, &timeo); if (data < 0) { copied = data; goto unlock; } if (data && !sk_psock_queue_empty(psock)) goto msg_bytes_ready; + if (!data && timeo > 0) + goto msg_bytes_ready; copied = -EAGAIN; } out: @@ -355,6 +358,7 @@ static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, { struct sk_psock *psock; int copied, ret; + long timeo; if (unlikely(flags & MSG_ERRQUEUE)) return inet_recv_error(sk, msg, len); @@ -371,14 +375,52 @@ static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, return tcp_recvmsg(sk, msg, len, flags); } lock_sock(sk); + + timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); + msg_bytes_ready: copied = sk_msg_recvmsg(sk, psock, msg, len, flags); if (!copied) { - long timeo; int data; - timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); - data = tcp_msg_wait_data(sk, psock, timeo); + if (sock_flag(sk, SOCK_DONE)) { + ret = 0; + goto check_queues; + } + + if (sk->sk_err) { + if (!sk_psock_queue_empty(psock)) + goto msg_bytes_ready; + if (!skb_queue_empty(&sk->sk_receive_queue)) { + release_sock(sk); + sk_psock_put(sk, psock); + return tcp_recvmsg(sk, msg, len, flags); + } + ret = sock_error(sk); + goto unlock; + } + + if (sk->sk_shutdown & RCV_SHUTDOWN) { + ret = 0; + goto check_queues; + } + + if (sk->sk_state == TCP_CLOSE) { + ret = -ENOTCONN; + goto check_queues; + } + + if (!timeo) { + ret = -EAGAIN; + goto check_queues; + } + + if (signal_pending(current)) { + ret = sock_intr_errno(timeo); + goto check_queues; + } + + data = tcp_msg_wait_data(sk, psock, &timeo); if (data < 0) { ret = data; goto unlock; @@ -390,6 +432,8 @@ static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, sk_psock_put(sk, psock); return tcp_recvmsg(sk, msg, len, flags); } + if (!data && timeo > 0) + goto msg_bytes_ready; copied = -EAGAIN; } ret = copied; @@ -398,6 +442,16 @@ static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, release_sock(sk); sk_psock_put(sk, psock); return ret; + +check_queues: + if (!sk_psock_queue_empty(psock)) + goto msg_bytes_ready; + if (!skb_queue_empty(&sk->sk_receive_queue)) { + release_sock(sk); + sk_psock_put(sk, psock); + return tcp_recvmsg(sk, msg, len, flags); + } + goto unlock; } static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock, -- 2.52.0 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v7 1/2] bpf, sockmap: handle spurious tcp_msg_wait_data() wakeup 2026-07-21 22:38 ` [PATCH v7 1/2] " Nnamdi Onyeyiri @ 2026-07-22 11:01 ` Jakub Sitnicki 2026-07-22 11:22 ` Nnamdi Onyeyiri 0 siblings, 1 reply; 5+ messages in thread From: Jakub Sitnicki @ 2026-07-22 11:01 UTC (permalink / raw) To: Nnamdi Onyeyiri Cc: bpf, davem, edumazet, horms, jiayuan.chen, john.fastabend, kuba, kuniyu, ncardwell, netdev, pabeni, sashiko-reviews, linux-kernel, emil On Tue, Jul 21, 2026 at 11:38 PM +01, Nnamdi Onyeyiri wrote: > recvfrom()/recv() are documented as only returning EAGAIN for blocking > sockets when they have a receive timeout configured. However, adding a > blocking ipv4 tcp socket without a receive timeout to a sockmap will cause > EAGAIN errors sporadically. A socket with a receive timeout may return > EAGAIN before the timeout expires. > > There are 2 code paths affected by this: > > 1. tcp_bpf_recvmsg() - Used when the socket has been added to a sockmap > that has no verdict program attached. > > 2. tcp_bpf_recvmsg_parser() - Used when the socket has been added to a > sockmap that has a verdict program. To reproduce this issue, it is > enough for the verdict program to do nothing but return SK_PASS. > > In both cases this happens when tcp_msg_wait_data() wakes spuriously > (returning 0). To fix it, we now loop back to msg_bytes_ready instead > of returning -EAGAIN on spurious wakeup. > > To ensure the looping does not cause sockets with a SO_RCVTIMEO set to > wait excessively long, tcp_msg_wait_data() now takes a pointer to timeo, > allowing sk_wait_event() to update it as appropriate. > > The logic in tcp_bpf_recvmsg_parser() that allow it to handle signals, > socket errors and closuers in its loop was also added to tcp_bpf_recvmsg(). > > Signed-off-by: Nnamdi Onyeyiri <nnamdio@gmail.com> > --- > net/ipv4/tcp_bpf.c | 72 ++++++++++++++++++++++++++++++++++++++++------ > 1 file changed, 63 insertions(+), 9 deletions(-) > > diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c > index cc0bd73f36b6..1755fcf726fc 100644 > --- a/net/ipv4/tcp_bpf.c > +++ b/net/ipv4/tcp_bpf.c > @@ -179,7 +179,7 @@ EXPORT_SYMBOL_GPL(tcp_bpf_sendmsg_redir); > > #ifdef CONFIG_BPF_SYSCALL > static int tcp_msg_wait_data(struct sock *sk, struct sk_psock *psock, > - long timeo) > + long *timeo) > { > DEFINE_WAIT_FUNC(wait, woken_wake_function); > int ret = 0; > @@ -187,12 +187,12 @@ static int tcp_msg_wait_data(struct sock *sk, struct sk_psock *psock, > if (sk->sk_shutdown & RCV_SHUTDOWN) > return 1; > > - if (!timeo) > + if (!*timeo) > return ret; > > add_wait_queue(sk_sleep(sk), &wait); > sk_set_bit(SOCKWQ_ASYNC_WAITDATA, sk); > - ret = sk_wait_event(sk, &timeo, > + ret = sk_wait_event(sk, timeo, > !list_empty(&psock->ingress_msg) || > !skb_queue_empty_lockless(&sk->sk_receive_queue), &wait); > sk_clear_bit(SOCKWQ_ASYNC_WAITDATA, sk); > @@ -229,6 +229,7 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, > int copied_from_self = 0; > int copied = 0; > u32 seq; > + long timeo; > > if (unlikely(flags & MSG_ERRQUEUE)) > return inet_recv_error(sk, msg, len); > @@ -262,6 +263,8 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, > } > } > > + timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); > + > msg_bytes_ready: > copied = __sk_msg_recvmsg(sk, psock, msg, len, flags, &copied_from_self); > /* The typical case for EFAULT is the socket was gracefully > @@ -280,7 +283,6 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, > } > seq += copied_from_self; > if (!copied) { > - long timeo; > int data; > > if (sock_flag(sk, SOCK_DONE)) > @@ -299,7 +301,6 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, > goto out; > } > > - timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); > if (!timeo) { > copied = -EAGAIN; > goto out; > @@ -310,13 +311,15 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, > goto out; > } > > - data = tcp_msg_wait_data(sk, psock, timeo); > + data = tcp_msg_wait_data(sk, psock, &timeo); > if (data < 0) { > copied = data; > goto unlock; > } > if (data && !sk_psock_queue_empty(psock)) > goto msg_bytes_ready; > + if (!data && timeo > 0) > + goto msg_bytes_ready; > copied = -EAGAIN; > } > out: > @@ -355,6 +358,7 @@ static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, > { > struct sk_psock *psock; > int copied, ret; > + long timeo; > > if (unlikely(flags & MSG_ERRQUEUE)) > return inet_recv_error(sk, msg, len); > @@ -371,14 +375,52 @@ static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, > return tcp_recvmsg(sk, msg, len, flags); > } > lock_sock(sk); > + > + timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); > + > msg_bytes_ready: > copied = sk_msg_recvmsg(sk, psock, msg, len, flags); > if (!copied) { > - long timeo; > int data; > > - timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); > - data = tcp_msg_wait_data(sk, psock, timeo); > + if (sock_flag(sk, SOCK_DONE)) { > + ret = 0; > + goto check_queues; > + } > + > + if (sk->sk_err) { > + if (!sk_psock_queue_empty(psock)) > + goto msg_bytes_ready; > + if (!skb_queue_empty(&sk->sk_receive_queue)) { > + release_sock(sk); > + sk_psock_put(sk, psock); > + return tcp_recvmsg(sk, msg, len, flags); > + } > + ret = sock_error(sk); > + goto unlock; > + } Isn't that branch the same as: if (sk->sk_err) { ret = sock_error(sk); goto check_queues; } ? > + > + if (sk->sk_shutdown & RCV_SHUTDOWN) { > + ret = 0; > + goto check_queues; > + } > + > + if (sk->sk_state == TCP_CLOSE) { > + ret = -ENOTCONN; > + goto check_queues; > + } > + > + if (!timeo) { > + ret = -EAGAIN; > + goto check_queues; > + } > + > + if (signal_pending(current)) { > + ret = sock_intr_errno(timeo); > + goto check_queues; > + } > + > + data = tcp_msg_wait_data(sk, psock, &timeo); > if (data < 0) { > ret = data; > goto unlock; > @@ -390,6 +432,8 @@ static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, > sk_psock_put(sk, psock); > return tcp_recvmsg(sk, msg, len, flags); > } > + if (!data && timeo > 0) > + goto msg_bytes_ready; > copied = -EAGAIN; > } > ret = copied; > @@ -398,6 +442,16 @@ static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, > release_sock(sk); > sk_psock_put(sk, psock); > return ret; > + > +check_queues: > + if (!sk_psock_queue_empty(psock)) > + goto msg_bytes_ready; > + if (!skb_queue_empty(&sk->sk_receive_queue)) { > + release_sock(sk); > + sk_psock_put(sk, psock); > + return tcp_recvmsg(sk, msg, len, flags); > + } > + goto unlock; > } > > static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock, ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v7 1/2] bpf, sockmap: handle spurious tcp_msg_wait_data() wakeup 2026-07-22 11:01 ` Jakub Sitnicki @ 2026-07-22 11:22 ` Nnamdi Onyeyiri 0 siblings, 0 replies; 5+ messages in thread From: Nnamdi Onyeyiri @ 2026-07-22 11:22 UTC (permalink / raw) To: Jakub Sitnicki Cc: bpf, davem, edumazet, horms, jiayuan.chen, john.fastabend, kuba, kuniyu, ncardwell, netdev, pabeni, sashiko-reviews, linux-kernel, emil On Wed, Jul 22, 2026 at 01:01:09PM +0200, Jakub Sitnicki wrote: > On Tue, Jul 21, 2026 at 11:38 PM +01, Nnamdi Onyeyiri wrote: > > recvfrom()/recv() are documented as only returning EAGAIN for blocking > > sockets when they have a receive timeout configured. However, adding a > > blocking ipv4 tcp socket without a receive timeout to a sockmap will cause > > EAGAIN errors sporadically. A socket with a receive timeout may return > > EAGAIN before the timeout expires. > > > > There are 2 code paths affected by this: > > > > 1. tcp_bpf_recvmsg() - Used when the socket has been added to a sockmap > > that has no verdict program attached. > > > > 2. tcp_bpf_recvmsg_parser() - Used when the socket has been added to a > > sockmap that has a verdict program. To reproduce this issue, it is > > enough for the verdict program to do nothing but return SK_PASS. > > > > In both cases this happens when tcp_msg_wait_data() wakes spuriously > > (returning 0). To fix it, we now loop back to msg_bytes_ready instead > > of returning -EAGAIN on spurious wakeup. > > > > To ensure the looping does not cause sockets with a SO_RCVTIMEO set to > > wait excessively long, tcp_msg_wait_data() now takes a pointer to timeo, > > allowing sk_wait_event() to update it as appropriate. > > > > The logic in tcp_bpf_recvmsg_parser() that allow it to handle signals, > > socket errors and closuers in its loop was also added to tcp_bpf_recvmsg(). > > > > Signed-off-by: Nnamdi Onyeyiri <nnamdio@gmail.com> > > --- > > net/ipv4/tcp_bpf.c | 72 ++++++++++++++++++++++++++++++++++++++++------ > > 1 file changed, 63 insertions(+), 9 deletions(-) > > > > diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c > > index cc0bd73f36b6..1755fcf726fc 100644 > > --- a/net/ipv4/tcp_bpf.c > > +++ b/net/ipv4/tcp_bpf.c > > @@ -179,7 +179,7 @@ EXPORT_SYMBOL_GPL(tcp_bpf_sendmsg_redir); > > > > #ifdef CONFIG_BPF_SYSCALL > > static int tcp_msg_wait_data(struct sock *sk, struct sk_psock *psock, > > - long timeo) > > + long *timeo) > > { > > DEFINE_WAIT_FUNC(wait, woken_wake_function); > > int ret = 0; > > @@ -187,12 +187,12 @@ static int tcp_msg_wait_data(struct sock *sk, struct sk_psock *psock, > > if (sk->sk_shutdown & RCV_SHUTDOWN) > > return 1; > > > > - if (!timeo) > > + if (!*timeo) > > return ret; > > > > add_wait_queue(sk_sleep(sk), &wait); > > sk_set_bit(SOCKWQ_ASYNC_WAITDATA, sk); > > - ret = sk_wait_event(sk, &timeo, > > + ret = sk_wait_event(sk, timeo, > > !list_empty(&psock->ingress_msg) || > > !skb_queue_empty_lockless(&sk->sk_receive_queue), &wait); > > sk_clear_bit(SOCKWQ_ASYNC_WAITDATA, sk); > > @@ -229,6 +229,7 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, > > int copied_from_self = 0; > > int copied = 0; > > u32 seq; > > + long timeo; > > > > if (unlikely(flags & MSG_ERRQUEUE)) > > return inet_recv_error(sk, msg, len); > > @@ -262,6 +263,8 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, > > } > > } > > > > + timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); > > + > > msg_bytes_ready: > > copied = __sk_msg_recvmsg(sk, psock, msg, len, flags, &copied_from_self); > > /* The typical case for EFAULT is the socket was gracefully > > @@ -280,7 +283,6 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, > > } > > seq += copied_from_self; > > if (!copied) { > > - long timeo; > > int data; > > > > if (sock_flag(sk, SOCK_DONE)) > > @@ -299,7 +301,6 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, > > goto out; > > } > > > > - timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); > > if (!timeo) { > > copied = -EAGAIN; > > goto out; > > @@ -310,13 +311,15 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, > > goto out; > > } > > > > - data = tcp_msg_wait_data(sk, psock, timeo); > > + data = tcp_msg_wait_data(sk, psock, &timeo); > > if (data < 0) { > > copied = data; > > goto unlock; > > } > > if (data && !sk_psock_queue_empty(psock)) > > goto msg_bytes_ready; > > + if (!data && timeo > 0) > > + goto msg_bytes_ready; > > copied = -EAGAIN; > > } > > out: > > @@ -355,6 +358,7 @@ static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, > > { > > struct sk_psock *psock; > > int copied, ret; > > + long timeo; > > > > if (unlikely(flags & MSG_ERRQUEUE)) > > return inet_recv_error(sk, msg, len); > > @@ -371,14 +375,52 @@ static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, > > return tcp_recvmsg(sk, msg, len, flags); > > } > > lock_sock(sk); > > + > > + timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); > > + > > msg_bytes_ready: > > copied = sk_msg_recvmsg(sk, psock, msg, len, flags); > > if (!copied) { > > - long timeo; > > int data; > > > > - timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); > > - data = tcp_msg_wait_data(sk, psock, timeo); > > + if (sock_flag(sk, SOCK_DONE)) { > > + ret = 0; > > + goto check_queues; > > + } > > + > > + if (sk->sk_err) { > > + if (!sk_psock_queue_empty(psock)) > > + goto msg_bytes_ready; > > + if (!skb_queue_empty(&sk->sk_receive_queue)) { > > + release_sock(sk); > > + sk_psock_put(sk, psock); > > + return tcp_recvmsg(sk, msg, len, flags); > > + } > > + ret = sock_error(sk); > > + goto unlock; > > + } > > Isn't that branch the same as: > > if (sk->sk_err) { > ret = sock_error(sk); > goto check_queues; > } > > ? > sock_error clears the error in sk->sk_err. using check_queues means tcp_recvmsg would not see it. is that ok? > > + > > + if (sk->sk_shutdown & RCV_SHUTDOWN) { > > + ret = 0; > > + goto check_queues; > > + } > > + > > + if (sk->sk_state == TCP_CLOSE) { > > + ret = -ENOTCONN; > > + goto check_queues; > > + } > > + > > + if (!timeo) { > > + ret = -EAGAIN; > > + goto check_queues; > > + } > > + > > + if (signal_pending(current)) { > > + ret = sock_intr_errno(timeo); > > + goto check_queues; > > + } > > + > > + data = tcp_msg_wait_data(sk, psock, &timeo); > > if (data < 0) { > > ret = data; > > goto unlock; > > @@ -390,6 +432,8 @@ static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, > > sk_psock_put(sk, psock); > > return tcp_recvmsg(sk, msg, len, flags); > > } > > + if (!data && timeo > 0) > > + goto msg_bytes_ready; > > copied = -EAGAIN; > > } > > ret = copied; > > @@ -398,6 +442,16 @@ static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, > > release_sock(sk); > > sk_psock_put(sk, psock); > > return ret; > > + > > +check_queues: > > + if (!sk_psock_queue_empty(psock)) > > + goto msg_bytes_ready; > > + if (!skb_queue_empty(&sk->sk_receive_queue)) { > > + release_sock(sk); > > + sk_psock_put(sk, psock); > > + return tcp_recvmsg(sk, msg, len, flags); > > + } > > + goto unlock; > > } > > > > static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock, ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v7 2/2] selftests/bpf: add sockmap recvfrom EAGAIN selftest 2026-07-21 22:38 [PATCH v7 0/2] bpf, sockmap: handle spurious tcp_msg_wait_data() wakeup Nnamdi Onyeyiri 2026-07-21 22:38 ` [PATCH v7 1/2] " Nnamdi Onyeyiri @ 2026-07-21 22:38 ` Nnamdi Onyeyiri 1 sibling, 0 replies; 5+ messages in thread From: Nnamdi Onyeyiri @ 2026-07-21 22:38 UTC (permalink / raw) To: nnamdio Cc: bpf, davem, edumazet, horms, jakub, jiayuan.chen, john.fastabend, kuba, kuniyu, ncardwell, netdev, pabeni, sashiko-reviews, linux-kernel, emil These selftests exercise the tcp_bpf_recvmsg() and tcp_bpf_recvmsg_parser() functions, to ensure that they are properly handling spurious wakeups in tcp_msg_wait_data(). The expected behaviour is that recvfrom() does not return an EAGAIN error. If the spurious wakeups are incorrectly handled, this assertion will fail. Signed-off-by: Nnamdi Onyeyiri <nnamdio@gmail.com> --- .../selftests/bpf/prog_tests/sockmap_basic.c | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c index cb3229711f93..afda4d90e573 100644 --- a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c +++ b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c @@ -1373,6 +1373,138 @@ static void test_sockmap_multi_channels(int sotype) test_sockmap_pass_prog__destroy(skel); } +static void *test_sockmap_recvfrom_eagain_thread(void *arg) +{ + int fd = *(int *)arg; + char buf[1024]; + void *result = NULL; + + while (true) { + ssize_t len = recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL); + + if (len == -1) { + if (errno == EINTR) + continue; + result = (void *)1; + break; + } + + if (!len || buf[len - 1] == 'e') + break; + } + + send(fd, "test", 4, MSG_NOSIGNAL); + + close(fd); + + return result; +} + +static void test_sockmap_recvfrom_eagain(bool with_verdict) +{ + struct test_sockmap_pass_prog *skel = NULL; + struct bpf_program *prog = NULL; + char *buf = NULL; + int map, err; + + /* + * the size of the buffer to send. completion of the sending can trigger a spurious wake + * up. larger values make the issue more likely, but make successful test runs longer. + */ + const size_t buflen = 1024 * 1024 * 25; + + /* + * maximum number of attempts to reproduce EAGAIN on spurious wake up. more attempts + * increases the chances of triggering the issue, and the test ends as soon as we do. + * however, successful runs would take longer to complete. + */ + const int max_attempts = 300; + + skel = test_sockmap_pass_prog__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open_and_load")) + return; + + map = bpf_map__fd(skel->maps.sock_map_rx); + + if (with_verdict) { + prog = skel->progs.prog_skb_verdict; + err = bpf_prog_attach(bpf_program__fd(prog), map, BPF_SK_SKB_STREAM_VERDICT, 0); + if (!ASSERT_OK(err, "bpf_prog_attach verdict")) + goto cleanup; + } + + buf = malloc(buflen); + if (!ASSERT_OK_PTR(buf, "malloc buf")) + goto cleanup; + memset(buf, 0, buflen); + buf[buflen - 1] = 'e'; + + for (int i = 0; i < max_attempts; ++i) { + ssize_t len; + char ignored[128]; + pthread_t thread; + bool thread_created = false; + size_t rem = buflen; + int c = -1, p = -1, zero = 0; + bool success = false; + + err = create_pair(AF_INET, SOCK_STREAM, &c, &p); + if (!ASSERT_OK(err, "create_pair")) + goto end_attempt; + + err = pthread_create(&thread, NULL, &test_sockmap_recvfrom_eagain_thread, &p); + if (!ASSERT_OK(err, "pthread_create")) + goto end_attempt; + thread_created = true; + + err = bpf_map_update_elem(map, &zero, &c, BPF_ANY); + if (!ASSERT_OK(err, "bpf_map_update_elem")) + goto end_attempt; + + while (rem) { + len = xsend(c, buf + (buflen - rem), rem, 0); + if (len == -1) + goto end_attempt; + rem -= len; + } + + /* we cannot use recv_timeout(), otherwise EAGAIN would be an expected errno. */ + len = recvfrom(c, ignored, sizeof(ignored), 0, NULL, NULL); + + /* + * we are checking for the invalid return of EAGAIN, any other return is considered + * successful for the purposes of this test. + */ + if (len < 0 && !ASSERT_NEQ(errno, EAGAIN, "recvfrom eagain")) + goto end_attempt; + + success = true; + +end_attempt: + if (c >= 0) + close(c); + + if (thread_created) { + void *retval = NULL; + + pthread_join(thread, &retval); + if (!ASSERT_NULL(retval, "retval")) + success = false; + } + + if (!thread_created && p >= 0) + close(p); + if (!success) + break; + } + +cleanup: + if (buf) + free(buf); + + test_sockmap_pass_prog__destroy(skel); +} + void test_sockmap_basic(void) { if (test__start_subtest("sockmap create_update_free")) @@ -1451,4 +1583,8 @@ void test_sockmap_basic(void) test_sockmap_multi_channels(SOCK_STREAM); if (test__start_subtest("sockmap udp multi channels")) test_sockmap_multi_channels(SOCK_DGRAM); + if (test__start_subtest("sockmap recvfrom eagain")) + test_sockmap_recvfrom_eagain(false); + if (test__start_subtest("sockmap recvfrom eagain with verdict")) + test_sockmap_recvfrom_eagain(true); } -- 2.52.0 ^ permalink raw reply related [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-07-22 11:22 UTC | newest] Thread overview: 5+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-07-21 22:38 [PATCH v7 0/2] bpf, sockmap: handle spurious tcp_msg_wait_data() wakeup Nnamdi Onyeyiri 2026-07-21 22:38 ` [PATCH v7 1/2] " Nnamdi Onyeyiri 2026-07-22 11:01 ` Jakub Sitnicki 2026-07-22 11:22 ` Nnamdi Onyeyiri 2026-07-21 22:38 ` [PATCH v7 2/2] selftests/bpf: add sockmap recvfrom EAGAIN selftest Nnamdi Onyeyiri
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox