FAILED: patch "[PATCH] net: advertise TCP MSS from the configured MTU, not the" failed to apply to 6.6-stable tree
Subsystems (3)
[email protected]
maintainerThe patch below does not apply to the 6.6-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 <[email protected]>.
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-6.6.y
git checkout FETCH_HEAD
git cherry-pick -x 2640e64195948a601430d230c9864f5426574cde
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026083115-payer-hate-2a8e@gregkh' --subject-prefix 'PATCH 6.6.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 <[email protected]>
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.
include/net/ip.h +25 -0
@@ -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) {
include/net/ip6_route.h +37 -0
@@ -387,6 +387,43 @@ static inline unsigned int ip6_dst_mtu_maybe_forward(const struct dst_entry *dstreturn 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);
net/ipv4/route.c +2 -2
@@ -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);
net/ipv6/route.c +1 -1
@@ -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 );
Sasha Levin
maintainerPATCH 1/3 net: dst: introduce dst->dev_rcu
From: Eric Dumazet <[email protected]>
[ 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.
Stable-dep-of: 2640e6419594 ("net: advertise TCP MSS from the configured MTU, not the learned PMTU")
--- include/net/dst.h | 14 ++++++++++---- include/net/ip6_route.h | 3 +++ net/core/dst.c | 2 +- net/ipv4/route.c | 4 ++-- net/ipv6/route.c | 5 ++--- 5 files changed, 18 insertions(+), 10 deletions(-)
include/net/dst.h +10 -4
@@ -24,7 +24,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;
@@ -571,9 +574,12 @@ 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); +} + +static inline struct net *dst_dev_net_rcu (const struct dst_entry *dst) +{ +return dev_net_rcu (dst_dev_rcu (dst)); }static inline struct net_device *skb_dst_dev_rcu (const struct sk_buff *skb)
include/net/ip6_route.h +3 -0
@@ -266,6 +266,9 @@ 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 *)); +/* Compatibility spelling used by newer IPv6 route code. */ +#define dst6_mtu (dst) dst_mtu(dst) +static inline unsigned int ip6_skb_dst_mtu (const struct sk_buff *skb) { const struct ipv6_pinfo *np = skb->sk && !dev_recursion_level() ?
net/core/dst.c +1 -1
@@ -153,7 +153,7 @@ void dst_dev_put(struct dst_entry *dst) dst->ops->ifdown (dst, dev);WRITE_ONCE (dst->input, dst_discard);WRITE_ONCE (dst->output, dst_discard_out); - dst->dev = blackhole_netdev; +rcu_assign_pointer (dst->dev_rcu, blackhole_netdev);netdev_ref_replace (dev, blackhole_netdev, &dst->dev_tracker,GFP_ATOMIC ); }
net/ipv4/route.c +2 -2
@@ -1069,7 +1069,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); + 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);
@@ -1367,7 +1367,7 @@ static unsigned int ipv4_default_advmss(const struct dst_entry *dst)struct net *net;rcu_read_lock (); - net =dev_net_rcu (dst->dev); + 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 ();
net/ipv6/route.c +2 -3
@@ -3234,15 +3234,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;
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/3 ipv6: pass proto by value to ipv6_push_nfrag_opts() and ipv6_push_frag_opts()
From: Eric Dumazet <[email protected]>
[ Upstream commit 1bc46dd209667346c5aa56c351995e7c260696d0 ]
With CONFIG_STACKPROTECTOR_STRONG=y, it is better to avoid passing a pointer to an automatic variable.
Change these exported functions to return 'u8 proto' instead of void.
- ipv6_push_nfrag_opts()
- ipv6_push_frag_opts()
For instance, replace
ipv6_push_frag_opts(skb, opt, &proto); with: proto = ipv6_push_frag_opts(skb, opt, proto);
Note that even after this change, ip6_xmit() has to use a stack canary because of @first_hop variable.
Stable-dep-of: 2640e6419594 ("net: advertise TCP MSS from the configured MTU, not the learned PMTU")
--- include/net/ipv6.h | 10 ++++---- net/ipv6/exthdrs.c | 55 +++++++++++++++++++++++-------------------- net/ipv6/ip6_output.c | 19 ++++++++------- net/ipv6/ip6_tunnel.c | 2 +- 4 files changed, 46 insertions(+), 40 deletions(-)
include/net/ipv6.h +5 -5
@@ -1154,11 +1154,11 @@ int ip6_local_out(struct net *net, struct sock *sk, struct sk_buff *skb);* Extension header (options) processing */ -void ipv6_push_nfrag_opts (struct sk_buff *skb,struct ipv6_txoptions *opt, -u8 *proto,struct in6_addr **daddr_p, -struct in6_addr *saddr); -void ipv6_push_frag_opts (struct sk_buff *skb,struct ipv6_txoptions *opt, -u8 *proto); +u8 ipv6_push_nfrag_opts (struct sk_buff *skb,struct ipv6_txoptions *opt, +u8 proto,struct in6_addr **daddr_p, +struct in6_addr *saddr); +u8 ipv6_push_frag_opts (struct sk_buff *skb,struct ipv6_txoptions *opt, +u8 proto);int ipv6_skip_exthdr (const struct sk_buff *,int start,u8 *nexthdrp,__be16 *frag_offp);
net/ipv6/exthdrs.c +29 -26
109 lines not on this page · raw message
net/ipv6/ip6_output.c +11 -8
@@ -299,11 +299,12 @@ int ip6_xmit(const struct sock *sk, struct sk_buff *skb, struct flowi6 *fl6, seg_len += opt->opt_nflen + opt->opt_flen;if (opt->opt_flen) -ipv6_push_frag_opts (skb, opt, &proto); + proto =ipv6_push_frag_opts (skb, opt, proto);if (opt->opt_nflen) -ipv6_push_nfrag_opts (skb, opt, &proto, &first_hop, - &fl6->saddr); + proto =ipv6_push_nfrag_opts (skb, opt, proto, + &first_hop, + &fl6->saddr); }if (unlikely (seg_len >IPV6_MAXPLEN )) {
@@ -1930,11 +1931,13 @@ struct sk_buff *__ip6_make_skb(struct sock *sk,__skb_pull (skb,skb_network_header_len (skb)); final_dst = &fl6->daddr; -if (opt && opt->opt_flen) -ipv6_push_frag_opts (skb, opt, &proto); -if (opt && opt->opt_nflen) -ipv6_push_nfrag_opts (skb, opt, &proto, &final_dst, &fl6->saddr); - +if (opt) { +if (opt->opt_flen) + proto =ipv6_push_frag_opts (skb, opt, proto); +if (opt->opt_nflen) + proto =ipv6_push_nfrag_opts (skb, opt, proto, + &final_dst, &fl6->saddr); + }skb_push (skb,sizeof (struct ipv6hdr ));skb_reset_network_header (skb); hdr =ipv6_hdr (skb);
net/ipv6/ip6_tunnel.c +1 -1
@@ -1271,7 +1271,7 @@ int ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev, __u8 dsfield,if (encap_limit >=0 ) {init_tel_txopt (&opt, encap_limit); -ipv6_push_frag_opts (skb, &opt.ops, &proto); + proto =ipv6_push_frag_opts (skb, &opt.ops, proto); }skb_push (skb,sizeof (struct ipv6hdr ));
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 3/3 net: advertise TCP MSS from the configured MTU, not the learned PMTU
In reply to Sasha LevinFrom: Jiayuan Chen <[email protected]>
[ 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.
--- 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(-)
include/net/ip.h +25 -0
@@ -487,6 +487,31 @@ static inline unsigned int ip_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 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) {
include/net/ip6_route.h +37 -0
@@ -345,6 +345,43 @@ static inline unsigned int ip6_dst_mtu_maybe_forward(const struct dst_entry *dstreturn 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);
net/ipv4/route.c +2 -2
@@ -1368,8 +1368,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);
net/ipv6/route.c +1 -1
@@ -3234,7 +3234,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 );
Signature · 1 line
2.53.0