From ab78c2353f79697fefac653589b1ec6efbb828c6 Mon Sep 17 00:00:00 2001 From: Lukas Prause Date: Thu, 8 Oct 2026 15:30:30 +0200 Subject: [PATCH] Update the repository with the changes made since the last LKML review. Changes since last commit: * Don't discard all rtt samples after fast recovery in min RTT probing * Enter LAUNCH after sufficient idle period * Fix outdated comments * Apply snd_cwnd_clamp everywhere * Remove possibility for double ROCCET CE in ORBITER * Fix incorrect implicit s32->u64 cast for `sent_more_than_acked` * Fix some incorrect parameter resets on roccet_reset --- tcp_roccet.c | 170 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 100 insertions(+), 70 deletions(-) diff --git a/tcp_roccet.c b/tcp_roccet.c index ebd7b8f..a2ccdcd 100644 --- a/tcp_roccet.c +++ b/tcp_roccet.c @@ -100,7 +100,7 @@ static const struct kernel_param_ops beta_param_ops = { .get = param_get_int, }; module_param_cb(beta, &beta_param_ops, &beta, 0644); -MODULE_PARM_DESC(beta, "beta for multiplicative increase"); +MODULE_PARM_DESC(beta, "beta factor for multiplicative decrease"); module_param(fast_convergence, int, 0644); MODULE_PARM_DESC(fast_convergence, "turn on/off fast convergence"); @@ -191,8 +191,14 @@ static void param_precompute(void) static void roccet_reset(struct sock *sk, struct roccettcp *ca) { + /* Save previous state for parameters that should not be reset */ + bool prev_initial_limit_reached = ca->initial_limit_reached; + bool prev_is_in_initial_launch = ca->is_in_initial_launch; + + /* Reset all the roccet parameters and later restore the ones that + * shouldn't be zero. + */ memset(ca, 0, sizeof(struct roccettcp)); - ca->next_srrtt_check_ts = 0; /* Initialize all RTT values to U32_MAX, so that any lower samples * will be accepted. @@ -206,26 +212,21 @@ static void roccet_reset(struct sock *sk, struct roccettcp *ca) * after init). */ ca->roccet_last_event_time_us = jiffies_to_usecs(tcp_jiffies32); - ca->last_event_time_set = false; - ca->ack_rate_last_rate = 0; /* Initialize to current time to avoid an * overflow in the ack rate calculation */ ca->last_ack_rate_time = jiffies_to_usecs(tcp_jiffies32); - ca->curr_ack_rate = 0; - ca->ack_rate_cnt = 0; ca->interval_snd_seq_start = tcp_sk(sk)->snd_nxt; ca->interval_una_seq_start = tcp_sk(sk)->snd_una; /* Start state is LAUNCH */ ca->state = LAUNCH; - ca->probe_min_rtt_until = 0; - ca->refill_until = 0; - ca->rtt_probe_timers_set = false; - ca->is_in_initial_launch = false; + /* Set saved state for certain parameters */ + ca->initial_limit_reached = prev_initial_limit_reached; + ca->is_in_initial_launch = prev_is_in_initial_launch; } static void roccet_init(struct sock *sk) @@ -378,9 +379,9 @@ static void update_srrtt(struct roccettcp *ca) ca->curr_min_rtt); /* (1 - alpha) * srRTT + alpha * rRTT */ - ca->curr_srrtt = ((100 - ROCCET_ALPHA_TIMES_100) * (u64)ca->curr_srrtt + - ROCCET_ALPHA_TIMES_100 * rrtt) / - 100; + ca->curr_srrtt = div_u64((100 - ROCCET_ALPHA_TIMES_100) + * (u64)ca->curr_srrtt + ROCCET_ALPHA_TIMES_100 + * rrtt, 100); } /* Handle ROCCET loss/ECN during min RTT probing. @@ -510,7 +511,7 @@ static void roccet_enter_min_rtt_probe(struct sock *sk, u32 now) ca->cwnd_before_min_rtt_probe = tcp_snd_cwnd(tp); /* Reduce the cwnd to drain the buffer for probing. */ - tcp_snd_cwnd_set(tp, probe_cwnd); + tcp_snd_cwnd_set(tp, min(probe_cwnd, tp->snd_cwnd_clamp)); /* Reset current min RTT to allow probing for * a new lower and higher minimum RTT. @@ -542,7 +543,7 @@ static void roccet_min_rtt_probe(struct sock *sk, u32 now) /* Here we are in RTT_PROBE, so the probing time should be set. * If not just continue and enter refill phase. * - * probe_min_rtt_until wrap-arounds are handled by the time_before32 + * probe_min_rtt_until wrap-arounds are handled by the time_between32 * check. */ if (!ca->rtt_probe_timers_set) @@ -571,7 +572,8 @@ static void roccet_min_rtt_probe(struct sock *sk, u32 now) if (ca->cwnd_before_min_rtt_probe == 0) pr_warn_once("TCP ROCCET: cwnd_before_min_rtt_probe is 0 during RTT_PROBE.\n"); else - tcp_snd_cwnd_set(tp, ca->cwnd_before_min_rtt_probe); + tcp_snd_cwnd_set(tp, min(ca->cwnd_before_min_rtt_probe, + tp->snd_cwnd_clamp)); ca->cwnd_before_min_rtt_probe = 0; @@ -590,24 +592,6 @@ static void roccet_rtt_probe_refill(struct roccettcp *ca, u32 now) } } -static void roccet_cwnd_event_tx_start(struct sock *sk) -{ - struct roccettcp *ca = inet_csk_ca(sk); - u32 now = tcp_jiffies32; - s32 delta; - - delta = now - tcp_sk(sk)->lsndtime; - - /* We were application limited (idle) for a while. - * Shift epoch_start to keep cwnd growth to cubic curve. - */ - if (ca->epoch_start && delta > 0) { - ca->epoch_start += delta; - if (after(ca->epoch_start, now)) - ca->epoch_start = now; - } -} - /* calculate the cubic root of x using a table lookup followed by one * Newton-Raphson iteration. * Avg err ~= 0.195% @@ -786,6 +770,8 @@ static void roccet_launch_update(struct sock *sk, u32 acked) get_ack_rate_diff(ca) <= ack_rate_diff_ss) || !tcp_in_slow_start(tp) || (!tcp_is_cwnd_limited(sk) && ca->initial_limit_reached)) { + u32 new_cwnd; + ca->epoch_start = 0; /* Handle initial LAUNCH. Most bufferbloat occurs here */ @@ -793,13 +779,12 @@ static void roccet_launch_update(struct sock *sk, u32 acked) /* Halving the cwnd will undo the previous step of slow * start. Which is fine since the pipe is already full. */ - tcp_snd_cwnd_set(tp, max(tcp_snd_cwnd(tp) / 2, - TCP_INIT_CWND)); + new_cwnd = tcp_snd_cwnd(tp) / 2; } else { - tcp_snd_cwnd_set(tp, max(tcp_snd_cwnd(tp) - - (tcp_snd_cwnd(tp) / 3), - TCP_INIT_CWND)); + new_cwnd = tcp_snd_cwnd(tp) - (tcp_snd_cwnd(tp) / 3); } + tcp_snd_cwnd_set(tp, min(max(new_cwnd, TCP_INIT_CWND), + tp->snd_cwnd_clamp)); WRITE_ONCE(tp->snd_ssthresh, tcp_snd_cwnd(tp)); ca->roccet_last_event_time_us = now; ca->last_event_time_set = true; @@ -886,10 +871,13 @@ static void roccet_orbiter_update(struct sock *sk, u32 acked) * Here we use a guard space of 1% of the current cwnd. * We do this to avoid a false positive evaluation due * to delays caused by jitter or scheduling. + * + * (s64)(s32)(sent - received) => Preserves negative wrap-behavior + * with explicit s64 conversion afterwards. */ - sent_more_than_acked = (s32)(sent - received) > - div_u64((u64)tcp_snd_cwnd(tp) * - (u64)tp->mss_cache, 100); + sent_more_than_acked = (s64)(s32)(sent - received) > + (s64)div_u64((u64)tcp_snd_cwnd(tp) * + (u64)tp->mss_cache, 100); /* Check if it's time to evaluate the srRTT */ if (!time_between32(now, ca->next_srrtt_check_ts @@ -905,6 +893,10 @@ static void roccet_orbiter_update(struct sock *sk, u32 acked) ca->interval_una_seq_start = tp->snd_una; } + /* If we are in recovery, skip growth and further ROCCET-CE's. */ + if (tcp_in_cwnd_reduction(sk)) + return; + /* Respects the jitter of the connection and add it on top of * the upper bound for the srRTT. */ @@ -927,11 +919,8 @@ static void roccet_orbiter_update(struct sock *sk, u32 acked) * bandwidth and leave the cwnd at an excessively high value. * The cwnd cannot therefore be fully utilized because it is * limited by the connection capacity. - * - * Also avoid cwnd-growth if we are in cwnd reduction phase. */ - if (!tcp_is_cwnd_limited(sk) || sent_more_than_acked || - tcp_in_cwnd_reduction(sk)) + if (!tcp_is_cwnd_limited(sk) || sent_more_than_acked) return; /* In case there are any ACKs left over from LAUNCH, @@ -1014,7 +1003,7 @@ static u32 roccet_handle_recovery(struct sock *sk) * set a state when entering certain states. * * This is used whenever a state change is possible - * (e.g. in roccet_state() or roccet_control()). + * (e.g. in roccet_state(), roccet_control(), ...). */ static void roccet_handle_state_transitions(struct sock *sk, enum roccet_state prev_state, @@ -1028,9 +1017,8 @@ static void roccet_handle_state_transitions(struct sock *sk, return; /* When we exit LAUNCH we can be sure that we are no longer in the - * initial_launch. Even on LAUNCH to LAUNCH transitions (on RTO). - * Also on any state transition from LAUNCH, we can say that the - * tcp_is_cwnd_limited() is now valid. + * initial_launch. Also on any state transition from LAUNCH, we can + * say that the tcp_is_cwnd_limited() is now valid. */ if (prev_state == LAUNCH) { ca->is_in_initial_launch = false; @@ -1086,6 +1074,36 @@ static void roccet_handle_state_transitions(struct sock *sk, } } +static void roccet_cwnd_event_tx_start(struct sock *sk) +{ + struct roccettcp *ca = inet_csk_ca(sk); + struct tcp_sock *tp = tcp_sk(sk); + u32 now = tcp_jiffies32; + enum roccet_state prev_state = ca->state; + s32 delta; + + delta = now - tcp_sk(sk)->lsndtime; + + /* We were application limited (idle) for a while. + * Shift epoch_start to keep cwnd growth to cubic curve. + */ + if (ca->epoch_start && delta > 0) { + ca->epoch_start += delta; + if (after(ca->epoch_start, now)) + ca->epoch_start = now; + } + + /* After idle periods, go into LAUNCH with slow start */ + if (READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_slow_start_after_idle) && + delta >= inet_csk(sk)->icsk_rto) { + tp->snd_ssthresh = tcp_snd_cwnd(tp); + tcp_snd_cwnd_set(tp, min(TCP_INIT_CWND, tp->snd_cwnd_clamp)); + ca->state = LAUNCH; + } + + roccet_handle_state_transitions(sk, prev_state, jiffies_to_usecs(now)); +} + /* Handle different loss-states and perform adequate cwnd reductions. */ static void roccet_state(struct sock *sk, u8 new_state) @@ -1098,9 +1116,9 @@ static void roccet_state(struct sock *sk, u8 new_state) if (new_state == TCP_CA_Loss) { roccet_reset(sk, ca); - tcp_snd_cwnd_set(tp, TCP_INIT_CWND); + tcp_snd_cwnd_set(tp, min(TCP_INIT_CWND, tp->snd_cwnd_clamp)); WRITE_ONCE(tp->snd_ssthresh, max(tp->snd_ssthresh, - TCP_INIT_CWND)); + tcp_snd_cwnd(tp))); } else if (tcp_in_cwnd_reduction(sk)) { /* In this case we don't want a further window reduction */ } else if (new_state == TCP_CA_CWR) { @@ -1116,7 +1134,7 @@ static void roccet_state(struct sock *sk, u8 new_state) } else if (new_state == TCP_CA_Recovery) { /* Directly reduce cwnd and rely on pacing */ cwnd = roccet_handle_recovery(sk); - tcp_snd_cwnd_set(tp, cwnd); + tcp_snd_cwnd_set(tp, min(cwnd, tp->snd_cwnd_clamp)); } roccet_handle_state_transitions(sk, prev_state, now); @@ -1127,14 +1145,30 @@ static void roccet_state(struct sock *sk, u8 new_state) static void roccet_acked(struct sock *sk, const struct ack_sample *sample) { struct roccettcp *ca = inet_csk_ca(sk); + bool after_fast_recovery; u32 delay; /* Some calls are for duplicates without timestamps */ if (sample->rtt_us < 0) return; - /* Discard delay samples right after fast recovery */ - if (ca->epoch_start && (s32)(tcp_jiffies32 - ca->epoch_start) < HZ) + /* Check if we are right after fast recovery. */ + after_fast_recovery = ca->epoch_start && + (s32)(tcp_jiffies32 - ca->epoch_start) < HZ; + + /* If we are not after fast recovery or we are in RTT_PROBE however, + * we want to record every new min-sample. + */ + if (!after_fast_recovery || ca->state == RTT_PROBE) { + /* Get valid sample for roccet */ + if (sample->rtt_us > 0) { + ca->last_rtt = ca->curr_rtt; + ca->curr_rtt = sample->rtt_us; + } + } + + /* Otherwise discard delay samples right after fast recovery. */ + if (after_fast_recovery) return; delay = sample->rtt_us; @@ -1145,19 +1179,14 @@ static void roccet_acked(struct sock *sk, const struct ack_sample *sample) /* first call or link delay decreases */ if (ca->delay_min == 0 || (s32)(delay - ca->delay_min) < 0) ca->delay_min = delay; - - /* Get valid sample for roccet */ - if (sample->rtt_us > 0) { - ca->last_rtt = ca->curr_rtt; - ca->curr_rtt = sample->rtt_us; - } } /* Custom Pacing Rate for ROCCET TCP. - * The code here is similar to the pacing rate adjustments in tcp_input.c - * tcp_cong_control(). In (modified) slow start we want a pacing of 200% and - * otherwise (congestion avoidance) we adjust the pacing to 100% and do not - * use the sysctl_tcp_pacing_ca_ratio. + * The code here is similar to the pacing rate adjustments in tcp_input.c's + * tcp_update_pacing_rate(). In (modified) slow start we want a pacing following the + * sysctl_tcp_pacing_ss_ratio (usually 200%) parameter and otherwise + * (congestion avoidance) we adjust the pacing to 100% and do not use the + * sysctl_tcp_pacing_ca_ratio. */ static void roccet_update_pacing_rate(struct sock *sk) { @@ -1166,11 +1195,12 @@ static void roccet_update_pacing_rate(struct sock *sk) u64 rate; /* set sk_pacing_rate to 200 % of current rate (mss * cwnd / srtt) */ - rate = (u64)tp->mss_cache * ((USEC_PER_SEC / 100) << 3); + rate = (u64)tp->mss_cache * ((div64_u64(USEC_PER_SEC, 100) << 3)); /* current rate is (cwnd * mss) / srtt - * In slow-start [1], set sk_pacing_rate to 200% the current rate. - * Otherwise, set it to 100% the current rate. + * In slow-start [1], set sk_pacing_rate to sysctl_tcp_pacing_ss_ratio + * (usually 200%) the current rate. Otherwise, set it to 100% the + * current rate. * * [1]: Normal Slow Start cond is (tp->snd_cwnd < tp->snd_ssthresh) * If snd_cwnd >= (tp->snd_ssthresh / 2), we are approaching @@ -1178,8 +1208,8 @@ static void roccet_update_pacing_rate(struct sock *sk) */ if (ca->state == RTT_PROBE || tcp_snd_cwnd(tp) >= tp->snd_ssthresh / 2) /* Pacing rate of 100% - * (instead of ipv4.sysctl_tcp_pacing_ca_ratio) - */ + * (instead of ipv4.sysctl_tcp_pacing_ca_ratio) + */ rate *= 100; else /* Here we are well inside of slow start