From 956f195ddc99c73ff4e900c8617784757232cbaa Mon Sep 17 00:00:00 2001 From: Lukas Prause Date: Wed, 30 Sep 2026 12:47:22 +0200 Subject: [PATCH] Update the repository with the changes made since the last LKML review. --- tcp_roccet.c | 380 ++++++++++++++++++++++++++++++++++++--------------- tcp_roccet.h | 21 +-- 2 files changed, 283 insertions(+), 118 deletions(-) diff --git a/tcp_roccet.c b/tcp_roccet.c index aafe752..ebd7b8f 100644 --- a/tcp_roccet.c +++ b/tcp_roccet.c @@ -67,6 +67,13 @@ /* min RTT probe period in ms */ #define ROCCET_NEXT_MIN_RTT_PROBE 5000 +/* The maximum interval length of a min RTT probe + * (and consequently also the max interval for the refill). + */ +#define ROCCET_MAX_PROBE_INTERVAL_MS 10000 + +#define ROCCET_MAX_SRRTT_CHECK_INTERVAL_MS (5 * 200) + /* Parameters that are specific to the ROCCET-Algorithm */ static uint sr_rtt_upper_bound __read_mostly = 100; static int ack_rate_diff_ss __read_mostly = 10; @@ -87,11 +94,16 @@ static u32 cube_rtt_scale __read_mostly; static u32 beta_scale __read_mostly; static u64 cube_factor __read_mostly; -/* Note parameters that are used for precomputing scale factors are read-only */ +static int beta_param_set(const char *val, const struct kernel_param *kp); +static const struct kernel_param_ops beta_param_ops = { + .set = beta_param_set, + .get = param_get_int, +}; +module_param_cb(beta, &beta_param_ops, &beta, 0644); +MODULE_PARM_DESC(beta, "beta for multiplicative increase"); + module_param(fast_convergence, int, 0644); MODULE_PARM_DESC(fast_convergence, "turn on/off fast convergence"); -module_param(beta, int, 0644); -MODULE_PARM_DESC(beta, "beta for multiplicative increase"); module_param(initial_ssthresh, int, 0644); MODULE_PARM_DESC(initial_ssthresh, "initial value of slow start threshold"); module_param(bic_scale, int, 0444); @@ -100,6 +112,26 @@ MODULE_PARM_DESC(bic_scale, module_param(tcp_friendliness, int, 0644); MODULE_PARM_DESC(tcp_friendliness, "turn on/off tcp friendliness"); +/* Check & set the new beta parameter candidate value. + */ +static int beta_param_set(const char *val, const struct kernel_param *kp) +{ + int beta_candidate; + int ret = kstrtoint(val, 10, &beta_candidate); + + if (ret) + return ret; + + if (beta_candidate <= 0 || beta_candidate >= BICTCP_BETA_SCALE) { + pr_err_once("TCP ROCCET: beta must be between 0 and %d\n", + BICTCP_BETA_SCALE); + + return -EINVAL; + } + + return param_set_int(val, kp); +} + /* Used to check certain roccet parameters used in `param_precompute` in order * to avoid invalid scale-related calculations. This validates the specified * parameters or rejects them. @@ -169,14 +201,19 @@ static void roccet_reset(struct sock *sk, struct roccettcp *ca) ca->curr_rtt = U32_MAX; ca->last_rtt = U32_MAX; - ca->roccet_last_event_time_us = 0; + /* Initialize the last event time to the current time, to use it for + * temporarily knowing when the connection started (only used for this + * 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->ack_rate_last_rate_time = jiffies_to_usecs(tcp_jiffies32); - ca->ack_rate_curr_rate = 0; + 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; @@ -186,8 +223,8 @@ static void roccet_reset(struct sock *sk, struct roccettcp *ca) ca->state = LAUNCH; ca->probe_min_rtt_until = 0; ca->refill_until = 0; + ca->rtt_probe_timers_set = false; - ca->initial_limit_reached = false; ca->is_in_initial_launch = false; } @@ -197,7 +234,8 @@ static void roccet_init(struct sock *sk) roccet_reset(sk, ca); - /* Reset here, so it is only set during init */ + /* Reset some parameters here, so they only get set during init */ + ca->initial_limit_reached = false; ca->is_in_initial_launch = true; if (initial_ssthresh) @@ -206,6 +244,32 @@ static void roccet_init(struct sock *sk) cmpxchg(&sk->sk_pacing_status, SK_PACING_NONE, SK_PACING_NEEDED); } +static void roccet_set_next_srrtt_check_ts(struct roccettcp *ca, u32 now) +{ + /* Check the srrtt after 5 RTT's. Cap the assumed RTT's + * at 200 ms each => 1 sec max. + */ + ca->next_srrtt_check_ts = now + min(5 * ca->curr_rtt, + ROCCET_MAX_SRRTT_CHECK_INTERVAL_MS + * USEC_PER_MSEC); +} + +static void roccet_set_next_min_rtt_probe_ts(struct roccettcp *ca) +{ + /* Probe for the min RTT in ROCCET_NEXT_MIN_RTT_PROBE_MS + * if no other update occurs. + * If we are in either RTT_PROBE or RTT_PROBE_REFILL, don't set + * the next probe time so we know when we approximately entered + * the probing phase. + */ + + if (ca->state == RTT_PROBE || ca->state == RTT_PROBE_REFILL) + return; + + ca->next_min_rtt_probe = jiffies_to_usecs(tcp_jiffies32) + + ROCCET_NEXT_MIN_RTT_PROBE_MS * USEC_PER_MSEC; +} + static void update_min_rtt(struct roccettcp *ca) { /* Check if new lower min RTT was found. If so, set it directly. @@ -214,12 +278,7 @@ static void update_min_rtt(struct roccettcp *ca) */ if (ca->curr_rtt < ca->curr_min_rtt) { ca->curr_min_rtt = max(ca->curr_rtt, 1); - /* Probe for the min RTT in ROCCET_NEXT_MIN_RTT_PROBE ms - * if no other update occurs. - */ - ca->next_min_rtt_probe = - jiffies_to_usecs(tcp_jiffies32) + - ROCCET_NEXT_MIN_RTT_PROBE * USEC_PER_MSEC; + roccet_set_next_min_rtt_probe_ts(ca); } } @@ -227,9 +286,9 @@ static void update_min_rtt(struct roccettcp *ca) */ static s32 get_ack_rate_diff(struct roccettcp *ca) { - if (ca->ack_rate_curr_rate < ca->ack_rate_last_rate) + if (ca->curr_ack_rate < ca->ack_rate_last_rate) return 0; - return (s32)(ca->ack_rate_curr_rate - ca->ack_rate_last_rate); + return (s32)(ca->curr_ack_rate - ca->ack_rate_last_rate); } /* Update ack rate sampled by 100ms. @@ -239,37 +298,41 @@ static void update_ack_rate(struct roccettcp *ca, u32 acked, u32 now) const s32 idle_threshold = USEC_PER_SEC * 2; const s32 interval = USEC_PER_MSEC * 100; - s32 time_delta = (s32)(ca->ack_rate_last_rate_time - now); - - /* Check if the time has arrived in the new interval. - * Alternatively if the connection was considered to be idle, - * also treat as a new interval in order to avoid timing-overflow - * problems. + /* Check if the last interval has elapsed. + * + * Check via time_between32, so that idle connections + * (for more than U32_MAX/2 usecs), also trigger a new update + * and don't stall the ack-counting. */ - if (time_delta < -interval || ca->was_idle) { - /* Check if the connection was idle for X seconds - * (e.g. no ACK for X seconds) + bool interval_elapsed = !time_between32(now, ca->last_ack_rate_time + - interval, + ca->last_ack_rate_time + + interval); + + if (interval_elapsed) { + /* Check if the connection was idle for *idle_threshold* + * seconds (e.g. no ACK for X seconds). If so, reset the ack + * counting as if a new connection was created. */ - if (time_delta < -idle_threshold) { - /* Reset ack counting as if a new - * connection was created - */ + if (!time_between32(now, ca->last_ack_rate_time + - idle_threshold, + ca->last_ack_rate_time + + idle_threshold)) { ca->ack_rate_last_rate = 0; - ca->ack_rate_last_rate_time = - jiffies_to_usecs(tcp_jiffies32); - ca->ack_rate_curr_rate = 0; - ca->ack_rate_cnt = 0; + ca->curr_ack_rate = 0; } else { /* start counting for the new interval */ - ca->ack_rate_last_rate_time = now; - ca->ack_rate_last_rate = ca->ack_rate_curr_rate; - ca->ack_rate_curr_rate = ca->ack_rate_cnt; - ca->ack_rate_cnt = min_t(u32, acked, U16_MAX); + ca->ack_rate_last_rate = ca->curr_ack_rate; + ca->curr_ack_rate = ca->ack_rate_cnt; } - - ca->was_idle = false; + /* Start ack-counting with the already present ACKs */ + ca->ack_rate_cnt = min_t(u32, acked, U16_MAX); + ca->last_ack_rate_time = now; } else { - /* Cap the ack count to avoid overflow */ + /* Cap the ack count to avoid overflow. + * If we there are more than U16_MAX ACKs in 100ms, + * the resulting LAUNCH-exit won't cause any harm. + */ ca->ack_rate_cnt = min_t(u32, ca->ack_rate_cnt + acked, U16_MAX); } @@ -295,7 +358,7 @@ static void update_srrtt(struct roccettcp *ca) * anyways in order to avoid division by zero errors. */ if (ca->curr_min_rtt == 0) { - pr_err_once("TCP ROCCET: Recorded curr_min_rtt is 0"); + pr_err_once("TCP ROCCET: Recorded curr_min_rtt is 0\n"); return; /* skip srRTT update */ } @@ -338,7 +401,7 @@ static void roccet_min_rtt_probe_ce(struct roccettcp *ca, u32 cwnd) */ if (ca->cwnd_before_min_rtt_probe == 0) - pr_warn_once("ROCCET: cwnd_before_min_rtt_probe is 0 during RTT_PROBE. This should not happen."); + pr_warn_once("TCP ROCCET: cwnd_before_min_rtt_probe is 0 during RTT_PROBE. This should not happen.\n"); else cwnd = ca->cwnd_before_min_rtt_probe; @@ -357,6 +420,7 @@ static void roccet_congestion_event(struct sock *sk, u32 now) ca->epoch_start = 0; ca->roccet_last_event_time_us = now; + ca->last_event_time_set = true; if (ca->state == RTT_PROBE) { /* In case we are in RTT_PROBE, continue with this state, as @@ -378,9 +442,10 @@ static void roccet_congestion_event(struct sock *sk, u32 now) max((curr_cwnd * beta) / BICTCP_BETA_SCALE, 2U))); + /* Re-adjust ssthresh in case of ORBITER Congestion Event */ + WRITE_ONCE(tp->snd_ssthresh, tcp_snd_cwnd(tp)); + if (ca->state == LAUNCH) { - /* Set ssthresh on ECN, so that roccet is not in slow-start */ - tp->snd_ssthresh = tcp_snd_cwnd(tp); ca->state = ORBITER; } else if (ca->state == ORBITER || ca->state == RTT_PROBE_REFILL) { /* If we are in orbiter or currently refilling the pipe, @@ -402,21 +467,31 @@ static void roccet_enter_min_rtt_probe(struct sock *sk, u32 now) /* If probing is already set, there was a mix up. * Continue anyway so we can recover. */ - if (ca->probe_min_rtt_until > 0) - pr_warn_once("ROCCET: Probing time should not be set"); + if (ca->rtt_probe_timers_set) + pr_warn_once("TCP ROCCET: Probing time should not be set when entering RTT Probe\n"); /* Safeguard for an "infinite" probe. This should logically never * happen but still safeguard it. */ if (ca->curr_rtt == U32_MAX) { - pr_warn_once("ROCCET: curr_rtt is U32_MAX, cannot enter RTT_PROBE"); + pr_warn_once("TCP ROCCET: curr_rtt is U32_MAX. Won't enter RTT_PROBE\n"); ca->state = ORBITER; return; } - /* Start min RTT probing */ - /* Probe 1*RTT or at least 200ms */ - interval = max(200 * USEC_PER_MSEC, ca->curr_rtt); + /* Use field to store when we entered min RTT probing. Here we use + * jiffies to stay consistent with the epoch_start format. + */ + ca->next_min_rtt_probe = tcp_jiffies32; + + /* Start min RTT probing. Probe for 1 RTT or at least 200ms. + * Also bound the probe to a maximum of ROCCET_MAX_PROBE_INTERVAL_MS + * to avoid any "invalid"/very large RTT samples from breaking + * the probing and allowing ROCCET to recover afterwards. + */ + interval = max(200 * USEC_PER_MSEC, min(ca->curr_rtt, + ROCCET_MAX_PROBE_INTERVAL_MS + * USEC_PER_MSEC)); /* This is to handle deep shared buffers with loss-based * congestion control like CUBIC. If the cwnd is not limited @@ -434,7 +509,7 @@ static void roccet_enter_min_rtt_probe(struct sock *sk, u32 now) ca->probe_min_rtt_until = now + interval; ca->cwnd_before_min_rtt_probe = tcp_snd_cwnd(tp); - /* Half the cwnd to drain the buffer for probing. */ + /* Reduce the cwnd to drain the buffer for probing. */ tcp_snd_cwnd_set(tp, probe_cwnd); /* Reset current min RTT to allow probing for @@ -446,6 +521,7 @@ static void roccet_enter_min_rtt_probe(struct sock *sk, u32 now) * For this we use the previous cwnd for another probing interval. */ ca->refill_until = ca->probe_min_rtt_until + interval; + ca->rtt_probe_timers_set = true; /* Now both timers are set. */ /* Now we know that ca->probe_min_rtt_until < ca->refill_until * and we advance to the next state of the probing phase. @@ -453,7 +529,6 @@ static void roccet_enter_min_rtt_probe(struct sock *sk, u32 now) * Wrap-arounds of these values are handled by the relevant * if-conditions. */ - ca->state = RTT_PROBE; } @@ -470,28 +545,31 @@ static void roccet_min_rtt_probe(struct sock *sk, u32 now) * probe_min_rtt_until wrap-arounds are handled by the time_before32 * check. */ - if (ca->probe_min_rtt_until == 0) - pr_warn_once("ROCCET: Probing time should be set"); - else if (time_before32(now, ca->probe_min_rtt_until)) + if (!ca->rtt_probe_timers_set) + pr_warn_once("TCP ROCCET: Probing time should be set\n"); + else if (time_between32(now, ca->probe_min_rtt_until + - ROCCET_MAX_PROBE_INTERVAL_MS + * USEC_PER_MSEC, ca->probe_min_rtt_until)) /* No state change if we are in the probing interval. */ return; - - /* If the interval has passed we likely just entered the refill - * interval, so go into RTT_PROBE_REFILL. - * If we are not in the interval, something went wrong and we should - * quickly exit the probing phase. - * - * Wraps-arounds of refill_until are caught by the time_after32 check. - */ - if (time_after32(now, ca->refill_until)) - pr_warn_once("ROCCET: Skipped refill interval."); + else if (time_after32(now, ca->refill_until)) + /* If the probe_min_rtt_until interval has passed we likely + * just entered the refill interval, so go into + * RTT_PROBE_REFILL. If we are not in the refill_until + * interval, something went wrong and we should quickly exit + * the probing phase. + * + * Wraps-arounds of refill_until are caught by the time_after32 + * check. + */ + pr_warn_once("TCP ROCCET: Skipped refill interval.\n"); /* Reset cwnd to refill the pipe and consequently clear the stored * window. - * Also perform sanity check. THis should never happen. + * Also perform sanity check. This should never happen. */ if (ca->cwnd_before_min_rtt_probe == 0) - pr_warn_once("ROCCET: cwnd_before_min_rtt_probe is 0 during RTT_PROBE."); + 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); @@ -504,7 +582,9 @@ static void roccet_min_rtt_probe(struct sock *sk, u32 now) static void roccet_rtt_probe_refill(struct roccettcp *ca, u32 now) { /* Once the refill interval is over, we can end the probing phase. */ - if (time_after32(now, ca->refill_until)) { + if (!time_between32(now, ca->refill_until + - ROCCET_MAX_PROBE_INTERVAL_MS + * USEC_PER_MSEC, ca->refill_until)) { /* End min RTT probing phase. */ ca->state = ORBITER; } @@ -526,8 +606,6 @@ static void roccet_cwnd_event_tx_start(struct sock *sk) if (after(ca->epoch_start, now)) ca->epoch_start = now; } - - ca->was_idle = true; } /* calculate the cubic root of x using a table lookup followed by one @@ -618,14 +696,6 @@ static void bictcp_update(struct roccettcp *ca, u32 cwnd, } } - /* In case there are any ACKs left over from LAUNCH, - * apply them after the new epoch-reset - */ - if (ca->ack_carry_over) { - ca->ack_cnt += ca->ack_carry_over; - ca->ack_carry_over = 0; - } - /* cubic function - calc */ /* calculate c * time^3 / rtt, * while considering overflow in calculation of time^3 @@ -714,6 +784,7 @@ static void roccet_launch_update(struct sock *sk, u32 acked) */ if ((ca->curr_srrtt > sr_rtt_upper_bound && get_ack_rate_diff(ca) <= ack_rate_diff_ss) || + !tcp_in_slow_start(tp) || (!tcp_is_cwnd_limited(sk) && ca->initial_limit_reached)) { ca->epoch_start = 0; @@ -729,8 +800,9 @@ static void roccet_launch_update(struct sock *sk, u32 acked) (tcp_snd_cwnd(tp) / 3), TCP_INIT_CWND)); } - tp->snd_ssthresh = tcp_snd_cwnd(tp); + WRITE_ONCE(tp->snd_ssthresh, tcp_snd_cwnd(tp)); ca->roccet_last_event_time_us = now; + ca->last_event_time_set = true; ca->state = ORBITER; return; } @@ -763,9 +835,11 @@ static void roccet_orbiter_update(struct sock *sk, u32 acked) u32 roccet_xj, jitter, sent, received; /* Enter DRAIN when roccet was recently triggered */ - if (ca->roccet_last_event_time_us && - time_before32(now, ca->roccet_last_event_time_us + - 100 * USEC_PER_MSEC)) { + if (ca->last_event_time_set && + time_between32(now, ca->roccet_last_event_time_us + - 100 * USEC_PER_MSEC, + ca->roccet_last_event_time_us + + 100 * USEC_PER_MSEC)) { ca->state = DRAIN; return; } @@ -774,8 +848,13 @@ static void roccet_orbiter_update(struct sock *sk, u32 acked) * * Since we are in ORBITER, we should have already received at least * one RTT sample. However safeguard against it if not. + * + * Use double the probing period as a guard space to avoid + * idle-time locking us out of probing. */ - if (time_after32(now, ca->next_min_rtt_probe) && + if (!time_between32(now, ca->next_min_rtt_probe + - 2 * ROCCET_NEXT_MIN_RTT_PROBE_MS + * USEC_PER_MSEC, ca->next_min_rtt_probe) && ca->curr_rtt != U32_MAX) { ca->state = RTT_PROBE_ENTER; return; @@ -808,16 +887,18 @@ static void roccet_orbiter_update(struct sock *sk, u32 acked) * We do this to avoid a false positive evaluation due * to delays caused by jitter or scheduling. */ - sent_more_than_acked = - sent > - received + ((tcp_snd_cwnd(tp) * tp->mss_cache) / 100); + sent_more_than_acked = (s32)(sent - received) > + div_u64((u64)tcp_snd_cwnd(tp) * + (u64)tp->mss_cache, 100); /* Check if it's time to evaluate the srRTT */ - if (time_after32(now, ca->next_srrtt_check_ts)) { + if (!time_between32(now, ca->next_srrtt_check_ts + - 2 * ROCCET_MAX_SRRTT_CHECK_INTERVAL_MS + * USEC_PER_MSEC, ca->next_srrtt_check_ts)) { evaluate_srrtt = true; /* reset struct and set next end of period */ - ca->next_srrtt_check_ts = now + 5 * ca->curr_rtt; + roccet_set_next_srrtt_check_ts(ca, now); /* Reset Rate calculation */ ca->interval_snd_seq_start = tp->snd_nxt; @@ -846,10 +927,21 @@ 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) + if (!tcp_is_cwnd_limited(sk) || sent_more_than_acked || + tcp_in_cwnd_reduction(sk)) return; + /* In case there are any ACKs left over from LAUNCH, + * apply them now. + */ + if (ca->ack_carry_over) { + acked += ca->ack_carry_over; + ca->ack_carry_over = 0; + } + bictcp_update(ca, tcp_snd_cwnd(tp), acked); tcp_cong_avoid_ai(tp, max(1, ca->cnt), acked); } @@ -924,25 +1016,63 @@ static u32 roccet_handle_recovery(struct sock *sk) * This is used whenever a state change is possible * (e.g. in roccet_state() or roccet_control()). */ -static void roccet_handle_state_transitions(struct roccettcp *ca, +static void roccet_handle_state_transitions(struct sock *sk, enum roccet_state prev_state, u32 now) { + struct roccettcp *ca = inet_csk_ca(sk); + struct tcp_sock *tp = tcp_sk(sk); + u32 now_jiffies = tcp_jiffies32; + if (ca->state == prev_state) 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. */ - if (prev_state == LAUNCH) + if (prev_state == LAUNCH) { ca->is_in_initial_launch = false; + ca->initial_limit_reached = true; + } /* Whenever we enter ORBITER, we need to schedule the next SRTT check. * This will be set before evaluating the next_srrtt_check_ts condition * as this is only done in ORBITER. */ - if (ca->state == ORBITER) - ca->next_srrtt_check_ts = now + 5 * ca->curr_rtt; + if (ca->state == ORBITER) { + roccet_set_next_srrtt_check_ts(ca, now); + ca->interval_snd_seq_start = tp->snd_nxt; + ca->interval_una_seq_start = tp->snd_una; + + /* Add the time we spent in the min RTT probing phase to the + * epoch_start, so the cubic growth won't suddenly jump + * after probing. Here we use the next_min_rtt_probe field + * which we (ab)used to store the time when we entered min RTT + * probing (in jiffies). + */ + if ((prev_state == RTT_PROBE || + prev_state == RTT_PROBE_REFILL)) { + if (ca->epoch_start != 0) { + ca->epoch_start += now_jiffies - + ca->next_min_rtt_probe; + /* Clamp so that epoch_start doesn't go + * in the future. + */ + if (after(ca->epoch_start, now_jiffies)) + ca->epoch_start = now_jiffies; + } + + roccet_set_next_min_rtt_probe_ts(ca); + } + } + + /* In refill we can transition to DRAIN. In this case, overwrite the + * jiffies ts + */ + if (ca->state == DRAIN && prev_state == RTT_PROBE_REFILL) + roccet_set_next_min_rtt_probe_ts(ca); /* Whenever we leave the min RTT probing states (and not just * transition between them), we want to reset the probing timers. @@ -952,6 +1082,7 @@ static void roccet_handle_state_transitions(struct roccettcp *ca, (ca->state != RTT_PROBE && ca->state != RTT_PROBE_REFILL)) { ca->probe_min_rtt_until = 0; ca->refill_until = 0; + ca->rtt_probe_timers_set = false; } } @@ -968,20 +1099,27 @@ 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); + WRITE_ONCE(tp->snd_ssthresh, max(tp->snd_ssthresh, + TCP_INIT_CWND)); + } 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) { /* Handle CWR as ROCCET congestion event, - * however afterwards always set Wmax to the current cwnd. + * however afterwards always set Wmax to the current cwnd + * except during min RTT probing. */ cwnd = tcp_snd_cwnd(tp); roccet_congestion_event(sk, now); - ca->last_max_cwnd = cwnd; + if (ca->state != RTT_PROBE_ENTER && ca->state != RTT_PROBE && + ca->state != RTT_PROBE_REFILL) + ca->last_max_cwnd = cwnd; } 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); } - roccet_handle_state_transitions(ca, prev_state, now); + roccet_handle_state_transitions(sk, prev_state, now); } /* Update RTT samples and min RTT. @@ -1017,12 +1155,13 @@ static void roccet_acked(struct sock *sk, const struct ack_sample *sample) /* Custom Pacing Rate for ROCCET TCP. * The code here is similar to the pacing rate adjustments in tcp_input.c - * tcp_cong_control(). In LAUNCH (slow start) we want a pacing of 200% and - * in ORBITER (congestion avoidance) we adjust the pacing to 100% and do not + * 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. */ static void roccet_update_pacing_rate(struct sock *sk) { + struct roccettcp *ca = inet_csk_ca(sk); struct tcp_sock *tp = tcp_sk(sk); u64 rate; @@ -1037,14 +1176,17 @@ static void roccet_update_pacing_rate(struct sock *sk) * If snd_cwnd >= (tp->snd_ssthresh / 2), we are approaching * end of slow start and should slow down. */ - if (tcp_snd_cwnd(tp) < tp->snd_ssthresh / 2) + 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) + */ + rate *= 100; + else + /* Here we are well inside of slow start + * (tcp_snd_cwnd(tp) < tp->snd_ssthresh / 2) + */ rate *= READ_ONCE (sock_net(sk)->ipv4.sysctl_tcp_pacing_ss_ratio); - else - /* Pacing rate of 100% - * (instead of ipv4.sysctl_tcp_pacing_ca_ratio) - */ - rate *= 100; rate *= max(tcp_snd_cwnd(tp), tp->packets_out); @@ -1064,9 +1206,15 @@ static void roccet_drain_update(struct sock *sk, u32 now) struct tcp_sock *tp = tcp_sk(sk); struct roccettcp *ca = inet_csk_ca(sk); - if (ca->roccet_last_event_time_us && - time_after32(now, ca->roccet_last_event_time_us + - 100 * USEC_PER_MSEC)) { + /* If we are in DRAIN, ca->roccet_last_event_time_us has to be set. + * If more than 100 ms have passed after a roccet event, exit DRAIN. + * The between check is here to handle idle-times and timer-wraps. + * It is ok if we "accidentally" exit DRAIN in such an occasion. + */ + if (!time_between32(now, ca->roccet_last_event_time_us + - 100 * USEC_PER_MSEC, + ca->roccet_last_event_time_us + + 100 * USEC_PER_MSEC)) { if (tcp_in_slow_start(tp)) ca->state = LAUNCH; else @@ -1114,14 +1262,26 @@ static void roccet_control(struct sock *sk, u32 ack, int flag, roccet_rtt_probe_refill(ca, now); break; default: - pr_err_once("TCP ROCCET: Invalid state %d", ca->state); + pr_err_once("TCP ROCCET: Invalid state %d\n", ca->state); } - roccet_handle_state_transitions(ca, prev_state, now); + roccet_handle_state_transitions(sk, prev_state, now); roccet_update_pacing_rate(sk); - if (tcp_is_cwnd_limited(sk)) + /* Abuse ca->roccet_last_event_time_us as the start time for this + * connection (only after init, while initial_limit_reached is not + * set). When the connection starts sending enough data or after 500 ms + * we know that we can use tcp_is_cwnd_limited() to check if the + * connection is actually limited by the cwnd and is not just + * starting up. + */ + if (!ca->initial_limit_reached && + (tcp_is_cwnd_limited(sk) || + !time_between32(now, ca->roccet_last_event_time_us + - 500 * USEC_PER_MSEC, + ca->roccet_last_event_time_us + + 500 * USEC_PER_MSEC))) ca->initial_limit_reached = true; } diff --git a/tcp_roccet.h b/tcp_roccet.h index 85fbdda..005cc84 100644 --- a/tcp_roccet.h +++ b/tcp_roccet.h @@ -54,24 +54,29 @@ struct roccettcp { u32 interval_snd_seq_start; u32 interval_una_seq_start; - u32 ack_rate_last_rate_time; /* Timestamp of the last ACK-rate */ + u32 ack_carry_over; /* Used to carry over leftover acks from + * LAUNCH to ORBITER + */ + + u32 last_ack_rate_time; /* Timestamp of the last ACK-rate */ u16 ack_rate_last_rate; /* Last ACK-rate */ - u16 ack_rate_curr_rate; /* Current ACK-rate */ + u16 curr_ack_rate; /* Current ACK-rate */ u16 ack_rate_cnt; /* Used for counting acks */ enum roccet_state state : 3; /* Current operating state of roccet */ - bool was_idle : 1; /* Tracks whether the connection was idle - * (had no in-flight packets) - */ bool initial_limit_reached: 1; /* Set to true after the connection * initially gets cwnd-limited */ bool is_in_initial_launch: 1; /* true if the connection is in * the initial launch phase. */ - u32 ack_carry_over; /* Used to carry over leftover acks from - * LAUNCH to ORBITER - */ + bool last_event_time_set: 1; /* true if the last event time + * has been set/initialized. + */ + bool rtt_probe_timers_set: 1; /* true if refill_until and + * probe_min_rtt_until are currently + * set. + */ }; #endif /* __TCP_ROCCET_H */