123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485 |
- #include "internal/quic_cc.h"
- #include "internal/quic_types.h"
- #include "internal/safe_math.h"
- OSSL_SAFE_MATH_UNSIGNED(u64, uint64_t)
- typedef struct ossl_cc_newreno_st {
- /* Dependencies. */
- OSSL_TIME (*now_cb)(void *arg);
- void *now_cb_arg;
- /* 'Constants' (which we allow to be configurable). */
- uint64_t k_init_wnd, k_min_wnd;
- uint32_t k_loss_reduction_factor_num, k_loss_reduction_factor_den;
- uint32_t persistent_cong_thresh;
- /* State. */
- size_t max_dgram_size;
- uint64_t bytes_in_flight, cong_wnd, slow_start_thresh, bytes_acked;
- OSSL_TIME cong_recovery_start_time;
- /* Unflushed state during multiple on-loss calls. */
- int processing_loss; /* 1 if not flushed */
- OSSL_TIME tx_time_of_last_loss;
- /* Diagnostic state. */
- int in_congestion_recovery;
- /* Diagnostic output locations. */
- size_t *p_diag_max_dgram_payload_len;
- uint64_t *p_diag_cur_cwnd_size;
- uint64_t *p_diag_min_cwnd_size;
- uint64_t *p_diag_cur_bytes_in_flight;
- uint32_t *p_diag_cur_state;
- } OSSL_CC_NEWRENO;
- #define MIN_MAX_INIT_WND_SIZE 14720 /* RFC 9002 s. 7.2 */
- /* TODO(QUIC FUTURE): Pacing support. */
- static void newreno_set_max_dgram_size(OSSL_CC_NEWRENO *nr,
- size_t max_dgram_size);
- static void newreno_update_diag(OSSL_CC_NEWRENO *nr);
- static void newreno_reset(OSSL_CC_DATA *cc);
- static OSSL_CC_DATA *newreno_new(OSSL_TIME (*now_cb)(void *arg),
- void *now_cb_arg)
- {
- OSSL_CC_NEWRENO *nr;
- if ((nr = OPENSSL_zalloc(sizeof(*nr))) == NULL)
- return NULL;
- nr->now_cb = now_cb;
- nr->now_cb_arg = now_cb_arg;
- newreno_set_max_dgram_size(nr, QUIC_MIN_INITIAL_DGRAM_LEN);
- newreno_reset((OSSL_CC_DATA *)nr);
- return (OSSL_CC_DATA *)nr;
- }
- static void newreno_free(OSSL_CC_DATA *cc)
- {
- OPENSSL_free(cc);
- }
- static void newreno_set_max_dgram_size(OSSL_CC_NEWRENO *nr,
- size_t max_dgram_size)
- {
- size_t max_init_wnd;
- int is_reduced = (max_dgram_size < nr->max_dgram_size);
- nr->max_dgram_size = max_dgram_size;
- max_init_wnd = 2 * max_dgram_size;
- if (max_init_wnd < MIN_MAX_INIT_WND_SIZE)
- max_init_wnd = MIN_MAX_INIT_WND_SIZE;
- nr->k_init_wnd = 10 * max_dgram_size;
- if (nr->k_init_wnd > max_init_wnd)
- nr->k_init_wnd = max_init_wnd;
- nr->k_min_wnd = 2 * max_dgram_size;
- if (is_reduced)
- nr->cong_wnd = nr->k_init_wnd;
- newreno_update_diag(nr);
- }
- static void newreno_reset(OSSL_CC_DATA *cc)
- {
- OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
- nr->k_loss_reduction_factor_num = 1;
- nr->k_loss_reduction_factor_den = 2;
- nr->persistent_cong_thresh = 3;
- nr->cong_wnd = nr->k_init_wnd;
- nr->bytes_in_flight = 0;
- nr->bytes_acked = 0;
- nr->slow_start_thresh = UINT64_MAX;
- nr->cong_recovery_start_time = ossl_time_zero();
- nr->processing_loss = 0;
- nr->tx_time_of_last_loss = ossl_time_zero();
- nr->in_congestion_recovery = 0;
- }
- static int newreno_set_input_params(OSSL_CC_DATA *cc, const OSSL_PARAM *params)
- {
- OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
- const OSSL_PARAM *p;
- size_t value;
- p = OSSL_PARAM_locate_const(params, OSSL_CC_OPTION_MAX_DGRAM_PAYLOAD_LEN);
- if (p != NULL) {
- if (!OSSL_PARAM_get_size_t(p, &value))
- return 0;
- if (value < QUIC_MIN_INITIAL_DGRAM_LEN)
- return 0;
- newreno_set_max_dgram_size(nr, value);
- }
- return 1;
- }
- static int bind_diag(OSSL_PARAM *params, const char *param_name, size_t len,
- void **pp)
- {
- const OSSL_PARAM *p = OSSL_PARAM_locate_const(params, param_name);
- *pp = NULL;
- if (p == NULL)
- return 1;
- if (p->data_type != OSSL_PARAM_UNSIGNED_INTEGER
- || p->data_size != len)
- return 0;
- *pp = p->data;
- return 1;
- }
- static int newreno_bind_diagnostic(OSSL_CC_DATA *cc, OSSL_PARAM *params)
- {
- OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
- size_t *new_p_max_dgram_payload_len;
- uint64_t *new_p_cur_cwnd_size;
- uint64_t *new_p_min_cwnd_size;
- uint64_t *new_p_cur_bytes_in_flight;
- uint32_t *new_p_cur_state;
- if (!bind_diag(params, OSSL_CC_OPTION_MAX_DGRAM_PAYLOAD_LEN,
- sizeof(size_t), (void **)&new_p_max_dgram_payload_len)
- || !bind_diag(params, OSSL_CC_OPTION_CUR_CWND_SIZE,
- sizeof(uint64_t), (void **)&new_p_cur_cwnd_size)
- || !bind_diag(params, OSSL_CC_OPTION_MIN_CWND_SIZE,
- sizeof(uint64_t), (void **)&new_p_min_cwnd_size)
- || !bind_diag(params, OSSL_CC_OPTION_CUR_BYTES_IN_FLIGHT,
- sizeof(uint64_t), (void **)&new_p_cur_bytes_in_flight)
- || !bind_diag(params, OSSL_CC_OPTION_CUR_STATE,
- sizeof(uint32_t), (void **)&new_p_cur_state))
- return 0;
- if (new_p_max_dgram_payload_len != NULL)
- nr->p_diag_max_dgram_payload_len = new_p_max_dgram_payload_len;
- if (new_p_cur_cwnd_size != NULL)
- nr->p_diag_cur_cwnd_size = new_p_cur_cwnd_size;
- if (new_p_min_cwnd_size != NULL)
- nr->p_diag_min_cwnd_size = new_p_min_cwnd_size;
- if (new_p_cur_bytes_in_flight != NULL)
- nr->p_diag_cur_bytes_in_flight = new_p_cur_bytes_in_flight;
- if (new_p_cur_state != NULL)
- nr->p_diag_cur_state = new_p_cur_state;
- newreno_update_diag(nr);
- return 1;
- }
- static void unbind_diag(OSSL_PARAM *params, const char *param_name,
- void **pp)
- {
- const OSSL_PARAM *p = OSSL_PARAM_locate_const(params, param_name);
- if (p != NULL)
- *pp = NULL;
- }
- static int newreno_unbind_diagnostic(OSSL_CC_DATA *cc, OSSL_PARAM *params)
- {
- OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
- unbind_diag(params, OSSL_CC_OPTION_MAX_DGRAM_PAYLOAD_LEN,
- (void **)&nr->p_diag_max_dgram_payload_len);
- unbind_diag(params, OSSL_CC_OPTION_CUR_CWND_SIZE,
- (void **)&nr->p_diag_cur_cwnd_size);
- unbind_diag(params, OSSL_CC_OPTION_MIN_CWND_SIZE,
- (void **)&nr->p_diag_min_cwnd_size);
- unbind_diag(params, OSSL_CC_OPTION_CUR_BYTES_IN_FLIGHT,
- (void **)&nr->p_diag_cur_bytes_in_flight);
- unbind_diag(params, OSSL_CC_OPTION_CUR_STATE,
- (void **)&nr->p_diag_cur_state);
- return 1;
- }
- static void newreno_update_diag(OSSL_CC_NEWRENO *nr)
- {
- if (nr->p_diag_max_dgram_payload_len != NULL)
- *nr->p_diag_max_dgram_payload_len = nr->max_dgram_size;
- if (nr->p_diag_cur_cwnd_size != NULL)
- *nr->p_diag_cur_cwnd_size = nr->cong_wnd;
- if (nr->p_diag_min_cwnd_size != NULL)
- *nr->p_diag_min_cwnd_size = nr->k_min_wnd;
- if (nr->p_diag_cur_bytes_in_flight != NULL)
- *nr->p_diag_cur_bytes_in_flight = nr->bytes_in_flight;
- if (nr->p_diag_cur_state != NULL) {
- if (nr->in_congestion_recovery)
- *nr->p_diag_cur_state = 'R';
- else if (nr->cong_wnd < nr->slow_start_thresh)
- *nr->p_diag_cur_state = 'S';
- else
- *nr->p_diag_cur_state = 'A';
- }
- }
- static int newreno_in_cong_recovery(OSSL_CC_NEWRENO *nr, OSSL_TIME tx_time)
- {
- return ossl_time_compare(tx_time, nr->cong_recovery_start_time) <= 0;
- }
- static void newreno_cong(OSSL_CC_NEWRENO *nr, OSSL_TIME tx_time)
- {
- int err = 0;
- /* No reaction if already in a recovery period. */
- if (newreno_in_cong_recovery(nr, tx_time))
- return;
- /* Start a new recovery period. */
- nr->in_congestion_recovery = 1;
- nr->cong_recovery_start_time = nr->now_cb(nr->now_cb_arg);
- /* slow_start_thresh = cong_wnd * loss_reduction_factor */
- nr->slow_start_thresh
- = safe_muldiv_u64(nr->cong_wnd,
- nr->k_loss_reduction_factor_num,
- nr->k_loss_reduction_factor_den,
- &err);
- if (err)
- nr->slow_start_thresh = UINT64_MAX;
- nr->cong_wnd = nr->slow_start_thresh;
- if (nr->cong_wnd < nr->k_min_wnd)
- nr->cong_wnd = nr->k_min_wnd;
- }
- static void newreno_flush(OSSL_CC_NEWRENO *nr, uint32_t flags)
- {
- if (!nr->processing_loss)
- return;
- newreno_cong(nr, nr->tx_time_of_last_loss);
- if ((flags & OSSL_CC_LOST_FLAG_PERSISTENT_CONGESTION) != 0) {
- nr->cong_wnd = nr->k_min_wnd;
- nr->cong_recovery_start_time = ossl_time_zero();
- }
- nr->processing_loss = 0;
- newreno_update_diag(nr);
- }
- static uint64_t newreno_get_tx_allowance(OSSL_CC_DATA *cc)
- {
- OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
- if (nr->bytes_in_flight >= nr->cong_wnd)
- return 0;
- return nr->cong_wnd - nr->bytes_in_flight;
- }
- static OSSL_TIME newreno_get_wakeup_deadline(OSSL_CC_DATA *cc)
- {
- if (newreno_get_tx_allowance(cc) > 0) {
- /* We have TX allowance now so wakeup immediately */
- return ossl_time_zero();
- } else {
- /*
- * The NewReno congestion controller does not vary its state in time,
- * only in response to stimulus.
- */
- return ossl_time_infinite();
- }
- }
- static int newreno_on_data_sent(OSSL_CC_DATA *cc, uint64_t num_bytes)
- {
- OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
- nr->bytes_in_flight += num_bytes;
- newreno_update_diag(nr);
- return 1;
- }
- static int newreno_is_cong_limited(OSSL_CC_NEWRENO *nr)
- {
- uint64_t wnd_rem;
- /* We are congestion-limited if we are already at the congestion window. */
- if (nr->bytes_in_flight >= nr->cong_wnd)
- return 1;
- wnd_rem = nr->cong_wnd - nr->bytes_in_flight;
- /*
- * Consider ourselves congestion-limited if less than three datagrams' worth
- * of congestion window remains to be spent, or if we are in slow start and
- * have consumed half of our window.
- */
- return (nr->cong_wnd < nr->slow_start_thresh && wnd_rem <= nr->cong_wnd / 2)
- || wnd_rem <= 3 * nr->max_dgram_size;
- }
- static int newreno_on_data_acked(OSSL_CC_DATA *cc,
- const OSSL_CC_ACK_INFO *info)
- {
- OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
- /*
- * Packet has been acked. Firstly, remove it from the aggregate count of
- * bytes in flight.
- */
- nr->bytes_in_flight -= info->tx_size;
- /*
- * We use acknowledgement of data as a signal that we are not at channel
- * capacity and that it may be reasonable to increase the congestion window.
- * However, acknowledgement is not a useful signal that there is further
- * capacity if we are not actually saturating the congestion window that we
- * already have (for example, if the application is not generating much data
- * or we are limited by flow control). Therefore, we only expand the
- * congestion window if we are consuming a significant fraction of the
- * congestion window.
- */
- if (!newreno_is_cong_limited(nr))
- goto out;
- /*
- * We can handle acknowledgement of a packet in one of three ways
- * depending on our current state:
- *
- * - Congestion Recovery: Do nothing. We don't start increasing
- * the congestion window in response to acknowledgements until
- * we are no longer in the Congestion Recovery state.
- *
- * - Slow Start: Increase the congestion window using the slow
- * start scale.
- *
- * - Congestion Avoidance: Increase the congestion window using
- * the congestion avoidance scale.
- */
- if (newreno_in_cong_recovery(nr, info->tx_time)) {
- /* Congestion recovery, do nothing. */
- } else if (nr->cong_wnd < nr->slow_start_thresh) {
- /* When this condition is true we are in the Slow Start state. */
- nr->cong_wnd += info->tx_size;
- nr->in_congestion_recovery = 0;
- } else {
- /* Otherwise, we are in the Congestion Avoidance state. */
- nr->bytes_acked += info->tx_size;
- /*
- * Avoid integer division as per RFC 9002 s. B.5. / RFC3465 s. 2.1.
- */
- if (nr->bytes_acked >= nr->cong_wnd) {
- nr->bytes_acked -= nr->cong_wnd;
- nr->cong_wnd += nr->max_dgram_size;
- }
- nr->in_congestion_recovery = 0;
- }
- out:
- newreno_update_diag(nr);
- return 1;
- }
- static int newreno_on_data_lost(OSSL_CC_DATA *cc,
- const OSSL_CC_LOSS_INFO *info)
- {
- OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
- if (info->tx_size > nr->bytes_in_flight)
- return 0;
- nr->bytes_in_flight -= info->tx_size;
- if (!nr->processing_loss) {
- if (ossl_time_compare(info->tx_time, nr->tx_time_of_last_loss) <= 0)
- /*
- * After triggering congestion due to a lost packet at time t, don't
- * trigger congestion again due to any subsequently detected lost
- * packet at a time s < t, as we've effectively already signalled
- * congestion on loss of that and subsequent packets.
- */
- goto out;
- nr->processing_loss = 1;
- /*
- * Cancel any pending window increase in the Congestion Avoidance state.
- */
- nr->bytes_acked = 0;
- }
- nr->tx_time_of_last_loss
- = ossl_time_max(nr->tx_time_of_last_loss, info->tx_time);
- out:
- newreno_update_diag(nr);
- return 1;
- }
- static int newreno_on_data_lost_finished(OSSL_CC_DATA *cc, uint32_t flags)
- {
- OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
- newreno_flush(nr, flags);
- return 1;
- }
- static int newreno_on_data_invalidated(OSSL_CC_DATA *cc,
- uint64_t num_bytes)
- {
- OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
- nr->bytes_in_flight -= num_bytes;
- newreno_update_diag(nr);
- return 1;
- }
- static int newreno_on_ecn(OSSL_CC_DATA *cc,
- const OSSL_CC_ECN_INFO *info)
- {
- OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
- nr->processing_loss = 1;
- nr->bytes_acked = 0;
- nr->tx_time_of_last_loss = info->largest_acked_time;
- newreno_flush(nr, 0);
- return 1;
- }
- const OSSL_CC_METHOD ossl_cc_newreno_method = {
- newreno_new,
- newreno_free,
- newreno_reset,
- newreno_set_input_params,
- newreno_bind_diagnostic,
- newreno_unbind_diagnostic,
- newreno_get_tx_allowance,
- newreno_get_wakeup_deadline,
- newreno_on_data_sent,
- newreno_on_data_acked,
- newreno_on_data_lost,
- newreno_on_data_lost_finished,
- newreno_on_data_invalidated,
- newreno_on_ecn,
- };
|