mirror of
https://github.com/openssl/openssl.git
synced 2024-12-21 06:09:35 +08:00
878df9be67
Reviewed-by: Tomas Mraz <tomas@openssl.org> Reviewed-by: Matt Caswell <matt@openssl.org> (Merged from https://github.com/openssl/openssl/pull/20423)
481 lines
14 KiB
C
481 lines
14 KiB
C
#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): 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)
|
|
{
|
|
/*
|
|
* 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,
|
|
};
|