Home | History | Annotate | Line # | Download | only in quic
      1 #include "internal/quic_cc.h"
      2 #include "internal/quic_types.h"
      3 #include "internal/safe_math.h"
      4 
      5 OSSL_SAFE_MATH_UNSIGNED(u64, uint64_t)
      6 
      7 typedef struct ossl_cc_newreno_st {
      8     /* Dependencies. */
      9     OSSL_TIME (*now_cb)(void *arg);
     10     void *now_cb_arg;
     11 
     12     /* 'Constants' (which we allow to be configurable). */
     13     uint64_t k_init_wnd, k_min_wnd;
     14     uint32_t k_loss_reduction_factor_num, k_loss_reduction_factor_den;
     15     uint32_t persistent_cong_thresh;
     16 
     17     /* State. */
     18     size_t max_dgram_size;
     19     uint64_t bytes_in_flight, cong_wnd, slow_start_thresh, bytes_acked;
     20     OSSL_TIME cong_recovery_start_time;
     21 
     22     /* Unflushed state during multiple on-loss calls. */
     23     int processing_loss; /* 1 if not flushed */
     24     OSSL_TIME tx_time_of_last_loss;
     25 
     26     /* Diagnostic state. */
     27     int in_congestion_recovery;
     28 
     29     /* Diagnostic output locations. */
     30     size_t *p_diag_max_dgram_payload_len;
     31     uint64_t *p_diag_cur_cwnd_size;
     32     uint64_t *p_diag_min_cwnd_size;
     33     uint64_t *p_diag_cur_bytes_in_flight;
     34     uint32_t *p_diag_cur_state;
     35 } OSSL_CC_NEWRENO;
     36 
     37 #define MIN_MAX_INIT_WND_SIZE 14720 /* RFC 9002 s. 7.2 */
     38 
     39 /* TODO(QUIC FUTURE): Pacing support. */
     40 
     41 static void newreno_set_max_dgram_size(OSSL_CC_NEWRENO *nr,
     42     size_t max_dgram_size);
     43 static void newreno_update_diag(OSSL_CC_NEWRENO *nr);
     44 
     45 static void newreno_reset(OSSL_CC_DATA *cc);
     46 
     47 static OSSL_CC_DATA *newreno_new(OSSL_TIME (*now_cb)(void *arg),
     48     void *now_cb_arg)
     49 {
     50     OSSL_CC_NEWRENO *nr;
     51 
     52     if ((nr = OPENSSL_zalloc(sizeof(*nr))) == NULL)
     53         return NULL;
     54 
     55     nr->now_cb = now_cb;
     56     nr->now_cb_arg = now_cb_arg;
     57 
     58     newreno_set_max_dgram_size(nr, QUIC_MIN_INITIAL_DGRAM_LEN);
     59     newreno_reset((OSSL_CC_DATA *)nr);
     60 
     61     return (OSSL_CC_DATA *)nr;
     62 }
     63 
     64 static void newreno_free(OSSL_CC_DATA *cc)
     65 {
     66     OPENSSL_free(cc);
     67 }
     68 
     69 static void newreno_set_max_dgram_size(OSSL_CC_NEWRENO *nr,
     70     size_t max_dgram_size)
     71 {
     72     size_t max_init_wnd;
     73     int is_reduced = (max_dgram_size < nr->max_dgram_size);
     74 
     75     nr->max_dgram_size = max_dgram_size;
     76 
     77     max_init_wnd = 2 * max_dgram_size;
     78     if (max_init_wnd < MIN_MAX_INIT_WND_SIZE)
     79         max_init_wnd = MIN_MAX_INIT_WND_SIZE;
     80 
     81     nr->k_init_wnd = 10 * max_dgram_size;
     82     if (nr->k_init_wnd > max_init_wnd)
     83         nr->k_init_wnd = max_init_wnd;
     84 
     85     nr->k_min_wnd = 2 * max_dgram_size;
     86 
     87     if (is_reduced)
     88         nr->cong_wnd = nr->k_init_wnd;
     89 
     90     newreno_update_diag(nr);
     91 }
     92 
     93 static void newreno_reset(OSSL_CC_DATA *cc)
     94 {
     95     OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
     96 
     97     nr->k_loss_reduction_factor_num = 1;
     98     nr->k_loss_reduction_factor_den = 2;
     99     nr->persistent_cong_thresh = 3;
    100 
    101     nr->cong_wnd = nr->k_init_wnd;
    102     nr->bytes_in_flight = 0;
    103     nr->bytes_acked = 0;
    104     nr->slow_start_thresh = UINT64_MAX;
    105     nr->cong_recovery_start_time = ossl_time_zero();
    106 
    107     nr->processing_loss = 0;
    108     nr->tx_time_of_last_loss = ossl_time_zero();
    109     nr->in_congestion_recovery = 0;
    110 }
    111 
    112 static int newreno_set_input_params(OSSL_CC_DATA *cc, const OSSL_PARAM *params)
    113 {
    114     OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
    115     const OSSL_PARAM *p;
    116     size_t value;
    117 
    118     p = OSSL_PARAM_locate_const(params, OSSL_CC_OPTION_MAX_DGRAM_PAYLOAD_LEN);
    119     if (p != NULL) {
    120         if (!OSSL_PARAM_get_size_t(p, &value))
    121             return 0;
    122         if (value < QUIC_MIN_INITIAL_DGRAM_LEN)
    123             return 0;
    124 
    125         newreno_set_max_dgram_size(nr, value);
    126     }
    127 
    128     return 1;
    129 }
    130 
    131 static int bind_diag(OSSL_PARAM *params, const char *param_name, size_t len,
    132     void **pp)
    133 {
    134     const OSSL_PARAM *p = OSSL_PARAM_locate_const(params, param_name);
    135 
    136     *pp = NULL;
    137 
    138     if (p == NULL)
    139         return 1;
    140 
    141     if (p->data_type != OSSL_PARAM_UNSIGNED_INTEGER
    142         || p->data_size != len)
    143         return 0;
    144 
    145     *pp = p->data;
    146     return 1;
    147 }
    148 
    149 static int newreno_bind_diagnostic(OSSL_CC_DATA *cc, OSSL_PARAM *params)
    150 {
    151     OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
    152     size_t *new_p_max_dgram_payload_len;
    153     uint64_t *new_p_cur_cwnd_size;
    154     uint64_t *new_p_min_cwnd_size;
    155     uint64_t *new_p_cur_bytes_in_flight;
    156     uint32_t *new_p_cur_state;
    157 
    158     if (!bind_diag(params, OSSL_CC_OPTION_MAX_DGRAM_PAYLOAD_LEN,
    159             sizeof(size_t), (void **)&new_p_max_dgram_payload_len)
    160         || !bind_diag(params, OSSL_CC_OPTION_CUR_CWND_SIZE,
    161             sizeof(uint64_t), (void **)&new_p_cur_cwnd_size)
    162         || !bind_diag(params, OSSL_CC_OPTION_MIN_CWND_SIZE,
    163             sizeof(uint64_t), (void **)&new_p_min_cwnd_size)
    164         || !bind_diag(params, OSSL_CC_OPTION_CUR_BYTES_IN_FLIGHT,
    165             sizeof(uint64_t), (void **)&new_p_cur_bytes_in_flight)
    166         || !bind_diag(params, OSSL_CC_OPTION_CUR_STATE,
    167             sizeof(uint32_t), (void **)&new_p_cur_state))
    168         return 0;
    169 
    170     if (new_p_max_dgram_payload_len != NULL)
    171         nr->p_diag_max_dgram_payload_len = new_p_max_dgram_payload_len;
    172 
    173     if (new_p_cur_cwnd_size != NULL)
    174         nr->p_diag_cur_cwnd_size = new_p_cur_cwnd_size;
    175 
    176     if (new_p_min_cwnd_size != NULL)
    177         nr->p_diag_min_cwnd_size = new_p_min_cwnd_size;
    178 
    179     if (new_p_cur_bytes_in_flight != NULL)
    180         nr->p_diag_cur_bytes_in_flight = new_p_cur_bytes_in_flight;
    181 
    182     if (new_p_cur_state != NULL)
    183         nr->p_diag_cur_state = new_p_cur_state;
    184 
    185     newreno_update_diag(nr);
    186     return 1;
    187 }
    188 
    189 static void unbind_diag(OSSL_PARAM *params, const char *param_name,
    190     void **pp)
    191 {
    192     const OSSL_PARAM *p = OSSL_PARAM_locate_const(params, param_name);
    193 
    194     if (p != NULL)
    195         *pp = NULL;
    196 }
    197 
    198 static int newreno_unbind_diagnostic(OSSL_CC_DATA *cc, OSSL_PARAM *params)
    199 {
    200     OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
    201 
    202     unbind_diag(params, OSSL_CC_OPTION_MAX_DGRAM_PAYLOAD_LEN,
    203         (void **)&nr->p_diag_max_dgram_payload_len);
    204     unbind_diag(params, OSSL_CC_OPTION_CUR_CWND_SIZE,
    205         (void **)&nr->p_diag_cur_cwnd_size);
    206     unbind_diag(params, OSSL_CC_OPTION_MIN_CWND_SIZE,
    207         (void **)&nr->p_diag_min_cwnd_size);
    208     unbind_diag(params, OSSL_CC_OPTION_CUR_BYTES_IN_FLIGHT,
    209         (void **)&nr->p_diag_cur_bytes_in_flight);
    210     unbind_diag(params, OSSL_CC_OPTION_CUR_STATE,
    211         (void **)&nr->p_diag_cur_state);
    212     return 1;
    213 }
    214 
    215 static void newreno_update_diag(OSSL_CC_NEWRENO *nr)
    216 {
    217     if (nr->p_diag_max_dgram_payload_len != NULL)
    218         *nr->p_diag_max_dgram_payload_len = nr->max_dgram_size;
    219 
    220     if (nr->p_diag_cur_cwnd_size != NULL)
    221         *nr->p_diag_cur_cwnd_size = nr->cong_wnd;
    222 
    223     if (nr->p_diag_min_cwnd_size != NULL)
    224         *nr->p_diag_min_cwnd_size = nr->k_min_wnd;
    225 
    226     if (nr->p_diag_cur_bytes_in_flight != NULL)
    227         *nr->p_diag_cur_bytes_in_flight = nr->bytes_in_flight;
    228 
    229     if (nr->p_diag_cur_state != NULL) {
    230         if (nr->in_congestion_recovery)
    231             *nr->p_diag_cur_state = 'R';
    232         else if (nr->cong_wnd < nr->slow_start_thresh)
    233             *nr->p_diag_cur_state = 'S';
    234         else
    235             *nr->p_diag_cur_state = 'A';
    236     }
    237 }
    238 
    239 static int newreno_in_cong_recovery(OSSL_CC_NEWRENO *nr, OSSL_TIME tx_time)
    240 {
    241     return ossl_time_compare(tx_time, nr->cong_recovery_start_time) <= 0;
    242 }
    243 
    244 static void newreno_cong(OSSL_CC_NEWRENO *nr, OSSL_TIME tx_time)
    245 {
    246     int err = 0;
    247 
    248     /* No reaction if already in a recovery period. */
    249     if (newreno_in_cong_recovery(nr, tx_time))
    250         return;
    251 
    252     /* Start a new recovery period. */
    253     nr->in_congestion_recovery = 1;
    254     nr->cong_recovery_start_time = nr->now_cb(nr->now_cb_arg);
    255 
    256     /* slow_start_thresh = cong_wnd * loss_reduction_factor */
    257     nr->slow_start_thresh
    258         = safe_muldiv_u64(nr->cong_wnd,
    259             nr->k_loss_reduction_factor_num,
    260             nr->k_loss_reduction_factor_den,
    261             &err);
    262 
    263     if (err)
    264         nr->slow_start_thresh = UINT64_MAX;
    265 
    266     nr->cong_wnd = nr->slow_start_thresh;
    267     if (nr->cong_wnd < nr->k_min_wnd)
    268         nr->cong_wnd = nr->k_min_wnd;
    269 }
    270 
    271 static void newreno_flush(OSSL_CC_NEWRENO *nr, uint32_t flags)
    272 {
    273     if (!nr->processing_loss)
    274         return;
    275 
    276     newreno_cong(nr, nr->tx_time_of_last_loss);
    277 
    278     if ((flags & OSSL_CC_LOST_FLAG_PERSISTENT_CONGESTION) != 0) {
    279         nr->cong_wnd = nr->k_min_wnd;
    280         nr->cong_recovery_start_time = ossl_time_zero();
    281     }
    282 
    283     nr->processing_loss = 0;
    284     newreno_update_diag(nr);
    285 }
    286 
    287 static uint64_t newreno_get_tx_allowance(OSSL_CC_DATA *cc)
    288 {
    289     OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
    290 
    291     if (nr->bytes_in_flight >= nr->cong_wnd)
    292         return 0;
    293 
    294     return nr->cong_wnd - nr->bytes_in_flight;
    295 }
    296 
    297 static OSSL_TIME newreno_get_wakeup_deadline(OSSL_CC_DATA *cc)
    298 {
    299     if (newreno_get_tx_allowance(cc) > 0) {
    300         /* We have TX allowance now so wakeup immediately */
    301         return ossl_time_zero();
    302     } else {
    303         /*
    304          * The NewReno congestion controller does not vary its state in time,
    305          * only in response to stimulus.
    306          */
    307         return ossl_time_infinite();
    308     }
    309 }
    310 
    311 static int newreno_on_data_sent(OSSL_CC_DATA *cc, uint64_t num_bytes)
    312 {
    313     OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
    314 
    315     nr->bytes_in_flight += num_bytes;
    316     newreno_update_diag(nr);
    317     return 1;
    318 }
    319 
    320 static int newreno_is_cong_limited(OSSL_CC_NEWRENO *nr)
    321 {
    322     uint64_t wnd_rem;
    323 
    324     /* We are congestion-limited if we are already at the congestion window. */
    325     if (nr->bytes_in_flight >= nr->cong_wnd)
    326         return 1;
    327 
    328     wnd_rem = nr->cong_wnd - nr->bytes_in_flight;
    329 
    330     /*
    331      * Consider ourselves congestion-limited if less than three datagrams' worth
    332      * of congestion window remains to be spent, or if we are in slow start and
    333      * have consumed half of our window.
    334      */
    335     return (nr->cong_wnd < nr->slow_start_thresh && wnd_rem <= nr->cong_wnd / 2)
    336         || wnd_rem <= 3 * nr->max_dgram_size;
    337 }
    338 
    339 static int newreno_on_data_acked(OSSL_CC_DATA *cc,
    340     const OSSL_CC_ACK_INFO *info)
    341 {
    342     OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
    343 
    344     /*
    345      * Packet has been acked. Firstly, remove it from the aggregate count of
    346      * bytes in flight.
    347      */
    348     nr->bytes_in_flight -= info->tx_size;
    349 
    350     /*
    351      * We use acknowledgement of data as a signal that we are not at channel
    352      * capacity and that it may be reasonable to increase the congestion window.
    353      * However, acknowledgement is not a useful signal that there is further
    354      * capacity if we are not actually saturating the congestion window that we
    355      * already have (for example, if the application is not generating much data
    356      * or we are limited by flow control). Therefore, we only expand the
    357      * congestion window if we are consuming a significant fraction of the
    358      * congestion window.
    359      */
    360     if (!newreno_is_cong_limited(nr))
    361         goto out;
    362 
    363     /*
    364      * We can handle acknowledgement of a packet in one of three ways
    365      * depending on our current state:
    366      *
    367      *   - Congestion Recovery: Do nothing. We don't start increasing
    368      *     the congestion window in response to acknowledgements until
    369      *     we are no longer in the Congestion Recovery state.
    370      *
    371      *   - Slow Start: Increase the congestion window using the slow
    372      *     start scale.
    373      *
    374      *   - Congestion Avoidance: Increase the congestion window using
    375      *     the congestion avoidance scale.
    376      */
    377     if (newreno_in_cong_recovery(nr, info->tx_time)) {
    378         /* Congestion recovery, do nothing. */
    379     } else if (nr->cong_wnd < nr->slow_start_thresh) {
    380         /* When this condition is true we are in the Slow Start state. */
    381         nr->cong_wnd += info->tx_size;
    382         nr->in_congestion_recovery = 0;
    383     } else {
    384         /* Otherwise, we are in the Congestion Avoidance state. */
    385         nr->bytes_acked += info->tx_size;
    386 
    387         /*
    388          * Avoid integer division as per RFC 9002 s. B.5. / RFC3465 s. 2.1.
    389          */
    390         if (nr->bytes_acked >= nr->cong_wnd) {
    391             nr->bytes_acked -= nr->cong_wnd;
    392             nr->cong_wnd += nr->max_dgram_size;
    393         }
    394 
    395         nr->in_congestion_recovery = 0;
    396     }
    397 
    398 out:
    399     newreno_update_diag(nr);
    400     return 1;
    401 }
    402 
    403 static int newreno_on_data_lost(OSSL_CC_DATA *cc,
    404     const OSSL_CC_LOSS_INFO *info)
    405 {
    406     OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
    407 
    408     if (info->tx_size > nr->bytes_in_flight)
    409         return 0;
    410 
    411     nr->bytes_in_flight -= info->tx_size;
    412 
    413     if (!nr->processing_loss) {
    414 
    415         if (ossl_time_compare(info->tx_time, nr->tx_time_of_last_loss) <= 0)
    416             /*
    417              * After triggering congestion due to a lost packet at time t, don't
    418              * trigger congestion again due to any subsequently detected lost
    419              * packet at a time s < t, as we've effectively already signalled
    420              * congestion on loss of that and subsequent packets.
    421              */
    422             goto out;
    423 
    424         nr->processing_loss = 1;
    425 
    426         /*
    427          * Cancel any pending window increase in the Congestion Avoidance state.
    428          */
    429         nr->bytes_acked = 0;
    430     }
    431 
    432     nr->tx_time_of_last_loss
    433         = ossl_time_max(nr->tx_time_of_last_loss, info->tx_time);
    434 
    435 out:
    436     newreno_update_diag(nr);
    437     return 1;
    438 }
    439 
    440 static int newreno_on_data_lost_finished(OSSL_CC_DATA *cc, uint32_t flags)
    441 {
    442     OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
    443 
    444     newreno_flush(nr, flags);
    445     return 1;
    446 }
    447 
    448 static int newreno_on_data_invalidated(OSSL_CC_DATA *cc,
    449     uint64_t num_bytes)
    450 {
    451     OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
    452 
    453     nr->bytes_in_flight -= num_bytes;
    454     newreno_update_diag(nr);
    455     return 1;
    456 }
    457 
    458 static int newreno_on_ecn(OSSL_CC_DATA *cc,
    459     const OSSL_CC_ECN_INFO *info)
    460 {
    461     OSSL_CC_NEWRENO *nr = (OSSL_CC_NEWRENO *)cc;
    462 
    463     nr->processing_loss = 1;
    464     nr->bytes_acked = 0;
    465     nr->tx_time_of_last_loss = info->largest_acked_time;
    466     newreno_flush(nr, 0);
    467     return 1;
    468 }
    469 
    470 const OSSL_CC_METHOD ossl_cc_newreno_method = {
    471     newreno_new,
    472     newreno_free,
    473     newreno_reset,
    474     newreno_set_input_params,
    475     newreno_bind_diagnostic,
    476     newreno_unbind_diagnostic,
    477     newreno_get_tx_allowance,
    478     newreno_get_wakeup_deadline,
    479     newreno_on_data_sent,
    480     newreno_on_data_acked,
    481     newreno_on_data_lost,
    482     newreno_on_data_lost_finished,
    483     newreno_on_data_invalidated,
    484     newreno_on_ecn,
    485 };
    486