mirror of https://github.com/openssl/openssl.git
				
				
				
			
		
			
				
	
	
		
			486 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			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,
 | |
| };
 |