]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
tcp: fix TFO max_qlen accounting across reuseport migration
authorJiayuan Chen <jiayuan.chen@linux.dev>
Mon, 3 Aug 2026 06:17:38 +0000 (14:17 +0800)
committerJakub Kicinski <kuba@kernel.org>
Thu, 6 Aug 2026 00:10:28 +0000 (17:10 -0700)
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: 54b92e841937 ("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>
net/ipv4/inet_connection_sock.c

index 56902bba54838b1c60d1bca419df6a630179bac6..6257459bcee2477bd393a16f1cfb14298b6a11a5 100644 (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;
 }