1117 lines
42 KiB
C
1117 lines
42 KiB
C
/*
|
|
* Author: Christian Huitema
|
|
* Copyright (c) 2019, Private Octopus, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL Private Octopus, Inc. BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "picoquic_internal.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "cc_common.h"
|
|
|
|
/*
|
|
Implementation of the BBR algorithm, tuned for Picoquic.
|
|
|
|
The main idea of BBR is to track the "bottleneck bandwidth", and to tune the
|
|
transport stack to send exactly at that speed. This ensures good network
|
|
utilisation while avoiding the building of queues. To do that the stack
|
|
needs to constantly estimate the available data rate. It does that by
|
|
measuring the rate at which acknowledgements come back, providing what it
|
|
calls the delivery rate.
|
|
|
|
That approach includes an implicit feedback loop. The delivery rate can never
|
|
exceed the sending rate. That will effectively detects a transmission slow
|
|
down due to partial congestion, but if the algorithm just did that the
|
|
sending rate will remain constant when the network is lightly loaded
|
|
and ratchet down during time of congestion, leading to very low efficiency.
|
|
The available bandwidth can only be tested by occasionally sending faster
|
|
than the measured delivery rate.
|
|
|
|
BBR does that by following a cycle of "send, test and drain". During the
|
|
sending period, the stack sends at the measured rate. During the testing
|
|
period, it sends faster, 25% faster with recommended parameters. This
|
|
risk creating a queue if the bandwidth had not increased, so the test
|
|
period is followed by a drain period during which the stack sends 25%
|
|
slower than the measured rate. If the test is successful, the new bandwidth
|
|
measurement will be available at the end of the draining period, and
|
|
the increased bandwidth will be used in the next cycle.
|
|
|
|
Tuning the sending rate does not guarantee a short queue, it only
|
|
guarantees a stable queue. BBR controls the queue by limiting the
|
|
amount of data "in flight" (congestion window, CWIN) to the product
|
|
of the bandwidth estimate by the RTT estimate, plus a safety marging to ensure
|
|
continuous transmission. Using the average RTT there would lead to a runaway
|
|
loop in which oversized windows lead to increased queues and then increased
|
|
average RTT. Instead of average RTT, BBR uses a minimum RTT. Since the
|
|
mimimum RTT might vary with routing changes, the minimum RTT is measured
|
|
on a sliding window of 10 seconds.
|
|
|
|
The bandwidth estimation needs to be robust against short term variations
|
|
common in wireless networks. BBR retains the maximum
|
|
delivery rate observed over a series of probing intervals. Each interval
|
|
starts with a specific packet transmission and ends when that packet
|
|
or a later transmission is acknowledged. BBR does that by tracking
|
|
the delivered counter associated with packets and comparing it to
|
|
the delivered counter at start of period.
|
|
|
|
During start-up, BBR performs its own equivalent of Reno's slow-start.
|
|
It does that by using a pacing gain of 2.89, i.e. sending 2.89 times
|
|
faster than the measured maximum. It exits slow start when it found
|
|
a bandwidth sufficient to fill the pipe.
|
|
|
|
The bandwidth measurements can be wrong if the application is not sending
|
|
enough data to fill the pipe. BBR tracks that, and does not reduce bandwidth
|
|
or exit slow start if the application is limiting transmission.
|
|
|
|
This implementation follows draft-cardwell-iccrg-bbr-congestion-control,
|
|
with a couple of changes for handling the multipath nature of quic.
|
|
There is a BBR control state per path.
|
|
Most of BBR the variables defined in the draft are implemented
|
|
in the "BBR state" structure, with a few exceptions:
|
|
|
|
* BBR.delivered is represented by path_x.delivered, and is maintained
|
|
as part of ACK processing
|
|
|
|
* Instead of "bytes_in_transit", we use "bytes_in_transit", which is
|
|
already maintained by the stack.
|
|
|
|
* Compute bytes_delivered by summing all calls to ACK(bytes) before
|
|
the call to RTT update.
|
|
|
|
* In the Probe BW mode, the draft suggests cwnd_gain = 2. We observed
|
|
that this results in queue sizes of 2, which is too high, so we
|
|
reset that to 1.125.
|
|
|
|
The "packet" variables are defined in the picoquic_packet_t.
|
|
|
|
Early testing showed that BBR startup phase requires several more RTT
|
|
than the Hystart process used in modern versions of Reno or Cubic. BBR
|
|
only ramps up the data rate after the first bandwidth measurement is
|
|
available, 2*RTT after start, while Reno or Cubic start ramping up
|
|
after just 1 RTT. BBR only exits startup if three consecutive RTT
|
|
pass without significant BW measurement increase, which not only
|
|
adds delay but also creates big queues as data is sent at 2.89 times
|
|
the bottleneck rate. This is a tradeoff: longer search for bandwidth in
|
|
slow start is less likely to stop too early because of transient
|
|
issues, but one high bandwidth and long delay links this translates
|
|
to long delays and a big batch of packet losses.
|
|
|
|
This BBR implementation addresses these issues by switching to
|
|
Hystart instead of startup if the RTT is above the Reno target of
|
|
100 ms.
|
|
|
|
*/
|
|
|
|
/* Detection of leaky-bucket pacers.
|
|
* This is based on code added to BBR after the IETF draft was published.
|
|
* The code detect whether the connection is being "policed" by a leaky-bucket based
|
|
* parser, and introduces state variables:
|
|
* - lt_use_bw, boolean: check whether the connection is currently constrained
|
|
* to use a limited bandwidth.
|
|
* - lt_rtt_cnt: number of RTT during which the bandwidth has been limited. Exit the
|
|
* limited bandwidth state when this reaches the maximum value.
|
|
* - lt_is_sampling: whether the connection is sampling the number of loss
|
|
* intervals. This starts when the first losses are noticed. It is reset if
|
|
* the bandwidth is app limited.
|
|
* Sampling lasts for a number of rounds, as counted by the round_start
|
|
* variable. No action taken except updating counters in that state.
|
|
* Sampling can only ends on an interval when losses are detected, to avoid
|
|
* undercounting. If no loss, sampling continues after the min required
|
|
* interval. If no losses for too long, sampling state is reset.
|
|
* At the end of the sampling interval compute the ratio of packets lost
|
|
* to packet delivered. If it is below threshold, continue sampling, i.e.,
|
|
* extend the interval.
|
|
* If loss rate exceed the threshold, compute the duration of the interval.
|
|
* If it is too short, extend. If is is too long, reset the sampling.
|
|
* Else, compute the delivery rate for the interval. Check whether this
|
|
* is the first detection, in which case do nothing but remember the estimated
|
|
* rate in "lt_bw". Else, check whether the new rate is "close enough" to
|
|
* the previous rate, which indicates active policing. If that is true,
|
|
* activate policing to the average rate between current and previous sample.
|
|
*/
|
|
|
|
/* Reaction to losses and ECN
|
|
* This code is an implementation of BBRv1, which pretty much ignores packet
|
|
* losses or ECN marks. Google has still developed BBRv2, which is generally
|
|
* considered much more robust. Once the BBRv2 specification is available,
|
|
* we should develop it. However, before BBRv2 is there, we need to fix the
|
|
* most egregious issues in BBR v1. For example, in a test, we show that if
|
|
* a receiver starts a high speed download and then disappears, the sender
|
|
* will only close the connection after repeating over 1000 packets,
|
|
* compared to only 32 with New Reno or Cubic. This is because BBR does
|
|
* not slow down or reduce the CWIN on loss indication, even when there
|
|
* are many loss indications.
|
|
*
|
|
* We implement the following fixes:
|
|
*
|
|
* - On basic loss indication, run a filter to determine whether the loss
|
|
* rate is getting too high. This will allow the code to continue
|
|
* ignoring low loss rates, but somehow react to high loss rates.
|
|
*
|
|
* - If high loss rate is detected, halve the congestion window. Do
|
|
* the same if an EC mark is received.
|
|
*
|
|
* - If a timeout loss is detected, reduce the window to the minimum
|
|
* value.
|
|
*
|
|
* This needs to be coordinated with the BBR state machine. We implement
|
|
* it as such:
|
|
*
|
|
* - if the state is start-up or start-up-long-rtt, exit startup
|
|
* and move to a drain state.
|
|
* - if the state is probe-bw, start the new period with a conservative
|
|
* packet window (trigger by cycle_on_loss state variable)
|
|
* - if the state is probe-RTT, do nothing special...
|
|
*
|
|
* The packet losses and congestion signals should be used only once per
|
|
* RTT. We filter with a "loss period start time" value, and only
|
|
* take signals into account if they happen 1-RTT after the current
|
|
* loss start time. However, if the previous loss was not due to
|
|
* timeout, the timeout will still be handled.
|
|
*/
|
|
|
|
typedef enum {
|
|
picoquic_bbr_alg_startup = 0,
|
|
picoquic_bbr_alg_drain,
|
|
picoquic_bbr_alg_probe_bw,
|
|
picoquic_bbr_alg_probe_rtt,
|
|
picoquic_bbr_alg_startup_long_rtt
|
|
} picoquic_bbr_alg_state_t;
|
|
|
|
#define BBR_BTL_BW_FILTER_LENGTH 10
|
|
#define BBR_RT_PROP_FILTER_LENGTH 10
|
|
#define BBR_HIGH_GAIN 2.8853900817779 /* 2/ln(2) */
|
|
#define BBR_MIN_PIPE_CWND(mss) (4*mss)
|
|
#define BBR_GAIN_CYCLE_LEN 8
|
|
#define BBR_PROBE_RTT_INTERVAL 10000000 /* 10 sec, 10000000 microsecs */
|
|
#define BBR_PROBE_RTT_DURATION 200000 /* 200msec, 200000 microsecs */
|
|
#define BBR_PACING_RATE_LOW 150000.0 /* 150000 B/s = 1.2 Mbps */
|
|
#define BBR_PACING_RATE_MEDIUM 3000000.0 /* 3000000 B/s = 24 Mbps */
|
|
#define BBR_GAIN_CYCLE_LEN 8
|
|
#define BBR_GAIN_CYCLE_MAX_START 5
|
|
#define BBR_LT_BW_INTERVAL_MIN_RTT 4
|
|
#define BBR_LT_BW_RATIO_SCALE 1024
|
|
#define BBR_LT_BW_RATIO_SCALED_TARGET 205 /* 205/1024 is very close 20% */
|
|
|
|
#define BBR_LT_BW_INTERVAL_MAX_RTT (4*BBR_LT_BW_INTERVAL_MIN_RTT)
|
|
#define BBR_LT_BW_RATIO_INVERSE 8
|
|
#define BBR_LT_BW_BYTES_PER_SEC_DIFF 4000
|
|
#define BBR_LT_BW_MAX_RTTS 48
|
|
|
|
|
|
static const double bbr_pacing_gain_cycle[BBR_GAIN_CYCLE_LEN] = { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.25, 0.75};
|
|
|
|
typedef struct st_picoquic_bbr_state_t {
|
|
picoquic_bbr_alg_state_t state;
|
|
uint64_t btl_bw;
|
|
uint64_t next_round_delivered;
|
|
uint64_t round_start_time;
|
|
uint64_t btl_bw_filter[BBR_BTL_BW_FILTER_LENGTH];
|
|
uint64_t full_bw;
|
|
uint64_t rt_prop;
|
|
uint64_t rt_prop_stamp;
|
|
uint64_t cycle_stamp;
|
|
uint64_t probe_rtt_done_stamp;
|
|
uint64_t prior_cwnd;
|
|
uint64_t prior_in_flight;
|
|
uint64_t bytes_delivered; /* Number of bytes signalled in ACK notify, but not processed yet */
|
|
uint64_t send_quantum;
|
|
picoquic_min_max_rtt_t rtt_filter;
|
|
uint64_t target_cwnd;
|
|
double pacing_gain;
|
|
double cwnd_gain;
|
|
double pacing_rate;
|
|
unsigned int cycle_index;
|
|
unsigned int cycle_start;
|
|
int round_count;
|
|
int full_bw_count;
|
|
int lt_rtt_cnt;
|
|
uint64_t lt_bw;
|
|
uint64_t lt_last_stamp; /* Time in microsec at start of interval */
|
|
uint64_t previous_round_lost;
|
|
uint64_t previous_sampling_delivered;
|
|
uint64_t previous_sampling_lost;
|
|
uint64_t loss_interval_start; /* Time in microsec when last loss considered */
|
|
|
|
unsigned int filled_pipe : 1;
|
|
unsigned int round_start : 1;
|
|
unsigned int rt_prop_expired : 1;
|
|
unsigned int probe_rtt_round_done : 1;
|
|
unsigned int idle_restart : 1;
|
|
unsigned int packet_conservation : 1;
|
|
unsigned int btl_bw_increased : 1;
|
|
unsigned int lt_use_bw : 1;
|
|
unsigned int lt_is_sampling : 1;
|
|
unsigned int last_loss_was_timeout : 1;
|
|
unsigned int cycle_on_loss : 1;
|
|
|
|
} picoquic_bbr_state_t;
|
|
|
|
void BBRltbwSampling(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x, uint64_t current_time);
|
|
static void BBRResetProbeBwMode(picoquic_bbr_state_t* bbr_state, uint64_t current_time);
|
|
|
|
static uint64_t BBRGetBtlBW(picoquic_bbr_state_t* bbr_state)
|
|
{
|
|
return (bbr_state->lt_use_bw) ? bbr_state->lt_bw : bbr_state->btl_bw;
|
|
}
|
|
|
|
void BBREnterStartupLongRTT(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x)
|
|
{
|
|
uint64_t cwnd = PICOQUIC_CWIN_INITIAL;
|
|
bbr_state->state = picoquic_bbr_alg_startup_long_rtt;
|
|
|
|
if (path_x->rtt_min > PICOQUIC_TARGET_RENO_RTT) {
|
|
if (path_x->rtt_min > PICOQUIC_TARGET_SATELLITE_RTT) {
|
|
cwnd = (uint64_t)((double)cwnd * (double)PICOQUIC_TARGET_SATELLITE_RTT / (double)PICOQUIC_TARGET_RENO_RTT);
|
|
}
|
|
else {
|
|
cwnd = (uint64_t)((double)cwnd * (double)path_x->rtt_min / (double)PICOQUIC_TARGET_RENO_RTT);
|
|
}
|
|
}
|
|
if (cwnd > path_x->cwin) {
|
|
path_x->cwin = cwnd;
|
|
}
|
|
}
|
|
|
|
void BBREnterStartup(picoquic_bbr_state_t* bbr_state)
|
|
{
|
|
bbr_state->state = picoquic_bbr_alg_startup;
|
|
bbr_state->pacing_gain = BBR_HIGH_GAIN;
|
|
bbr_state->cwnd_gain = BBR_HIGH_GAIN;
|
|
}
|
|
|
|
void BBRSetSendQuantum(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x)
|
|
{
|
|
if (bbr_state->pacing_rate < BBR_PACING_RATE_LOW) {
|
|
bbr_state->send_quantum = 1ull * path_x->send_mtu;
|
|
}
|
|
else if (bbr_state->pacing_rate < BBR_PACING_RATE_MEDIUM) {
|
|
bbr_state->send_quantum = 2ull * path_x->send_mtu;
|
|
}
|
|
else {
|
|
bbr_state->send_quantum = (uint64_t)(bbr_state->pacing_rate * 0.001);
|
|
if (bbr_state->send_quantum > 0x10000) {
|
|
bbr_state->send_quantum = 0x10000;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint64_t BBRInflight(picoquic_bbr_state_t* bbr_state, double gain)
|
|
{
|
|
uint64_t cwnd = PICOQUIC_CWIN_INITIAL;
|
|
if (bbr_state->rt_prop != UINT64_MAX){
|
|
/* Bandwidth is estimated in bytes per second, rtt in microseconds*/
|
|
double estimated_bdp = (((double)BBRGetBtlBW(bbr_state) * (double)bbr_state->rt_prop) / 1000000.0);
|
|
uint64_t quanta = 3 * bbr_state->send_quantum;
|
|
cwnd = (uint64_t)(gain * estimated_bdp) + quanta;
|
|
}
|
|
return cwnd;
|
|
}
|
|
|
|
|
|
void BBRUpdateTargetCwnd(picoquic_bbr_state_t* bbr_state)
|
|
{
|
|
bbr_state->target_cwnd = BBRInflight(bbr_state, bbr_state->cwnd_gain);
|
|
}
|
|
|
|
static void picoquic_bbr_reset(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x, uint64_t current_time)
|
|
{
|
|
memset(bbr_state, 0, sizeof(picoquic_bbr_state_t));
|
|
path_x->cwin = PICOQUIC_CWIN_INITIAL;
|
|
bbr_state->rt_prop = UINT64_MAX;
|
|
|
|
bbr_state->rt_prop_stamp = current_time;
|
|
bbr_state->cycle_stamp = current_time;
|
|
bbr_state->cycle_index = 0;
|
|
bbr_state->cycle_start = 0;
|
|
|
|
BBREnterStartup(bbr_state);
|
|
BBRSetSendQuantum(bbr_state, path_x);
|
|
BBRUpdateTargetCwnd(bbr_state);
|
|
}
|
|
|
|
static void picoquic_bbr_init(picoquic_path_t* path_x, uint64_t current_time)
|
|
{
|
|
/* Initialize the state of the congestion control algorithm */
|
|
picoquic_bbr_state_t* bbr_state = (picoquic_bbr_state_t*)malloc(sizeof(picoquic_bbr_state_t));
|
|
path_x->congestion_alg_state = (void*)bbr_state;
|
|
if (bbr_state != NULL) {
|
|
picoquic_bbr_reset(bbr_state, path_x, current_time);
|
|
}
|
|
}
|
|
|
|
/* Release the state of the congestion control algorithm */
|
|
static void picoquic_bbr_delete(picoquic_path_t* path_x)
|
|
{
|
|
if (path_x->congestion_alg_state != NULL) {
|
|
free(path_x->congestion_alg_state);
|
|
path_x->congestion_alg_state = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/* Implementation of leaky-bucket pacer detection
|
|
*/
|
|
|
|
void BBRltbwResetInterval(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x, uint64_t current_time)
|
|
{
|
|
bbr_state->lt_last_stamp = current_time;
|
|
bbr_state->previous_sampling_delivered = path_x->delivered;
|
|
bbr_state->previous_sampling_lost = path_x->total_bytes_lost;
|
|
bbr_state->previous_round_lost = path_x->total_bytes_lost;
|
|
bbr_state->lt_rtt_cnt = 0;
|
|
}
|
|
|
|
void BBRltbwResetSampling(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x, uint64_t current_time)
|
|
{
|
|
bbr_state->lt_bw = 0;
|
|
bbr_state->lt_use_bw = 0;
|
|
bbr_state->lt_is_sampling = 0;
|
|
BBRltbwResetInterval(bbr_state, path_x, current_time);
|
|
}
|
|
|
|
void BBRltbwIntervalDone(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x, uint64_t bw, uint64_t current_time)
|
|
{
|
|
if (bbr_state->lt_bw) {
|
|
/* This is not the first limited interval. Look whether it is close enough */
|
|
uint64_t diff = (bw > bbr_state->lt_bw) ? bw - bbr_state->lt_bw : bbr_state->lt_bw - bw;
|
|
if (diff * BBR_LT_BW_RATIO_INVERSE < bbr_state->lt_bw ||
|
|
diff < BBR_LT_BW_BYTES_PER_SEC_DIFF) {
|
|
bbr_state->lt_bw = (bbr_state->lt_bw + bw) / 2;
|
|
bbr_state->lt_use_bw = 1;
|
|
bbr_state->pacing_gain = 1.0;
|
|
bbr_state->lt_rtt_cnt = 0;
|
|
return;
|
|
}
|
|
}
|
|
/* If first interval or non-matching rate, just remember */
|
|
bbr_state->lt_bw = bw;
|
|
BBRltbwResetInterval(bbr_state, path_x, current_time);
|
|
}
|
|
|
|
void BBRltbwSampling(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x, uint64_t current_time)
|
|
{
|
|
uint64_t losses = (path_x->total_bytes_lost > bbr_state->previous_round_lost) ?
|
|
path_x->total_bytes_lost - bbr_state->previous_round_lost : 0;
|
|
uint64_t delivered;
|
|
uint64_t interval_microsec;
|
|
uint64_t bw;
|
|
|
|
if (bbr_state->lt_use_bw) {
|
|
if (bbr_state->state == picoquic_bbr_alg_probe_bw && bbr_state->round_start) {
|
|
bbr_state->lt_rtt_cnt++;
|
|
if (bbr_state->lt_rtt_cnt > BBR_LT_BW_MAX_RTTS) {
|
|
BBRltbwResetSampling(bbr_state, path_x, current_time);
|
|
BBRResetProbeBwMode(bbr_state, current_time);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bbr_state->lt_is_sampling) {
|
|
/* Return if no loss; */
|
|
if (losses == 0) {
|
|
return;
|
|
}
|
|
/* Reset sampling otherwise. */
|
|
BBRltbwResetSampling(bbr_state, path_x, current_time);
|
|
bbr_state->lt_is_sampling = 1;
|
|
}
|
|
|
|
/* Reset sampling if app is limited */
|
|
if (path_x->last_bw_estimate_path_limited) {
|
|
BBRltbwResetSampling(bbr_state, path_x, current_time);
|
|
return;
|
|
}
|
|
/* Check whether we are reaching the end of the interval */
|
|
if (!bbr_state->round_start) {
|
|
return;
|
|
} else {
|
|
bbr_state->lt_rtt_cnt++; /* count round trips in this interval */
|
|
bbr_state->previous_round_lost = path_x->total_bytes_lost;
|
|
|
|
if (bbr_state->lt_rtt_cnt < BBR_LT_BW_INTERVAL_MIN_RTT) {
|
|
return; /* sampling interval needs to be longer */
|
|
}
|
|
if (bbr_state->lt_rtt_cnt > BBR_LT_BW_INTERVAL_MAX_RTT) {
|
|
BBRltbwResetSampling(bbr_state, path_x, current_time); /* interval is too long */
|
|
return;
|
|
}
|
|
}
|
|
/* Continue sampling if no losses on this round */
|
|
if (losses == 0) {
|
|
return;
|
|
}
|
|
/* Calculate bytes lost and delivered in sampling interval.
|
|
* Notice that the previous values of losses and delivered were for the round, not the interval.
|
|
*/
|
|
if (path_x->delivered <= bbr_state->previous_sampling_delivered) {
|
|
/* No delivery at all, cannot calculate any ratio, wait some more. */
|
|
return;
|
|
}
|
|
losses = (path_x->total_bytes_lost > bbr_state->previous_sampling_lost) ?
|
|
path_x->total_bytes_lost - bbr_state->previous_sampling_lost : 0;
|
|
delivered = path_x->delivered - bbr_state->previous_sampling_delivered;
|
|
/* Check the loss ratio */
|
|
if (losses * BBR_LT_BW_RATIO_SCALE < BBR_LT_BW_RATIO_SCALED_TARGET * delivered) {
|
|
/* Not enough losses, continue sampling */
|
|
return;
|
|
}
|
|
/* Find average delivery rate in this sampling interval. */
|
|
interval_microsec = current_time - bbr_state->lt_last_stamp;
|
|
if (interval_microsec < 1000) {
|
|
/* Interval too small for significant measurements, wait a bit */
|
|
return;
|
|
}
|
|
/* Compute bw in bytes per second */
|
|
bw = (delivered * 1000000) / interval_microsec;
|
|
/* Apply the changes */
|
|
BBRltbwIntervalDone(bbr_state, path_x, bw, current_time);
|
|
}
|
|
|
|
/* Track the round count using the "delivered" counter. The value carried per
|
|
* packet is the delivered count when this packet was sent. If it is greater
|
|
* than next_round_delivered, it means that the packet was sent at or after
|
|
* the beginning of the round, and thus that at least one RTT has elapsed
|
|
* for this round. */
|
|
|
|
void BBRUpdateBtlBw(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x, uint64_t current_time)
|
|
{
|
|
uint64_t bandwidth_estimate = path_x->bandwidth_estimate;
|
|
|
|
if (bbr_state->state == picoquic_bbr_alg_startup &&
|
|
bandwidth_estimate < (path_x->max_bandwidth_estimate / 2)) {
|
|
bandwidth_estimate = path_x->max_bandwidth_estimate/2;
|
|
}
|
|
|
|
if (bbr_state->rt_prop > 0) {
|
|
/* Stop the bandwidth estimate from falling too low. */
|
|
uint64_t min_bandwidth = (((uint64_t)PICOQUIC_CWIN_MINIMUM) * 1000000) / bbr_state->rt_prop;
|
|
if (bandwidth_estimate < min_bandwidth) {
|
|
bandwidth_estimate = min_bandwidth;
|
|
}
|
|
}
|
|
|
|
if (path_x->delivered_last_packet >= bbr_state->next_round_delivered)
|
|
{
|
|
bbr_state->next_round_delivered = path_x->delivered;
|
|
bbr_state->round_count++;
|
|
bbr_state->round_start = 1;
|
|
}
|
|
else {
|
|
bbr_state->round_start = 0;
|
|
}
|
|
|
|
BBRltbwSampling(bbr_state, path_x, current_time);
|
|
|
|
if (bbr_state->round_start) {
|
|
/* Forget the oldest BW round, shift by 1, compute the max BTL_BW for
|
|
* the remaining rounds, set current round max to current value */
|
|
|
|
bbr_state->btl_bw = 0;
|
|
|
|
for (int i = BBR_BTL_BW_FILTER_LENGTH - 2; i >= 0; i--) {
|
|
uint64_t b = bbr_state->btl_bw_filter[i];
|
|
bbr_state->btl_bw_filter[i + 1] = b;
|
|
if (b > bbr_state->btl_bw) {
|
|
bbr_state->btl_bw = b;
|
|
}
|
|
}
|
|
|
|
bbr_state->btl_bw_filter[0] = 0;
|
|
}
|
|
|
|
if (bandwidth_estimate > bbr_state->btl_bw_filter[0]) {
|
|
bbr_state->btl_bw_filter[0] =bandwidth_estimate;
|
|
if (bandwidth_estimate > bbr_state->btl_bw) {
|
|
bbr_state->btl_bw = bandwidth_estimate;
|
|
bbr_state->btl_bw_increased = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* This will use one way samples if available */
|
|
/* Should augment that with common RTT filter to suppress jitter */
|
|
void BBRUpdateRTprop(picoquic_bbr_state_t* bbr_state, uint64_t rtt_sample, uint64_t current_time)
|
|
{
|
|
bbr_state->rt_prop_expired =
|
|
current_time > bbr_state->rt_prop_stamp + BBR_PROBE_RTT_INTERVAL &&
|
|
current_time > bbr_state->rt_prop_stamp + 20 * bbr_state->rt_prop;
|
|
if (rtt_sample <= bbr_state->rt_prop || bbr_state->rt_prop_expired) {
|
|
bbr_state->rt_prop = rtt_sample;
|
|
bbr_state->rt_prop_stamp = current_time;
|
|
}
|
|
else {
|
|
uint64_t delta = rtt_sample - bbr_state->rt_prop;
|
|
if (20 * delta < bbr_state->rt_prop) {
|
|
bbr_state->rt_prop_stamp = current_time;
|
|
}
|
|
}
|
|
}
|
|
|
|
int BBRIsNextCyclePhase(picoquic_bbr_state_t* bbr_state, uint64_t prior_in_flight, uint64_t packets_lost, uint64_t current_time)
|
|
{
|
|
int is_full_length = bbr_state->cycle_on_loss || (current_time - bbr_state->cycle_stamp) > bbr_state->rt_prop;
|
|
|
|
if (bbr_state->pacing_gain != 1.0) {
|
|
if (bbr_state->pacing_gain > 1.0) {
|
|
is_full_length &=
|
|
(packets_lost > 0 ||
|
|
prior_in_flight >= BBRInflight(bbr_state, bbr_state->pacing_gain));
|
|
}
|
|
else { /* (BBR.pacing_gain < 1) */
|
|
is_full_length &= prior_in_flight <= BBRInflight(bbr_state, 1.0);
|
|
}
|
|
}
|
|
return is_full_length;
|
|
}
|
|
|
|
void BBRSetMinimalGain(picoquic_bbr_state_t* bbr_state)
|
|
{
|
|
if (bbr_state->pacing_gain > 1.0 && bbr_state->rt_prop > 0) {
|
|
uint64_t target_cwin = bbr_state->btl_bw * bbr_state->rt_prop / 1000000;
|
|
|
|
if (target_cwin < 4 * PICOQUIC_MAX_PACKET_SIZE) {
|
|
double d_target = (double)target_cwin;
|
|
double d_gain = ((double)(4 * PICOQUIC_MAX_PACKET_SIZE)) / d_target;
|
|
|
|
if (d_gain > bbr_state->pacing_gain) {
|
|
bbr_state->pacing_gain = d_gain;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BBRAdvanceCyclePhase(picoquic_bbr_state_t* bbr_state, uint64_t current_time)
|
|
{
|
|
bbr_state->cycle_on_loss = 0;
|
|
bbr_state->cycle_stamp = current_time;
|
|
bbr_state->cycle_index++;
|
|
if (bbr_state->cycle_index >= BBR_GAIN_CYCLE_LEN) {
|
|
unsigned int start = bbr_state->cycle_start;
|
|
if (bbr_state->btl_bw_increased) {
|
|
bbr_state->btl_bw_increased = 0;
|
|
start++;
|
|
if (start > BBR_GAIN_CYCLE_MAX_START) {
|
|
start = BBR_GAIN_CYCLE_MAX_START;
|
|
}
|
|
}
|
|
else if (start > 0) {
|
|
start--;
|
|
}
|
|
bbr_state->cycle_index = start;
|
|
bbr_state->cycle_start = start;
|
|
}
|
|
|
|
bbr_state->pacing_gain = bbr_pacing_gain_cycle[bbr_state->cycle_index];
|
|
BBRSetMinimalGain(bbr_state);
|
|
}
|
|
|
|
void BBRCheckCyclePhase(picoquic_bbr_state_t* bbr_state, uint64_t packets_lost, uint64_t current_time)
|
|
{
|
|
if (bbr_state->state == picoquic_bbr_alg_probe_bw &&
|
|
BBRIsNextCyclePhase(bbr_state, bbr_state->prior_in_flight, packets_lost, current_time)) {
|
|
BBRAdvanceCyclePhase(bbr_state, current_time);
|
|
}
|
|
}
|
|
|
|
static void BBRResetProbeBwMode(picoquic_bbr_state_t* bbr_state, uint64_t current_time)
|
|
{
|
|
bbr_state->state = picoquic_bbr_alg_probe_bw;
|
|
bbr_state->cycle_index = 2;
|
|
BBRAdvanceCyclePhase(bbr_state, current_time);
|
|
}
|
|
|
|
void BBRCheckFullPipe(picoquic_bbr_state_t* bbr_state, int rs_is_app_limited)
|
|
{
|
|
if (!bbr_state->filled_pipe && bbr_state->round_start && !rs_is_app_limited) {
|
|
if (bbr_state->btl_bw >= bbr_state->full_bw * 1.25) { // BBR.BtlBw still growing?
|
|
bbr_state->full_bw = bbr_state->btl_bw; // record new baseline level
|
|
bbr_state->full_bw_count = 0;
|
|
}
|
|
else {
|
|
bbr_state->full_bw_count++; // another round w/o much growth
|
|
if (bbr_state->full_bw_count >= 3) {
|
|
bbr_state->filled_pipe = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BBREnterProbeBW(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x, uint64_t current_time)
|
|
{
|
|
unsigned int start = 0;
|
|
bbr_state->state = picoquic_bbr_alg_probe_bw;
|
|
bbr_state->pacing_gain = 1.0;
|
|
bbr_state->cwnd_gain = 2.0;
|
|
|
|
if (bbr_state->rt_prop > PICOQUIC_TARGET_RENO_RTT) {
|
|
uint64_t ref_rt = (bbr_state->rt_prop > PICOQUIC_TARGET_SATELLITE_RTT) ? PICOQUIC_TARGET_SATELLITE_RTT : bbr_state->rt_prop;
|
|
start = (unsigned int)(ref_rt / PICOQUIC_TARGET_RENO_RTT);
|
|
if (start > BBR_GAIN_CYCLE_MAX_START) {
|
|
start = BBR_GAIN_CYCLE_MAX_START;
|
|
}
|
|
}
|
|
else {
|
|
start = 2;
|
|
}
|
|
|
|
bbr_state->cycle_index = start;
|
|
bbr_state->cycle_start = start;
|
|
bbr_state->btl_bw_increased = 1;
|
|
|
|
BBRAdvanceCyclePhase(bbr_state, current_time);
|
|
/* Start sampling */
|
|
BBRltbwSampling(bbr_state, path_x, current_time);
|
|
}
|
|
|
|
void BBREnterDrain(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x, uint64_t current_time)
|
|
{
|
|
bbr_state->state = picoquic_bbr_alg_drain;
|
|
bbr_state->pacing_gain = 1.0 / BBR_HIGH_GAIN; /* pace slowly */
|
|
bbr_state->cwnd_gain = BBR_HIGH_GAIN; /* maintain cwnd */
|
|
/* Start sampling */
|
|
BBRltbwSampling(bbr_state, path_x, current_time);
|
|
}
|
|
|
|
void BBRCheckDrain(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x, uint64_t bytes_in_transit, uint64_t current_time)
|
|
{
|
|
if (bbr_state->state == picoquic_bbr_alg_startup && bbr_state->filled_pipe) {
|
|
BBREnterDrain(bbr_state, path_x, current_time);
|
|
}
|
|
|
|
if (bbr_state->state == picoquic_bbr_alg_drain && bytes_in_transit <= BBRInflight(bbr_state, 1.0)) {
|
|
BBREnterProbeBW(bbr_state, path_x, current_time); /* we estimate queue is drained */
|
|
}
|
|
}
|
|
|
|
void BBRExitStartupLongRtt(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x, uint64_t current_time)
|
|
{
|
|
/* Reset the round filter so it will start at current time */
|
|
bbr_state->next_round_delivered = path_x->delivered;
|
|
bbr_state->round_count++;
|
|
bbr_state->round_start = 1;
|
|
/* Set the filled pipe indicator */
|
|
bbr_state->full_bw = bbr_state->btl_bw;
|
|
bbr_state->full_bw_count = 3;
|
|
bbr_state->filled_pipe = 1;
|
|
/* Check the RTT measurement for pathological cases */
|
|
if ((bbr_state->rtt_filter.is_init || bbr_state->rtt_filter.sample_current > 0) &&
|
|
bbr_state->rt_prop > 30000000 &&
|
|
bbr_state->rtt_filter.sample_max < bbr_state->rt_prop) {
|
|
bbr_state->rt_prop = bbr_state->rtt_filter.sample_max;
|
|
bbr_state->rt_prop_stamp = current_time;
|
|
}
|
|
/* Enter drain */
|
|
BBREnterDrain(bbr_state, path_x, current_time);
|
|
/* If there were just few bytes in transit, enter probe */
|
|
if (path_x->bytes_in_transit <= BBRInflight(bbr_state, 1.0)) {
|
|
BBREnterProbeBW(bbr_state, path_x, current_time);
|
|
}
|
|
}
|
|
|
|
void BBREnterProbeRTT(picoquic_bbr_state_t* bbr_state)
|
|
{
|
|
bbr_state->state = picoquic_bbr_alg_probe_rtt;
|
|
bbr_state->pacing_gain = 1.0;
|
|
bbr_state->cwnd_gain = 1.0;
|
|
}
|
|
|
|
void BBRExitProbeRTT(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x, uint64_t current_time)
|
|
{
|
|
if (bbr_state->filled_pipe) {
|
|
BBREnterProbeBW(bbr_state, path_x, current_time);
|
|
}
|
|
else {
|
|
BBREnterStartup(bbr_state);
|
|
}
|
|
}
|
|
|
|
int InLossRecovery(picoquic_bbr_state_t* bbr_state)
|
|
{
|
|
return bbr_state->packet_conservation;
|
|
}
|
|
|
|
uint64_t BBRSaveCwnd(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x) {
|
|
uint64_t w = path_x->cwin;
|
|
|
|
if ((InLossRecovery(bbr_state) || bbr_state->state == picoquic_bbr_alg_probe_bw) &&
|
|
(path_x->cwin < bbr_state->prior_cwnd)){
|
|
w = bbr_state->prior_cwnd;
|
|
}
|
|
|
|
return w;
|
|
}
|
|
|
|
void BBRRestoreCwnd(picoquic_bbr_state_t* bbr_state, picoquic_path_t * path_x)
|
|
{
|
|
if (path_x->cwin < bbr_state->prior_cwnd) {
|
|
path_x->cwin = bbr_state->prior_cwnd;
|
|
}
|
|
}
|
|
|
|
|
|
void BBRHandleProbeRTT(picoquic_bbr_state_t* bbr_state, picoquic_path_t * path_x, uint64_t bytes_in_transit, uint64_t current_time)
|
|
{
|
|
#if 0
|
|
/* Ignore low rate samples during ProbeRTT: */
|
|
C.app_limited =
|
|
(BW.delivered + bytes_in_transit) ? 0 : 1;
|
|
#endif
|
|
|
|
if (bbr_state->probe_rtt_done_stamp == 0 &&
|
|
bytes_in_transit <= BBR_MIN_PIPE_CWND(path_x->send_mtu)) {
|
|
bbr_state->probe_rtt_done_stamp =
|
|
current_time + BBR_PROBE_RTT_DURATION;
|
|
bbr_state->probe_rtt_round_done = 0;
|
|
bbr_state->next_round_delivered = path_x->delivered;
|
|
}
|
|
else if (bbr_state->probe_rtt_done_stamp != 0) {
|
|
if (bbr_state->round_start) {
|
|
bbr_state->probe_rtt_round_done = 1;
|
|
}
|
|
|
|
if (bbr_state->probe_rtt_round_done &&
|
|
current_time > bbr_state->probe_rtt_done_stamp) {
|
|
bbr_state->rt_prop_stamp = current_time;
|
|
BBRRestoreCwnd(bbr_state, path_x);
|
|
BBRExitProbeRTT(bbr_state, path_x, current_time);
|
|
}
|
|
}
|
|
}
|
|
|
|
void BBRCheckProbeRTT(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x, uint64_t bytes_in_transit, uint64_t current_time)
|
|
{
|
|
if (bbr_state->state != picoquic_bbr_alg_probe_rtt &&
|
|
bbr_state->rt_prop_expired &&
|
|
!bbr_state->idle_restart) {
|
|
BBREnterProbeRTT(bbr_state);
|
|
bbr_state->prior_cwnd = BBRSaveCwnd(bbr_state, path_x);
|
|
bbr_state->probe_rtt_done_stamp = 0;
|
|
}
|
|
|
|
if (bbr_state->state == picoquic_bbr_alg_probe_rtt) {
|
|
BBRHandleProbeRTT(bbr_state, path_x, bytes_in_transit, current_time);
|
|
bbr_state->idle_restart = 0;
|
|
}
|
|
}
|
|
|
|
void BBRUpdateModelAndState(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x,
|
|
uint64_t rtt_sample, uint64_t bytes_in_transit, uint64_t packets_lost, uint64_t current_time)
|
|
{
|
|
BBRUpdateBtlBw(bbr_state, path_x, current_time);
|
|
BBRCheckCyclePhase(bbr_state, packets_lost, current_time);
|
|
BBRCheckFullPipe(bbr_state, path_x->last_bw_estimate_path_limited);
|
|
BBRCheckDrain(bbr_state, path_x, bytes_in_transit, current_time);
|
|
BBRUpdateRTprop(bbr_state, rtt_sample, current_time);
|
|
BBRCheckProbeRTT(bbr_state, path_x, bytes_in_transit, current_time);
|
|
}
|
|
|
|
void BBRSetPacingRateWithGain(picoquic_bbr_state_t* bbr_state, double pacing_gain)
|
|
{
|
|
double rate = pacing_gain * (double)BBRGetBtlBW(bbr_state);
|
|
|
|
if (bbr_state->filled_pipe || rate > bbr_state->pacing_rate){
|
|
bbr_state->pacing_rate = rate;
|
|
}
|
|
}
|
|
|
|
void BBRSetPacingRate(picoquic_bbr_state_t* bbr_state)
|
|
{
|
|
BBRSetPacingRateWithGain(bbr_state, bbr_state->pacing_gain);
|
|
}
|
|
|
|
/* TODO: clarity on bytes vs packets */
|
|
void BBRModulateCwndForRecovery(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x,
|
|
uint64_t bytes_in_transit, uint64_t bytes_lost, uint64_t bytes_delivered)
|
|
{
|
|
if (bytes_lost > 0) {
|
|
if (path_x->cwin > bytes_lost) {
|
|
path_x->cwin -= bytes_lost;
|
|
}
|
|
else {
|
|
path_x->cwin = path_x->send_mtu;
|
|
}
|
|
}
|
|
if (bbr_state->packet_conservation) {
|
|
if (path_x->cwin < bytes_in_transit + bytes_delivered) {
|
|
path_x->cwin = bytes_in_transit + bytes_delivered;
|
|
}
|
|
}
|
|
}
|
|
|
|
void BBRModulateCwndForProbeRTT(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x)
|
|
{
|
|
if (bbr_state->state == picoquic_bbr_alg_probe_rtt)
|
|
{
|
|
if (path_x->cwin > BBR_MIN_PIPE_CWND(path_x->send_mtu)) {
|
|
path_x->cwin = BBR_MIN_PIPE_CWND(path_x->send_mtu);
|
|
}
|
|
}
|
|
}
|
|
|
|
void BBRSetCwnd(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x, uint64_t bytes_in_transit, uint64_t packets_lost, uint64_t bytes_delivered)
|
|
{
|
|
BBRUpdateTargetCwnd(bbr_state);
|
|
BBRModulateCwndForRecovery(bbr_state, path_x, bytes_in_transit, packets_lost, bytes_delivered);
|
|
if (!bbr_state->packet_conservation) {
|
|
if (bbr_state->filled_pipe) {
|
|
path_x->cwin += bytes_delivered;
|
|
if (path_x->cwin > bbr_state->target_cwnd) {
|
|
path_x->cwin = bbr_state->target_cwnd;
|
|
}
|
|
}
|
|
else if (path_x->cwin < bbr_state->target_cwnd || path_x->delivered < PICOQUIC_CWIN_INITIAL)
|
|
{
|
|
path_x->cwin += bytes_delivered;
|
|
}
|
|
if (path_x->cwin < BBR_MIN_PIPE_CWND(path_x->send_mtu))
|
|
{
|
|
path_x->cwin = BBR_MIN_PIPE_CWND(path_x->send_mtu);
|
|
}
|
|
}
|
|
|
|
BBRModulateCwndForProbeRTT(bbr_state, path_x);
|
|
}
|
|
|
|
|
|
void BBRUpdateControlParameters(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x, uint64_t bytes_in_transit, uint64_t packets_lost, uint64_t bytes_delivered)
|
|
{
|
|
BBRSetPacingRate(bbr_state);
|
|
BBRSetSendQuantum(bbr_state, path_x);
|
|
BBRSetCwnd(bbr_state, path_x, bytes_in_transit, packets_lost, bytes_delivered);
|
|
}
|
|
|
|
void BBRHandleRestartFromIdle(picoquic_bbr_state_t* bbr_state, uint64_t bytes_in_transit, int is_app_limited)
|
|
{
|
|
if (bytes_in_transit == 0 && is_app_limited)
|
|
{
|
|
bbr_state->idle_restart = 1;
|
|
if (bbr_state->state == picoquic_bbr_alg_probe_bw) {
|
|
BBRSetPacingRateWithGain(bbr_state, 1.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* This is the per ACK processing, activated upon receiving an ACK.
|
|
* At that point, we expect the following:
|
|
* - delivered has been updated to reflect all the data acked on the path.
|
|
* - the delivery rate sample has been computed.
|
|
*/
|
|
|
|
void BBRUpdateOnACK(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x,
|
|
uint64_t rtt_sample, uint64_t bytes_in_transit, uint64_t packets_lost, uint64_t bytes_delivered,
|
|
uint64_t current_time)
|
|
{
|
|
BBRUpdateModelAndState(bbr_state, path_x, rtt_sample, bytes_in_transit,
|
|
packets_lost, current_time);
|
|
BBRUpdateControlParameters(bbr_state, path_x, bytes_in_transit, packets_lost, bytes_delivered);
|
|
}
|
|
|
|
void BBROnTransmit(picoquic_bbr_state_t* bbr_state, uint64_t bytes_in_transit, int is_app_limited)
|
|
{
|
|
BBRHandleRestartFromIdle(bbr_state, bytes_in_transit, is_app_limited);
|
|
}
|
|
|
|
/* Dealing with recovery. What happens when all
|
|
* the packets are lost, when all packets have been retransmitted.. */
|
|
|
|
void BBROnAllPacketsLost(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x)
|
|
{
|
|
bbr_state->prior_cwnd = BBRSaveCwnd(bbr_state, path_x);
|
|
path_x->cwin = path_x->send_mtu;
|
|
}
|
|
|
|
void BBROnEnterFastRecovery(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x, uint64_t bytes_in_transit, uint64_t bytes_delivered )
|
|
{
|
|
if (bytes_delivered < path_x->send_mtu) {
|
|
bytes_delivered = path_x->send_mtu;
|
|
}
|
|
bbr_state->prior_cwnd = BBRSaveCwnd(bbr_state, path_x);
|
|
path_x->cwin = bytes_in_transit + bytes_delivered;
|
|
bbr_state->packet_conservation = 1;
|
|
}
|
|
|
|
void BBRAfterOneRoundtripInFastRecovery(picoquic_bbr_state_t* bbr_state)
|
|
{
|
|
bbr_state->packet_conservation = 0;
|
|
}
|
|
|
|
void BBRExitFastRecovery(picoquic_bbr_state_t* bbr_state, picoquic_path_t* path_x)
|
|
{
|
|
bbr_state->packet_conservation = 0;
|
|
BBRRestoreCwnd(bbr_state, path_x);
|
|
}
|
|
|
|
/* Reaction to ECN or sustained losses
|
|
*/
|
|
void picoquic_bbr_notify_congestion(
|
|
picoquic_bbr_state_t* bbr_state,
|
|
picoquic_path_t* path_x,
|
|
uint64_t current_time,
|
|
int is_timeout)
|
|
{
|
|
/* Apply filter of last loss */
|
|
if ((bbr_state->cycle_on_loss || current_time < bbr_state->loss_interval_start + path_x->smoothed_rtt) &&
|
|
(!is_timeout || bbr_state->last_loss_was_timeout)) {
|
|
/* filter repeated loss events */
|
|
return;
|
|
}
|
|
path_x->cwin = path_x->cwin / 2;
|
|
if (is_timeout || path_x->cwin < PICOQUIC_CWIN_MINIMUM) {
|
|
path_x->cwin = PICOQUIC_CWIN_MINIMUM;
|
|
}
|
|
|
|
bbr_state->loss_interval_start = current_time;
|
|
bbr_state->last_loss_was_timeout = is_timeout;
|
|
|
|
/* Update and check the packet loss rate */
|
|
if (bbr_state->state == picoquic_bbr_alg_startup_long_rtt) {
|
|
BBRExitStartupLongRtt(bbr_state, path_x, current_time);
|
|
}
|
|
else if (bbr_state->state == picoquic_bbr_alg_startup) {
|
|
bbr_state->filled_pipe = 1;
|
|
BBREnterDrain(bbr_state, path_x, current_time);
|
|
}
|
|
else {
|
|
bbr_state->cycle_on_loss = 1;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* In order to implement BBR, we map generic congestion notification
|
|
* signals to the corresponding BBR actions.
|
|
*/
|
|
static void picoquic_bbr_notify(
|
|
picoquic_cnx_t* cnx,
|
|
picoquic_path_t* path_x,
|
|
picoquic_congestion_notification_t notification,
|
|
uint64_t rtt_measurement,
|
|
uint64_t one_way_delay,
|
|
uint64_t nb_bytes_acknowledged,
|
|
uint64_t lost_packet_number,
|
|
uint64_t current_time)
|
|
{
|
|
#ifdef _WINDOWS
|
|
UNREFERENCED_PARAMETER(lost_packet_number);
|
|
#endif
|
|
picoquic_bbr_state_t* bbr_state = (picoquic_bbr_state_t*)path_x->congestion_alg_state;
|
|
|
|
if (bbr_state != NULL) {
|
|
switch (notification) {
|
|
case picoquic_congestion_notification_acknowledgement:
|
|
/* sum the amount of data acked per packet */
|
|
bbr_state->bytes_delivered += nb_bytes_acknowledged;
|
|
break;
|
|
case picoquic_congestion_notification_ecn_ec:
|
|
/* Non standard code to react on ECN_EC */
|
|
picoquic_bbr_notify_congestion(bbr_state, path_x, current_time, 0);
|
|
break;
|
|
case picoquic_congestion_notification_repeat:
|
|
case picoquic_congestion_notification_timeout:
|
|
/* Non standard code to react to high rate of packet loss, or timeout loss */
|
|
if (picoquic_hystart_loss_test(&bbr_state->rtt_filter, notification, lost_packet_number)) {
|
|
picoquic_bbr_notify_congestion(bbr_state, path_x, current_time,
|
|
(notification == picoquic_congestion_notification_timeout) ? 1 : 0);
|
|
}
|
|
break;
|
|
case picoquic_congestion_notification_spurious_repeat:
|
|
break;
|
|
case picoquic_congestion_notification_rtt_measurement:
|
|
if (bbr_state->state == picoquic_bbr_alg_startup && path_x->rtt_min > PICOQUIC_TARGET_RENO_RTT) {
|
|
BBREnterStartupLongRTT(bbr_state, path_x);
|
|
}
|
|
if (bbr_state->state == picoquic_bbr_alg_startup_long_rtt) {
|
|
if (picoquic_hystart_test(&bbr_state->rtt_filter, (cnx->is_time_stamp_enabled) ? one_way_delay : rtt_measurement,
|
|
cnx->path[0]->pacing_packet_time_microsec, current_time, cnx->is_time_stamp_enabled)) {
|
|
BBRExitStartupLongRtt(bbr_state, path_x, current_time);
|
|
}
|
|
}
|
|
break;
|
|
case picoquic_congestion_notification_bw_measurement:
|
|
/* RTT measurements will happen after the bandwidth is estimated */
|
|
if (bbr_state->state == picoquic_bbr_alg_startup_long_rtt) {
|
|
uint64_t max_win;
|
|
uint64_t min_win;
|
|
|
|
BBRUpdateBtlBw(bbr_state, path_x, current_time);
|
|
if (rtt_measurement <= bbr_state->rt_prop) {
|
|
bbr_state->rt_prop = rtt_measurement;
|
|
bbr_state->rt_prop_stamp = current_time;
|
|
}
|
|
if (path_x->last_time_acked_data_frame_sent > path_x->last_sender_limited_time) {
|
|
picoquic_hystart_increase(path_x, &bbr_state->rtt_filter, bbr_state->bytes_delivered);
|
|
}
|
|
bbr_state->bytes_delivered = 0;
|
|
|
|
max_win = path_x->max_bandwidth_estimate * bbr_state->rt_prop / 1000000;
|
|
min_win = max_win /= 2;
|
|
|
|
if (path_x->cwin < min_win) {
|
|
path_x->cwin =min_win;
|
|
}
|
|
|
|
picoquic_update_pacing_data(cnx, path_x, 1);
|
|
} else {
|
|
BBRUpdateOnACK(bbr_state, path_x,
|
|
rtt_measurement, path_x->bytes_in_transit, 0 /* packets_lost */, bbr_state->bytes_delivered,
|
|
current_time);
|
|
/* Remember the number in flight before the next ACK -- TODO: update after send instead. */
|
|
bbr_state->prior_in_flight = path_x->bytes_in_transit;
|
|
/* Reset the number of bytes delivered */
|
|
bbr_state->bytes_delivered = 0;
|
|
|
|
if (bbr_state->pacing_rate > 0) {
|
|
/* Set the pacing rate in picoquic sender */
|
|
picoquic_update_pacing_rate(cnx, path_x, bbr_state->pacing_rate, bbr_state->send_quantum);
|
|
}
|
|
}
|
|
break;
|
|
case picoquic_congestion_notification_cwin_blocked:
|
|
break;
|
|
case picoquic_congestion_notification_reset:
|
|
picoquic_bbr_reset(bbr_state, path_x, current_time);
|
|
break;
|
|
default:
|
|
/* ignore */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Observe the state of congestion control */
|
|
|
|
void picoquic_bbr_observe(picoquic_path_t* path_x, uint64_t* cc_state, uint64_t* cc_param)
|
|
{
|
|
picoquic_bbr_state_t* bbr_state = (picoquic_bbr_state_t*)path_x->congestion_alg_state;
|
|
*cc_state = (uint64_t)bbr_state->state;
|
|
*cc_param = bbr_state->btl_bw;
|
|
}
|
|
|
|
#define picoquic_bbr_ID "bbr" /* BBR */
|
|
|
|
picoquic_congestion_algorithm_t picoquic_bbr_algorithm_struct = {
|
|
picoquic_bbr_ID, 5,
|
|
picoquic_bbr_init,
|
|
picoquic_bbr_notify,
|
|
picoquic_bbr_delete,
|
|
picoquic_bbr_observe
|
|
};
|
|
|
|
picoquic_congestion_algorithm_t* picoquic_bbr_algorithm = &picoquic_bbr_algorithm_struct; |