On Tue Jun 30, 2026 at 8:01 AM EDT, chia-yu.chang wrote: > From: Chia-Yu Chang <[email protected]> > > This patch replaces existing min_tso_segs() with tso_segs() CC callbak > for CC algorithm to provides explicit tso segment number of each data > burst and overrides tcp_tso_autosize(). > > This change provides below impacts on BPF struct_ops users: > - The callback is renamed from min_tso_segs to tso_segs > - The signature gains an extra u32 mss_now argument > - The return value semantics is changed from "floor value passed into > tcp_tso_autosize()" to "final tso_segs value", bypassing autosizing > > As a result, BPF programs shall be updated, beccause retuning a small > constans will now directly limit tso_segs instead of the minimum. > > Signed-off-by: Ilpo Järvinen <[email protected]> > Signed-off-by: Chia-Yu Chang <[email protected]>
As mentioned in v4, from a BPF standpoint this is cleaner. Feel free to add: Reviewed-by: Emil Tsalapatis <[email protected]> > --- > include/net/tcp.h | 13 +++++++++++-- > net/ipv4/bpf_tcp_ca.c | 8 +++++--- > net/ipv4/tcp_bbr.c | 13 ++++++++++--- > net/ipv4/tcp_output.c | 13 +++++++------ > tools/testing/selftests/bpf/progs/tcp_ca_kfunc.c | 8 ++++---- > 5 files changed, 37 insertions(+), 18 deletions(-) > > diff --git a/include/net/tcp.h b/include/net/tcp.h > index 6d376ea4d1c0..7fb42a0ce7da 100644 > --- a/include/net/tcp.h > +++ b/include/net/tcp.h > @@ -824,6 +824,9 @@ unsigned int tcp_sync_mss(struct sock *sk, u32 pmtu); > unsigned int tcp_current_mss(struct sock *sk); > u32 tcp_clamp_probe0_to_user_timeout(const struct sock *sk, u32 when); > > +u32 tcp_tso_autosize(const struct sock *sk, unsigned int mss_now, > + int min_tso_segs); > + > /* Bound MSS / TSO packet size with the half of the window */ > static inline int tcp_bound_to_half_wnd(struct tcp_sock *tp, int pktsize) > { > @@ -1361,8 +1364,14 @@ struct tcp_congestion_ops { > /* hook for packet ack accounting (optional) */ > void (*pkts_acked)(struct sock *sk, const struct ack_sample *sample); > > - /* override sysctl_tcp_min_tso_segs (optional) */ > - u32 (*min_tso_segs)(struct sock *sk); > + /* > + * Override tcp_tso_autosize (optional) > + * > + * If provided, this callback returns the final TSO segment number > + * and will bypass tcp_tso_autosize() entirely. The implementation > + * must derive an appropriate value and ensure the result is valid. > + */ > + u32 (*tso_segs)(struct sock *sk, u32 mss_now); > > /* new value of cwnd after loss (required) */ > u32 (*undo_cwnd)(struct sock *sk); > diff --git a/net/ipv4/bpf_tcp_ca.c b/net/ipv4/bpf_tcp_ca.c > index 791e15063237..27c4cdfd80a8 100644 > --- a/net/ipv4/bpf_tcp_ca.c > +++ b/net/ipv4/bpf_tcp_ca.c > @@ -284,9 +284,11 @@ static void bpf_tcp_ca_pkts_acked(struct sock *sk, const > struct ack_sample *samp > { > } > > -static u32 bpf_tcp_ca_min_tso_segs(struct sock *sk) > +static u32 bpf_tcp_ca_tso_segs(struct sock *sk, u32 mss_now) > { > - return 0; > + if (unlikely(!mss_now)) > + return U32_MAX; > + return tcp_tso_autosize(sk, mss_now, 0); > } > > static void bpf_tcp_ca_cong_control(struct sock *sk, u32 ack, int flag, > @@ -320,7 +322,7 @@ static struct tcp_congestion_ops > __bpf_ops_tcp_congestion_ops = { > .cwnd_event_tx_start = bpf_tcp_ca_cwnd_event_tx_start, > .in_ack_event = bpf_tcp_ca_in_ack_event, > .pkts_acked = bpf_tcp_ca_pkts_acked, > - .min_tso_segs = bpf_tcp_ca_min_tso_segs, > + .tso_segs = bpf_tcp_ca_tso_segs, > .cong_control = bpf_tcp_ca_cong_control, > .undo_cwnd = bpf_tcp_ca_undo_cwnd, > .sndbuf_expand = bpf_tcp_ca_sndbuf_expand, > diff --git a/net/ipv4/tcp_bbr.c b/net/ipv4/tcp_bbr.c > index 82378a2bfd1e..b63e77b14c65 100644 > --- a/net/ipv4/tcp_bbr.c > +++ b/net/ipv4/tcp_bbr.c > @@ -297,11 +297,18 @@ static void bbr_set_pacing_rate(struct sock *sk, u32 > bw, int gain) > } > > /* override sysctl_tcp_min_tso_segs */ > -__bpf_kfunc static u32 bbr_min_tso_segs(struct sock *sk) > +static u32 bbr_min_tso_segs(struct sock *sk) > { > return READ_ONCE(sk->sk_pacing_rate) < (bbr_min_tso_rate >> 3) ? 1 : 2; > } > > +__bpf_kfunc static u32 bbr_tso_segs(struct sock *sk, u32 mss_now) > +{ > + if (unlikely(!mss_now)) > + return U32_MAX; > + return tcp_tso_autosize(sk, mss_now, bbr_min_tso_segs(sk)); > +} > + > static u32 bbr_tso_segs_goal(struct sock *sk) > { > struct tcp_sock *tp = tcp_sk(sk); > @@ -1151,7 +1158,7 @@ static struct tcp_congestion_ops tcp_bbr_cong_ops > __read_mostly = { > .undo_cwnd = bbr_undo_cwnd, > .cwnd_event_tx_start = bbr_cwnd_event_tx_start, > .ssthresh = bbr_ssthresh, > - .min_tso_segs = bbr_min_tso_segs, > + .tso_segs = bbr_tso_segs, > .get_info = bbr_get_info, > .set_state = bbr_set_state, > }; > @@ -1163,7 +1170,7 @@ BTF_ID_FLAGS(func, bbr_sndbuf_expand) > BTF_ID_FLAGS(func, bbr_undo_cwnd) > BTF_ID_FLAGS(func, bbr_cwnd_event_tx_start) > BTF_ID_FLAGS(func, bbr_ssthresh) > -BTF_ID_FLAGS(func, bbr_min_tso_segs) > +BTF_ID_FLAGS(func, bbr_tso_segs) > BTF_ID_FLAGS(func, bbr_set_state) > BTF_KFUNCS_END(tcp_bbr_check_kfunc_ids) > > diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c > index 00ec4b5900f2..f3fc4b64e61d 100644 > --- a/net/ipv4/tcp_output.c > +++ b/net/ipv4/tcp_output.c > @@ -2253,8 +2253,8 @@ static bool tcp_nagle_check(bool partial, const struct > tcp_sock *tp, > * for every 2^9 usec (aka 512 us) of RTT, so that the RTT-based allowance > * is below 1500 bytes after 6 * ~500 usec = 3ms. > */ > -static u32 tcp_tso_autosize(const struct sock *sk, unsigned int mss_now, > - int min_tso_segs) > +u32 tcp_tso_autosize(const struct sock *sk, unsigned int mss_now, > + int min_tso_segs) > { > unsigned long bytes; > u32 r; > @@ -2269,6 +2269,7 @@ static u32 tcp_tso_autosize(const struct sock *sk, > unsigned int mss_now, > > return max_t(u32, bytes / mss_now, min_tso_segs); > } > +EXPORT_SYMBOL(tcp_tso_autosize); > > /* Return the number of segments we want in the skb we are transmitting. > * See if congestion control module wants to decide; otherwise, autosize. > @@ -2278,11 +2279,11 @@ static u32 tcp_tso_segs(struct sock *sk, unsigned int > mss_now) > const struct tcp_congestion_ops *ca_ops = inet_csk(sk)->icsk_ca_ops; > u32 min_tso, tso_segs; > > - min_tso = ca_ops->min_tso_segs ? > - ca_ops->min_tso_segs(sk) : > - READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_min_tso_segs); > + min_tso = READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_min_tso_segs); > > - tso_segs = tcp_tso_autosize(sk, mss_now, min_tso); > + tso_segs = ca_ops->tso_segs ? > + ca_ops->tso_segs(sk, mss_now) : > + tcp_tso_autosize(sk, mss_now, min_tso); > return min_t(u32, tso_segs, sk->sk_gso_max_segs); > } > > diff --git a/tools/testing/selftests/bpf/progs/tcp_ca_kfunc.c > b/tools/testing/selftests/bpf/progs/tcp_ca_kfunc.c > index 0a3e9d35bf6f..58262e490336 100644 > --- a/tools/testing/selftests/bpf/progs/tcp_ca_kfunc.c > +++ b/tools/testing/selftests/bpf/progs/tcp_ca_kfunc.c > @@ -10,7 +10,7 @@ extern u32 bbr_sndbuf_expand(struct sock *sk) __ksym; > extern u32 bbr_undo_cwnd(struct sock *sk) __ksym; > extern void bbr_cwnd_event_tx_start(struct sock *sk) __ksym; > extern u32 bbr_ssthresh(struct sock *sk) __ksym; > -extern u32 bbr_min_tso_segs(struct sock *sk) __ksym; > +extern u32 bbr_tso_segs(struct sock *sk, u32 mss_now) __ksym; > extern void bbr_set_state(struct sock *sk, u8 new_state) __ksym; > > extern void dctcp_init(struct sock *sk) __ksym; > @@ -90,9 +90,9 @@ u32 BPF_PROG(ssthresh, struct sock *sk) > } > > SEC("struct_ops") > -u32 BPF_PROG(min_tso_segs, struct sock *sk) > +u32 BPF_PROG(tso_segs, struct sock *sk, u32 mss_now) > { > - return bbr_min_tso_segs(sk); > + return bbr_tso_segs(sk, mss_now); > } > > SEC("struct_ops") > @@ -120,7 +120,7 @@ struct tcp_congestion_ops tcp_ca_kfunc = { > .cwnd_event = (void *)cwnd_event, > .cwnd_event_tx_start = (void *)cwnd_event_tx_start, > .ssthresh = (void *)ssthresh, > - .min_tso_segs = (void *)min_tso_segs, > + .tso_segs = (void *)tso_segs, > .set_state = (void *)set_state, > .pkts_acked = (void *)pkts_acked, > .name = "tcp_ca_kfunc",
