[PATCH net 0/2] tcp: fix listener wakeup after reuseport migration

From: Zhenzhong Wu

Date: Sat Apr 18 2026 - 00:17:43 EST


Hi,

this small series fixes a missing wakeup after listener migration in
the SO_REUSEPORT close path and adds regression selftests.

The issue shows up when a fully established child has already been
queued on listener A, userspace has not accepted it yet, and
listener A is then closed. The kernel migrates that child to
listener B in the same SO_REUSEPORT group via
inet_csk_reqsk_queue_add(), but the target listener's waiters are
not notified.

As a result, a nonblocking accept() still succeeds because it checks
the accept queue directly, but waiters that sleep for listener
readiness can remain asleep until another connection generates a
wakeup. This affects poll()/epoll_wait()-based waiters, and can also
leave a blocking accept() asleep after migration even though the
child is already in the target listener's accept queue.

The fix is to notify the target listener after a successful
inet_csk_reqsk_queue_add() in inet_csk_listen_stop().

I also checked the half-open migration path in
reqsk_timer_handler(). That path does not need an extra wakeup here
because the listener becomes readable only after the final ACK
completes the handshake, and tcp_child_process() already wakes the
parent listener at that point.

The series adds selftests under tools/testing/selftests/net/ that
reproduce the regression for both IPv4 and IPv6. They cover both
epoll-based waiters and a blocking accept() waiter.

Patch 1 contains only the runtime fix so it can stand on its own and
be considered for stable backporting. Patch 2 adds the selftest
coverage.

Testing:

On an unpatched host kernel:

unshare -Ur sh -c \
'./tools/testing/selftests/net/reuseport_migrate_epoll'
unshare -Ur sh -c \
'./tools/testing/selftests/net/reuseport_migrate_accept'

The epoll selftest fails for both IPv4 and IPv6 with:

accept queue was populated, but epoll_wait() timed out

The blocking accept selftest fails for both IPv4 and IPv6, for example
with:

blocking accept() completed only in cleanup

On a patched kernel booted under QEMU with a minimal initramfs, both
selftests pass:

ok 1 ipv4 epoll wake after reuseport migration
ok 2 ipv6 epoll wake after reuseport migration
reuseport_migrate_epoll_RC=0

ok 1 ipv4 blocking accept wake after reuseport migration
ok 2 ipv6 blocking accept wake after reuseport migration
reuseport_migrate_accept_RC=0

Zhenzhong Wu (2):
tcp: call sk_data_ready() after listener migration
selftests: net: add reuseport migration wakeup regression tests

net/ipv4/inet_connection_sock.c | 1 +
tools/testing/selftests/net/Makefile | 3 +
.../selftests/net/reuseport_migrate_accept.c | 533 ++++++++++++++++++
.../selftests/net/reuseport_migrate_epoll.c | 353 ++++++++++++
4 files changed, 890 insertions(+)
create mode 100644 tools/testing/selftests/net/reuseport_migrate_accept.c
create mode 100644 tools/testing/selftests/net/reuseport_migrate_epoll.c


base-commit: 52bcb57a4e8a0865a76c587c2451906342ae1b2d
--
2.43.0