[PATCH net-next v3 1/2] tcp: keep the route window metric when SO_RCVBUF is set before connect
flat view
WARM1d
From: Shashank Mohan Jain <hidden>
Date: 2026-10-12 13:26:32
Also in:
lkml
Subsystem:
networking [general], networking [tcp], the rest · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Neal Cardwell, Linus Torvalds
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 <redacted>
---
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