tcp: fix TFO max_qlen accounting across reuseport migration

A listener's TCP_FASTOPEN max_qlen stops being accurate and lets through
far more pending Fast Open requests than it was configured for.

This only shows up with SO_REUSEPORT listener migration, where closing a
listener hands its still-pending TFO children over to a surviving one.

fastopenq.qlen is charged in tcp_fastopen_create_child() when the child
is created and uncharged in reqsk_fastopen_remove() when the handshake
completes.  The uncharge follows rsk_listener of the request the child
points at, and inet_reqsk_clone() has repointed the child at a new
request owned by the new listener, so the ++ and the -- land on two
different sockets.  The new listener's qlen drifts negative and its
limit no longer binds.

Charge the new listener during migration, like reqsk_queue_migrated()
already does for queue->young and queue->qlen.

Fixes: 54b92e8419 ("tcp: Migrate TCP_ESTABLISHED/TCP_SYN_RECV sockets in accept queues.")
Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
Reviewed-by: Eric Dumazet <edumazet@google.com>
Link: https://patch.msgid.link/20260803061739.134737-1-jiayuan.chen@linux.dev
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jiayuan Chen
2026-08-03 14:17:38 +08:00
committed by Jakub Kicinski
parent 9113e98eb7
commit a0ab2ba83e

View File

@@ -943,11 +943,23 @@ static struct request_sock *inet_reqsk_clone(struct request_sock *req,
nreq->rsk_listener = sk;
/* We need not acquire fastopenq->lock
* because the child socket is locked in inet_csk_listen_stop().
*/
if (sk->sk_protocol == IPPROTO_TCP && tcp_rsk(nreq)->tfo_listener)
if (sk->sk_protocol == IPPROTO_TCP && tcp_rsk(nreq)->tfo_listener) {
struct fastopen_queue *fastopenq;
/* reqsk_fastopen_remove() will uncharge nreq->rsk_listener,
* that is @sk, so charge it here. Unlike the listener
* being closed, @sk is live and needs its lock.
*/
fastopenq = &inet_csk(sk)->icsk_accept_queue.fastopenq;
spin_lock_bh(&fastopenq->lock);
fastopenq->qlen++;
spin_unlock_bh(&fastopenq->lock);
/* We need not acquire fastopenq->lock
* because the child socket is locked in inet_csk_listen_stop().
*/
rcu_assign_pointer(tcp_sk(nreq->sk)->fastopen_rsk, nreq);
}
return nreq;
}