From: Shashank Mohan Jain <jain.sm@gmail.com>
To: Eric Dumazet <edumazet@kernel.org>,
Neal Cardwell <ncardwell@google.com>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
"David S. Miller" <davem@davemloft.net>
Cc: Kuniyuki Iwashima <kuniyu@google.com>,
Simon Horman <horms@kernel.org>,
netdev@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH net-next v3 1/2] tcp: keep the route window metric when SO_RCVBUF is set before connect
Date: Mon, 12 Oct 2026 18:56:21 +0530 [thread overview]
Message-ID: <20261012132622.48550-2-jain.sm@gmail.com> (raw)
In-Reply-To: <20261012132622.48550-1-jain.sm@gmail.com>
Since commit b025461303d8 ("tcp: update window_clamp when SO_RCVBUF is
set"), setsockopt(SO_RCVBUF) writes tp->window_clamp in every socket
state through tcp_set_window_clamp().
tcp_connect_init() and tcp_openreq_init_rwin() treat any non-zero
window_clamp as a clamp the user asked for and, in that case, skip the
route window metric (RTAX_WINDOW, "ip route ... window N"). A clamp left
behind by an SO_RCVBUF call therefore has two observable effects:
(1) on a route that carries an RTAX_WINDOW metric, SO_RCVBUF set before
connect() (or on a listener) changes the window and window scale
advertised in the SYN / SYN-ACK, because the route metric is no
longer applied;
(2) a window clamp the application set with setsockopt(TCP_WINDOW_CLAMP)
is overwritten by a later SO_RCVBUF on the still unconnected socket
or on a listener.
Without an RTAX_WINDOW metric there is no change: both before and after
this patch the advertised window and scale are derived from sk_rcvbuf
(tcp_full_space()), so only (1) and (2) are affected.
Keying the decision off the socket state is not enough, because the
socket is already in TCP_SYN_SENT before tcp_connect_init() runs.
SO_RCVBUF applied in that gap - through TCP_FASTOPEN_CONNECT before the
first write(), or through bpf_setsockopt() in a
BPF_SOCK_OPS_TCP_CONNECT_CB program (as samples/bpf/tcp_clamp_kern.c and
friends do) - still lands before tcp_connect_init() and still overrides
the metric.
Record instead whether window_clamp was set explicitly by the user via
setsockopt(TCP_WINDOW_CLAMP), in a new tp->window_clamp_user bit, and key
tcp_connect_init() and tcp_openreq_init_rwin() off that bit rather than
off a non-zero window_clamp. The route metric is then applied on all
pre-connection paths, regardless of socket state, whenever the clamp was
not set by the user; a passive socket inherits the decision from its
listener. SO_RCVBUF no longer overwrites a genuine TCP_WINDOW_CLAMP
either. When SOCK_RCVBUF_LOCK is set both functions still clamp
window_clamp to tcp_full_space(), and without a user clamp SO_RCVBUF
keeps updating window_clamp, so SO_RCVBUF on an established socket still
grows the advertised window as b025461303d8 intended.
Example: two netns joined by a veth pair, "ip route ... window 4096" on
the route to the peer, SO_RCVBUF set to 1 MiB before connect():
before: SYN win 65535, wscale 5
after: SYN win 2920, wscale 0
(the window scale option is still sent, with a shift of 0.)
Fixes: b025461303d8 ("tcp: update window_clamp when SO_RCVBUF is set")
Assisted-by: LLM
Signed-off-by: Shashank Mohan Jain <jain.sm@gmail.com>
---
include/linux/tcp.h | 3 ++-
net/ipv4/tcp.c | 12 ++++++++++++
net/ipv4/tcp_minisocks.c | 7 +++++--
net/ipv4/tcp_output.c | 2 +-
4 files changed, 20 insertions(+), 4 deletions(-)
diff --git a/include/linux/tcp.h b/include/linux/tcp.h
index e4d1720f4..2324841e5 100644
--- a/include/linux/tcp.h
+++ b/include/linux/tcp.h
@@ -301,7 +301,8 @@ struct tcp_sock {
* 0x5?10 << 16 + snd_wnd in net byte order
*/
u8 nonagle : 4,/* Disable Nagle algorithm? */
- rate_app_limited:1; /* rate_{delivered,interval_us} limited? */
+ rate_app_limited:1, /* rate_{delivered,interval_us} limited? */
+ window_clamp_user:1; /* TCP_WINDOW_CLAMP set by the user */
u8 received_ce_pending:4, /* Not yet transmit cnt of received_ce */
accecn_opt_sent_w_dsack:1,/* Sent ACCECN opt in previous ACK w/ D-SACK */
unused2:3;
diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
index f4c9101e5..5f364928b 100644
--- a/net/ipv4/tcp.c
+++ b/net/ipv4/tcp.c
@@ -1858,6 +1858,15 @@ int tcp_set_rcvlowat(struct sock *sk, int val)
void tcp_set_rcvbuf(struct sock *sk, int val)
{
+ /* A window clamp set explicitly via setsockopt(TCP_WINDOW_CLAMP)
+ * takes precedence: SO_RCVBUF must not overwrite it. Without such
+ * a user clamp, keep updating window_clamp from the buffer size so
+ * that SO_RCVBUF on an established socket still grows the advertised
+ * window (commit b025461303d8).
+ */
+ if (tcp_sk(sk)->window_clamp_user)
+ return;
+
tcp_set_window_clamp(sk, tcp_win_from_space(sk, val));
}
@@ -3427,6 +3436,7 @@ int tcp_disconnect(struct sock *sk, int flags)
tp->is_cwnd_limited = 0;
tp->max_packets_out = 0;
tp->window_clamp = 0;
+ tp->window_clamp_user = 0;
tp->delivered = 0;
tp->delivered_ce = 0;
tp->accecn_fail_mode = 0;
@@ -4056,6 +4066,8 @@ int do_tcp_setsockopt(struct sock *sk, int level, int optname,
case TCP_WINDOW_CLAMP:
err = tcp_set_window_clamp(sk, val);
+ if (!err)
+ tp->window_clamp_user = !!val;
break;
case TCP_QUICKACK:
diff --git a/net/ipv4/tcp_minisocks.c b/net/ipv4/tcp_minisocks.c
index 0ddfd5af6..c24dc975a 100644
--- a/net/ipv4/tcp_minisocks.c
+++ b/net/ipv4/tcp_minisocks.c
@@ -442,8 +442,11 @@ void tcp_openreq_init_rwin(struct request_sock *req,
mss = tcp_mss_clamp(tp, tcp_dst_advmss(dst));
window_clamp = READ_ONCE(tp->window_clamp);
- /* Set this up on the first call only */
- req->rsk_window_clamp = window_clamp ? : dst_metric(dst, RTAX_WINDOW);
+ /* Set this up on the first call only. Honour a window clamp the
+ * listener set via TCP_WINDOW_CLAMP; otherwise use the route metric.
+ */
+ req->rsk_window_clamp = tp->window_clamp_user ? window_clamp :
+ dst_metric(dst, RTAX_WINDOW);
/* limit the window selection if the user enforce a smaller rx buffer */
if (sk_listener->sk_userlocks & SOCK_RCVBUF_LOCK &&
diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c
index c8865205c..21b4ae303 100644
--- a/net/ipv4/tcp_output.c
+++ b/net/ipv4/tcp_output.c
@@ -4137,7 +4137,7 @@ static void tcp_connect_init(struct sock *sk)
tcp_ca_dst_init(sk, dst);
- if (!tp->window_clamp)
+ if (!tp->window_clamp_user)
WRITE_ONCE(tp->window_clamp, dst_metric(dst, RTAX_WINDOW));
tp->advmss = tcp_mss_clamp(tp, tcp_dst_advmss(dst));
--
2.43.0
next prev parent reply other threads:[~2026-10-12 13:26 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-12 13:26 [PATCH net-next v3 0/2] tcp: keep the route window metric when SO_RCVBUF is set before connect Shashank Mohan Jain
2026-10-12 13:26 ` Shashank Mohan Jain [this message]
2026-10-12 13:26 ` [PATCH net-next v3 2/2] selftests/net/packetdrill: cover SO_RCVBUF vs the route window clamp Shashank Mohan Jain
2026-10-12 13:30 ` [PATCH net-next v3 0/2] tcp: keep the route window metric when SO_RCVBUF is set before connect netdev-bot+sinfo
2026-10-12 13:34 ` shashank Jain
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261012132622.48550-2-jain.sm@gmail.com \
--to=jain.sm@gmail.com \
--cc=davem@davemloft.net \
--cc=edumazet@kernel.org \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=kuniyu@google.com \
--cc=linux-kernel@vger.kernel.org \
--cc=ncardwell@google.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox