public inbox for netdev@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH net v4 0/2] tcp: fix listener wakeup after reuseport migration
@ 2026-04-22  2:45 Zhenzhong Wu
  2026-04-22  2:45 ` [PATCH net v4 1/2] tcp: call sk_data_ready() after listener migration Zhenzhong Wu
  2026-04-22  2:45 ` [PATCH net v4 2/2] selftests/bpf: check epoll readiness during reuseport migration Zhenzhong Wu
  0 siblings, 2 replies; 6+ messages in thread
From: Zhenzhong Wu @ 2026-04-22  2:45 UTC (permalink / raw)
  To: netdev
  Cc: edumazet, ncardwell, kuniyu, davem, dsahern, kuba, pabeni, horms,
	shuah, tamird, linux-kernel, linux-kselftest, Zhenzhong Wu

This series fixes a missing wakeup when inet_csk_listen_stop() migrates
an established child socket from a closing listener to another socket
in the same SO_REUSEPORT group after the child has already been queued
for accept.

The target listener receives the migrated accept-queue entry via
inet_csk_reqsk_queue_add(), but its waiters are not notified.
Nonblocking accept() still succeeds because it checks the accept queue
directly, but readiness-based waiters can remain asleep until another
connection generates a wakeup.

Patch 1 notifies the target listener after a successful migration in
inet_csk_listen_stop() and protects the post-queue_add() nsk accesses
with rcu_read_lock()/rcu_read_unlock().

Patch 2 extends the existing migrate_reuseport BPF selftest with epoll
readiness checks inside migrate_dance(), around shutdown() where the
migration happens. The test now verifies that the target listener is
not ready before migration and becomes ready immediately after it, for
both TCP_ESTABLISHED and TCP_SYN_RECV. TCP_NEW_SYN_RECV remains
excluded because it still depends on later handshake completion.

Testing:
- On a local unpatched kernel, the focused migrate_reuseport test
  fails for the listener-migration cases and passes for the
  TCP_NEW_SYN_RECV cases:
    not ok 1 IPv4 TCP_ESTABLISHED  inet_csk_listen_stop
    not ok 2 IPv4 TCP_SYN_RECV     inet_csk_listen_stop
    ok 3 IPv4 TCP_NEW_SYN_RECV reqsk_timer_handler
    ok 4 IPv4 TCP_NEW_SYN_RECV inet_csk_complete_hashdance
    not ok 5 IPv6 TCP_ESTABLISHED  inet_csk_listen_stop
    not ok 6 IPv6 TCP_SYN_RECV     inet_csk_listen_stop
    ok 7 IPv6 TCP_NEW_SYN_RECV reqsk_timer_handler
    ok 8 IPv6 TCP_NEW_SYN_RECV inet_csk_complete_hashdance
- On a patched kernel booted under QEMU, the full migrate_reuseport
  selftest passes:
    ok 1 IPv4 TCP_ESTABLISHED  inet_csk_listen_stop
    ok 2 IPv4 TCP_SYN_RECV     inet_csk_listen_stop
    ok 3 IPv4 TCP_NEW_SYN_RECV reqsk_timer_handler
    ok 4 IPv4 TCP_NEW_SYN_RECV inet_csk_complete_hashdance
    ok 5 IPv6 TCP_ESTABLISHED  inet_csk_listen_stop
    ok 6 IPv6 TCP_SYN_RECV     inet_csk_listen_stop
    ok 7 IPv6 TCP_NEW_SYN_RECV reqsk_timer_handler
    ok 8 IPv6 TCP_NEW_SYN_RECV inet_csk_complete_hashdance
    SELFTEST_RC=0

---

v4:
- fix patch 2 to route shutdown() failures through the epoll cleanup path
- guard patch 2's epoll cleanup when no epoll fd was created

v3:
  https://lore.kernel.org/netdev/20260421123106.142299-1-jt26wzz@gmail.com/

v2:
  https://lore.kernel.org/netdev/20260418181333.1713389-1-jt26wzz@gmail.com/

v1:
  https://lore.kernel.org/netdev/20260418041633.691435-1-jt26wzz@gmail.com/

Zhenzhong Wu (2):
  tcp: call sk_data_ready() after listener migration
  selftests/bpf: check epoll readiness during reuseport migration

 net/ipv4/inet_connection_sock.c               |  3 ++
 .../bpf/prog_tests/migrate_reuseport.c        | 49 ++++++++++++++++---
 2 files changed, 45 insertions(+), 7 deletions(-)


base-commit: 52bcb57a4e8a0865a76c587c2451906342ae1b2d
-- 
2.43.0

^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH net v4 1/2] tcp: call sk_data_ready() after listener migration
  2026-04-22  2:45 [PATCH net v4 0/2] tcp: fix listener wakeup after reuseport migration Zhenzhong Wu
@ 2026-04-22  2:45 ` Zhenzhong Wu
  2026-04-22  2:45 ` [PATCH net v4 2/2] selftests/bpf: check epoll readiness during reuseport migration Zhenzhong Wu
  1 sibling, 0 replies; 6+ messages in thread
From: Zhenzhong Wu @ 2026-04-22  2:45 UTC (permalink / raw)
  To: netdev
  Cc: edumazet, ncardwell, kuniyu, davem, dsahern, kuba, pabeni, horms,
	shuah, tamird, linux-kernel, linux-kselftest, Zhenzhong Wu,
	stable

When inet_csk_listen_stop() migrates an established child socket from
a closing listener to another socket in the same SO_REUSEPORT group,
the target listener gets a new accept-queue entry via
inet_csk_reqsk_queue_add(), but that path never notifies the target
listener's waiters. A nonblocking accept() still works because it
checks the queue directly, but poll()/epoll_wait() waiters and
blocking accept() callers can also remain asleep indefinitely.

Call READ_ONCE(nsk->sk_data_ready)(nsk) after a successful migration
in inet_csk_listen_stop().

However, after inet_csk_reqsk_queue_add() succeeds, the ref acquired
in reuseport_migrate_sock() is effectively transferred to
nreq->rsk_listener. Another CPU can then dequeue nreq via accept()
or listener shutdown, hit reqsk_put(), and drop that listener ref.
Since listeners are SOCK_RCU_FREE, wrap the post-queue_add()
dereferences of nsk in rcu_read_lock()/rcu_read_unlock(), which also
covers the existing sock_net(nsk) access in that path.

The reqsk_timer_handler() path does not need the same changes for two
reasons: half-open requests become readable only after the final ACK,
where tcp_child_process() already wakes the listener; and once nreq is
visible via inet_ehash_insert(), the success path no longer touches
nsk directly.

Fixes: 54b92e841937 ("tcp: Migrate TCP_ESTABLISHED/TCP_SYN_RECV sockets in accept queues.")
Cc: stable@vger.kernel.org
Suggested-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
Signed-off-by: Zhenzhong Wu <jt26wzz@gmail.com>
---
 net/ipv4/inet_connection_sock.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c
index 4ac3ae1bc..928654c34 100644
--- a/net/ipv4/inet_connection_sock.c
+++ b/net/ipv4/inet_connection_sock.c
@@ -1479,16 +1479,19 @@ void inet_csk_listen_stop(struct sock *sk)
 			if (nreq) {
 				refcount_set(&nreq->rsk_refcnt, 1);
 
+				rcu_read_lock();
 				if (inet_csk_reqsk_queue_add(nsk, nreq, child)) {
 					__NET_INC_STATS(sock_net(nsk),
 							LINUX_MIB_TCPMIGRATEREQSUCCESS);
 					reqsk_migrate_reset(req);
+					READ_ONCE(nsk->sk_data_ready)(nsk);
 				} else {
 					__NET_INC_STATS(sock_net(nsk),
 							LINUX_MIB_TCPMIGRATEREQFAILURE);
 					reqsk_migrate_reset(nreq);
 					__reqsk_free(nreq);
 				}
+				rcu_read_unlock();
 
 				/* inet_csk_reqsk_queue_add() has already
 				 * called inet_child_forget() on failure case.
-- 
2.43.0

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH net v4 2/2] selftests/bpf: check epoll readiness during reuseport migration
  2026-04-22  2:45 [PATCH net v4 0/2] tcp: fix listener wakeup after reuseport migration Zhenzhong Wu
  2026-04-22  2:45 ` [PATCH net v4 1/2] tcp: call sk_data_ready() after listener migration Zhenzhong Wu
@ 2026-04-22  2:45 ` Zhenzhong Wu
  2026-04-22  3:03   ` Tamir Duberstein
  1 sibling, 1 reply; 6+ messages in thread
From: Zhenzhong Wu @ 2026-04-22  2:45 UTC (permalink / raw)
  To: netdev
  Cc: edumazet, ncardwell, kuniyu, davem, dsahern, kuba, pabeni, horms,
	shuah, tamird, linux-kernel, linux-kselftest, Zhenzhong Wu

Inside migrate_dance(), add epoll checks around shutdown() to
verify that the target listener is not ready before shutdown()
and becomes ready immediately after shutdown() triggers migration.

Cover TCP_ESTABLISHED and TCP_SYN_RECV. Exclude TCP_NEW_SYN_RECV
as it depends on later handshake completion.

Suggested-by: Kuniyuki Iwashima <kuniyu@google.com>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
Signed-off-by: Zhenzhong Wu <jt26wzz@gmail.com>
---
 .../bpf/prog_tests/migrate_reuseport.c        | 49 ++++++++++++++++---
 1 file changed, 42 insertions(+), 7 deletions(-)

diff --git a/tools/testing/selftests/bpf/prog_tests/migrate_reuseport.c b/tools/testing/selftests/bpf/prog_tests/migrate_reuseport.c
index 653b0a20f..c62907732 100644
--- a/tools/testing/selftests/bpf/prog_tests/migrate_reuseport.c
+++ b/tools/testing/selftests/bpf/prog_tests/migrate_reuseport.c
@@ -7,24 +7,29 @@
  *   3. call listen() for 1 server socket. (migration target)
  *   4. update a map to migrate all child sockets
  *        to the last server socket (migrate_map[cookie] = 4)
- *   5. call shutdown() for first 4 server sockets
+ *   5. for TCP_ESTABLISHED and TCP_SYN_RECV cases, verify via epoll
+ *        that the last server socket is not ready before migration.
+ *   6. call shutdown() for first 4 server sockets
  *        and migrate the requests in the accept queue
  *        to the last server socket.
- *   6. call listen() for the second server socket.
- *   7. call shutdown() for the last server
+ *   7. for TCP_ESTABLISHED and TCP_SYN_RECV cases, verify via epoll
+ *        that the last server socket is ready after migration.
+ *   8. call listen() for the second server socket.
+ *   9. call shutdown() for the last server
  *        and migrate the requests in the accept queue
  *        to the second server socket.
- *   8. call listen() for the last server.
- *   9. call shutdown() for the second server
+ *  10. call listen() for the last server.
+ *  11. call shutdown() for the second server
  *        and migrate the requests in the accept queue
  *        to the last server socket.
- *  10. call accept() for the last server socket.
+ *  12. call accept() for the last server socket.
  *
  * Author: Kuniyuki Iwashima <kuniyu@amazon.co.jp>
  */
 
 #include <bpf/bpf.h>
 #include <bpf/libbpf.h>
+#include <sys/epoll.h>
 
 #include "test_progs.h"
 #include "test_migrate_reuseport.skel.h"
@@ -350,21 +355,51 @@ static int update_maps(struct migrate_reuseport_test_case *test_case,
 
 static int migrate_dance(struct migrate_reuseport_test_case *test_case)
 {
+	struct epoll_event ev = {
+		.events = EPOLLIN,
+	};
+	int epoll = -1, nfds;
 	int i, err;
 
+	if (test_case->state != BPF_TCP_NEW_SYN_RECV) {
+		epoll = epoll_create1(0);
+		if (!ASSERT_NEQ(epoll, -1, "epoll_create1"))
+			return -1;
+
+		ev.data.fd = test_case->servers[MIGRATED_TO];
+		if (!ASSERT_OK(epoll_ctl(epoll, EPOLL_CTL_ADD,
+					 test_case->servers[MIGRATED_TO], &ev),
+			       "epoll_ctl"))
+			goto close_epoll;
+
+		nfds = epoll_wait(epoll, &ev, 1, 0);
+		if (!ASSERT_EQ(nfds, 0, "epoll_wait 1"))
+			goto close_epoll;
+	}
+
 	/* Migrate TCP_ESTABLISHED and TCP_SYN_RECV requests
 	 * to the last listener based on eBPF.
 	 */
 	for (i = 0; i < MIGRATED_TO; i++) {
 		err = shutdown(test_case->servers[i], SHUT_RDWR);
 		if (!ASSERT_OK(err, "shutdown"))
-			return -1;
+			goto close_epoll;
 	}
 
 	/* No dance for TCP_NEW_SYN_RECV to migrate based on eBPF */
 	if (test_case->state == BPF_TCP_NEW_SYN_RECV)
 		return 0;
 
+	nfds = epoll_wait(epoll, &ev, 1, 0);
+	if (!ASSERT_EQ(nfds, 1, "epoll_wait 2")) {
+close_epoll:
+		if (epoll >= 0)
+			close(epoll);
+		return -1;
+	}
+
+	close(epoll);
+
 	/* Note that we use the second listener instead of the
 	 * first one here.
 	 *
-- 
2.43.0

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH net v4 2/2] selftests/bpf: check epoll readiness during reuseport migration
  2026-04-22  2:45 ` [PATCH net v4 2/2] selftests/bpf: check epoll readiness during reuseport migration Zhenzhong Wu
@ 2026-04-22  3:03   ` Tamir Duberstein
  2026-04-22  6:34     ` Zhenzhong Wu
  0 siblings, 1 reply; 6+ messages in thread
From: Tamir Duberstein @ 2026-04-22  3:03 UTC (permalink / raw)
  To: Zhenzhong Wu
  Cc: netdev, edumazet, ncardwell, kuniyu, davem, dsahern, kuba, pabeni,
	horms, shuah, linux-kernel, linux-kselftest

On Tue, Apr 21, 2026 at 7:46 PM Zhenzhong Wu <jt26wzz@gmail.com> wrote:
>
> Inside migrate_dance(), add epoll checks around shutdown() to
> verify that the target listener is not ready before shutdown()
> and becomes ready immediately after shutdown() triggers migration.
>
> Cover TCP_ESTABLISHED and TCP_SYN_RECV. Exclude TCP_NEW_SYN_RECV
> as it depends on later handshake completion.
>
> Suggested-by: Kuniyuki Iwashima <kuniyu@google.com>
> Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
> Signed-off-by: Zhenzhong Wu <jt26wzz@gmail.com>
> ---
>  .../bpf/prog_tests/migrate_reuseport.c        | 49 ++++++++++++++++---
>  1 file changed, 42 insertions(+), 7 deletions(-)
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/migrate_reuseport.c b/tools/testing/selftests/bpf/prog_tests/migrate_reuseport.c
> index 653b0a20f..c62907732 100644
> --- a/tools/testing/selftests/bpf/prog_tests/migrate_reuseport.c
> +++ b/tools/testing/selftests/bpf/prog_tests/migrate_reuseport.c
> @@ -7,24 +7,29 @@
>   *   3. call listen() for 1 server socket. (migration target)
>   *   4. update a map to migrate all child sockets
>   *        to the last server socket (migrate_map[cookie] = 4)
> - *   5. call shutdown() for first 4 server sockets
> + *   5. for TCP_ESTABLISHED and TCP_SYN_RECV cases, verify via epoll
> + *        that the last server socket is not ready before migration.
> + *   6. call shutdown() for first 4 server sockets
>   *        and migrate the requests in the accept queue
>   *        to the last server socket.
> - *   6. call listen() for the second server socket.
> - *   7. call shutdown() for the last server
> + *   7. for TCP_ESTABLISHED and TCP_SYN_RECV cases, verify via epoll
> + *        that the last server socket is ready after migration.
> + *   8. call listen() for the second server socket.
> + *   9. call shutdown() for the last server
>   *        and migrate the requests in the accept queue
>   *        to the second server socket.
> - *   8. call listen() for the last server.
> - *   9. call shutdown() for the second server
> + *  10. call listen() for the last server.
> + *  11. call shutdown() for the second server
>   *        and migrate the requests in the accept queue
>   *        to the last server socket.
> - *  10. call accept() for the last server socket.
> + *  12. call accept() for the last server socket.
>   *
>   * Author: Kuniyuki Iwashima <kuniyu@amazon.co.jp>
>   */
>
>  #include <bpf/bpf.h>
>  #include <bpf/libbpf.h>
> +#include <sys/epoll.h>
>
>  #include "test_progs.h"
>  #include "test_migrate_reuseport.skel.h"
> @@ -350,21 +355,51 @@ static int update_maps(struct migrate_reuseport_test_case *test_case,
>
>  static int migrate_dance(struct migrate_reuseport_test_case *test_case)
>  {
> +       struct epoll_event ev = {
> +               .events = EPOLLIN,
> +       };
> +       int epoll = -1, nfds;
>         int i, err;
>
> +       if (test_case->state != BPF_TCP_NEW_SYN_RECV) {
> +               epoll = epoll_create1(0);
> +               if (!ASSERT_NEQ(epoll, -1, "epoll_create1"))
> +                       return -1;
> +
> +               ev.data.fd = test_case->servers[MIGRATED_TO];
> +               if (!ASSERT_OK(epoll_ctl(epoll, EPOLL_CTL_ADD,
> +                                        test_case->servers[MIGRATED_TO], &ev),
> +                              "epoll_ctl"))
> +                       goto close_epoll;
> +
> +               nfds = epoll_wait(epoll, &ev, 1, 0);
> +               if (!ASSERT_EQ(nfds, 0, "epoll_wait 1"))
> +                       goto close_epoll;
> +       }
> +
>         /* Migrate TCP_ESTABLISHED and TCP_SYN_RECV requests
>          * to the last listener based on eBPF.
>          */
>         for (i = 0; i < MIGRATED_TO; i++) {
>                 err = shutdown(test_case->servers[i], SHUT_RDWR);
>                 if (!ASSERT_OK(err, "shutdown"))
> -                       return -1;
> +                       goto close_epoll;
>         }
>
>         /* No dance for TCP_NEW_SYN_RECV to migrate based on eBPF */
>         if (test_case->state == BPF_TCP_NEW_SYN_RECV)
>                 return 0;
>
> +       nfds = epoll_wait(epoll, &ev, 1, 0);
> +       if (!ASSERT_EQ(nfds, 1, "epoll_wait 2")) {
> +close_epoll:
> +               if (epoll >= 0)
> +                       close(epoll);
> +               return -1;
> +       }

I think you can avoid this goto using `__attribute__((cleanup(...))`:

  static void close_fd(int *fd)
  {
        if (*fd >= 0)
                close(*fd);
  }

  #define __close_fd __attribute__((cleanup(close_fd)))

then in the test:

__close_fd int epoll = -1;

there's an example of this in
tools/testing/selftests/bpf/prog_tests/socket_helpers.h.

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH net v4 2/2] selftests/bpf: check epoll readiness during reuseport migration
  2026-04-22  3:03   ` Tamir Duberstein
@ 2026-04-22  6:34     ` Zhenzhong Wu
  2026-04-22  6:40       ` Kuniyuki Iwashima
  0 siblings, 1 reply; 6+ messages in thread
From: Zhenzhong Wu @ 2026-04-22  6:34 UTC (permalink / raw)
  To: Tamir Duberstein
  Cc: netdev, edumazet, ncardwell, kuniyu, davem, dsahern, kuba, pabeni,
	horms, shuah, linux-kernel, linux-kselftest

Thanks Tamir, I will fold this into v5 - there's already a precedent
in socket_helpers.h, so I will just include it from there.

On Wed, Apr 22, 2026 at 11:04 AM Tamir Duberstein <tamird@kernel.org> wrote:
>
> On Tue, Apr 21, 2026 at 7:46 PM Zhenzhong Wu <jt26wzz@gmail.com> wrote:
> >
> > Inside migrate_dance(), add epoll checks around shutdown() to
> > verify that the target listener is not ready before shutdown()
> > and becomes ready immediately after shutdown() triggers migration.
> >
> > Cover TCP_ESTABLISHED and TCP_SYN_RECV. Exclude TCP_NEW_SYN_RECV
> > as it depends on later handshake completion.
> >
> > Suggested-by: Kuniyuki Iwashima <kuniyu@google.com>
> > Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
> > Signed-off-by: Zhenzhong Wu <jt26wzz@gmail.com>
> > ---
> >  .../bpf/prog_tests/migrate_reuseport.c        | 49 ++++++++++++++++---
> >  1 file changed, 42 insertions(+), 7 deletions(-)
> >
> > diff --git a/tools/testing/selftests/bpf/prog_tests/migrate_reuseport.c b/tools/testing/selftests/bpf/prog_tests/migrate_reuseport.c
> > index 653b0a20f..c62907732 100644
> > --- a/tools/testing/selftests/bpf/prog_tests/migrate_reuseport.c
> > +++ b/tools/testing/selftests/bpf/prog_tests/migrate_reuseport.c
> > @@ -7,24 +7,29 @@
> >   *   3. call listen() for 1 server socket. (migration target)
> >   *   4. update a map to migrate all child sockets
> >   *        to the last server socket (migrate_map[cookie] = 4)
> > - *   5. call shutdown() for first 4 server sockets
> > + *   5. for TCP_ESTABLISHED and TCP_SYN_RECV cases, verify via epoll
> > + *        that the last server socket is not ready before migration.
> > + *   6. call shutdown() for first 4 server sockets
> >   *        and migrate the requests in the accept queue
> >   *        to the last server socket.
> > - *   6. call listen() for the second server socket.
> > - *   7. call shutdown() for the last server
> > + *   7. for TCP_ESTABLISHED and TCP_SYN_RECV cases, verify via epoll
> > + *        that the last server socket is ready after migration.
> > + *   8. call listen() for the second server socket.
> > + *   9. call shutdown() for the last server
> >   *        and migrate the requests in the accept queue
> >   *        to the second server socket.
> > - *   8. call listen() for the last server.
> > - *   9. call shutdown() for the second server
> > + *  10. call listen() for the last server.
> > + *  11. call shutdown() for the second server
> >   *        and migrate the requests in the accept queue
> >   *        to the last server socket.
> > - *  10. call accept() for the last server socket.
> > + *  12. call accept() for the last server socket.
> >   *
> >   * Author: Kuniyuki Iwashima <kuniyu@amazon.co.jp>
> >   */
> >
> >  #include <bpf/bpf.h>
> >  #include <bpf/libbpf.h>
> > +#include <sys/epoll.h>
> >
> >  #include "test_progs.h"
> >  #include "test_migrate_reuseport.skel.h"
> > @@ -350,21 +355,51 @@ static int update_maps(struct migrate_reuseport_test_case *test_case,
> >
> >  static int migrate_dance(struct migrate_reuseport_test_case *test_case)
> >  {
> > +       struct epoll_event ev = {
> > +               .events = EPOLLIN,
> > +       };
> > +       int epoll = -1, nfds;
> >         int i, err;
> >
> > +       if (test_case->state != BPF_TCP_NEW_SYN_RECV) {
> > +               epoll = epoll_create1(0);
> > +               if (!ASSERT_NEQ(epoll, -1, "epoll_create1"))
> > +                       return -1;
> > +
> > +               ev.data.fd = test_case->servers[MIGRATED_TO];
> > +               if (!ASSERT_OK(epoll_ctl(epoll, EPOLL_CTL_ADD,
> > +                                        test_case->servers[MIGRATED_TO], &ev),
> > +                              "epoll_ctl"))
> > +                       goto close_epoll;
> > +
> > +               nfds = epoll_wait(epoll, &ev, 1, 0);
> > +               if (!ASSERT_EQ(nfds, 0, "epoll_wait 1"))
> > +                       goto close_epoll;
> > +       }
> > +
> >         /* Migrate TCP_ESTABLISHED and TCP_SYN_RECV requests
> >          * to the last listener based on eBPF.
> >          */
> >         for (i = 0; i < MIGRATED_TO; i++) {
> >                 err = shutdown(test_case->servers[i], SHUT_RDWR);
> >                 if (!ASSERT_OK(err, "shutdown"))
> > -                       return -1;
> > +                       goto close_epoll;
> >         }
> >
> >         /* No dance for TCP_NEW_SYN_RECV to migrate based on eBPF */
> >         if (test_case->state == BPF_TCP_NEW_SYN_RECV)
> >                 return 0;
> >
> > +       nfds = epoll_wait(epoll, &ev, 1, 0);
> > +       if (!ASSERT_EQ(nfds, 1, "epoll_wait 2")) {
> > +close_epoll:
> > +               if (epoll >= 0)
> > +                       close(epoll);
> > +               return -1;
> > +       }
>
> I think you can avoid this goto using `__attribute__((cleanup(...))`:
>
>   static void close_fd(int *fd)
>   {
>         if (*fd >= 0)
>                 close(*fd);
>   }
>
>   #define __close_fd __attribute__((cleanup(close_fd)))
>
> then in the test:
>
> __close_fd int epoll = -1;
>
> there's an example of this in
> tools/testing/selftests/bpf/prog_tests/socket_helpers.h.

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH net v4 2/2] selftests/bpf: check epoll readiness during reuseport migration
  2026-04-22  6:34     ` Zhenzhong Wu
@ 2026-04-22  6:40       ` Kuniyuki Iwashima
  0 siblings, 0 replies; 6+ messages in thread
From: Kuniyuki Iwashima @ 2026-04-22  6:40 UTC (permalink / raw)
  To: Zhenzhong Wu
  Cc: Tamir Duberstein, netdev, edumazet, ncardwell, davem, dsahern,
	kuba, pabeni, horms, shuah, linux-kernel, linux-kselftest

On Tue, Apr 21, 2026 at 11:34 PM Zhenzhong Wu <jt26wzz@gmail.com> wrote:
>
> Thanks Tamir, I will fold this into v5 - there's already a precedent
> in socket_helpers.h, so I will just include it from there.

The test does not use cleanup in other places,
no need to mix them up.


>
> On Wed, Apr 22, 2026 at 11:04 AM Tamir Duberstein <tamird@kernel.org> wrote:
> >
> > On Tue, Apr 21, 2026 at 7:46 PM Zhenzhong Wu <jt26wzz@gmail.com> wrote:
> > >
> > > Inside migrate_dance(), add epoll checks around shutdown() to
> > > verify that the target listener is not ready before shutdown()
> > > and becomes ready immediately after shutdown() triggers migration.
> > >
> > > Cover TCP_ESTABLISHED and TCP_SYN_RECV. Exclude TCP_NEW_SYN_RECV
> > > as it depends on later handshake completion.
> > >
> > > Suggested-by: Kuniyuki Iwashima <kuniyu@google.com>
> > > Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
> > > Signed-off-by: Zhenzhong Wu <jt26wzz@gmail.com>
> > > ---
> > >  .../bpf/prog_tests/migrate_reuseport.c        | 49 ++++++++++++++++---
> > >  1 file changed, 42 insertions(+), 7 deletions(-)
> > >
> > > diff --git a/tools/testing/selftests/bpf/prog_tests/migrate_reuseport.c b/tools/testing/selftests/bpf/prog_tests/migrate_reuseport.c
> > > index 653b0a20f..c62907732 100644
> > > --- a/tools/testing/selftests/bpf/prog_tests/migrate_reuseport.c
> > > +++ b/tools/testing/selftests/bpf/prog_tests/migrate_reuseport.c
> > > @@ -7,24 +7,29 @@
> > >   *   3. call listen() for 1 server socket. (migration target)
> > >   *   4. update a map to migrate all child sockets
> > >   *        to the last server socket (migrate_map[cookie] = 4)
> > > - *   5. call shutdown() for first 4 server sockets
> > > + *   5. for TCP_ESTABLISHED and TCP_SYN_RECV cases, verify via epoll
> > > + *        that the last server socket is not ready before migration.
> > > + *   6. call shutdown() for first 4 server sockets
> > >   *        and migrate the requests in the accept queue
> > >   *        to the last server socket.
> > > - *   6. call listen() for the second server socket.
> > > - *   7. call shutdown() for the last server
> > > + *   7. for TCP_ESTABLISHED and TCP_SYN_RECV cases, verify via epoll
> > > + *        that the last server socket is ready after migration.
> > > + *   8. call listen() for the second server socket.
> > > + *   9. call shutdown() for the last server
> > >   *        and migrate the requests in the accept queue
> > >   *        to the second server socket.
> > > - *   8. call listen() for the last server.
> > > - *   9. call shutdown() for the second server
> > > + *  10. call listen() for the last server.
> > > + *  11. call shutdown() for the second server
> > >   *        and migrate the requests in the accept queue
> > >   *        to the last server socket.
> > > - *  10. call accept() for the last server socket.
> > > + *  12. call accept() for the last server socket.
> > >   *
> > >   * Author: Kuniyuki Iwashima <kuniyu@amazon.co.jp>
> > >   */
> > >
> > >  #include <bpf/bpf.h>
> > >  #include <bpf/libbpf.h>
> > > +#include <sys/epoll.h>
> > >
> > >  #include "test_progs.h"
> > >  #include "test_migrate_reuseport.skel.h"
> > > @@ -350,21 +355,51 @@ static int update_maps(struct migrate_reuseport_test_case *test_case,
> > >
> > >  static int migrate_dance(struct migrate_reuseport_test_case *test_case)
> > >  {
> > > +       struct epoll_event ev = {
> > > +               .events = EPOLLIN,
> > > +       };
> > > +       int epoll = -1, nfds;
> > >         int i, err;
> > >
> > > +       if (test_case->state != BPF_TCP_NEW_SYN_RECV) {
> > > +               epoll = epoll_create1(0);
> > > +               if (!ASSERT_NEQ(epoll, -1, "epoll_create1"))
> > > +                       return -1;
> > > +
> > > +               ev.data.fd = test_case->servers[MIGRATED_TO];
> > > +               if (!ASSERT_OK(epoll_ctl(epoll, EPOLL_CTL_ADD,
> > > +                                        test_case->servers[MIGRATED_TO], &ev),
> > > +                              "epoll_ctl"))
> > > +                       goto close_epoll;
> > > +
> > > +               nfds = epoll_wait(epoll, &ev, 1, 0);
> > > +               if (!ASSERT_EQ(nfds, 0, "epoll_wait 1"))
> > > +                       goto close_epoll;
> > > +       }
> > > +
> > >         /* Migrate TCP_ESTABLISHED and TCP_SYN_RECV requests
> > >          * to the last listener based on eBPF.
> > >          */
> > >         for (i = 0; i < MIGRATED_TO; i++) {
> > >                 err = shutdown(test_case->servers[i], SHUT_RDWR);
> > >                 if (!ASSERT_OK(err, "shutdown"))
> > > -                       return -1;
> > > +                       goto close_epoll;
> > >         }
> > >
> > >         /* No dance for TCP_NEW_SYN_RECV to migrate based on eBPF */
> > >         if (test_case->state == BPF_TCP_NEW_SYN_RECV)
> > >                 return 0;
> > >
> > > +       nfds = epoll_wait(epoll, &ev, 1, 0);
> > > +       if (!ASSERT_EQ(nfds, 1, "epoll_wait 2")) {
> > > +close_epoll:
> > > +               if (epoll >= 0)
> > > +                       close(epoll);
> > > +               return -1;
> > > +       }
> >
> > I think you can avoid this goto using `__attribute__((cleanup(...))`:
> >
> >   static void close_fd(int *fd)
> >   {
> >         if (*fd >= 0)
> >                 close(*fd);
> >   }
> >
> >   #define __close_fd __attribute__((cleanup(close_fd)))
> >
> > then in the test:
> >
> > __close_fd int epoll = -1;
> >
> > there's an example of this in
> > tools/testing/selftests/bpf/prog_tests/socket_helpers.h.

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-04-22  6:40 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-22  2:45 [PATCH net v4 0/2] tcp: fix listener wakeup after reuseport migration Zhenzhong Wu
2026-04-22  2:45 ` [PATCH net v4 1/2] tcp: call sk_data_ready() after listener migration Zhenzhong Wu
2026-04-22  2:45 ` [PATCH net v4 2/2] selftests/bpf: check epoll readiness during reuseport migration Zhenzhong Wu
2026-04-22  3:03   ` Tamir Duberstein
2026-04-22  6:34     ` Zhenzhong Wu
2026-04-22  6:40       ` Kuniyuki Iwashima

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox