123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511 |
- /*
- * ngtcp2
- *
- * Copyright (c) 2018 ngtcp2 contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining
- * a copy of this software and associated documentation files (the
- * "Software"), to deal in the Software without restriction, including
- * without limitation the rights to use, copy, modify, merge, publish,
- * distribute, sublicense, and/or sell copies of the Software, and to
- * permit persons to whom the Software is furnished to do so, subject to
- * the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- */
- #include "ngtcp2_cc.h"
- #include <assert.h>
- #include <string.h>
- #include "ngtcp2_log.h"
- #include "ngtcp2_macro.h"
- #include "ngtcp2_mem.h"
- #include "ngtcp2_rcvry.h"
- #include "ngtcp2_conn_stat.h"
- #include "ngtcp2_rst.h"
- #include "ngtcp2_unreachable.h"
- uint64_t ngtcp2_cc_compute_initcwnd(size_t max_udp_payload_size) {
- uint64_t n = 2 * max_udp_payload_size;
- n = ngtcp2_max_uint64(n, 14720);
- return ngtcp2_min_uint64(10 * max_udp_payload_size, n);
- }
- ngtcp2_cc_pkt *ngtcp2_cc_pkt_init(ngtcp2_cc_pkt *pkt, int64_t pkt_num,
- size_t pktlen, ngtcp2_pktns_id pktns_id,
- ngtcp2_tstamp sent_ts, uint64_t lost,
- uint64_t tx_in_flight, int is_app_limited) {
- pkt->pkt_num = pkt_num;
- pkt->pktlen = pktlen;
- pkt->pktns_id = pktns_id;
- pkt->sent_ts = sent_ts;
- pkt->lost = lost;
- pkt->tx_in_flight = tx_in_flight;
- pkt->is_app_limited = is_app_limited;
- return pkt;
- }
- static void reno_cc_reset(ngtcp2_cc_reno *reno) { reno->pending_add = 0; }
- void ngtcp2_cc_reno_init(ngtcp2_cc_reno *reno, ngtcp2_log *log) {
- memset(reno, 0, sizeof(*reno));
- reno->cc.log = log;
- reno->cc.on_pkt_acked = ngtcp2_cc_reno_cc_on_pkt_acked;
- reno->cc.congestion_event = ngtcp2_cc_reno_cc_congestion_event;
- reno->cc.on_persistent_congestion =
- ngtcp2_cc_reno_cc_on_persistent_congestion;
- reno->cc.reset = ngtcp2_cc_reno_cc_reset;
- reno_cc_reset(reno);
- }
- static int in_congestion_recovery(const ngtcp2_conn_stat *cstat,
- ngtcp2_tstamp sent_time) {
- return cstat->congestion_recovery_start_ts != UINT64_MAX &&
- sent_time <= cstat->congestion_recovery_start_ts;
- }
- void ngtcp2_cc_reno_cc_on_pkt_acked(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
- const ngtcp2_cc_pkt *pkt,
- ngtcp2_tstamp ts) {
- ngtcp2_cc_reno *reno = ngtcp2_struct_of(cc, ngtcp2_cc_reno, cc);
- uint64_t m;
- (void)ts;
- if (in_congestion_recovery(cstat, pkt->sent_ts) || pkt->is_app_limited) {
- return;
- }
- if (cstat->cwnd < cstat->ssthresh) {
- cstat->cwnd += pkt->pktlen;
- ngtcp2_log_info(reno->cc.log, NGTCP2_LOG_EVENT_CCA,
- "pkn=%" PRId64 " acked, slow start cwnd=%" PRIu64,
- pkt->pkt_num, cstat->cwnd);
- return;
- }
- m = cstat->max_tx_udp_payload_size * pkt->pktlen + reno->pending_add;
- reno->pending_add = m % cstat->cwnd;
- cstat->cwnd += m / cstat->cwnd;
- }
- void ngtcp2_cc_reno_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
- ngtcp2_tstamp sent_ts,
- uint64_t bytes_lost, ngtcp2_tstamp ts) {
- ngtcp2_cc_reno *reno = ngtcp2_struct_of(cc, ngtcp2_cc_reno, cc);
- uint64_t min_cwnd;
- (void)bytes_lost;
- if (in_congestion_recovery(cstat, sent_ts)) {
- return;
- }
- cstat->congestion_recovery_start_ts = ts;
- cstat->cwnd >>= NGTCP2_LOSS_REDUCTION_FACTOR_BITS;
- min_cwnd = 2 * cstat->max_tx_udp_payload_size;
- cstat->cwnd = ngtcp2_max_uint64(cstat->cwnd, min_cwnd);
- cstat->ssthresh = cstat->cwnd;
- reno->pending_add = 0;
- ngtcp2_log_info(reno->cc.log, NGTCP2_LOG_EVENT_CCA,
- "reduce cwnd because of packet loss cwnd=%" PRIu64,
- cstat->cwnd);
- }
- void ngtcp2_cc_reno_cc_on_persistent_congestion(ngtcp2_cc *cc,
- ngtcp2_conn_stat *cstat,
- ngtcp2_tstamp ts) {
- (void)cc;
- (void)ts;
- cstat->cwnd = 2 * cstat->max_tx_udp_payload_size;
- cstat->congestion_recovery_start_ts = UINT64_MAX;
- }
- void ngtcp2_cc_reno_cc_reset(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
- ngtcp2_tstamp ts) {
- ngtcp2_cc_reno *reno = ngtcp2_struct_of(cc, ngtcp2_cc_reno, cc);
- (void)cstat;
- (void)ts;
- reno_cc_reset(reno);
- }
- static void cubic_vars_reset(ngtcp2_cubic_vars *v) {
- v->cwnd_prior = 0;
- v->w_max = 0;
- v->k = 0;
- v->epoch_start = UINT64_MAX;
- v->w_est = 0;
- v->state = NGTCP2_CUBIC_STATE_INITIAL;
- v->app_limited_start_ts = UINT64_MAX;
- v->app_limited_duration = 0;
- v->pending_bytes_delivered = 0;
- v->pending_est_bytes_delivered = 0;
- }
- static void cubic_cc_reset(ngtcp2_cc_cubic *cubic) {
- cubic_vars_reset(&cubic->current);
- cubic_vars_reset(&cubic->undo.v);
- cubic->undo.cwnd = 0;
- cubic->undo.ssthresh = 0;
- cubic->hs.current_round_min_rtt = UINT64_MAX;
- cubic->hs.last_round_min_rtt = UINT64_MAX;
- cubic->hs.curr_rtt = UINT64_MAX;
- cubic->hs.rtt_sample_count = 0;
- cubic->hs.css_baseline_min_rtt = UINT64_MAX;
- cubic->hs.css_round = 0;
- cubic->next_round_delivered = 0;
- }
- void ngtcp2_cc_cubic_init(ngtcp2_cc_cubic *cubic, ngtcp2_log *log,
- ngtcp2_rst *rst) {
- memset(cubic, 0, sizeof(*cubic));
- cubic->cc.log = log;
- cubic->cc.on_ack_recv = ngtcp2_cc_cubic_cc_on_ack_recv;
- cubic->cc.congestion_event = ngtcp2_cc_cubic_cc_congestion_event;
- cubic->cc.on_spurious_congestion = ngtcp2_cc_cubic_cc_on_spurious_congestion;
- cubic->cc.on_persistent_congestion =
- ngtcp2_cc_cubic_cc_on_persistent_congestion;
- cubic->cc.reset = ngtcp2_cc_cubic_cc_reset;
- cubic->rst = rst;
- cubic_cc_reset(cubic);
- }
- uint64_t ngtcp2_cbrt(uint64_t n) {
- size_t s;
- uint64_t y = 0;
- uint64_t b;
- for (s = 63; s > 0; s -= 3) {
- y <<= 1;
- b = 3 * y * (y + 1) + 1;
- if ((n >> s) >= b) {
- n -= b << s;
- y++;
- }
- }
- y <<= 1;
- b = 3 * y * (y + 1) + 1;
- if (n >= b) {
- n -= b;
- y++;
- }
- return y;
- }
- /* RFC 9406 HyStart++ constants */
- #define NGTCP2_HS_MIN_RTT_THRESH (4 * NGTCP2_MILLISECONDS)
- #define NGTCP2_HS_MAX_RTT_THRESH (16 * NGTCP2_MILLISECONDS)
- #define NGTCP2_HS_MIN_RTT_DIVISOR 8
- #define NGTCP2_HS_N_RTT_SAMPLE 8
- #define NGTCP2_HS_CSS_GROWTH_DIVISOR 4
- #define NGTCP2_HS_CSS_ROUNDS 5
- static uint64_t cubic_cc_compute_w_cubic(ngtcp2_cc_cubic *cubic,
- const ngtcp2_conn_stat *cstat,
- ngtcp2_tstamp ts) {
- ngtcp2_duration t = ts - cubic->current.epoch_start;
- uint64_t delta;
- uint64_t tx = (t << 10) / NGTCP2_SECONDS;
- uint64_t kx = (cubic->current.k << 10) / NGTCP2_SECONDS;
- uint64_t time_delta;
- if (tx < kx) {
- return UINT64_MAX;
- }
- time_delta = tx - kx;
- delta = cstat->max_tx_udp_payload_size *
- ((((time_delta * time_delta) >> 10) * time_delta) >> 10) * 4 / 10;
- return cubic->current.w_max + (delta >> 10);
- }
- void ngtcp2_cc_cubic_cc_on_ack_recv(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
- const ngtcp2_cc_ack *ack,
- ngtcp2_tstamp ts) {
- ngtcp2_cc_cubic *cubic = ngtcp2_struct_of(cc, ngtcp2_cc_cubic, cc);
- uint64_t w_cubic, w_cubic_next, target, m;
- ngtcp2_duration rtt_thresh;
- int round_start;
- if (in_congestion_recovery(cstat, ack->largest_pkt_sent_ts)) {
- return;
- }
- if (cubic->current.state == NGTCP2_CUBIC_STATE_CONGESTION_AVOIDANCE) {
- if (cubic->rst->rs.is_app_limited && !cubic->rst->is_cwnd_limited) {
- if (cubic->current.app_limited_start_ts == UINT64_MAX) {
- cubic->current.app_limited_start_ts = ts;
- }
- return;
- }
- if (cubic->current.app_limited_start_ts != UINT64_MAX) {
- cubic->current.app_limited_duration +=
- ts - cubic->current.app_limited_start_ts;
- cubic->current.app_limited_start_ts = UINT64_MAX;
- }
- } else if (cubic->rst->rs.is_app_limited && !cubic->rst->is_cwnd_limited) {
- return;
- }
- round_start = ack->pkt_delivered >= cubic->next_round_delivered;
- if (round_start) {
- cubic->next_round_delivered = cubic->rst->delivered;
- cubic->rst->is_cwnd_limited = 0;
- }
- if (cstat->cwnd < cstat->ssthresh) {
- /* slow-start */
- if (cubic->hs.css_round) {
- cstat->cwnd += ack->bytes_delivered / NGTCP2_HS_CSS_GROWTH_DIVISOR;
- } else {
- cstat->cwnd += ack->bytes_delivered;
- }
- ngtcp2_log_info(cubic->cc.log, NGTCP2_LOG_EVENT_CCA,
- "%" PRIu64 " bytes acked, slow start cwnd=%" PRIu64,
- ack->bytes_delivered, cstat->cwnd);
- if (round_start) {
- cubic->hs.last_round_min_rtt = cubic->hs.current_round_min_rtt;
- cubic->hs.current_round_min_rtt = UINT64_MAX;
- cubic->hs.rtt_sample_count = 0;
- if (cubic->hs.css_round) {
- ++cubic->hs.css_round;
- }
- }
- cubic->hs.current_round_min_rtt =
- ngtcp2_min_uint64(cubic->hs.current_round_min_rtt, ack->rtt);
- ++cubic->hs.rtt_sample_count;
- if (cubic->hs.css_round) {
- if (cubic->hs.current_round_min_rtt < cubic->hs.css_baseline_min_rtt) {
- cubic->hs.css_baseline_min_rtt = UINT64_MAX;
- cubic->hs.css_round = 0;
- return;
- }
- if (cubic->hs.css_round >= NGTCP2_HS_CSS_ROUNDS) {
- ngtcp2_log_info(cubic->cc.log, NGTCP2_LOG_EVENT_CCA,
- "HyStart++ exit slow start");
- cstat->ssthresh = cstat->cwnd;
- }
- return;
- }
- if (cubic->hs.rtt_sample_count >= NGTCP2_HS_N_RTT_SAMPLE &&
- cubic->hs.current_round_min_rtt != UINT64_MAX &&
- cubic->hs.last_round_min_rtt != UINT64_MAX) {
- rtt_thresh =
- ngtcp2_max_uint64(NGTCP2_HS_MIN_RTT_THRESH,
- ngtcp2_min_uint64(cubic->hs.last_round_min_rtt /
- NGTCP2_HS_MIN_RTT_DIVISOR,
- NGTCP2_HS_MAX_RTT_THRESH));
- if (cubic->hs.current_round_min_rtt >=
- cubic->hs.last_round_min_rtt + rtt_thresh) {
- cubic->hs.css_baseline_min_rtt = cubic->hs.current_round_min_rtt;
- cubic->hs.css_round = 1;
- }
- }
- return;
- }
- /* congestion avoidance */
- switch (cubic->current.state) {
- case NGTCP2_CUBIC_STATE_INITIAL:
- m = cstat->max_tx_udp_payload_size * ack->bytes_delivered +
- cubic->current.pending_bytes_delivered;
- cstat->cwnd += m / cstat->cwnd;
- cubic->current.pending_bytes_delivered = m % cstat->cwnd;
- return;
- case NGTCP2_CUBIC_STATE_RECOVERY:
- cubic->current.state = NGTCP2_CUBIC_STATE_CONGESTION_AVOIDANCE;
- cubic->current.epoch_start = ts;
- break;
- default:
- break;
- }
- w_cubic = cubic_cc_compute_w_cubic(cubic, cstat,
- ts - cubic->current.app_limited_duration);
- w_cubic_next = cubic_cc_compute_w_cubic(
- cubic, cstat,
- ts - cubic->current.app_limited_duration + cstat->smoothed_rtt);
- if (w_cubic_next == UINT64_MAX || w_cubic_next < cstat->cwnd) {
- target = cstat->cwnd;
- } else if (2 * w_cubic_next > 3 * cstat->cwnd) {
- target = cstat->cwnd * 3 / 2;
- } else {
- target = w_cubic_next;
- }
- m = ack->bytes_delivered * cstat->max_tx_udp_payload_size +
- cubic->current.pending_est_bytes_delivered;
- cubic->current.pending_est_bytes_delivered = m % cstat->cwnd;
- if (cubic->current.w_est < cubic->current.cwnd_prior) {
- cubic->current.w_est += m * 9 / 17 / cstat->cwnd;
- } else {
- cubic->current.w_est += m / cstat->cwnd;
- }
- if (w_cubic == UINT64_MAX || cubic->current.w_est > w_cubic) {
- cstat->cwnd = cubic->current.w_est;
- } else {
- m = (target - cstat->cwnd) * cstat->max_tx_udp_payload_size +
- cubic->current.pending_bytes_delivered;
- cstat->cwnd += m / cstat->cwnd;
- cubic->current.pending_bytes_delivered = m % cstat->cwnd;
- }
- ngtcp2_log_info(cubic->cc.log, NGTCP2_LOG_EVENT_CCA,
- "%" PRIu64 " bytes acked, cubic-ca cwnd=%" PRIu64
- " k=%" PRIi64 " target=%" PRIu64 " w_est=%" PRIu64,
- ack->bytes_delivered, cstat->cwnd, cubic->current.k, target,
- cubic->current.w_est);
- }
- void ngtcp2_cc_cubic_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
- ngtcp2_tstamp sent_ts,
- uint64_t bytes_lost,
- ngtcp2_tstamp ts) {
- ngtcp2_cc_cubic *cubic = ngtcp2_struct_of(cc, ngtcp2_cc_cubic, cc);
- uint64_t flight_size;
- if (in_congestion_recovery(cstat, sent_ts)) {
- return;
- }
- if (cubic->undo.cwnd < cstat->cwnd) {
- cubic->undo.v = cubic->current;
- cubic->undo.cwnd = cstat->cwnd;
- cubic->undo.ssthresh = cstat->ssthresh;
- }
- cstat->congestion_recovery_start_ts = ts;
- cubic->current.state = NGTCP2_CUBIC_STATE_RECOVERY;
- cubic->current.epoch_start = UINT64_MAX;
- cubic->current.app_limited_start_ts = UINT64_MAX;
- cubic->current.app_limited_duration = 0;
- cubic->current.pending_bytes_delivered = 0;
- cubic->current.pending_est_bytes_delivered = 0;
- if (cstat->cwnd < cubic->current.w_max) {
- cubic->current.w_max = cstat->cwnd * 17 / 20;
- } else {
- cubic->current.w_max = cstat->cwnd;
- }
- cstat->ssthresh = cstat->cwnd * 7 / 10;
- if (cubic->rst->rs.delivered * 2 < cstat->cwnd) {
- flight_size = cstat->bytes_in_flight + bytes_lost;
- cstat->ssthresh = ngtcp2_min_uint64(
- cstat->ssthresh,
- ngtcp2_max_uint64(cubic->rst->rs.delivered, flight_size) * 7 / 10);
- }
- cstat->ssthresh =
- ngtcp2_max_uint64(cstat->ssthresh, 2 * cstat->max_tx_udp_payload_size);
- cubic->current.cwnd_prior = cstat->cwnd;
- cstat->cwnd = cstat->ssthresh;
- cubic->current.w_est = cstat->cwnd;
- if (cstat->cwnd < cubic->current.w_max) {
- cubic->current.k =
- ngtcp2_cbrt(((cubic->current.w_max - cstat->cwnd) << 10) * 10 / 4 /
- cstat->max_tx_udp_payload_size) *
- NGTCP2_SECONDS;
- cubic->current.k >>= 10;
- } else {
- cubic->current.k = 0;
- }
- ngtcp2_log_info(cubic->cc.log, NGTCP2_LOG_EVENT_CCA,
- "reduce cwnd because of packet loss cwnd=%" PRIu64,
- cstat->cwnd);
- }
- void ngtcp2_cc_cubic_cc_on_spurious_congestion(ngtcp2_cc *cc,
- ngtcp2_conn_stat *cstat,
- ngtcp2_tstamp ts) {
- ngtcp2_cc_cubic *cubic = ngtcp2_struct_of(cc, ngtcp2_cc_cubic, cc);
- (void)ts;
- cstat->congestion_recovery_start_ts = UINT64_MAX;
- if (cstat->cwnd < cubic->undo.cwnd) {
- cubic->current = cubic->undo.v;
- cstat->cwnd = cubic->undo.cwnd;
- cstat->ssthresh = cubic->undo.ssthresh;
- ngtcp2_log_info(cubic->cc.log, NGTCP2_LOG_EVENT_CCA,
- "spurious congestion is detected and congestion state is "
- "restored cwnd=%" PRIu64,
- cstat->cwnd);
- }
- cubic_vars_reset(&cubic->undo.v);
- cubic->undo.cwnd = 0;
- cubic->undo.ssthresh = 0;
- }
- void ngtcp2_cc_cubic_cc_on_persistent_congestion(ngtcp2_cc *cc,
- ngtcp2_conn_stat *cstat,
- ngtcp2_tstamp ts) {
- ngtcp2_cc_cubic *cubic = ngtcp2_struct_of(cc, ngtcp2_cc_cubic, cc);
- (void)ts;
- cubic_cc_reset(cubic);
- cstat->cwnd = 2 * cstat->max_tx_udp_payload_size;
- cstat->congestion_recovery_start_ts = UINT64_MAX;
- }
- void ngtcp2_cc_cubic_cc_reset(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
- ngtcp2_tstamp ts) {
- ngtcp2_cc_cubic *cubic = ngtcp2_struct_of(cc, ngtcp2_cc_cubic, cc);
- (void)cstat;
- (void)ts;
- cubic_cc_reset(cubic);
- }
|