Browse Source

congestion: заменить AIMD на BBR v3 (per-link), добавить юнит-тесты BBR

- Новые файлы: src/etcp_bbr.h/c — полный порт BBR v3 из BBR/bbr_v3.c (референс сохранён)
- struct ETCP_LINK: удалены AIMD-поля (rtt_history, rtt_swm, stat_win, stats_timer, inflight_phase...), добавлены bbr*, delivered_bytes, bbr_pacing_rate
- struct INFLIGHT_PACKET: +3 поля для rate_sample (delivered_at_send, inflight_at_send, is_app_limited)
- etcp_ack_recv: вызов bbr_main() с rate_sample, обновление inflight_lim_bytes и pacing
- etcp_request_pkt: заполнение BBR rate_sample полей при отправке
- Шейпер: bandwidth обновляется из bbr_pacing_rate
- Конфиг: inflight_min/max заменены на bbr_max_cwnd
- Дамп: BBR поля (mode, cycle, pacing, min_rtt_us, bw_hi/lo, inflight_hi/lo)
- Time injection: bbr->now_tb для детерминированных тестов
- tests/test_etcp_bbr.c: 8 юнит-тестов (init, startup, full_bw/drain, loss, cwnd cap, loss cut, fast_path, probe_rtt)
- Отключены test_etcp_congestion и test_etcp_reinit_inflight (старый congestion control)
- 40/40 тестов пройдено
etcp-inflight-fix
Evgeny 4 months ago
parent
commit
6c79939db3
  1. 2408
      src/BBR/bbr_v3.c
  2. 1
      src/Makefile.am
  3. 13
      src/config_parser.c
  4. 3
      src/config_parser.h
  5. 2
      src/control_server.c
  6. 117
      src/etcp.c
  7. 3
      src/etcp.h
  8. 928
      src/etcp_bbr.c
  9. 107
      src/etcp_bbr.h
  10. 198
      src/etcp_connections.c
  11. 42
      src/etcp_connections.h
  12. 40
      src/etcp_dump.c
  13. 25
      tests/Makefile.am
  14. 161
      tests/test_etcp_bbr.c

2408
src/BBR/bbr_v3.c

File diff suppressed because it is too large Load Diff

1
src/Makefile.am

@ -20,6 +20,7 @@ utun_CORE_SOURCES = \
tun_windows.c \ tun_windows.c \
etcp.c \ etcp.c \
etcp_connections.c \ etcp_connections.c \
etcp_bbr.c \
etcp_loadbalancer.c \ etcp_loadbalancer.c \
etcp_debug.c \ etcp_debug.c \
etcp_dump.c \ etcp_dump.c \

13
src/config_parser.c

@ -376,12 +376,8 @@ static int parse_global(const char *key, const char *value, struct global_config
global->keepalive_interval = atoi(value); global->keepalive_interval = atoi(value);
return 0; return 0;
} }
if (strcmp(key, "inflight_min_bytes") == 0) { if (strcmp(key, "bbr_max_cwnd") == 0) {
global->inflight_min_bytes = atoi(value); global->bbr_max_cwnd = atoi(value);
return 0;
}
if (strcmp(key, "inflight_max_bytes") == 0) {
global->inflight_max_bytes = atoi(value);
return 0; return 0;
} }
if (strcmp(key, "debug_level") == 0) { if (strcmp(key, "debug_level") == 0) {
@ -411,7 +407,7 @@ static int parse_global(const char *key, const char *value, struct global_config
global->tun_test_mode = atoi(value); global->tun_test_mode = atoi(value);
return 0; return 0;
} }
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown global option '%s'. Valid: my_node_name, my_private_key, my_public_key, my_node_id, tun_ifname, tun_ip, mtu, keepalive_timeout, keepalive_interval, inflight_min_bytes, inflight_max_bytes, debug_level, log_file, enable_timestamp, enable_function_names, enable_file_lines, enable_colors, tun_test_mode", filename, line_num, key); DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown global option '%s'. Valid: my_node_name, my_private_key, my_public_key, my_node_id, tun_ifname, tun_ip, mtu, keepalive_timeout, keepalive_interval, bbr_max_cwnd, debug_level, log_file, enable_timestamp, enable_function_names, enable_file_lines, enable_colors, tun_test_mode", filename, line_num, key);
return -1; return -1;
} }
@ -724,8 +720,7 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
cfg->global.name[0] = '\0'; cfg->global.name[0] = '\0';
cfg->global.keepalive_timeout = 2000; // Default 2 seconds cfg->global.keepalive_timeout = 2000; // Default 2 seconds
cfg->global.keepalive_interval = 200; // Default 0.2 s cfg->global.keepalive_interval = 200; // Default 0.2 s
cfg->global.inflight_min_bytes = 2000; // Default 2KB cfg->global.bbr_max_cwnd = 100000; // Default 100KB
cfg->global.inflight_max_bytes = 100000; // Default 100KB
cfg->global.firewall_rules = NULL; cfg->global.firewall_rules = NULL;
cfg->global.firewall_rule_count = 0; cfg->global.firewall_rule_count = 0;
cfg->global.firewall_bypass_all = 0; cfg->global.firewall_bypass_all = 0;

3
src/config_parser.h

@ -118,8 +118,7 @@ struct global_config {
int tun_test_mode; // test mode: 1 = don't open real TUN, queues only int tun_test_mode; // test mode: 1 = don't open real TUN, queues only
int keepalive_timeout; // keepalive timeout in ms (default: 2000) int keepalive_timeout; // keepalive timeout in ms (default: 2000)
int keepalive_interval; // keepalive interval in ms (default: 200) int keepalive_interval; // keepalive interval in ms (default: 200)
int inflight_min_bytes; // inflight window starting size (default: 2000) int bbr_max_cwnd; // BBR cwnd cap in bytes (default: 100000)
int inflight_max_bytes; // inflight window max cap (default: 100000)
// Firewall configuration // Firewall configuration
struct CFG_FIREWALL_RULE *firewall_rules; struct CFG_FIREWALL_RULE *firewall_rules;

2
src/control_server.c

@ -1068,7 +1068,7 @@ static void send_metrics(struct control_server* server, struct control_client* c
link_info[i].bandwidth = link->bandwidth; link_info[i].bandwidth = link->bandwidth;
link_info[i].nat_changes_count = link->nat_changes_count; link_info[i].nat_changes_count = link->nat_changes_count;
link_info[i].rtt_last = link->rtt_last; link_info[i].rtt_last = link->rtt_last;
link_info[i].rtt_avg10 = link->rtt_avg10; link_info[i].rtt_avg10 = link->bbr ? link->bbr->min_rtt_us / 100 : 0;
link_info[i].tt_last = link->tt_last; link_info[i].tt_last = link->tt_last;
link_info[i].init_timer_active = (link->init_timer != NULL) ? 1 : 0; link_info[i].init_timer_active = (link->init_timer != NULL) ? 1 : 0;
link_info[i].keepalive_timer_active = (link->keepalive_timer != NULL) ? 1 : 0; link_info[i].keepalive_timer_active = (link->keepalive_timer != NULL) ? 1 : 0;

117
src/etcp.c

@ -392,14 +392,10 @@ void etcp_conn_reset(struct ETCP_CONN* etcp) {
// В etcp_conn_reset(), после очистки очередей добавьте: // В etcp_conn_reset(), после очистки очередей добавьте:
struct ETCP_LINK* l = etcp->links; struct ETCP_LINK* l = etcp->links;
while (l) { while (l) {
l->window_pkt_transmitted = 0;
l->window_retransmissions = 0;
l->win_timebase = 50000;
l->win_ptr = 0;
memset(l->stat_win, 0, sizeof(l->stat_win));
start_stats_timer(l); // перезапускаем с дефолтным интервалом
l->inflight_bytes = 0; l->inflight_bytes = 0;
l->inflight_packets = 0; l->inflight_packets = 0;
l->delivered_bytes = 0;
l->last_ack_time_tb = get_time_tb();
l = l->next; l = l->next;
} }
@ -923,7 +919,6 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
// Always subtract from previous last_link (if any) – this handles retransmission // Always subtract from previous last_link (if any) – this handles retransmission
if (inf_pkt->last_link) { if (inf_pkt->last_link) {
inf_pkt->last_link->window_retransmissions++;
inf_pkt->last_link->total_retransmissions++; inf_pkt->last_link->total_retransmissions++;
inf_pkt->last_link->inflight_bytes -= inf_pkt->ll.len; inf_pkt->last_link->inflight_bytes -= inf_pkt->ll.len;
inf_pkt->last_link->inflight_packets--; inf_pkt->last_link->inflight_packets--;
@ -933,7 +928,11 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
// Always add to the CURRENT link (first send or retransmission) // Always add to the CURRENT link (first send or retransmission)
link->inflight_bytes += inf_pkt->ll.len; link->inflight_bytes += inf_pkt->ll.len;
link->inflight_packets++; link->inflight_packets++;
link->window_pkt_transmitted++;
// BBR: сохраняем снэпшот на момент отправки
inf_pkt->delivered_at_send = link->delivered_bytes;
inf_pkt->inflight_at_send = link->inflight_bytes;
inf_pkt->is_app_limited = (etcp->input_queue->count == 0) ? 1 : 0;
// Update last_link for future ACK/retrans // Update last_link for future ACK/retrans
inf_pkt->last_link = link; inf_pkt->last_link = link;
@ -1212,11 +1211,40 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d
return; return;
} }
// === NEW: subtract inflight from the LAST link the packet was sent on === // === subtract inflight from the LAST link the packet was sent on ===
if (acked_pkt->last_link) { if (acked_pkt->last_link) {
acked_pkt->last_link->inflight_bytes -= acked_pkt->ll.len; struct ETCP_LINK* link = acked_pkt->last_link;
acked_pkt->last_link->inflight_packets--; link->inflight_bytes -= acked_pkt->ll.len;
if (acked_pkt->last_link->send_blocked_inflight && acked_pkt->last_link->inflight_bytes < acked_pkt->last_link->inflight_lim_bytes) loadbalancer_link_ready(acked_pkt->last_link); link->inflight_packets--;
// BBR: собираем rate_sample и вызываем bbr_main
uint64_t now_tb = get_time_tb();
uint32_t interval_us = link->last_ack_time_tb ? (uint32_t)((now_tb - link->last_ack_time_tb) * 100) : 0;
struct bbr_rate_sample rs = {
.delivered = acked_pkt->ll.len,
.interval_us = interval_us,
.rtt_us = (uint32_t)link->rtt_last * 100,
.acked_sacked = acked_pkt->ll.len,
.prior_delivered = (uint32_t)acked_pkt->delivered_at_send,
.tx_in_flight = acked_pkt->inflight_at_send,
.lost = 0,
.is_app_limited = acked_pkt->is_app_limited,
};
link->delivered_bytes += rs.delivered;
link->last_ack_time_tb = now_tb;
uint32_t new_cwnd = link->inflight_lim_bytes;
uint32_t new_pacing = link->bbr_pacing_rate;
bbr_main(link->bbr, &rs, &new_cwnd, &new_pacing, link->mtu, link->inflight_bytes,
(link->inflight_bytes >= link->inflight_lim_bytes));
link->inflight_lim_bytes = new_cwnd;
link->bbr_pacing_rate = new_pacing;
link->bandwidth = (uint32_t)((uint64_t)new_pacing * 8 / 1000);
if (link->send_blocked_inflight && link->inflight_bytes < new_cwnd) {
link->send_blocked_inflight = 0;
loadbalancer_link_ready(link);
}
} }
@ -1298,45 +1326,6 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
uint16_t new_rtt=cur_ts-ret_ts; uint16_t new_rtt=cur_ts-ret_ts;
pkt->link->rtt_last=new_rtt; pkt->link->rtt_last=new_rtt;
uint8_t old_idx = pkt->link->rtt_history_index;
uint16_t old_val = pkt->link->rtt_history[old_idx];
pkt->link->rtt_history[old_idx] = new_rtt;
pkt->link->rtt_history_index = (old_idx + 1) % 10;
if (pkt->link->rtt_history_count < 10) pkt->link->rtt_history_count++;
if (new_rtt >= pkt->link->rtt_max_val) {
pkt->link->rtt_max_val = new_rtt;
pkt->link->rtt_max_idx = old_idx;
} else if (old_val == pkt->link->rtt_max_val) {
pkt->link->rtt_max_idx = 255;
}
uint8_t rtt_cnt = pkt->link->rtt_history_count;
if (rtt_cnt == 1) {
pkt->link->rtt_avg10 = new_rtt;
} else {
uint32_t sum = 0;
if (pkt->link->rtt_max_idx == 255) {
uint16_t max_val = 0;
uint8_t max_pos = 0;
for (uint8_t i = 0; i < rtt_cnt; i++) {
uint16_t v = pkt->link->rtt_history[i];
sum += v;
if (v > max_val) {
max_val = v;
max_pos = i;
}
}
pkt->link->rtt_max_val = max_val;
pkt->link->rtt_max_idx = max_pos;
} else {
for (uint8_t i = 0; i < rtt_cnt; i++) {
sum += pkt->link->rtt_history[i];
}
}
pkt->link->rtt_avg10 = (sum - pkt->link->rtt_max_val) / (rtt_cnt - 1);
}
int recv_dt_tx1=data[3] | (data[4]<<8);// localtime удаленной стороны момента принятия пакета - timestamp этого пакета (на стороне отправителя, т.е. у нас) int recv_dt_tx1=data[3] | (data[4]<<8);// localtime удаленной стороны момента принятия пакета - timestamp этого пакета (на стороне отправителя, т.е. у нас)
int recv_dt_rx=cur_ts - pkt->link->rtt_last/2 - 1000 - ts; int recv_dt_rx=cur_ts - pkt->link->rtt_last/2 - 1000 - ts;
@ -1347,29 +1336,30 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
pkt->link->recv_dt_avg_tx +=((int32_t)(recv_dt_tx*65536 - pkt->link->recv_dt_avg_tx))/32; pkt->link->recv_dt_avg_tx +=((int32_t)(recv_dt_tx*65536 - pkt->link->recv_dt_avg_tx))/32;
pkt->link->rt_last = cur_ts - ts - pkt->link->recv_dt_avg_rx/65536; pkt->link->rt_last = cur_ts - ts - pkt->link->recv_dt_avg_rx/65536;
pkt->link->tt_last = recv_dt_tx1 - pkt->link->recv_dt_avg_tx/65536; pkt->link->tt_last = recv_dt_tx1 - pkt->link->recv_dt_avg_tx/65536;
//tts_correction += ((NOW - RTT/2 - TTS) - tts_correction)/16 (инициализируем сразу по 1 пакету) //tts_correction += ((NOW - RTT/2 - TTS) - tts_correction)/16 (инициализируем сразу по 1 пакету)
data+=5; len-=5; data+=5; len-=5;
int d_rtt=pkt->link->rtt_avg10 - pkt->link->rtt_last; int prev_rtt = pkt->link->rtt_last;
int d_rtt = new_rtt - prev_rtt;
if (d_rtt<0) d_rtt=-d_rtt; if (d_rtt<0) d_rtt=-d_rtt;
pkt->link->jitter +=((int32_t)(d_rtt*65536 - pkt->link->jitter))/32; pkt->link->jitter +=((int32_t)(d_rtt*65536 - pkt->link->jitter))/32;
struct ETCP_LINK* c=etcp->links; struct ETCP_LINK* c=etcp->links;
int rtt_sum=0; int rtt_sum=0;
int rtt_a10_max=0; int rtt_max=0;
int tt_sum=0; int tt_sum=0;
int j_sum=0; int j_sum=0;
int cnt=0; int cnt=0;
while (c) { while (c) {
rtt_sum += c->rtt_last; rtt_sum += c->rtt_last;
if (rtt_a10_max < c->rtt_avg10) rtt_a10_max = c->rtt_avg10; if (rtt_max < c->rtt_last) rtt_max = c->rtt_last;
tt_sum += c->tt_last; tt_sum += c->tt_last;
j_sum += c->jitter/32768; j_sum += c->jitter/32768;
cnt++; cnt++;
c=c->next; c=c->next;
} }
etcp->rtt_last=rtt_sum/cnt; etcp->rtt_last=rtt_sum/cnt;
etcp->rtt_avg_10=rtt_a10_max; etcp->rtt_avg_10=rtt_max;
etcp->tt_last=tt_sum/cnt; etcp->tt_last=tt_sum/cnt;
etcp->jitter=j_sum/cnt; etcp->jitter=j_sum/cnt;
break; break;
@ -1523,18 +1513,13 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
uint64_t bw_kbps = (uint64_t)link->burst_pkt_size * 8000ULL / gap_min; uint64_t bw_kbps = (uint64_t)link->burst_pkt_size * 8000ULL / gap_min;
if (bw_kbps > 10000000) bw_kbps = 10000000; if (bw_kbps > 10000000) bw_kbps = 10000000;
link->bandwidth = (uint32_t)bw_kbps; link->bandwidth = (uint32_t)bw_kbps;
float rtt_sec = (float)link->rtt_min / 10000.f; float rtt_sec = (float)link->bbr->min_rtt_us / 1000000.f;
if (rtt_sec < 0.005f) rtt_sec = 0.005f; if (rtt_sec < 0.005f) rtt_sec = 0.005f;
link->burst_target_bdp = (uint32_t)((float)bw_kbps * 1000.f / 8.f * rtt_sec); link->burst_target_bdp = (uint32_t)((float)bw_kbps * 1000.f / 8.f * rtt_sec);
link->slow_start_threshold = link->burst_target_bdp * 9 / 10; // BBR: инициализируем верхнюю границу из burst
if (link->inflight_phase == INFLIGHT_PHASE_SLOW_START) { if (link->bbr->inflight_hi == ~0U) link->bbr->inflight_hi = link->burst_target_bdp;
if (link->inflight_lim_bytes > link->slow_start_threshold) { DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] burst resp: BW=%u Kbps, BDP=%u gap_min=%u us pkt_cnt=%u",
link->inflight_lim_bytes = link->slow_start_threshold; link->etcp->log_name, (uint32_t)bw_kbps, link->burst_target_bdp, gap_min, pkt_cnt);
etcp_link_update_inflight_lim(link, link->inflight_lim_bytes);
}
}
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] burst resp: BW=%u Kbps, BDP=%u, slow_start_thr=%u gap_min=%u us pkt_cnt=%u",
link->etcp->log_name, (uint32_t)bw_kbps, link->burst_target_bdp, link->slow_start_threshold, gap_min, pkt_cnt);
} }
} }
data += MEAS_RESP_SECTION_SIZE; len -= MEAS_RESP_SECTION_SIZE; data += MEAS_RESP_SECTION_SIZE; len -= MEAS_RESP_SECTION_SIZE;

3
src/etcp.h

@ -72,6 +72,9 @@ struct INFLIGHT_PACKET {// выделяется из etcp->inflight_pool
uint8_t retrans_req_count; // Number of retrans requests uint8_t retrans_req_count; // Number of retrans requests
uint8_t state; // WAIT_ACK or WAIT_SEND uint8_t state; // WAIT_ACK or WAIT_SEND
uint8_t send_hist[8]; // через какие каналы передавался пакет (NEW). send_count - head ptr uint8_t send_hist[8]; // через какие каналы передавался пакет (NEW). send_count - head ptr
uint64_t delivered_at_send; // link->delivered_bytes на момент отправки (для BBR prior_delivered)
uint32_t inflight_at_send; // link->inflight_bytes на момент отправки (для BBR tx_in_flight)
uint8_t is_app_limited; // данные ограничены приложением на момент отправки
}; };
// Список пакетов для сборки. собирается в ll_queue (используем быстрый поиск с хешем) // Список пакетов для сборки. собирается в ll_queue (используем быстрый поиск с хешем)

928
src/etcp_bbr.c

@ -0,0 +1,928 @@
#include "etcp_bbr.h"
#include "../lib/u_async.h"
#include <stdlib.h>
static uint64_t bbr_now(const struct bbr* bbr) { return bbr->now_tb ? bbr->now_tb : bbr_now(bbr); }
static void bbr_start_bw_probe_down(struct bbr* bbr);
static void bbr_start_bw_probe_cruise(struct bbr* bbr);
static void bbr_exit_probe_rtt(struct bbr* bbr);
static const int bbr_pacing_gain[] = {
BBR_UNIT * 5 / 4, /* PROBE_UP: 1.25 */
BBR_UNIT * 3 / 4, /* PROBE_DOWN: 0.75 */
BBR_UNIT, /* CRUISE: 1.00 */
BBR_UNIT, /* REFILL: 1.00 */
};
static uint32_t bbr_max_bw(const struct bbr* bbr)
{
return (uint32_t)(bbr->bw_hi[0] > bbr->bw_hi[1] ? bbr->bw_hi[0] : bbr->bw_hi[1]);
}
static uint32_t bbr_bw(const struct bbr* bbr)
{
uint32_t max_bw = bbr_max_bw(bbr);
return (uint32_t)(max_bw < bbr->bw_lo ? max_bw : bbr->bw_lo);
}
static int bbr_full_bw_reached(const struct bbr* bbr)
{
return bbr->full_bw_reached;
}
static uint16_t bbr_extra_acked(const struct bbr* bbr)
{
return (uint16_t)(bbr->extra_acked[0] > bbr->extra_acked[1] ?
bbr->extra_acked[0] : bbr->extra_acked[1]);
}
static uint64_t bbr_rate_bytes_per_sec(uint64_t rate, int gain, int margin, uint32_t mss)
{
rate *= mss;
rate *= (uint64_t)gain;
rate >>= BBR_SCALE;
rate *= (uint64_t)(1000000 / 100) * (uint64_t)(100 - margin);
rate >>= BW_SCALE;
if (rate < 1) rate = 1;
return rate;
}
static uint32_t bbr_bw_to_pacing_rate(uint32_t bw, int gain, uint32_t mss)
{
uint64_t rate = bbr_rate_bytes_per_sec(bw, gain, 1, mss);
return (uint32_t)rate;
}
static uint32_t bbr_bdp(const struct bbr* bbr, uint32_t bw, int gain, uint32_t mss)
{
uint64_t w;
if (bbr->min_rtt_us == ~0U)
return (uint32_t)bbr->init_cwnd * mss;
w = (uint64_t)bw * bbr->min_rtt_us;
return (uint32_t)((((w * (uint64_t)gain) >> BBR_SCALE) + BW_UNIT - 1) / BW_UNIT) * mss;
}
static uint32_t bbr_quantization_budget(struct bbr* bbr, uint32_t cwnd, uint32_t mss)
{
(void)mss;
cwnd = (uint32_t)(cwnd > BBR_CWND_MIN_TARGET ? cwnd : BBR_CWND_MIN_TARGET);
if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_UP)
cwnd += 2;
return cwnd;
}
static uint32_t bbr_inflight(const struct bbr* bbr, uint32_t bw, int gain, uint32_t mss)
{
uint32_t inflight = bbr_bdp(bbr, bw, gain, mss);
return bbr_quantization_budget((struct bbr*)bbr, inflight, mss);
}
static uint32_t bbr_ack_aggregation_cwnd(const struct bbr* bbr, uint32_t mss)
{
uint32_t max_aggr_cwnd, aggr_cwnd = 0;
(void)mss;
if (BBR_EXTRA_ACKED_GAIN) {
max_aggr_cwnd = (uint32_t)((uint64_t)bbr_bw(bbr) * 100000U / BW_UNIT);
aggr_cwnd = (uint32_t)((uint32_t)BBR_EXTRA_ACKED_GAIN * bbr_extra_acked(bbr) >> BBR_SCALE);
aggr_cwnd = (uint32_t)(aggr_cwnd < max_aggr_cwnd ? aggr_cwnd : max_aggr_cwnd);
}
return aggr_cwnd;
}
static uint32_t bbr_target_inflight(const struct bbr* bbr, uint32_t cwnd, uint32_t mss)
{
uint32_t bdp = bbr_inflight(bbr, bbr_bw(bbr), BBR_UNIT, mss);
return (uint32_t)(bdp < cwnd ? bdp : cwnd);
}
static int bbr_is_probing_bandwidth(const struct bbr* bbr)
{
return (bbr->mode == BBR_STARTUP) ||
(bbr->mode == BBR_PROBE_BW &&
(bbr->cycle_idx == BBR_BW_PROBE_REFILL ||
bbr->cycle_idx == BBR_BW_PROBE_UP));
}
static int bbr_has_elapsed_in_phase(const struct bbr* bbr, uint32_t interval_us)
{
uint64_t now_tb = bbr_now(bbr);
uint64_t expire_tb = bbr->cycle_mstamp_tb + (uint64_t)(interval_us / 100U);
return (int32_t)((uint32_t)expire_tb - (uint32_t)now_tb) <= 0;
}
static uint32_t bbr_update_round_start(struct bbr* bbr, const struct bbr_rate_sample* rs)
{
uint32_t round_delivered = 0;
bbr->round_start = 0;
if (rs->interval_us > 0 && !((int32_t)(rs->prior_delivered - bbr->next_rtt_delivered) < 0)) {
round_delivered = bbr->delivered - bbr->next_rtt_delivered;
bbr->next_rtt_delivered = bbr->delivered;
bbr->round_start = 1;
}
return round_delivered;
}
static uint32_t bbr_calculate_bw_sample(const struct bbr_rate_sample* rs)
{
if (rs->interval_us > 0)
return (uint32_t)(((uint64_t)rs->delivered * BW_UNIT + rs->interval_us - 1) / rs->interval_us);
return 0;
}
static void bbr_update_latest_delivery_signals(struct bbr* bbr,
const struct bbr_rate_sample* rs, uint32_t sample_bw)
{
bbr->loss_round_start = 0;
if (rs->interval_us <= 0 || !rs->acked_sacked)
return;
bbr->bw_latest = (uint32_t)(bbr->bw_latest > sample_bw ? bbr->bw_latest : sample_bw);
bbr->inflight_latest = (uint32_t)(bbr->inflight_latest > (uint32_t)rs->delivered ?
bbr->inflight_latest : (uint32_t)rs->delivered);
if (!((int32_t)(rs->prior_delivered - bbr->loss_round_delivered) < 0)) {
bbr->loss_round_delivered = bbr->delivered;
bbr->loss_round_start = 1;
}
}
static void bbr_advance_latest_delivery_signals(struct bbr* bbr,
const struct bbr_rate_sample* rs, uint32_t sample_bw)
{
if (bbr->loss_round_start) {
bbr->bw_latest = sample_bw;
bbr->inflight_latest = (uint32_t)rs->delivered;
}
}
static void bbr_take_max_bw_sample(struct bbr* bbr, uint32_t bw)
{
bbr->bw_hi[1] = (uint32_t)(bw > bbr->bw_hi[1] ? bw : bbr->bw_hi[1]);
}
static void bbr_init_lower_bounds(struct bbr* bbr, int init_bw, uint32_t cwnd)
{
if (init_bw && bbr->bw_lo == ~0U)
bbr->bw_lo = bbr_max_bw(bbr);
if (bbr->inflight_lo == ~0U)
bbr->inflight_lo = cwnd;
}
static void bbr_loss_lower_bounds(struct bbr* bbr)
{
uint32_t loss_cut = BBR_UNIT - BBR_BETA;
bbr->bw_lo = (uint32_t)(bbr->bw_latest >
(uint32_t)((uint64_t)bbr->bw_lo * loss_cut >> BBR_SCALE) ?
bbr->bw_latest :
(uint32_t)((uint64_t)bbr->bw_lo * loss_cut >> BBR_SCALE));
bbr->inflight_lo = (uint32_t)(bbr->inflight_latest >
(uint32_t)((uint64_t)bbr->inflight_lo * loss_cut >> BBR_SCALE) ?
bbr->inflight_latest :
(uint32_t)((uint64_t)bbr->inflight_lo * loss_cut >> BBR_SCALE));
}
static void bbr_adapt_lower_bounds(struct bbr* bbr, uint32_t cwnd)
{
if (bbr_is_probing_bandwidth(bbr))
return;
if (bbr->loss_in_round) {
bbr_init_lower_bounds(bbr, 1, cwnd);
bbr_loss_lower_bounds(bbr);
}
bbr->bw_lo = (uint32_t)(bbr->bw_lo > 1U ? bbr->bw_lo : 1U);
}
static void bbr_reset_lower_bounds(struct bbr* bbr)
{
bbr->bw_lo = ~0U;
bbr->inflight_lo = ~0U;
}
static void bbr_reset_congestion_signals(struct bbr* bbr)
{
bbr->loss_in_round = 0;
bbr->loss_in_cycle = 0;
bbr->bw_latest = 0;
bbr->inflight_latest = 0;
}
static int bbr_is_inflight_too_high(const struct bbr* bbr, const struct bbr_rate_sample* rs)
{
uint32_t loss_thresh;
(void)bbr;
if (rs->lost > 0 && rs->tx_in_flight) {
loss_thresh = (uint32_t)((uint64_t)rs->tx_in_flight * BBR_LOSS_THRESH >> BBR_SCALE);
if ((uint32_t)rs->lost > loss_thresh)
return 1;
}
return 0;
}
static void bbr_handle_queue_too_high_in_startup(struct bbr* bbr, uint32_t mss)
{
uint32_t bdp;
(void)mss;
bbr->full_bw_reached = 1;
bdp = bbr_inflight(bbr, bbr_max_bw(bbr), BBR_UNIT, mss);
bbr->inflight_hi = (uint32_t)(bdp > bbr->inflight_latest ? bdp : bbr->inflight_latest);
}
static void bbr_check_loss_too_high_in_startup(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t mss)
{
if (bbr->full_bw_reached)
return;
if (rs->lost > 0 && bbr->loss_events_in_round < 0xf)
bbr->loss_events_in_round++;
if (bbr->loss_round_start &&
bbr->loss_events_in_round >= BBR_FULL_LOSS_CNT &&
bbr_is_inflight_too_high(bbr, rs)) {
bbr_handle_queue_too_high_in_startup(bbr, mss);
return;
}
if (bbr->loss_round_start)
bbr->loss_events_in_round = 0;
}
static void bbr_reset_full_bw(struct bbr* bbr)
{
bbr->full_bw = 0;
bbr->full_bw_cnt = 0;
bbr->full_bw_now = 0;
}
static void bbr_advance_max_bw_filter(struct bbr* bbr)
{
if (!bbr->bw_hi[1])
return;
bbr->bw_hi[0] = bbr->bw_hi[1];
bbr->bw_hi[1] = 0;
}
static void bbr_check_full_bw_reached(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t sample_bw)
{
uint32_t bw_thresh;
if (bbr->full_bw_now || rs->is_app_limited)
return;
bw_thresh = (uint32_t)((uint64_t)bbr->full_bw * BBR_FULL_BW_THRESH >> BBR_SCALE);
if (sample_bw >= bw_thresh) {
bbr_reset_full_bw(bbr);
bbr->full_bw = sample_bw;
return;
}
if (!bbr->round_start)
return;
bbr->full_bw_cnt = (uint8_t)(bbr->full_bw_cnt + 1);
bbr->full_bw_now = (uint8_t)(bbr->full_bw_cnt >= BBR_FULL_BW_CNT ? 1 : 0);
bbr->full_bw_reached |= bbr->full_bw_now;
}
static void bbr_raise_inflight_hi_slope(struct bbr* bbr, uint32_t cwnd)
{
uint32_t growth_this_round = 1U << bbr->bw_probe_up_rounds;
uint32_t cnt;
bbr->bw_probe_up_rounds = (uint8_t)(bbr->bw_probe_up_rounds + 1 < 30 ?
bbr->bw_probe_up_rounds + 1 : 30);
cnt = cwnd / growth_this_round;
cnt = (uint32_t)(cnt > 1U ? cnt : 1U);
bbr->bw_probe_up_cnt = cnt;
}
static void bbr_set_cycle_idx(struct bbr* bbr, int cycle_idx)
{
bbr->cycle_idx = (uint8_t)cycle_idx;
bbr->try_fast_path = 0;
}
static void bbr_start_bw_probe_refill(struct bbr* bbr, uint32_t bw_probe_up_rounds)
{
bbr_reset_lower_bounds(bbr);
bbr->bw_probe_up_rounds = (uint8_t)bw_probe_up_rounds;
bbr->bw_probe_up_acks = 0;
bbr->stopped_risky_probe = 0;
bbr->ack_phase = BBR_ACKS_REFILLING;
bbr->next_rtt_delivered = bbr->delivered;
bbr_set_cycle_idx(bbr, BBR_BW_PROBE_REFILL);
}
static void bbr_start_bw_probe_up(struct bbr* bbr, uint32_t sample_bw, uint32_t cwnd)
{
bbr->ack_phase = BBR_ACKS_PROBE_STARTING;
bbr->next_rtt_delivered = bbr->delivered;
bbr->cycle_mstamp_tb = bbr_now(bbr);
bbr_reset_full_bw(bbr);
bbr->full_bw = sample_bw;
bbr_set_cycle_idx(bbr, BBR_BW_PROBE_UP);
bbr_raise_inflight_hi_slope(bbr, cwnd);
}
static void bbr_save_cwnd(struct bbr* bbr, uint32_t cwnd)
{
if (bbr->mode != BBR_PROBE_RTT)
bbr->prior_cwnd = cwnd;
else
bbr->prior_cwnd = (uint32_t)(bbr->prior_cwnd > cwnd ? bbr->prior_cwnd : cwnd);
}
static void bbr_exit_probe_rtt(struct bbr* bbr)
{
bbr_reset_lower_bounds(bbr);
if (bbr->full_bw_reached) {
bbr->mode = BBR_PROBE_BW;
bbr_start_bw_probe_down(bbr);
bbr_start_bw_probe_cruise(bbr);
} else {
bbr->mode = BBR_STARTUP;
}
}
static uint32_t bbr_probe_rtt_cwnd(const struct bbr* bbr)
{
(void)bbr;
return BBR_CWND_MIN_TARGET;
}
static uint32_t bbr_inflight_with_headroom(const struct bbr* bbr)
{
uint32_t headroom;
if (bbr->inflight_hi == ~0U)
return ~0U;
headroom = (uint32_t)((uint64_t)bbr->inflight_hi * BBR_INFLIGHT_HEADROOM >> BBR_SCALE);
headroom = (uint32_t)(headroom > 1U ? headroom : 1U);
return bbr->inflight_hi - headroom;
}
static void bbr_pick_probe_wait(struct bbr* bbr)
{
bbr->rounds_since_probe = (uint8_t)((uint32_t)rand() % BBR_BW_PROBE_RAND_ROUNDS);
bbr->probe_wait_us = BBR_BW_PROBE_BASE_US + (uint32_t)rand() % BBR_BW_PROBE_RAND_US;
}
static void bbr_start_bw_probe_down(struct bbr* bbr)
{
bbr_reset_congestion_signals(bbr);
bbr->bw_probe_up_cnt = ~0U;
bbr_pick_probe_wait(bbr);
bbr->cycle_mstamp_tb = bbr_now(bbr);
bbr->ack_phase = BBR_ACKS_PROBE_STOPPING;
bbr->next_rtt_delivered = bbr->delivered;
bbr_set_cycle_idx(bbr, BBR_BW_PROBE_DOWN);
}
static void bbr_start_bw_probe_cruise(struct bbr* bbr)
{
if (bbr->inflight_lo != ~0U)
bbr->inflight_lo = (uint32_t)(bbr->inflight_lo < bbr->inflight_hi ?
bbr->inflight_lo : bbr->inflight_hi);
bbr_set_cycle_idx(bbr, BBR_BW_PROBE_CRUISE);
}
static void bbr_handle_inflight_too_high(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t cwnd, uint32_t mss)
{
const uint32_t beta = BBR_BETA;
bbr->prev_probe_too_high = 1;
bbr->bw_probe_samples = 0;
if (!rs->is_app_limited) {
uint32_t ti = bbr_target_inflight(bbr, cwnd, mss);
bbr->inflight_hi = (uint32_t)(rs->tx_in_flight >
(uint32_t)((uint64_t)ti * (BBR_UNIT - beta) >> BBR_SCALE) ?
rs->tx_in_flight :
(uint32_t)((uint64_t)ti * (BBR_UNIT - beta) >> BBR_SCALE));
}
if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_UP)
bbr_start_bw_probe_down(bbr);
}
static void bbr_probe_inflight_hi_upward(struct bbr* bbr, const struct bbr_rate_sample* rs,
int is_cwnd_limited, uint32_t cwnd)
{
uint32_t delta;
if (!is_cwnd_limited || cwnd < bbr->inflight_hi)
return;
bbr->bw_probe_up_acks += rs->acked_sacked;
if (bbr->bw_probe_up_acks >= bbr->bw_probe_up_cnt &&
bbr->bw_probe_up_cnt > 0) {
delta = bbr->bw_probe_up_acks / bbr->bw_probe_up_cnt;
bbr->bw_probe_up_acks -= delta * bbr->bw_probe_up_cnt;
bbr->inflight_hi += delta;
bbr->try_fast_path = 0;
}
if (bbr->round_start)
bbr_raise_inflight_hi_slope(bbr, cwnd);
}
static void bbr_adapt_upper_bounds(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t sample_bw, int is_cwnd_limited,
uint32_t cwnd, uint32_t mss, int* decided)
{
(void)sample_bw;
*decided = 0;
if (bbr->ack_phase == BBR_ACKS_PROBE_STARTING && bbr->round_start)
bbr->ack_phase = BBR_ACKS_PROBE_FEEDBACK;
if (bbr->ack_phase == BBR_ACKS_PROBE_STOPPING && bbr->round_start) {
bbr->bw_probe_samples = 0;
bbr->ack_phase = BBR_ACKS_INIT;
if (bbr->mode == BBR_PROBE_BW && !rs->is_app_limited)
bbr_advance_max_bw_filter(bbr);
if (bbr->mode == BBR_PROBE_BW && bbr->stopped_risky_probe && !bbr->prev_probe_too_high) {
bbr_start_bw_probe_refill(bbr, 0);
*decided = 1;
return;
}
}
if (bbr_is_inflight_too_high(bbr, rs)) {
if (bbr->bw_probe_samples)
bbr_handle_inflight_too_high(bbr, rs, cwnd, mss);
} else {
if (bbr->inflight_hi == ~0U)
return;
if (rs->tx_in_flight > bbr->inflight_hi)
bbr->inflight_hi = rs->tx_in_flight;
if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_UP)
bbr_probe_inflight_hi_upward(bbr, rs, is_cwnd_limited, cwnd);
}
}
static int bbr_is_reno_coexistence_probe_time(struct bbr* bbr, uint32_t cwnd, uint32_t mss)
{
uint32_t rounds = bbr_target_inflight(bbr, cwnd, mss);
if (rounds > BBR_BW_PROBE_MAX_ROUNDS)
rounds = BBR_BW_PROBE_MAX_ROUNDS;
return bbr->rounds_since_probe >= rounds;
}
static int bbr_check_time_to_probe_bw(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t cwnd, uint32_t mss)
{
(void)rs;
if (bbr_has_elapsed_in_phase(bbr, bbr->probe_wait_us) ||
bbr_is_reno_coexistence_probe_time(bbr, cwnd, mss)) {
bbr_start_bw_probe_refill(bbr, 0);
return 1;
}
return 0;
}
static int bbr_check_time_to_cruise(const struct bbr* bbr, uint32_t inflight,
uint32_t bw, uint32_t mss)
{
if (inflight > bbr_inflight_with_headroom(bbr))
return 0;
return inflight <= bbr_inflight(bbr, bw, BBR_UNIT, mss);
}
static void bbr_update_cycle_phase(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t sample_bw, uint32_t mss,
uint32_t inflight_packets, int is_cwnd_limited,
uint32_t cwnd)
{
int decided = 0;
int is_bw_probe_done;
uint32_t inflight, bw;
if (!bbr->full_bw_reached)
return;
bbr_adapt_upper_bounds(bbr, rs, sample_bw, is_cwnd_limited, cwnd, mss, &decided);
if (decided)
return;
if (bbr->mode != BBR_PROBE_BW)
return;
inflight = inflight_packets;
bw = bbr_max_bw(bbr);
switch (bbr->cycle_idx) {
case BBR_BW_PROBE_CRUISE:
if (bbr_check_time_to_probe_bw(bbr, rs, cwnd, mss))
return;
break;
case BBR_BW_PROBE_REFILL:
if (bbr->round_start) {
bbr->bw_probe_samples = 1;
bbr_start_bw_probe_up(bbr, sample_bw, cwnd);
}
break;
case BBR_BW_PROBE_UP:
is_bw_probe_done = 0;
if (bbr->prev_probe_too_high && inflight >= bbr->inflight_hi) {
bbr->stopped_risky_probe = 1;
is_bw_probe_done = 1;
} else {
if (is_cwnd_limited && cwnd >= bbr->inflight_hi) {
bbr_reset_full_bw(bbr);
bbr->full_bw = sample_bw;
} else if (bbr->full_bw_now) {
is_bw_probe_done = 1;
}
}
if (is_bw_probe_done) {
bbr->prev_probe_too_high = 0;
bbr_start_bw_probe_down(bbr);
}
break;
case BBR_BW_PROBE_DOWN:
if (bbr_check_time_to_probe_bw(bbr, rs, cwnd, mss))
return;
if (bbr_check_time_to_cruise(bbr, inflight, bw, mss))
bbr_start_bw_probe_cruise(bbr);
break;
default:
break;
}
}
static void bbr_check_drain(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t sample_bw, uint32_t mss, uint32_t inflight_packets)
{
(void)rs; (void)sample_bw;
if (bbr->mode == BBR_STARTUP && bbr->full_bw_reached) {
bbr->mode = BBR_DRAIN;
bbr_reset_congestion_signals(bbr);
}
if (bbr->mode == BBR_DRAIN &&
inflight_packets <= bbr_inflight(bbr, bbr_max_bw(bbr), BBR_UNIT, mss)) {
bbr->mode = BBR_PROBE_BW;
bbr_start_bw_probe_down(bbr);
}
}
static void bbr_check_probe_rtt_done(struct bbr* bbr, uint32_t* cwnd)
{
if (!bbr->probe_rtt_done_stamp_tb)
return;
if (!((int32_t)((uint32_t)bbr_now(bbr) - (uint32_t)bbr->probe_rtt_done_stamp_tb) >= 0))
return;
bbr->probe_rtt_min_stamp_tb = bbr_now(bbr);
*cwnd = (uint32_t)(*cwnd > bbr->prior_cwnd ? *cwnd : bbr->prior_cwnd);
bbr_exit_probe_rtt(bbr);
}
static void bbr_update_min_rtt(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t* cwnd, uint32_t mss, uint32_t inflight_packets)
{
uint32_t expire;
int probe_rtt_expired, min_rtt_expired;
(void)mss;
expire = (uint32_t)(bbr->probe_rtt_min_stamp_tb + BBR_PROBE_RTT_WIN_MS * 10U);
probe_rtt_expired = ((int32_t)(expire - (uint32_t)bbr_now(bbr)) < 0);
if (rs->rtt_us > 0 &&
(rs->rtt_us < bbr->probe_rtt_min_us || probe_rtt_expired)) {
bbr->probe_rtt_min_us = rs->rtt_us;
bbr->probe_rtt_min_stamp_tb = bbr_now(bbr);
bbr->has_seen_rtt = 1;
}
if (bbr->probe_rtt_min_us == 0)
bbr->probe_rtt_min_us = ~0U;
expire = (uint32_t)(bbr->min_rtt_stamp_tb + BBR_MIN_RTT_WIN_SEC * 10000U);
min_rtt_expired = ((int32_t)(expire - (uint32_t)bbr_now(bbr)) < 0);
if (bbr->probe_rtt_min_us <= bbr->min_rtt_us || min_rtt_expired) {
bbr->min_rtt_us = bbr->probe_rtt_min_us;
bbr->min_rtt_stamp_tb = bbr_now(bbr);
}
if (BBR_PROBE_RTT_MS > 0 && probe_rtt_expired &&
!bbr->idle_restart && bbr->mode != BBR_PROBE_RTT) {
bbr->mode = BBR_PROBE_RTT;
bbr_save_cwnd(bbr, *cwnd);
bbr->probe_rtt_done_stamp_tb = 0;
bbr->ack_phase = BBR_ACKS_PROBE_STOPPING;
bbr->next_rtt_delivered = bbr->delivered;
}
if (bbr->mode == BBR_PROBE_RTT) {
if (!bbr->probe_rtt_done_stamp_tb &&
inflight_packets <= bbr_probe_rtt_cwnd(bbr)) {
bbr->probe_rtt_done_stamp_tb = (uint32_t)bbr_now(bbr) + BBR_PROBE_RTT_MS * 10U;
bbr->probe_rtt_round_done = 0;
bbr->next_rtt_delivered = bbr->delivered;
} else if (bbr->probe_rtt_done_stamp_tb) {
if (bbr->round_start)
bbr->probe_rtt_round_done = 1;
if (bbr->probe_rtt_round_done)
bbr_check_probe_rtt_done(bbr, cwnd);
}
}
if (rs->delivered > 0)
bbr->idle_restart = 0;
}
static void bbr_reset_startup_mode(struct bbr* bbr)
{
bbr->mode = BBR_STARTUP;
}
static void bbr_update_gains(struct bbr* bbr)
{
switch (bbr->mode) {
case BBR_STARTUP:
bbr->pacing_gain = BBR_STARTUP_PACING_GAIN;
bbr->cwnd_gain = BBR_STARTUP_CWND_GAIN;
break;
case BBR_DRAIN:
bbr->pacing_gain = BBR_DRAIN_GAIN;
bbr->cwnd_gain = BBR_STARTUP_CWND_GAIN;
break;
case BBR_PROBE_BW:
bbr->pacing_gain = bbr_pacing_gain[bbr->cycle_idx];
bbr->cwnd_gain = BBR_CWND_GAIN;
break;
case BBR_PROBE_RTT:
bbr->pacing_gain = BBR_UNIT;
bbr->cwnd_gain = BBR_UNIT;
break;
default:
break;
}
}
static void bbr_update_congestion_signals(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t sample_bw, uint32_t cwnd)
{
uint32_t bw;
if (rs->interval_us <= 0 || !rs->acked_sacked)
return;
bw = sample_bw;
if (!rs->is_app_limited || bw >= bbr_max_bw(bbr))
bbr_take_max_bw_sample(bbr, bw);
bbr->loss_in_round |= (rs->lost > 0);
if (!bbr->loss_round_start)
return;
bbr_adapt_lower_bounds(bbr, cwnd);
bbr->loss_in_round = 0;
}
static void bbr_update_ack_aggregation(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t cwnd, uint32_t mss)
{
uint32_t epoch_us, expected_acked, extra_acked;
uint32_t extra_acked_win_rtts_thresh = BBR_EXTRA_ACKED_WIN_RTTS;
uint64_t now_us;
(void)mss;
if (!BBR_EXTRA_ACKED_GAIN || rs->acked_sacked <= 0 ||
rs->interval_us <= 0)
return;
if (bbr->round_start) {
bbr->extra_acked_win_rtts = (uint8_t)(bbr->extra_acked_win_rtts + 1 < 0x1F ?
bbr->extra_acked_win_rtts + 1 : 0x1F);
if (!bbr->full_bw_reached)
extra_acked_win_rtts_thresh = 1;
if (bbr->extra_acked_win_rtts >= extra_acked_win_rtts_thresh) {
bbr->extra_acked_win_rtts = 0;
bbr->extra_acked_win_idx = bbr->extra_acked_win_idx ? 0 : 1;
bbr->extra_acked[bbr->extra_acked_win_idx] = 0;
}
}
now_us = bbr_now(bbr) * 100ULL;
if (now_us > bbr->ack_epoch_mstamp_tb * 100ULL)
epoch_us = (uint32_t)(now_us - bbr->ack_epoch_mstamp_tb * 100ULL);
else
epoch_us = 0;
expected_acked = (uint32_t)((uint64_t)bbr_bw(bbr) * epoch_us / BW_UNIT);
if (bbr->ack_epoch_acked <= expected_acked ||
(bbr->ack_epoch_acked + rs->acked_sacked >= BBR_ACK_EPOCH_ACKED_RESET_THRESH)) {
bbr->ack_epoch_acked = 0;
bbr->ack_epoch_mstamp_tb = bbr_now(bbr);
expected_acked = 0;
}
bbr->ack_epoch_acked = (uint32_t)(bbr->ack_epoch_acked + rs->acked_sacked < 0xFFFFF ?
bbr->ack_epoch_acked + rs->acked_sacked : 0xFFFFF);
extra_acked = bbr->ack_epoch_acked - expected_acked;
extra_acked = (uint32_t)(extra_acked < cwnd ? extra_acked : cwnd);
if (extra_acked > bbr->extra_acked[bbr->extra_acked_win_idx])
bbr->extra_acked[bbr->extra_acked_win_idx] = (uint16_t)extra_acked;
}
static void bbr_init_pacing_rate_from_rtt(struct bbr* bbr, uint32_t cwnd,
uint32_t mss, uint32_t* pacing_rate_out)
{
uint64_t bw;
uint32_t rtt_us;
if (bbr->has_seen_rtt && bbr->min_rtt_us != ~0U) {
rtt_us = bbr->min_rtt_us;
if (rtt_us < 1) rtt_us = 1;
} else {
rtt_us = 1000;
}
bw = (uint64_t)cwnd * BW_UNIT / rtt_us;
*pacing_rate_out = (uint32_t)bbr_rate_bytes_per_sec(bw, BBR_STARTUP_PACING_GAIN, 1, mss);
}
static void bbr_set_pacing_rate(struct bbr* bbr, uint32_t bw, int gain,
uint32_t mss, uint32_t* pacing_rate_out)
{
uint32_t rate = bbr_bw_to_pacing_rate(bw, gain, mss);
if (!bbr->has_seen_rtt && bbr->min_rtt_us != ~0U)
bbr_init_pacing_rate_from_rtt(bbr, *pacing_rate_out > 0 ? *pacing_rate_out : 4, mss, pacing_rate_out);
if (bbr->full_bw_reached || rate > *pacing_rate_out)
*pacing_rate_out = rate;
}
static void bbr_set_cwnd(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t acked, uint32_t bw, int gain,
uint32_t mss, uint32_t* cwnd)
{
uint32_t target_cwnd = 0;
uint32_t local_cwnd = *cwnd;
(void)rs;
if (!acked)
goto done;
target_cwnd = bbr_bdp(bbr, bw, gain, mss);
target_cwnd += bbr_ack_aggregation_cwnd(bbr, mss);
target_cwnd = bbr_quantization_budget(bbr, target_cwnd, mss);
bbr->try_fast_path = 0;
if (bbr->full_bw_reached) {
local_cwnd += acked;
if (local_cwnd >= target_cwnd) {
local_cwnd = target_cwnd;
bbr->try_fast_path = 1;
}
} else if (local_cwnd < target_cwnd || local_cwnd < 2 * (uint32_t)bbr->init_cwnd * mss) {
local_cwnd += acked;
} else {
bbr->try_fast_path = 1;
}
local_cwnd = (uint32_t)(local_cwnd > BBR_CWND_MIN_TARGET ?
local_cwnd : BBR_CWND_MIN_TARGET);
done:
*cwnd = local_cwnd;
if (bbr->mode == BBR_PROBE_RTT) {
uint32_t pr_cwnd = bbr_probe_rtt_cwnd(bbr);
*cwnd = (uint32_t)(*cwnd < pr_cwnd ? *cwnd : pr_cwnd);
}
}
static void bbr_bound_cwnd_for_inflight_model(struct bbr* bbr, uint32_t* cwnd, uint32_t mss)
{
uint32_t cap = ~0U;
(void)mss;
if (!bbr->initialized)
return;
if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx != BBR_BW_PROBE_CRUISE)
cap = bbr->inflight_hi;
else if (bbr->mode == BBR_PROBE_RTT ||
(bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_CRUISE))
cap = bbr_inflight_with_headroom(bbr);
cap = (uint32_t)(cap < bbr->inflight_lo ? cap : bbr->inflight_lo);
cap = (uint32_t)(cap > BBR_CWND_MIN_TARGET ? cap : BBR_CWND_MIN_TARGET);
*cwnd = (uint32_t)(cap < *cwnd ? cap : *cwnd);
}
static int bbr_run_fast_path(struct bbr* bbr, int* update_model,
const struct bbr_rate_sample* rs, uint32_t sample_bw,
uint32_t mss, uint32_t inflight_packets,
int is_cwnd_limited, uint32_t* cwnd)
{
uint32_t prev_min_rtt_us;
uint8_t prev_mode;
if (bbr->try_fast_path && rs->is_app_limited &&
sample_bw < bbr_max_bw(bbr) && !bbr->loss_in_round) {
prev_mode = bbr->mode;
prev_min_rtt_us = bbr->min_rtt_us;
bbr_check_drain(bbr, rs, sample_bw, mss, inflight_packets);
bbr_update_cycle_phase(bbr, rs, sample_bw, mss, inflight_packets,
is_cwnd_limited, *cwnd);
bbr_update_min_rtt(bbr, rs, cwnd, mss, inflight_packets);
if (bbr->mode == prev_mode &&
bbr->min_rtt_us == prev_min_rtt_us &&
bbr->try_fast_path)
return 1;
*update_model = 0;
}
return 0;
}
void bbr_init(struct bbr* bbr)
{
bbr->initialized = 1;
bbr->init_cwnd = 0;
bbr->prior_cwnd = 0;
bbr->delivered = 0;
bbr->next_rtt_delivered = 0;
bbr->probe_rtt_done_stamp_tb = 0;
bbr->probe_rtt_round_done = 0;
bbr->probe_rtt_min_us = ~0U;
bbr->probe_rtt_min_stamp_tb = bbr_now(bbr);
bbr->min_rtt_us = ~0U;
bbr->min_rtt_stamp_tb = bbr_now(bbr);
bbr->has_seen_rtt = 0;
bbr->round_start = 0;
bbr->idle_restart = 0;
bbr->full_bw_reached = 0;
bbr->full_bw = 0;
bbr->full_bw_cnt = 0;
bbr->full_bw_now = 0;
bbr->cycle_mstamp_tb = 0;
bbr->cycle_idx = 0;
bbr_reset_startup_mode(bbr);
bbr->ack_epoch_mstamp_tb = bbr_now(bbr);
bbr->ack_epoch_acked = 0;
bbr->extra_acked_win_rtts = 0;
bbr->extra_acked_win_idx = 0;
bbr->extra_acked[0] = 0;
bbr->extra_acked[1] = 0;
bbr->try_fast_path = 0;
bbr->loss_round_delivered = 1;
bbr->loss_round_start = 0;
bbr->loss_events_in_round = 0;
bbr_reset_congestion_signals(bbr);
bbr->bw_lo = ~0U;
bbr->bw_hi[0] = 0;
bbr->bw_hi[1] = 0;
bbr->inflight_lo = ~0U;
bbr->inflight_hi = ~0U;
bbr_reset_full_bw(bbr);
bbr->bw_probe_up_cnt = ~0U;
bbr->bw_probe_up_acks = 0;
bbr->bw_probe_up_rounds = 0;
bbr->probe_wait_us = 0;
bbr->stopped_risky_probe = 0;
bbr->ack_phase = BBR_ACKS_INIT;
bbr->rounds_since_probe = 0;
bbr->bw_probe_samples = 0;
bbr->prev_probe_too_high = 0;
}
void bbr_main(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t* cwnd_out, uint32_t* pacing_rate_out,
uint32_t mss, uint32_t inflight_bytes, int is_cwnd_limited)
{
uint32_t cwnd = *cwnd_out;
uint32_t sample_bw, bw;
int update_model = 1;
bbr->delivered += rs->delivered;
bbr_update_round_start(bbr, rs);
if (bbr->round_start)
bbr->rounds_since_probe = (uint8_t)(bbr->rounds_since_probe + 1 < 0xFF ?
bbr->rounds_since_probe + 1 : 0xFF);
sample_bw = bbr_calculate_bw_sample(rs);
bbr_update_latest_delivery_signals(bbr, rs, sample_bw);
if (bbr_run_fast_path(bbr, &update_model, rs, sample_bw, mss,
inflight_bytes, is_cwnd_limited, &cwnd))
goto out;
if (update_model) {
bbr_update_congestion_signals(bbr, rs, sample_bw, cwnd);
bbr_update_ack_aggregation(bbr, rs, cwnd, mss);
bbr_check_loss_too_high_in_startup(bbr, rs, mss);
bbr_check_full_bw_reached(bbr, rs, sample_bw);
bbr_check_drain(bbr, rs, sample_bw, mss, inflight_bytes);
bbr_update_cycle_phase(bbr, rs, sample_bw, mss, inflight_bytes,
is_cwnd_limited, cwnd);
bbr_update_min_rtt(bbr, rs, &cwnd, mss, inflight_bytes);
}
bbr_update_gains(bbr);
bw = bbr_bw(bbr);
bbr_set_pacing_rate(bbr, bw, bbr->pacing_gain, mss, pacing_rate_out);
bbr_set_cwnd(bbr, rs, rs->acked_sacked, bw, bbr->cwnd_gain, mss, &cwnd);
bbr_bound_cwnd_for_inflight_model(bbr, &cwnd, mss);
out:
bbr_advance_latest_delivery_signals(bbr, rs, sample_bw);
bbr->loss_in_cycle |= (rs->lost > 0);
*cwnd_out = cwnd;
}
void bbr_note_loss(struct bbr* bbr)
{
if (!bbr->loss_in_round)
bbr->loss_round_delivered = bbr->delivered;
bbr->loss_in_round = 1;
bbr->loss_in_cycle = 1;
}
void bbr_tx_start(struct bbr* bbr)
{
bbr->idle_restart = 1;
bbr->ack_epoch_mstamp_tb = bbr_now(bbr);
bbr->ack_epoch_acked = 0;
}

107
src/etcp_bbr.h

@ -0,0 +1,107 @@
#pragma once
#include <stdint.h>
#define BBR_SCALE 8
#define BBR_UNIT (1 << BBR_SCALE)
#define BW_SCALE 24
#define BW_UNIT (1 << BW_SCALE)
#define BBR_STARTUP_PACING_GAIN (BBR_UNIT * 277 / 100 + 1)
#define BBR_STARTUP_CWND_GAIN (BBR_UNIT * 2)
#define BBR_DRAIN_GAIN (BBR_UNIT * 1000 / 2885)
#define BBR_CWND_GAIN (BBR_UNIT * 2)
#define BBR_PROBE_RTT_CWND_GAIN (BBR_UNIT * 1 / 2)
#define BBR_CWND_MIN_TARGET 4
#define BBR_FULL_BW_THRESH (BBR_UNIT * 5 / 4)
#define BBR_FULL_BW_CNT 3
#define BBR_LOSS_THRESH (BBR_UNIT * 2 / 100)
#define BBR_BETA (BBR_UNIT * 30 / 100)
#define BBR_INFLIGHT_HEADROOM (BBR_UNIT * 15 / 100)
#define BBR_EXTRA_ACKED_GAIN BBR_UNIT
#define BBR_EXTRA_ACKED_WIN_RTTS 5
#define BBR_ACK_EPOCH_ACKED_RESET_THRESH (1U << 20)
#define BBR_EXTRA_ACKED_MAX_US (100 * 1000)
#define BBR_FULL_LOSS_CNT 6
#define BBR_BW_PROBE_MAX_ROUNDS 63
#define BBR_BW_PROBE_RAND_ROUNDS 2
#define BBR_BW_PROBE_BASE_US (2 * 1000000)
#define BBR_BW_PROBE_RAND_US (1 * 1000000)
#define BBR_PROBE_RTT_WIN_MS 5000
#define BBR_MIN_RTT_WIN_SEC 10
#define BBR_PROBE_RTT_MS 200
enum bbr_mode { BBR_STARTUP = 0, BBR_DRAIN = 1, BBR_PROBE_BW = 2, BBR_PROBE_RTT = 3 };
enum bbr_bw_probe_cyc { BBR_BW_PROBE_UP = 0, BBR_BW_PROBE_DOWN = 1, BBR_BW_PROBE_CRUISE = 2, BBR_BW_PROBE_REFILL = 3 };
enum bbr_ack_phase { BBR_ACKS_INIT = 0, BBR_ACKS_REFILLING, BBR_ACKS_PROBE_STARTING, BBR_ACKS_PROBE_FEEDBACK, BBR_ACKS_PROBE_STOPPING };
struct bbr_rate_sample {
uint32_t delivered;
uint32_t interval_us;
uint32_t rtt_us;
uint32_t acked_sacked;
uint32_t prior_delivered;
uint32_t tx_in_flight;
int lost;
int is_app_limited;
};
struct bbr {
uint32_t min_rtt_us;
uint32_t bw_lo;
uint32_t bw_hi[2];
uint32_t inflight_lo;
uint32_t inflight_hi;
uint32_t bw_latest;
uint32_t inflight_latest;
uint8_t mode;
uint8_t cycle_idx;
uint16_t pacing_gain;
uint16_t cwnd_gain;
uint32_t next_rtt_delivered;
uint8_t round_start : 1;
uint8_t full_bw_reached : 1;
uint8_t loss_in_round : 1;
uint8_t loss_in_cycle : 1;
uint8_t idle_restart : 1;
uint8_t try_fast_path : 1;
uint8_t has_seen_rtt : 1;
uint8_t initialized : 1;
uint32_t loss_round_delivered;
uint32_t bw_probe_up_cnt;
uint32_t bw_probe_up_acks;
uint8_t bw_probe_up_rounds;
uint8_t bw_probe_samples : 1;
uint8_t prev_probe_too_high: 1;
uint8_t stopped_risky_probe: 1;
uint8_t rounds_since_probe;
uint32_t probe_wait_us;
uint64_t cycle_mstamp_tb;
uint32_t probe_rtt_min_us;
uint64_t probe_rtt_min_stamp_tb;
uint64_t probe_rtt_done_stamp_tb;
uint8_t probe_rtt_round_done : 1;
uint16_t extra_acked[2];
uint32_t ack_epoch_acked;
uint64_t ack_epoch_mstamp_tb;
uint8_t extra_acked_win_idx : 1;
uint8_t extra_acked_win_rtts : 5;
uint32_t prior_cwnd;
uint8_t init_cwnd : 7;
uint8_t full_bw_cnt : 2;
uint8_t ack_phase : 3;
uint8_t loss_events_in_round : 4;
uint32_t delivered;
uint32_t full_bw;
uint64_t min_rtt_stamp_tb;
uint8_t loss_round_start : 1;
uint8_t full_bw_now : 1;
uint8_t pad_unused : 6;
uint64_t now_tb; // 0 = real get_time_tb(); >0 = test-controlled time
};
void bbr_init(struct bbr* bbr);
void bbr_main(struct bbr* bbr, const struct bbr_rate_sample* rs,
uint32_t* cwnd_out, uint32_t* pacing_rate_out,
uint32_t mss, uint32_t inflight_bytes, int is_cwnd_limited);
void bbr_note_loss(struct bbr* bbr);
void bbr_tx_start(struct bbr* bbr);

198
src/etcp_connections.c

@ -39,23 +39,6 @@ static void keepalive_timer_cb(void* arg);
static void link_stats_timer_cb(void* arg); static void link_stats_timer_cb(void* arg);
static void burst_resp_timeout_cb(void* arg); static void burst_resp_timeout_cb(void* arg);
void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim) {
if (!link) return;
uint32_t old = link->inflight_lim_bytes;
link->inflight_lim_bytes = new_lim;
if (old != new_lim && link->inflight_bytes < new_lim && link->send_blocked_inflight) {
link->send_blocked_inflight = 0;
loadbalancer_link_ready(link);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_link_update_inflight_lim: unblocked link (lim %u->%u)", old, new_lim);
}
// recalc connection-level optimal_inflight
if (link->etcp) {
uint32_t sum = 0;
for (struct ETCP_LINK* l = link->etcp->links; l; l = l->next) sum += l->inflight_lim_bytes;
link->etcp->optimal_inflight = sum;
}
}
// === Burst sender functions === // === Burst sender functions ===
void etcp_link_burst_start(struct ETCP_LINK* link) { void etcp_link_burst_start(struct ETCP_LINK* link) {
@ -786,11 +769,6 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn
link->send_hook_ctx = NULL; link->send_hook_ctx = NULL;
link->handshake_minsize = 100; link->handshake_minsize = 100;
link->handshake_maxsize = mtu;// 28 = udp header size link->handshake_maxsize = mtu;// 28 = udp header size
link->rtt_swm=swm_create(1024);
if (!link->rtt_swm) {
u_free(link);
return NULL;
}
// Initialize keepalive timeout from global config // Initialize keepalive timeout from global config
if (etcp->instance && etcp->instance->config) { if (etcp->instance && etcp->instance->config) {
@ -803,18 +781,23 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn
if (link->keepalive_interval < 10) link->keepalive_interval = 10; if (link->keepalive_interval < 10) link->keepalive_interval = 10;
link->keepalive_sent_count = 0; link->keepalive_sent_count = 0;
link->keepalive_recv_count = 0; link->keepalive_recv_count = 0;
link->inflight_lim_bytes = etcp->instance->config link->inflight_lim_bytes = link->mtu * 4; // BBR init_cwnd (~4 packets)
? (uint32_t)etcp->instance->config->global.inflight_min_bytes : 2000;
if (link->inflight_lim_bytes < link->mtu * 2) link->inflight_lim_bytes = link->mtu * 2;
link->inflight_phase = INFLIGHT_PHASE_SLOW_START;
link->slow_start_threshold = 1000000; // будет обновлён после первого burst-замера
link->last_window_update_tb = 0;
link->bandwidth = 10000; // начальная оценка 10 Mbps для шейпера link->bandwidth = 10000; // начальная оценка 10 Mbps для шейпера
link->burst_id = 0; link->burst_id = 0;
link->burst_active = 0; link->burst_active = 0;
link->burst_last_time_tb = 0; link->burst_last_time_tb = 0;
link->burst_target_bdp = 0; link->burst_target_bdp = 0;
link->delivered_bytes = 0;
link->last_ack_time_tb = get_time_tb();
link->bbr_pacing_rate = 0;
link->bbr = u_calloc(1, sizeof(struct bbr));
if (!link->bbr) {
u_free(link);
return NULL;
}
bbr_init(link->bbr);
// Выделяем свободный local_link_id // Выделяем свободный local_link_id
int free_id = etcp_find_free_local_link_id(etcp); int free_id = etcp_find_free_local_link_id(etcp);
if (free_id <= 0) { if (free_id <= 0) {
@ -829,27 +812,11 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn
link->ip_port_hash = sockaddr_hash(remote_addr); link->ip_port_hash = sockaddr_hash(remote_addr);
link->last_recv_local_time = get_time_tb(); // Initialize to prevent immediate timeout link->last_recv_local_time = get_time_tb(); // Initialize to prevent immediate timeout
// RTT sliding window initialization
link->rtt_history_index = 0;
link->rtt_history_count = 0;
link->rtt_max_val = 0;
link->rtt_max_idx = 0;
// rtt_history[] is already zeroed by calloc
// Инициализация статистики
link->win_timebase = 50000; // 50 ms в микросекундах
link->win_ptr = 0;
link->window_pkt_transmitted = 0;
link->window_retransmissions = 0;
link->total_retransmissions = 0; link->total_retransmissions = 0;
memset(link->stat_win, 0, sizeof(link->stat_win));
start_stats_timer(link);
// insert_link(conn, link); // insert_link(conn, link);
if (insert_link(conn, link) < 0) { if (insert_link(conn, link) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "Can not insert link to socket"); DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "Can not insert link to socket");
uasync_cancel_timeout(link->etcp->instance->ua, link->stats_timer);
u_free(link); u_free(link);
return NULL; return NULL;
} }
@ -886,7 +853,6 @@ void etcp_link_close(struct ETCP_LINK* link) {
struct ETCP_LINK **pp = &link->etcp->links; struct ETCP_LINK **pp = &link->etcp->links;
while (*pp && *pp != link) pp = &(*pp)->next; while (*pp && *pp != link) pp = &(*pp)->next;
if (*pp) *pp = link->next; if (*pp) *pp = link->next;
if (link->stats_timer) uasync_cancel_timeout(link->etcp->instance->ua, link->stats_timer);
if (link->init_timer) uasync_cancel_timeout(link->etcp->instance->ua, link->init_timer); if (link->init_timer) uasync_cancel_timeout(link->etcp->instance->ua, link->init_timer);
if (link->shaper_timer) uasync_cancel_timeout(link->etcp->instance->ua, link->shaper_timer); if (link->shaper_timer) uasync_cancel_timeout(link->etcp->instance->ua, link->shaper_timer);
if (link->keepalive_timer) uasync_cancel_timeout(link->etcp->instance->ua, link->keepalive_timer); if (link->keepalive_timer) uasync_cancel_timeout(link->etcp->instance->ua, link->keepalive_timer);
@ -894,11 +860,6 @@ void etcp_link_close(struct ETCP_LINK* link) {
return; return;
} }
if (link->stats_timer) {
uasync_cancel_timeout(link->etcp->instance->ua, link->stats_timer);
link->stats_timer = NULL;
}
// Cancel init timer if active // Cancel init timer if active
if (link->init_timer) { if (link->init_timer) {
uasync_cancel_timeout(link->etcp->instance->ua, link->init_timer); uasync_cancel_timeout(link->etcp->instance->ua, link->init_timer);
@ -930,145 +891,10 @@ void etcp_link_close(struct ETCP_LINK* link) {
remove_link(link->conn, link->ip_port_hash); remove_link(link->conn, link->ip_port_hash);
swm_destroy(link->rtt_swm); u_free(link->bbr);
u_free(link); u_free(link);
} }
void start_stats_timer(struct ETCP_LINK* link) {
if (!link || !link->etcp || !link->etcp->instance) return;
if (link->stats_timer) {
uasync_cancel_timeout(link->etcp->instance->ua, link->stats_timer);
}
uint32_t tb = link->win_timebase / 100; // us → 0.1 ms units
if (tb < 50) tb = 50; // минимум 5 ms
if (tb > 5000) tb = 5000; // max 500 ms
link->stats_timer = uasync_set_timeout(
link->etcp->instance->ua,
tb,
link,
link_stats_timer_cb,
"link_stats"
);
}
// === Новый callback таймера ===
static void link_stats_timer_cb(void* arg) {
struct ETCP_LINK* link = (struct ETCP_LINK*)arg;
if (!link || !link->etcp || !link->etcp->instance) return;
// 1. Сохраняем снимок текущего окна
link->stat_win[link->win_ptr].rtt = link->rtt_avg10;
link->stat_win[link->win_ptr].pkt_loss = (uint16_t)link->window_retransmissions;
link->stat_win[link->win_ptr].pkt_transmitted = link->window_pkt_transmitted;
swm_add(link->rtt_swm, link->rtt_avg10);
link->rtt_min=swm_get_min(link->rtt_swm);
// === Burst check: запускаем если линк насыщен и прошло время ===
etcp_link_burst_check(link);
uint64_t now_tb = get_time_tb();
int do_update = 0;
// адаптивно: обновляем окно примерно раз в RTT
if (now_tb - link->last_window_update_tb >= link->rtt_avg10 || link->last_window_update_tb == 0) {
do_update = 1;
link->last_window_update_tb = now_tb;
}
if (do_update && link->initialized) {
int new_lim = link->inflight_lim_bytes;
uint32_t loss = link->window_retransmissions;
uint32_t sent = link->window_pkt_transmitted;
uint8_t old_phase = link->inflight_phase;
// === Phase 0: Slow Start ===
if (link->inflight_phase == INFLIGHT_PHASE_SLOW_START) {
if (link->slow_start_threshold == 0) link->slow_start_threshold = 30000;
if (loss > 0) {
link->inflight_phase = INFLIGHT_PHASE_CONG_AVOIDANCE; // первая потеря — выход
new_lim -= new_lim / 2;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] slow_start->cong_avoid (loss: %u) lim %u", link->etcp->log_name, loss, new_lim);
} else if (link->rtt_avg10 > link->rtt_min * 15 / 10) {
link->inflight_phase = INFLIGHT_PHASE_CONG_AVOIDANCE; // RTT вырос >50%
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] slow_start->cong_avoid (RTT spike: avg=%u min=%u) lim %u", link->etcp->log_name, link->rtt_avg10, link->rtt_min, new_lim);
} else if (new_lim >= link->slow_start_threshold) {
link->inflight_phase = INFLIGHT_PHASE_CONG_AVOIDANCE; // достигли порога burst
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] slow_start->cong_avoid (threshold %u reached) lim %u", link->etcp->log_name, link->slow_start_threshold, new_lim);
} else {
new_lim *= 2; // удвоение каждый RTT
}
}
// === Phase 1: Congestion Avoidance ===
if (link->inflight_phase == INFLIGHT_PHASE_CONG_AVOIDANCE) {
if (old_phase == INFLIGHT_PHASE_SLOW_START) {
// только что вышли из slow start — не трогаем лимит в этом тике
}
// Loss-based: мультипликативное снижение
else if (loss > 0 && sent > 0) {
int loss_pct = loss * 100 / sent;
int decrease = new_lim * loss_pct / 50; // loss_pct / 50 ≈ ×2 при 1% потерь
if (decrease < link->mtu) decrease = link->mtu;
if (decrease > new_lim / 2) decrease = new_lim / 2;
new_lim -= decrease;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] cong_avoid loss=%u/%u (%d%%) decrease=%d lim %u",
link->etcp->log_name, loss, sent, loss_pct, decrease, new_lim);
}
// RTT-based: пропорциональное снижение
else if (link->rtt_avg10 > link->rtt_min * 13 / 10) {
int excess = link->rtt_avg10 - link->rtt_min * 13 / 10;
int ratio = excess * 100 / link->rtt_avg10; // 0..100
int decrease = new_lim * ratio / 200; // ratio/200 ≈ до 50%
if (decrease < link->mtu) decrease = link->mtu;
if (decrease > new_lim / 4) decrease = new_lim / 4;
new_lim -= decrease;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] cong_avoid RTT spike avg=%u min=%u decrease=%d lim %u",
link->etcp->log_name, link->rtt_avg10, link->rtt_min, decrease, new_lim);
}
// Additive increase (если нет сигналов перегрузки)
else {
new_lim += link->mtu; // +1 пакет за RTT
}
}
// Clamp
if (new_lim < (int)link->mtu * 2) new_lim = link->mtu * 2;
{
int max_lim = link->etcp->instance->config
? link->etcp->instance->config->global.inflight_max_bytes : 100000;
if (new_lim > max_lim) new_lim = max_lim;
}
// Burst target cap (если есть burst_target_bdp — не превышаем 1.1×)
if (link->burst_target_bdp > 0 && new_lim > (int)(link->burst_target_bdp * 11 / 10))
new_lim = link->burst_target_bdp * 11 / 10;
if (new_lim != (int)link->inflight_lim_bytes) etcp_link_update_inflight_lim(link, (uint32_t)new_lim);
}
// DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] stats window updated (win_timebase=%u us, rtt=%u, retrans=%u, transmitted=%u)",
// link->etcp->log_name, link->win_timebase, link->rtt_avg10, link->window_retransmissions, link->window_pkt_transmitted);
// 2. Переходим к следующему слоту
link->win_ptr = (link->win_ptr + 1) % 32;
// 3. Обнуляем накопители для нового окна
link->window_pkt_transmitted = 0;
link->window_retransmissions = 0;
// 4. Плавная подстройка win_timebase под rtt/2 (в микросекундах)
uint32_t target_us = (uint32_t)link->rtt_avg10 * 50ULL; // rtt_avg10 (0.1 ms) → rtt/2 в us
if (target_us < 10000) target_us = 10000; // минимум 10 ms
if (target_us > 500000) target_us = 500000; // максимум 0.5 s
link->win_timebase = (link->win_timebase * 7 + target_us) / 8;
// 5. Перезапускаем таймер с новым интервалом
start_stats_timer(link);
}
ssize_t etcp_udp_send(struct ETCP_LINK* link, socket_t fd, const void* buf, size_t len, ssize_t etcp_udp_send(struct ETCP_LINK* link, socket_t fd, const void* buf, size_t len,
const struct sockaddr* addr, socklen_t addr_len) { const struct sockaddr* addr, socklen_t addr_len) {
if (link && link->send_hook) if (link && link->send_hook)

42
src/etcp_connections.h

@ -5,8 +5,8 @@
#include "secure_channel.h" #include "secure_channel.h"
#include "utun_instance.h" #include "utun_instance.h"
#include "etcp_bbr.h"
#include "../lib/socket_compat.h" #include "../lib/socket_compat.h"
#include "../lib/swm_min.h"
#include <stdint.h> #include <stdint.h>
#include <stddef.h> #include <stddef.h>
@ -121,7 +121,6 @@ struct ETCP_LINK {
struct ETCP_CONN* etcp; // подключение (parent) struct ETCP_CONN* etcp; // подключение (parent)
struct ETCP_SOCKET* conn; // сокет через который работаем struct ETCP_SOCKET* conn; // сокет через который работаем
SlidingWindowMin* rtt_swm;
// Путь соединения // Путь соединения
struct sockaddr_storage remote_addr; // Удалённый адрес struct sockaddr_storage remote_addr; // Удалённый адрес
@ -164,24 +163,7 @@ struct ETCP_LINK {
uint32_t inflight_lim_bytes; uint32_t inflight_lim_bytes;
/* Биты блокировки отправки по причине (для корректного resume) */ /* Биты блокировки отправки по причине (для корректного resume) */
// unsigned send_blocked_input_queue : 1;
// unsigned send_blocked_sack_full : 1;
// unsigned send_blocked_bandwidth : 1;
unsigned send_blocked_inflight : 1; unsigned send_blocked_inflight : 1;
// unsigned send_blocked_other : 4;
// lim периодически обновляется. = (rtt_avg10 + jitter * K2[=1.0]) * K1[=1.5] * channel_bandwidth
uint32_t win_timebase; // x1 us (по умолчанию 50 000 = 50 ms, потом подстраивается под rtt/2)
uint32_t window_pkt_transmitted; // текущие накопители для следующего окна (обнуляются в таймере)
uint32_t window_retransmissions;
void* stats_timer; // Таймер для обновления окна
uint32_t win_ptr; // текущий слот в кольцевом буфере (0..31)
struct {
uint16_t rtt; // x 0.1ms (avg10 в конеце окна)
// uint16_t avg_rtt; // x 0.1ms (avg10 усредненный за период)
uint16_t pkt_loss; // штук (за период)
uint32_t pkt_transmitted; // штук (за период)
} stat_win[32]; // последние 32 замера статистики
// statistics // statistics
size_t encrypt_errors; size_t encrypt_errors;
@ -193,14 +175,8 @@ struct ETCP_LINK {
uint32_t total_retransmissions; uint32_t total_retransmissions;
uint16_t rtt_last; // round trip (время отправки + приёма) uint16_t rtt_last; // round trip (время отправки + приёма)
uint16_t rtt_history[10]; // Circular buffer for last 10 RTT values
uint8_t rtt_history_count; // Number of valid entries in history (0-10)
uint8_t rtt_history_index; // Current write position in history buffer
uint32_t jitter; // Current jitter [>>16] x0.1 ms uint32_t jitter; // Current jitter [>>16] x0.1 ms
uint16_t rtt_max_val; // Current max RTT in history // rtt_avg10 удалён — заменён BBR (bbr->min_rtt_us/100)
uint8_t rtt_max_idx; // Index of max (255 = needs recalc)
uint16_t rtt_avg10; // round trip average (excl. max)
uint16_t rtt_min; // round trip min (rtt_swm floating window)
uint32_t recv_dt_avg_tx; // дельта времени для отправленных пакетов (относительное время отправки) x256 uint32_t recv_dt_avg_tx; // дельта времени для отправленных пакетов (относительное время отправки) x256
uint32_t recv_dt_avg_rx; // дельта времени для принятых пакетов (относительное время отправки) x256 uint32_t recv_dt_avg_rx; // дельта времени для принятых пакетов (относительное время отправки) x256
@ -249,11 +225,11 @@ struct ETCP_LINK {
uint8_t burst_resp_pkt_count; // число пакетов в измерении uint8_t burst_resp_pkt_count; // число пакетов в измерении
uint8_t burst_resp_valid; // валидность измерения uint8_t burst_resp_valid; // валидность измерения
// === Фазы управления inflight === // === BBR congestion control ===
// 0 = slow_start, 1 = congestion_avoidance struct bbr* bbr; // BBR state (per-link, создаётся в etcp_link_new)
uint8_t inflight_phase; uint64_t delivered_bytes; // кумулятивно delivered bytes на линке (для round detection)
uint64_t last_window_update_tb; // время последнего обновления окна (0.1ms) uint64_t last_ack_time_tb; // 0.1ms timestamp последнего ACK (для interval_us)
uint32_t slow_start_threshold; // порог выхода из slow start (burst_target_bdp * 0.9) uint32_t bbr_pacing_rate; // bytes/sec output BBR → shaper
etcp_udp_send_fn_t send_hook; // NULL = socket_sendto напрямую etcp_udp_send_fn_t send_hook; // NULL = socket_sendto напрямую
void* send_hook_ctx; void* send_hook_ctx;
@ -284,14 +260,10 @@ int etcp_encrypt_send(struct ETCP_DGRAM* dgram);// зашифровывает и
// find link by address // find link by address
struct ETCP_LINK* etcp_link_find_by_addr(struct ETCP_SOCKET* e_sock, struct sockaddr_storage* addr); struct ETCP_LINK* etcp_link_find_by_addr(struct ETCP_SOCKET* e_sock, struct sockaddr_storage* addr);
// обновляет лимит inflight_bytes для линка и корректно снимает блокировку если нужно
void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim);
// find free local_link_id for connection // find free local_link_id for connection
// scans all links in connection, marks used ids in bit array // scans all links in connection, marks used ids in bit array
// returns first free id (0-255) or -1 if all occupied // returns first free id (0-255) or -1 if all occupied
int etcp_find_free_local_link_id(struct ETCP_CONN* etcp); int etcp_find_free_local_link_id(struct ETCP_CONN* etcp);
void start_stats_timer(struct ETCP_LINK* link);
// Burst measurement functions // Burst measurement functions
void etcp_link_burst_start(struct ETCP_LINK* link); void etcp_link_burst_start(struct ETCP_LINK* link);

40
src/etcp_dump.c

@ -4,7 +4,6 @@
#include "../lib/ll_queue.h" #include "../lib/ll_queue.h"
#include "../lib/memory_pool.h" #include "../lib/memory_pool.h"
#include "../lib/socket_compat.h" #include "../lib/socket_compat.h"
#include "../lib/swm_min.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@ -153,32 +152,30 @@ void etcp_dump_conn_state(struct ETCP_CONN* conn) {
link->keepalive_interval, link->keepalive_timeout); link->keepalive_interval, link->keepalive_timeout);
/* timers */ /* timers */
DUMP_LINE("TIMERS: init=%s(%u/%u) ka=%s shaper=%s stats=%s burst=%s keepalive=%s", DUMP_LINE("TIMERS: init=%s(%u/%u) ka=%s shaper=%s burst=%s keepalive=%s",
link->init_timer ? "ACTIVE" : "free", link->init_timer ? "ACTIVE" : "free",
link->init_timeout, link->init_retry_count, link->init_timeout, link->init_retry_count,
link->keepalive_timer ? "ACTIVE" : "free", link->keepalive_timer ? "ACTIVE" : "free",
link->shaper_timer ? "ACTIVE" : "free", link->shaper_timer ? "ACTIVE" : "free",
link->stats_timer ? "ACTIVE" : "free",
link->burst_resp_timer ? "ACTIVE" : "free", link->burst_resp_timer ? "ACTIVE" : "free",
link->keepalive_sent_count > 0 ? "has_sent" : "idle"); link->keepalive_sent_count > 0 ? "has_sent" : "idle");
/* inflight */ /* inflight (BBR) */
DUMP_LINE("INFLIGHT: bytes=%u pkts=%u lim=%u blocked=%d phase=%d sst=%u last_win_tb=%llu", DUMP_LINE("BBR: bytes=%u pkts=%u lim=%u blocked=%d mode=%d cycle=%d pacing=%u",
link->inflight_bytes, link->inflight_packets, link->inflight_lim_bytes, link->inflight_bytes, link->inflight_packets, link->inflight_lim_bytes,
link->send_blocked_inflight, link->inflight_phase, link->send_blocked_inflight,
link->slow_start_threshold, (unsigned long long)link->last_window_update_tb); link->bbr ? link->bbr->mode : -1,
link->bbr ? link->bbr->cycle_idx : -1,
/* rtt */ link->bbr_pacing_rate);
char hist_str[128] = "";
int hist_pos = 0; /* rtt (BBR) */
for (int i = 0; i < 10 && i < (int)link->rtt_history_count; i++) { DUMP_LINE("RTT: last=%u jitter=%u min_rtt_us=%u bw_lo=%u bw_hi=%u inflight_lo=%u inflight_hi=%u",
hist_pos += snprintf(hist_str + hist_pos, sizeof(hist_str) - hist_pos, link->rtt_last, link->jitter,
"%s%u", i == 0 ? "" : ",", link->rtt_history[i]); link->bbr ? link->bbr->min_rtt_us : 0,
} link->bbr ? link->bbr->bw_lo : 0,
DUMP_LINE("RTT: last=%u avg10=%u min=%u max=%u jitter=%u hist=[%s] cnt=%d swm=%u", link->bbr ? (link->bbr->bw_hi[0] > link->bbr->bw_hi[1] ? link->bbr->bw_hi[0] : link->bbr->bw_hi[1]) : 0,
link->rtt_last, link->rtt_avg10, link->rtt_min, link->rtt_max_val, link->bbr ? link->bbr->inflight_lo : 0,
link->jitter, hist_str, link->rtt_history_count, link->bbr ? link->bbr->inflight_hi : 0);
link->rtt_swm ? swm_get_min(link->rtt_swm) : 0);
/* tt/rt/bandwidth */ /* tt/rt/bandwidth */
DUMP_LINE("TT/RT: tt=%u rt=%u recv_dt_tx=%u recv_dt_rx=%u bw=%u", DUMP_LINE("TT/RT: tt=%u rt=%u recv_dt_tx=%u recv_dt_rx=%u bw=%u",
@ -201,11 +198,6 @@ void etcp_dump_conn_state(struct ETCP_CONN* conn) {
link->nat_changes_count, link->nat_hits_count, link->nat_changes_count, link->nat_hits_count,
link->nat_check_status, link->nat_type); link->nat_check_status, link->nat_type);
/* stats win */
DUMP_LINE("WIN: ptr=%u tb=%u tx=%u retrans=%u",
link->win_ptr, link->win_timebase,
link->window_pkt_transmitted, link->window_retransmissions);
/* last recv */ /* last recv */
DUMP_LINE("LAST_RECV: time=%llu ts=%u updated=%d", DUMP_LINE("LAST_RECV: time=%llu ts=%u updated=%d",
(unsigned long long)link->last_recv_local_time, (unsigned long long)link->last_recv_local_time,

25
tests/Makefile.am

@ -2,6 +2,7 @@
# All available tests (check_PROGRAMS runs via automake check-TESTS) # All available tests (check_PROGRAMS runs via automake check-TESTS)
check_PROGRAMS = \ check_PROGRAMS = \
test_etcp_bbr \
test_etcp_crypto \ test_etcp_crypto \
test_etcp_two_instances \ test_etcp_two_instances \
test_etcp_simple_traffic \ test_etcp_simple_traffic \
@ -30,7 +31,6 @@ check_PROGRAMS = \
test_nat_engine \ test_nat_engine \
test_nat_transport \ test_nat_transport \
test_nat_stress \ test_nat_stress \
test_etcp_reinit_inflight \
test_tcp_proxy \ test_tcp_proxy \
test_lwip_tcp \ test_lwip_tcp \
test_etcp_router \ test_etcp_router \
@ -44,9 +44,9 @@ check_PROGRAMS = \
bench_uasync_timeouts bench_uasync_timeouts
# Долгие тесты: запускаются только вручную, не включаются в make check # Долгие тесты: запускаются только вручную, не включаются в make check
# test_etcp_congestion — 25+ секунд, congestion control simulation # test_etcp_congestion — DISABLED: старый congestion control удалён, ждёт новых BBR тестов
# test_tcp_proxy_remote — 2-node TCP через etcp, требует fix g_tcp_proxy singleton # test_tcp_proxy_remote — 2-node TCP через etcp, требует fix g_tcp_proxy singleton
noinst_PROGRAMS = test_etcp_congestion noinst_PROGRAMS =
# test_crypto and test_ecc_encrypt only needed for TinyCrypt (not when using OpenSSL) # test_crypto and test_ecc_encrypt only needed for TinyCrypt (not when using OpenSSL)
if USE_OPENSSL if USE_OPENSSL
@ -81,6 +81,7 @@ SECURE_CHANNEL_OBJS = $(top_builddir)/src/utun-secure_channel.o $(top_builddir)/
ETCP_CORE_OBJS = \ ETCP_CORE_OBJS = \
$(top_builddir)/src/utun-etcp.o \ $(top_builddir)/src/utun-etcp.o \
$(top_builddir)/src/utun-etcp_connections.o \ $(top_builddir)/src/utun-etcp_connections.o \
$(top_builddir)/src/utun-etcp_bbr.o \
$(top_builddir)/src/utun-etcp_loadbalancer.o \ $(top_builddir)/src/utun-etcp_loadbalancer.o \
$(top_builddir)/src/utun-pkt_normalizer.o \ $(top_builddir)/src/utun-pkt_normalizer.o \
$(top_builddir)/src/utun-etcp_api.o \ $(top_builddir)/src/utun-etcp_api.o \
@ -165,6 +166,10 @@ $(TINYCRYPT_OBJS): $(top_builddir)/src/utun
endif endif
# Test definitions # Test definitions
test_etcp_bbr_SOURCES = test_etcp_bbr.c
test_etcp_bbr_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_etcp_bbr_LDADD = $(top_builddir)/src/utun-etcp_bbr.o $(COMMON_LIBS)
test_etcp_crypto_SOURCES = test_etcp_crypto.c test_etcp_crypto_SOURCES = test_etcp_crypto.c
test_etcp_crypto_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source test_etcp_crypto_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_etcp_crypto_LDADD = $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS) test_etcp_crypto_LDADD = $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
@ -188,13 +193,15 @@ test_ipv6_sockets_SOURCES = test_ipv6_sockets.c
test_ipv6_sockets_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source test_ipv6_sockets_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_ipv6_sockets_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS) test_ipv6_sockets_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_etcp_congestion_SOURCES = test_etcp_congestion.c # test_etcp_congestion — отключён (старый congestion control удалён)
test_etcp_congestion_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source #test_etcp_congestion_SOURCES = test_etcp_congestion.c
test_etcp_congestion_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS) #test_etcp_congestion_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
#test_etcp_congestion_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_etcp_reinit_inflight_SOURCES = test_etcp_reinit_inflight.c # test_etcp_reinit_inflight — отключён (старый congestion control удалён)
test_etcp_reinit_inflight_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source #test_etcp_reinit_inflight_SOURCES = test_etcp_reinit_inflight.c
test_etcp_reinit_inflight_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS) #test_etcp_reinit_inflight_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
#test_etcp_reinit_inflight_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_tcp_proxy_SOURCES = test_tcp_proxy.c test_tcp_proxy_SOURCES = test_tcp_proxy.c
test_tcp_proxy_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS) test_tcp_proxy_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)

161
tests/test_etcp_bbr.c

@ -0,0 +1,161 @@
#include "../src/etcp_bbr.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define MSEG(s) do { test_total++; printf("TEST %d: %-50s ", test_total, s); fflush(stdout); } while(0)
#define PASS() do { puts("PASS"); test_passed++; } while(0)
#define FAIL(m) do { printf("FAIL: %s\n", m); test_failed++; return -1; } while(0)
static int test_total = 0;
static int test_passed = 0;
static int test_failed = 0;
static uint32_t g_cwnd;
static uint32_t g_pacing;
static uint32_t run_ack_ex(struct bbr* bbr, uint32_t acked, uint32_t rtt_us,
int app, int lost, uint32_t inflight, uint32_t prior_del)
{
bbr->now_tb += rtt_us / 100;
struct bbr_rate_sample rs = {
.delivered = acked, .interval_us = rtt_us, .rtt_us = rtt_us,
.acked_sacked = acked, .prior_delivered = prior_del,
.tx_in_flight = inflight > 0 ? inflight : acked,
.lost = lost, .is_app_limited = app,
};
bbr_main(bbr, &rs, &g_cwnd, &g_pacing, 1400, inflight, inflight >= g_cwnd);
return g_cwnd;
}
static uint32_t run_ack(struct bbr* bbr, uint32_t acked, uint32_t rtt_us,
int app, int lost, uint32_t inflight)
{ return run_ack_ex(bbr, acked, rtt_us, app, lost, inflight, bbr->delivered); }
/* --- tests --- */
static int test_init(void)
{
MSEG("bbr_init — startup mode");
struct bbr s; memset(&s, 0xFF, sizeof(s)); bbr_init(&s);
if (s.mode != BBR_STARTUP) FAIL("mode");
if (s.cycle_idx != 0) FAIL("cycle_idx");
if (s.min_rtt_us != ~0U) FAIL("min_rtt_us");
if (s.bw_lo != ~0U) FAIL("bw_lo");
if (s.inflight_lo != ~0U) FAIL("inflight_lo");
if (s.inflight_hi != ~0U) FAIL("inflight_hi");
if (s.initialized != 1) FAIL("initialized");
if (s.full_bw_reached) FAIL("full_bw_reached");
PASS(); return 0;
}
static int test_startup_growth(void)
{
MSEG("startup — cwnd grows");
struct bbr s; memset(&s, 0, sizeof(s)); s.now_tb = 10000; bbr_init(&s);
s.init_cwnd = 4; g_cwnd = 5600; g_pacing = 0;
uint32_t prev = g_cwnd;
for (int r = 0; r < 4; r++) {
s.now_tb += 10000;
for (int a = 0; a < 5; a++) run_ack(&s, 1400, 10000, 0, 0, prev);
if (g_cwnd <= prev) FAIL("cwnd did not grow");
prev = g_cwnd;
}
PASS(); return 0;
}
static int test_full_bw(void)
{
MSEG("full_bw → DRAIN → PROBE_BW");
struct bbr s; memset(&s, 0, sizeof(s)); s.now_tb = 10000; bbr_init(&s);
s.init_cwnd = 4; g_cwnd = 5600; g_pacing = 0;
// 4 rounds: first resets full_bw (sample_bw >= 0*1.25), next 3 count up
for (int r = 0; r < 4; r++) {
s.now_tb += 10000;
uint32_t rd = s.delivered;
for (int a = 0; a < 4; a++) run_ack_ex(&s, 1400, 10000, 0, 0, g_cwnd, rd);
}
if (!s.full_bw_reached) FAIL("full_bw_reached");
// Mode transitions STARTUP→DRAIN→PROBE_BW might all happen in one ACK
if (s.mode != BBR_DRAIN && s.mode != BBR_PROBE_BW) FAIL("mode not DRAIN/PROBE_BW after full_bw");
PASS(); return 0;
PASS(); return 0;
}
static int test_loss_startup(void)
{
MSEG("loss events in STARTUP → early full_bw");
struct bbr s; memset(&s, 0, sizeof(s)); s.now_tb = 10000; bbr_init(&s);
s.init_cwnd = 4; g_cwnd = 5600; g_pacing = 0; s.loss_round_start = 1;
for (int i = 0; i < 6; i++) { s.now_tb += 1000; run_ack(&s, 1400, 5000, 0, 1, 5600); }
if (!s.full_bw_reached) FAIL("full_bw_reached not set");
PASS(); return 0;
}
static int test_cwnd_bounded(void)
{
MSEG("cwnd capped by inflight_hi");
struct bbr s; memset(&s, 0, sizeof(s)); s.now_tb = 10000; bbr_init(&s);
s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; s.cycle_idx = BBR_BW_PROBE_UP;
s.inflight_hi = 4000; s.inflight_lo = 4000; g_cwnd = 5600; g_pacing = 0;
s.now_tb += 5000; run_ack(&s, 1400, 5000, 0, 0, 5000);
if (g_cwnd > 4000) FAIL("cwnd > inflight_hi");
PASS(); return 0;
}
static int test_loss_cut(void)
{
MSEG("loss cuts inflight_lo");
struct bbr s; memset(&s, 0, sizeof(s)); s.now_tb = 10000; bbr_init(&s);
s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; s.cycle_idx = BBR_BW_PROBE_CRUISE;
s.inflight_lo = 5000; s.bw_lo = 1000;
s.bw_latest = 500; s.inflight_latest = 3000;
s.delivered = 0; s.loss_round_delivered = 0;
s.cycle_mstamp_tb = s.now_tb; // prevent phase advance
s.probe_wait_us = 2000000;
uint32_t prev = s.inflight_lo;
s.now_tb += 5000; run_ack_ex(&s, 1400, 5000, 0, 1, 5000, 0);
if (s.inflight_lo >= prev) FAIL("inflight_lo not reduced");
PASS(); return 0;
}
static int test_fast_path(void)
{
MSEG("app-limited ACK triggers fast_path");
struct bbr s; memset(&s, 0, sizeof(s)); s.now_tb = 10000; bbr_init(&s);
s.init_cwnd = 4; s.full_bw_reached = 1;
s.mode = BBR_PROBE_BW; s.cycle_idx = BBR_BW_PROBE_CRUISE;
s.try_fast_path = 1; s.bw_hi[0] = 10000; s.bw_lo = 5000;
g_cwnd = 5600; g_pacing = 0;
s.now_tb += 1000; run_ack(&s, 1400, 5000, 1, 0, 2000);
PASS(); return 0;
}
static int test_probe_rtt(void)
{
MSEG("PROBE_RTT entry after 5s");
struct bbr s; memset(&s, 0, sizeof(s)); s.now_tb = 10000; bbr_init(&s);
s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; s.cycle_idx = BBR_BW_PROBE_CRUISE;
s.probe_rtt_min_us = ~0U; s.probe_rtt_min_stamp_tb = 10000;
s.min_rtt_us = ~0U; s.min_rtt_stamp_tb = 10000;
g_cwnd = 5600; g_pacing = 0;
s.now_tb = 10000 + 5000U * 10; // +5 seconds in 0.1ms
run_ack(&s, 1400, 1000, 0, 0, 5600);
if (s.mode != BBR_PROBE_RTT) FAIL("mode not PROBE_RTT");
if (g_cwnd > BBR_CWND_MIN_TARGET + 1400) FAIL("cwnd not capped");
PASS(); return 0;
}
int main(void)
{
test_init();
test_startup_growth();
test_full_bw();
test_loss_startup();
test_cwnd_bounded();
test_loss_cut();
test_fast_path();
test_probe_rtt();
printf("\n=== Results: %d/%d passed, %d failed ===\n", test_passed, test_total, test_failed);
return test_failed ? 1 : 0;
}
Loading…
Cancel
Save