openssl/ssl/quic/cc_newreno.c
Tomas Mraz 44cb36d04a Resolve some of the TODO(QUIC) items
For some of the items we add FUTURE/SERVER/TESTING/MULTIPATH
designation to indicate these do not need to be resolved
in QUIC MVP release.

Reviewed-by: Paul Dale <pauli@openssl.org>
Reviewed-by: Hugo Landau <hlandau@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/21539)
2023-08-08 15:58:59 +02:00

486 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 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,
};