* FAILED: patch "[PATCH] net: advertise TCP MSS from the configured MTU, not the" failed to apply to 5.15-stable tree
@ 2026-08-31 12:52 gregkh
2026-09-02 0:33 ` [PATCH 5.15.y 1/3] ipv6: mcast: Add ip6_mc_find_idev() helper Sasha Levin
0 siblings, 1 reply; 4+ messages in thread
From: gregkh @ 2026-08-31 12:52 UTC (permalink / raw)
To: jiayuan.chen, edumazet, kuba; +Cc: stable
The patch below does not apply to the 5.15-stable tree.
If someone wants it applied there, or to any other stable or longterm
tree, then please email the backport, including the original git commit
id to <stable@vger.kernel.org>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-5.15.y
git checkout FETCH_HEAD
git cherry-pick -x 2640e64195948a601430d230c9864f5426574cde
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<stable@vger.kernel.org>' --in-reply-to '2026083119-uncoated-animal-60e3@gregkh' --subject-prefix 'PATCH 5.15.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 2640e64195948a601430d230c9864f5426574cde Mon Sep 17 00:00:00 2001
From: Jiayuan Chen <jiayuan.chen@linux.dev>
Date: Sat, 15 Aug 2026 15:03:36 +0800
Subject: [PATCH] net: advertise TCP MSS from the configured MTU, not the
learned PMTU
The MSS a host puts in its SYN tells the peer how big a segment it may
send us. Right now we can shrink it with a PMTU we learned on our own
send path, which is the wrong direction entirely.
On asymmetric paths this bites - think DSR load balancers, where the
request side goes through a smaller-MTU overlay. We learn a small PMTU
going out, then advertise a small MSS, and the peer stays capped for the
whole connection even though its path back to us is wide. MSS only shows
up in the SYN and never grows back.
On symmetric paths we lose nothing by dropping it either: the peer runs
its own PMTU discovery and usually already knows the real path MTU.
So work out the advertised MSS from the configured route or device MTU
and ignore the learned PMTU. Our send side is unchanged, still clamped by
tcp_current_mss(). Add ip_dst_mtu_configured()/ip6_dst_mtu_configured()
and use them from the two default_advmss() paths.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Fixes: 164a5e7ad531 ("ipv4: ipv4_default_advmss() should use route mtu")
Cc: stable@vger.kernel.org
Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
Reviewed-by: Eric Dumazet <edumazet@google.com>
Link: https://patch.msgid.link/20260815070413.294559-1-jiayuan.chen@linux.dev
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
diff --git a/include/net/ip.h b/include/net/ip.h
index 7f2fe1a8401b..a8f57b4f4aa2 100644
--- a/include/net/ip.h
+++ b/include/net/ip.h
@@ -506,6 +506,31 @@ static inline unsigned int ip_dst_mtu_maybe_forward(const struct dst_entry *dst,
return res;
}
+/* Configured/administrative MTU of a route, for advertising the TCP MSS.
+ *
+ * Unlike ip_dst_mtu_maybe_forward(), this deliberately ignores the
+ * ICMP-learned path MTU (rt->rt_pmtu). The advertised MSS bounds what the
+ * peer may send to us and must reflect our receive capability (the device or
+ * route-configured MTU), not a path MTU learned on the reverse (send)
+ * direction, which may not apply to the peer->us path and outlives the fnhe
+ * for the whole connection. See RFC 2923 section 2.3 and the comment above
+ * tcp_advertise_mss().
+ */
+static inline unsigned int ip_dst_mtu_configured(const struct dst_entry *dst)
+{
+ unsigned int mtu, res;
+
+ rcu_read_lock();
+ mtu = dst_metric_raw(dst, RTAX_MTU);
+ if (!mtu)
+ mtu = READ_ONCE(dst_dev_rcu(dst)->mtu);
+ mtu = min_t(unsigned int, mtu, IP_MAX_MTU);
+ res = mtu - lwtunnel_headroom(dst->lwtstate, mtu);
+ rcu_read_unlock();
+
+ return res;
+}
+
static inline unsigned int ip_skb_dst_mtu(struct sock *sk,
const struct sk_buff *skb)
{
diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h
index cc045705862d..c69f1c871922 100644
--- a/include/net/ip6_route.h
+++ b/include/net/ip6_route.h
@@ -387,6 +387,43 @@ static inline unsigned int ip6_dst_mtu_maybe_forward(const struct dst_entry *dst
return mtu - lwtunnel_headroom(dst->lwtstate, mtu);
}
+/* Configured/administrative MTU of a route, for advertising the TCP MSS.
+ *
+ * Unlike ip6_dst_mtu_maybe_forward(), this ignores any ICMPv6-learned path
+ * MTU (which is kept on the RTF_CACHE exception route) and returns the MTU of
+ * the underlying route (fib6_pmtu) or the egress device. The advertised MSS
+ * bounds what the peer may send to us and must reflect our receive
+ * capability, not a path MTU learned on the reverse (send) direction. See
+ * RFC 2923 section 2.3 and the comment above tcp_advertise_mss().
+ */
+static inline unsigned int ip6_dst_mtu_configured(const struct dst_entry *dst)
+{
+ const struct rt6_info *rt = dst_rt6_info(dst);
+ const struct fib6_info *from;
+ struct inet6_dev *idev;
+ unsigned int mtu = 0;
+
+ rcu_read_lock();
+ /* IPv6 keeps the learned PMTU and the configured MTU in the same
+ * RTAX_MTU slot: the learned value sits on this (possibly RTF_CACHE)
+ * dst, the configured one on the underlying route. Reach the latter
+ * via ->from (fib6_pmtu), populated by ip6_route_info_create().
+ */
+ from = rcu_dereference(rt->from);
+ if (from)
+ mtu = from->fib6_pmtu;
+ if (!mtu) {
+ mtu = IPV6_MIN_MTU;
+ idev = __in6_dev_get(dst_dev_rcu(dst));
+ if (idev)
+ mtu = max_t(unsigned int, mtu, READ_ONCE(idev->cnf.mtu6));
+ }
+ rcu_read_unlock();
+
+ mtu = min_t(unsigned int, mtu, IP6_MAX_MTU);
+ return mtu - lwtunnel_headroom(dst->lwtstate, mtu);
+}
+
u32 ip6_mtu_from_fib6(const struct fib6_result *res,
const struct in6_addr *daddr,
const struct in6_addr *saddr);
diff --git a/net/ipv4/route.c b/net/ipv4/route.c
index 604cc51dfd9b..37674d76f90f 100644
--- a/net/ipv4/route.c
+++ b/net/ipv4/route.c
@@ -1363,8 +1363,8 @@ static unsigned int ipv4_default_advmss(const struct dst_entry *dst)
rcu_read_lock();
net = dst_dev_net_rcu(dst);
- advmss = max_t(unsigned int, ipv4_mtu(dst) - header_size,
- net->ipv4.ip_rt_min_advmss);
+ advmss = max_t(unsigned int, ip_dst_mtu_configured(dst) - header_size,
+ net->ipv4.ip_rt_min_advmss);
rcu_read_unlock();
return min(advmss, IPV4_MAX_PMTU - header_size);
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index 526535387512..93b4168044d9 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -3261,7 +3261,7 @@ void ip6_sk_redirect(struct sk_buff *skb, struct sock *sk)
static unsigned int ip6_default_advmss(const struct dst_entry *dst)
{
- unsigned int mtu = dst6_mtu(dst);
+ unsigned int mtu = ip6_dst_mtu_configured(dst);
struct net *net;
mtu -= sizeof(struct ipv6hdr) + sizeof(struct tcphdr);
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 5.15.y 1/3] ipv6: mcast: Add ip6_mc_find_idev() helper
2026-08-31 12:52 FAILED: patch "[PATCH] net: advertise TCP MSS from the configured MTU, not the" failed to apply to 5.15-stable tree gregkh
@ 2026-09-02 0:33 ` Sasha Levin
2026-09-02 0:33 ` [PATCH 5.15.y 2/3] net: dst: introduce dst->dev_rcu Sasha Levin
2026-09-02 0:33 ` [PATCH 5.15.y 3/3] net: advertise TCP MSS from the configured MTU, not the learned PMTU Sasha Levin
0 siblings, 2 replies; 4+ messages in thread
From: Sasha Levin @ 2026-09-02 0:33 UTC (permalink / raw)
To: stable; +Cc: Yue Haibing, Dawid Osuchowski, Jakub Kicinski, Sasha Levin
From: Yue Haibing <yuehaibing@huawei.com>
[ Upstream commit 60c481d4caa569001c708d4e9622d19650b6bedc ]
Extract the same code logic from __ipv6_sock_mc_join() and
ip6_mc_find_dev(), also add new helper ip6_mc_find_idev() to
reduce redundancy and enhance readability.
No functional changes intended.
Signed-off-by: Yue Haibing <yuehaibing@huawei.com>
Reviewed-by: Dawid Osuchowski <dawid.osuchowski@linux.intel.com>
Link: https://patch.msgid.link/20250822064051.2991480-1-yuehaibing@huawei.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Extract the same code logic from __ipv6_sock_mc_join() and ip6_mc_find_dev(), also add new helper ip6_mc_find_idev() to reduce redundancy and enhance readability.
No functional changes intended.
Stable 5.15 note: omit the multicast refactor, which is unrelated to the dependent MSS fix and conflicts with the stable multicast lifetime model. Repurpose this dependency as a context bridge by carrying the prerequisite per-netns IPv4 min_adv_mss conversion and compatibility macros needed by the newer route code.
[ sashal: Reduced backport -- upstream 60c481d4caa56 touches 1 file(s), this
backport carries 6. Not backported here:
net/ipv6/mcast.c
This note is generated from the file lists only; see the resolution record
for the reasoning. ]
Stable-dep-of: 2640e6419594 ("net: advertise TCP MSS from the configured MTU, not the learned PMTU")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
include/net/dst.h | 2 ++
include/net/ip6_fib.h | 2 ++
include/net/ip6_route.h | 2 ++
include/net/netns/ipv4.h | 1 +
net/ipv4/route.c | 27 +++++++++++++++++----------
net/ipv6/route.c | 5 ++---
6 files changed, 26 insertions(+), 13 deletions(-)
diff --git a/include/net/dst.h b/include/net/dst.h
index 9a23986f1071a..9de6b49cfd3dd 100644
--- a/include/net/dst.h
+++ b/include/net/dst.h
@@ -561,6 +561,8 @@ static inline struct net_device *dst_dev_rcu(const struct dst_entry *dst)
return READ_ONCE(dst->dev);
}
+#define dst_dev_net_rcu(dst) dev_net_rcu(dst_dev_rcu(dst))
+
static inline struct net_device *skb_dst_dev_rcu(const struct sk_buff *skb)
{
return dst_dev_rcu(skb_dst(skb));
diff --git a/include/net/ip6_fib.h b/include/net/ip6_fib.h
index d72cee4dff70b..275d35f0a5514 100644
--- a/include/net/ip6_fib.h
+++ b/include/net/ip6_fib.h
@@ -239,6 +239,8 @@ struct fib6_result {
for (rt = (w)->leaf; rt; \
rt = rcu_dereference_protected(rt->fib6_next, 1))
+#define dst_rt6_info(ptr) ((struct rt6_info *)(ptr))
+
static inline struct inet6_dev *ip6_dst_idev(struct dst_entry *dst)
{
return ((struct rt6_info *)dst)->rt6i_idev;
diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h
index 5efd0b71dc673..ebab85b70641a 100644
--- a/include/net/ip6_route.h
+++ b/include/net/ip6_route.h
@@ -263,6 +263,8 @@ static inline bool ipv6_anycast_destination(const struct dst_entry *dst,
int ip6_fragment(struct net *net, struct sock *sk, struct sk_buff *skb,
int (*output)(struct net *, struct sock *, struct sk_buff *));
+#define dst6_mtu(dst) dst_mtu(dst)
+
static inline unsigned int ip6_skb_dst_mtu(struct sk_buff *skb)
{
unsigned int mtu;
diff --git a/include/net/netns/ipv4.h b/include/net/netns/ipv4.h
index 8bc0d865338e4..a94de70312654 100644
--- a/include/net/netns/ipv4.h
+++ b/include/net/netns/ipv4.h
@@ -86,6 +86,7 @@ struct netns_ipv4 {
u32 ip_rt_min_pmtu;
int ip_rt_mtu_expires;
+ int ip_rt_min_advmss;
struct local_ports ip_local_ports;
diff --git a/net/ipv4/route.c b/net/ipv4/route.c
index 5b277e12f3984..f61bc949f601a 100644
--- a/net/ipv4/route.c
+++ b/net/ipv4/route.c
@@ -120,6 +120,7 @@
#define DEFAULT_MIN_PMTU (512 + 20 + 20)
#define DEFAULT_MTU_EXPIRES (10 * 60 * HZ)
+#define DEFAULT_MIN_ADVMSS 256
static int ip_rt_max_size;
static int ip_rt_redirect_number __read_mostly = 9;
@@ -127,7 +128,6 @@ static int ip_rt_redirect_load __read_mostly = HZ / 50;
static int ip_rt_redirect_silence __read_mostly = ((HZ / 50) << (9 + 1));
static int ip_rt_error_cost __read_mostly = HZ;
static int ip_rt_error_burst __read_mostly = 5 * HZ;
-static int ip_rt_min_advmss __read_mostly = 256;
static int ip_rt_gc_timeout __read_mostly = RT_GC_TIMEOUT;
@@ -1372,8 +1372,14 @@ static void set_class_tag(struct rtable *rt, u32 tag)
static unsigned int ipv4_default_advmss(const struct dst_entry *dst)
{
unsigned int header_size = sizeof(struct tcphdr) + sizeof(struct iphdr);
- unsigned int advmss = max_t(unsigned int, ipv4_mtu(dst) - header_size,
- ip_rt_min_advmss);
+ unsigned int advmss;
+ struct net *net;
+
+ rcu_read_lock();
+ net = dst_dev_net_rcu(dst);
+ advmss = max_t(unsigned int, ipv4_mtu(dst) - header_size,
+ net->ipv4.ip_rt_min_advmss);
+ rcu_read_unlock();
return min(advmss, IPV4_MAX_PMTU - header_size);
}
@@ -3579,13 +3585,6 @@ static struct ctl_table ipv4_route_table[] = {
.mode = 0644,
.proc_handler = proc_dointvec,
},
- {
- .procname = "min_adv_mss",
- .data = &ip_rt_min_advmss,
- .maxlen = sizeof(int),
- .mode = 0644,
- .proc_handler = proc_dointvec,
- },
{ }
};
@@ -3613,6 +3612,13 @@ static struct ctl_table ipv4_route_netns_table[] = {
.mode = 0644,
.proc_handler = proc_dointvec_jiffies,
},
+ {
+ .procname = "min_adv_mss",
+ .data = &init_net.ipv4.ip_rt_min_advmss,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ },
{ },
};
@@ -3675,6 +3681,7 @@ static __net_init int netns_ip_rt_init(struct net *net)
/* Set default value for namespaceified sysctls */
net->ipv4.ip_rt_min_pmtu = DEFAULT_MIN_PMTU;
net->ipv4.ip_rt_mtu_expires = DEFAULT_MTU_EXPIRES;
+ net->ipv4.ip_rt_min_advmss = DEFAULT_MIN_ADVMSS;
return 0;
}
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index 43b8995142e2e..8d3dc03238682 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -3183,15 +3183,14 @@ EXPORT_SYMBOL_GPL(ip6_sk_redirect);
static unsigned int ip6_default_advmss(const struct dst_entry *dst)
{
- struct net_device *dev = dst->dev;
- unsigned int mtu = dst_mtu(dst);
+ unsigned int mtu = dst6_mtu(dst);
struct net *net;
mtu -= sizeof(struct ipv6hdr) + sizeof(struct tcphdr);
rcu_read_lock();
- net = dev_net_rcu(dev);
+ net = dst_dev_net_rcu(dst);
if (mtu < net->ipv6.sysctl.ip6_rt_min_advmss)
mtu = net->ipv6.sysctl.ip6_rt_min_advmss;
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 5.15.y 2/3] net: dst: introduce dst->dev_rcu
2026-09-02 0:33 ` [PATCH 5.15.y 1/3] ipv6: mcast: Add ip6_mc_find_idev() helper Sasha Levin
@ 2026-09-02 0:33 ` Sasha Levin
2026-09-02 0:33 ` [PATCH 5.15.y 3/3] net: advertise TCP MSS from the configured MTU, not the learned PMTU Sasha Levin
1 sibling, 0 replies; 4+ messages in thread
From: Sasha Levin @ 2026-09-02 0:33 UTC (permalink / raw)
To: stable; +Cc: Eric Dumazet, David Ahern, Jakub Kicinski, Sasha Levin
From: Eric Dumazet <edumazet@google.com>
[ Upstream commit caedcc5b6df1b2e2b5f39079e3369c1d4d5c5f50 ]
Followup of commit 88fe14253e18 ("net: dst: add four helpers
to annotate data-races around dst->dev").
We want to gradually add explicit RCU protection to dst->dev,
including lockdep support.
Add an union to alias dst->dev_rcu and dst->dev.
Add dst_dev_net_rcu() helper.
Fixes: 4a6ce2b6f2ec ("net: introduce a new function dst_dev_put()")
Signed-off-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: David Ahern <dsahern@kernel.org>
Link: https://patch.msgid.link/20250828195823.3958522-2-edumazet@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Stable-dep-of: 2640e6419594 ("net: advertise TCP MSS from the configured MTU, not the learned PMTU")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
include/net/dst.h | 11 ++++++-----
include/net/ip.h | 15 +++++++++++----
net/core/dst.c | 2 +-
net/ipv4/route.c | 2 +-
4 files changed, 19 insertions(+), 11 deletions(-)
diff --git a/include/net/dst.h b/include/net/dst.h
index 9de6b49cfd3dd..0b30871014742 100644
--- a/include/net/dst.h
+++ b/include/net/dst.h
@@ -23,7 +23,10 @@
struct sk_buff;
struct dst_entry {
- struct net_device *dev;
+ union {
+ struct net_device *dev;
+ struct net_device __rcu *dev_rcu;
+ };
struct dst_ops *ops;
unsigned long _metrics;
unsigned long expires;
@@ -556,9 +559,7 @@ static inline void skb_dst_update_pmtu_no_confirm(struct sk_buff *skb, u32 mtu)
static inline struct net_device *dst_dev_rcu(const struct dst_entry *dst)
{
- /* In the future, use rcu_dereference(dst->dev) */
- WARN_ON_ONCE(!rcu_read_lock_held());
- return READ_ONCE(dst->dev);
+ return rcu_dereference(dst->dev_rcu);
}
#define dst_dev_net_rcu(dst) dev_net_rcu(dst_dev_rcu(dst))
@@ -585,7 +586,7 @@ static inline struct net *skb_dst_dev_net(const struct sk_buff *skb)
static inline struct net *skb_dst_dev_net_rcu(const struct sk_buff *skb)
{
- return dev_net_rcu(skb_dst_dev(skb));
+ return dev_net_rcu(skb_dst_dev_rcu(skb));
}
struct dst_entry *dst_blackhole_check(struct dst_entry *dst, u32 cookie);
diff --git a/include/net/ip.h b/include/net/ip.h
index e25a2d5519c5e..8c9f7b40d6fef 100644
--- a/include/net/ip.h
+++ b/include/net/ip.h
@@ -441,8 +441,13 @@ static inline unsigned int ip_dst_mtu_maybe_forward(const struct dst_entry *dst,
bool forwarding)
{
const struct rtable *rt = container_of(dst, struct rtable, dst);
- struct net *net = dev_net(dst_dev(dst));
- unsigned int mtu;
+ const struct net_device *dev;
+ unsigned int mtu, res;
+ struct net *net;
+
+ rcu_read_lock();
+ dev = dst_dev_rcu(dst);
+ net = dev_net_rcu(dev);
if (READ_ONCE(net->ipv4.sysctl_ip_fwd_use_pmtu) ||
ip_mtu_locked(dst) ||
@@ -457,7 +462,7 @@ static inline unsigned int ip_dst_mtu_maybe_forward(const struct dst_entry *dst,
if (mtu)
goto out;
- mtu = READ_ONCE(dst_dev(dst)->mtu);
+ mtu = READ_ONCE(dev->mtu);
if (unlikely(ip_mtu_locked(dst))) {
if (rt->rt_uses_gateway && mtu > 576)
@@ -466,8 +471,10 @@ static inline unsigned int ip_dst_mtu_maybe_forward(const struct dst_entry *dst,
out:
mtu = min_t(unsigned int, mtu, IP_MAX_MTU);
+ res = mtu - lwtunnel_headroom(dst->lwtstate, mtu);
+ rcu_read_unlock();
- return mtu - lwtunnel_headroom(dst->lwtstate, mtu);
+ return res;
}
static inline unsigned int ip_skb_dst_mtu(struct sock *sk,
diff --git a/net/core/dst.c b/net/core/dst.c
index 8707b7ffe4210..4376f04ac5145 100644
--- a/net/core/dst.c
+++ b/net/core/dst.c
@@ -151,7 +151,7 @@ void dst_dev_put(struct dst_entry *dst)
dst->ops->ifdown(dst, dev, true);
WRITE_ONCE(dst->input, dst_discard);
WRITE_ONCE(dst->output, dst_discard_out);
- WRITE_ONCE(dst->dev, blackhole_netdev);
+ rcu_assign_pointer(dst->dev_rcu, blackhole_netdev);
dev_hold(dst->dev);
dev_put(dev);
}
diff --git a/net/ipv4/route.c b/net/ipv4/route.c
index f61bc949f601a..65b539c94125f 100644
--- a/net/ipv4/route.c
+++ b/net/ipv4/route.c
@@ -1074,7 +1074,7 @@ static void __ip_rt_update_pmtu(struct rtable *rt, struct flowi4 *fl4, u32 mtu)
return;
rcu_read_lock();
- net = dev_net_rcu(dst_dev(dst));
+ net = dst_dev_net_rcu(dst);
if (mtu < net->ipv4.ip_rt_min_pmtu) {
lock = true;
mtu = min(old_mtu, net->ipv4.ip_rt_min_pmtu);
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 5.15.y 3/3] net: advertise TCP MSS from the configured MTU, not the learned PMTU
2026-09-02 0:33 ` [PATCH 5.15.y 1/3] ipv6: mcast: Add ip6_mc_find_idev() helper Sasha Levin
2026-09-02 0:33 ` [PATCH 5.15.y 2/3] net: dst: introduce dst->dev_rcu Sasha Levin
@ 2026-09-02 0:33 ` Sasha Levin
1 sibling, 0 replies; 4+ messages in thread
From: Sasha Levin @ 2026-09-02 0:33 UTC (permalink / raw)
To: stable; +Cc: Jiayuan Chen, Eric Dumazet, Jakub Kicinski, Sasha Levin
From: Jiayuan Chen <jiayuan.chen@linux.dev>
[ Upstream commit 2640e64195948a601430d230c9864f5426574cde ]
The MSS a host puts in its SYN tells the peer how big a segment it may
send us. Right now we can shrink it with a PMTU we learned on our own
send path, which is the wrong direction entirely.
On asymmetric paths this bites - think DSR load balancers, where the
request side goes through a smaller-MTU overlay. We learn a small PMTU
going out, then advertise a small MSS, and the peer stays capped for the
whole connection even though its path back to us is wide. MSS only shows
up in the SYN and never grows back.
On symmetric paths we lose nothing by dropping it either: the peer runs
its own PMTU discovery and usually already knows the real path MTU.
So work out the advertised MSS from the configured route or device MTU
and ignore the learned PMTU. Our send side is unchanged, still clamped by
tcp_current_mss(). Add ip_dst_mtu_configured()/ip6_dst_mtu_configured()
and use them from the two default_advmss() paths.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Fixes: 164a5e7ad531 ("ipv4: ipv4_default_advmss() should use route mtu")
Cc: stable@vger.kernel.org
Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
Reviewed-by: Eric Dumazet <edumazet@google.com>
Link: https://patch.msgid.link/20260815070413.294559-1-jiayuan.chen@linux.dev
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
include/net/ip.h | 25 +++++++++++++++++++++++++
include/net/ip6_route.h | 37 +++++++++++++++++++++++++++++++++++++
net/ipv4/route.c | 4 ++--
net/ipv6/route.c | 2 +-
4 files changed, 65 insertions(+), 3 deletions(-)
diff --git a/include/net/ip.h b/include/net/ip.h
index 8c9f7b40d6fef..28740cc6f3167 100644
--- a/include/net/ip.h
+++ b/include/net/ip.h
@@ -477,6 +477,31 @@ static inline unsigned int ip_dst_mtu_maybe_forward(const struct dst_entry *dst,
return res;
}
+/* Configured/administrative MTU of a route, for advertising the TCP MSS.
+ *
+ * Unlike ip_dst_mtu_maybe_forward(), this deliberately ignores the
+ * ICMP-learned path MTU (rt->rt_pmtu). The advertised MSS bounds what the
+ * peer may send to us and must reflect our receive capability (the device or
+ * route-configured MTU), not a path MTU learned on the reverse (send)
+ * direction, which may not apply to the peer->us path and outlives the fnhe
+ * for the whole connection. See RFC 2923 section 2.3 and the comment above
+ * tcp_advertise_mss().
+ */
+static inline unsigned int ip_dst_mtu_configured(const struct dst_entry *dst)
+{
+ unsigned int mtu, res;
+
+ rcu_read_lock();
+ mtu = dst_metric_raw(dst, RTAX_MTU);
+ if (!mtu)
+ mtu = READ_ONCE(dst_dev_rcu(dst)->mtu);
+ mtu = min_t(unsigned int, mtu, IP_MAX_MTU);
+ res = mtu - lwtunnel_headroom(dst->lwtstate, mtu);
+ rcu_read_unlock();
+
+ return res;
+}
+
static inline unsigned int ip_skb_dst_mtu(struct sock *sk,
const struct sk_buff *skb)
{
diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h
index ebab85b70641a..7b5006fc86297 100644
--- a/include/net/ip6_route.h
+++ b/include/net/ip6_route.h
@@ -341,6 +341,43 @@ static inline unsigned int ip6_dst_mtu_maybe_forward(const struct dst_entry *dst
return mtu - lwtunnel_headroom(dst->lwtstate, mtu);
}
+/* Configured/administrative MTU of a route, for advertising the TCP MSS.
+ *
+ * Unlike ip6_dst_mtu_maybe_forward(), this ignores any ICMPv6-learned path
+ * MTU (which is kept on the RTF_CACHE exception route) and returns the MTU of
+ * the underlying route (fib6_pmtu) or the egress device. The advertised MSS
+ * bounds what the peer may send to us and must reflect our receive
+ * capability, not a path MTU learned on the reverse (send) direction. See
+ * RFC 2923 section 2.3 and the comment above tcp_advertise_mss().
+ */
+static inline unsigned int ip6_dst_mtu_configured(const struct dst_entry *dst)
+{
+ const struct rt6_info *rt = dst_rt6_info(dst);
+ const struct fib6_info *from;
+ struct inet6_dev *idev;
+ unsigned int mtu = 0;
+
+ rcu_read_lock();
+ /* IPv6 keeps the learned PMTU and the configured MTU in the same
+ * RTAX_MTU slot: the learned value sits on this (possibly RTF_CACHE)
+ * dst, the configured one on the underlying route. Reach the latter
+ * via ->from (fib6_pmtu), populated by ip6_route_info_create().
+ */
+ from = rcu_dereference(rt->from);
+ if (from)
+ mtu = from->fib6_pmtu;
+ if (!mtu) {
+ mtu = IPV6_MIN_MTU;
+ idev = __in6_dev_get(dst_dev_rcu(dst));
+ if (idev)
+ mtu = max_t(unsigned int, mtu, READ_ONCE(idev->cnf.mtu6));
+ }
+ rcu_read_unlock();
+
+ mtu = min_t(unsigned int, mtu, IP6_MAX_MTU);
+ return mtu - lwtunnel_headroom(dst->lwtstate, mtu);
+}
+
u32 ip6_mtu_from_fib6(const struct fib6_result *res,
const struct in6_addr *daddr,
const struct in6_addr *saddr);
diff --git a/net/ipv4/route.c b/net/ipv4/route.c
index 65b539c94125f..654ff3ecb6869 100644
--- a/net/ipv4/route.c
+++ b/net/ipv4/route.c
@@ -1377,8 +1377,8 @@ static unsigned int ipv4_default_advmss(const struct dst_entry *dst)
rcu_read_lock();
net = dst_dev_net_rcu(dst);
- advmss = max_t(unsigned int, ipv4_mtu(dst) - header_size,
- net->ipv4.ip_rt_min_advmss);
+ advmss = max_t(unsigned int, ip_dst_mtu_configured(dst) - header_size,
+ net->ipv4.ip_rt_min_advmss);
rcu_read_unlock();
return min(advmss, IPV4_MAX_PMTU - header_size);
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index 8d3dc03238682..c925ba10eb96a 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -3183,7 +3183,7 @@ EXPORT_SYMBOL_GPL(ip6_sk_redirect);
static unsigned int ip6_default_advmss(const struct dst_entry *dst)
{
- unsigned int mtu = dst6_mtu(dst);
+ unsigned int mtu = ip6_dst_mtu_configured(dst);
struct net *net;
mtu -= sizeof(struct ipv6hdr) + sizeof(struct tcphdr);
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-09-02 0:33 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-31 12:52 FAILED: patch "[PATCH] net: advertise TCP MSS from the configured MTU, not the" failed to apply to 5.15-stable tree gregkh
2026-09-02 0:33 ` [PATCH 5.15.y 1/3] ipv6: mcast: Add ip6_mc_find_idev() helper Sasha Levin
2026-09-02 0:33 ` [PATCH 5.15.y 2/3] net: dst: introduce dst->dev_rcu Sasha Levin
2026-09-02 0:33 ` [PATCH 5.15.y 3/3] net: advertise TCP MSS from the configured MTU, not the learned PMTU Sasha Levin
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.