From 4f0baa37dba11ac6b171fcdb6ad0ef18261c38cd Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 1 Jun 2026 17:50:16 +0300 Subject: [PATCH] =?UTF-8?q?bbr:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=D0=B0=D1=87?= =?UTF-8?q?=D1=83=20=D0=BF=D0=BE=D1=82=D0=B5=D1=80=D1=8C=20=D0=B2=20BBR=20?= =?UTF-8?q?+=20=D0=BF=D0=BE=D0=BB=20=D0=B4=D0=BB=D1=8F=20cwnd=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D1=83=D1=80=D0=BE=D0=B2=D0=BD=D0=B5=20BBR=5FCWND=5FMI?= =?UTF-8?q?N=5FTARGET*mss?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - etcp.c: rs->lost из link->bbr_loss_since_ack (был хардкод 0), вызов bbr_note_loss при ретрансмите - etcp_connections.h: поле bbr_loss_since_ack для per-link учёта потерь в байтах - etcp_bbr.c: везде где BBR_CWND_MIN_TARGET — умножить на mss (юнит-мизматч пакеты/байты) - bbr_quantization_budget, bbr_set_cwnd, bbr_bound_cwnd_for_inflight_model, bbr_probe_rtt_cwnd - bbr_init_lower_bounds: инициализация inflight_hi из cwnd (был ~0U навсегда) - control_server.c + etcpmon: экспорт BBR-метрик (bw_hi/lo, inflight_hi/lo, режим, потери) - tests/bbr_integration: интеграционный тест BBR (15с, 50ms/5%loss/1Mbit) с таблицей метрик - устранён death spiral BBR при высоких потерях: cwnd теперь не падает ниже 4*mss --- src/control_server.c | 19 + src/etcp.c | 5 +- src/etcp_bbr.c | 20 +- src/etcp_connections.c | 1 + src/etcp_connections.h | 1 + tests/Makefile.am | 5 + tests/bbr_integration/run.sh | 9 + tests/bbr_integration/test_bbr_integration.c | 492 +++++++++++++++++++ tools/etcpmon/etcpmon_client.c | 56 +-- tools/etcpmon/etcpmon_client.h | 16 +- tools/etcpmon/etcpmon_graph.c | 32 +- tools/etcpmon/etcpmon_gui.c | 23 +- tools/etcpmon/etcpmon_protocol.h | 14 + 13 files changed, 615 insertions(+), 78 deletions(-) create mode 100755 tests/bbr_integration/run.sh create mode 100644 tests/bbr_integration/test_bbr_integration.c diff --git a/src/control_server.c b/src/control_server.c index 44dbc522..e10c0777 100644 --- a/src/control_server.c +++ b/src/control_server.c @@ -1078,6 +1078,25 @@ static void send_metrics(struct control_server* server, struct control_client* c link_info[i].inflight_bytes = link->inflight_bytes; link_info[i].inflight_packets = link->inflight_packets; link_info[i].inflight_lim_bytes = link->inflight_lim_bytes; + + /* BBR fields */ + if (link->bbr) { + link_info[i].bbr_mode = link->bbr->mode; + link_info[i].bbr_cycle_idx = link->bbr->cycle_idx; + link_info[i].bbr_full_bw_reached = link->bbr->full_bw_reached; + link_info[i].bbr_loss_in_round = link->bbr->loss_in_round; + link_info[i].bbr_pacing_rate = link->bbr_pacing_rate; + link_info[i].bbr_min_rtt_us = link->bbr->min_rtt_us; + link_info[i].bbr_pacing_gain = link->bbr->pacing_gain; + link_info[i].bbr_inflight_hi = link->bbr->inflight_hi; + link_info[i].bbr_inflight_lo = link->bbr->inflight_lo; + /* bw_hi/lo: convert from BW_UNIT scale (pkts/usec<<24) to bytes/sec */ + uint32_t max_bw = link->bbr->bw_hi[0] > link->bbr->bw_hi[1] ? link->bbr->bw_hi[0] : link->bbr->bw_hi[1]; + link_info[i].bbr_bw_hi = max_bw ? (uint32_t)((uint64_t)max_bw * link->mtu * 1000000ULL / 16777216ULL) : 0; + link_info[i].bbr_bw_lo = link->bbr->bw_lo != ~0U ? (uint32_t)((uint64_t)link->bbr->bw_lo * link->mtu * 1000000ULL / 16777216ULL) : 0; + } else { + memset(&link_info[i].bbr_mode, 0, 11 * 4 + 2 + 2); // zero out all BBR fields + } link = link->next; } diff --git a/src/etcp.c b/src/etcp.c index f2568a2e..a886228d 100644 --- a/src/etcp.c +++ b/src/etcp.c @@ -920,6 +920,8 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) { // Always subtract from previous last_link (if any) – this handles retransmission if (inf_pkt->last_link) { inf_pkt->last_link->total_retransmissions++; + inf_pkt->last_link->bbr_loss_since_ack += inf_pkt->ll.len; + bbr_note_loss(inf_pkt->last_link->bbr); inf_pkt->last_link->inflight_bytes -= inf_pkt->ll.len; inf_pkt->last_link->inflight_packets--; if (link->send_blocked_inflight && link->inflight_bytes < link->inflight_lim_bytes) loadbalancer_link_ready(link); @@ -1227,7 +1229,7 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d .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, + .lost = (int)link->bbr_loss_since_ack, .is_app_limited = acked_pkt->is_app_limited, }; link->delivered_bytes += rs.delivered; @@ -1245,6 +1247,7 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d link->send_blocked_inflight = 0; loadbalancer_link_ready(link); } + link->bbr_loss_since_ack = 0; } diff --git a/src/etcp_bbr.c b/src/etcp_bbr.c index e82e8b05..ed7260b4 100644 --- a/src/etcp_bbr.c +++ b/src/etcp_bbr.c @@ -65,8 +65,7 @@ static uint32_t bbr_bdp(const struct bbr* bbr, uint32_t bw, int gain, uint32_t m 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); + cwnd = (uint32_t)(cwnd > BBR_CWND_MIN_TARGET * mss ? cwnd : BBR_CWND_MIN_TARGET * mss); if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_UP) cwnd += 2; return cwnd; @@ -165,6 +164,8 @@ static void bbr_init_lower_bounds(struct bbr* bbr, int init_bw, uint32_t cwnd) bbr->bw_lo = bbr_max_bw(bbr); if (bbr->inflight_lo == ~0U) bbr->inflight_lo = cwnd; + if (bbr->inflight_hi == ~0U) + bbr->inflight_hi = (uint32_t)(cwnd > 4U * (uint32_t)bbr->init_cwnd ? cwnd : 4U * (uint32_t)bbr->init_cwnd); } static void bbr_loss_lower_bounds(struct bbr* bbr) @@ -336,10 +337,10 @@ static void bbr_exit_probe_rtt(struct bbr* bbr) } } -static uint32_t bbr_probe_rtt_cwnd(const struct bbr* bbr) +static uint32_t bbr_probe_rtt_cwnd(const struct bbr* bbr, uint32_t mss) { (void)bbr; - return BBR_CWND_MIN_TARGET; + return BBR_CWND_MIN_TARGET * mss; } static uint32_t bbr_inflight_with_headroom(const struct bbr* bbr) @@ -594,7 +595,7 @@ static void bbr_update_min_rtt(struct bbr* bbr, const struct bbr_rate_sample* rs if (bbr->mode == BBR_PROBE_RTT) { if (!bbr->probe_rtt_done_stamp_tb && - inflight_packets <= bbr_probe_rtt_cwnd(bbr)) { + inflight_packets <= bbr_probe_rtt_cwnd(bbr, mss)) { 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; @@ -755,12 +756,12 @@ static void bbr_set_cwnd(struct bbr* bbr, const struct bbr_rate_sample* rs, bbr->try_fast_path = 1; } - local_cwnd = (uint32_t)(local_cwnd > BBR_CWND_MIN_TARGET ? - local_cwnd : BBR_CWND_MIN_TARGET); + local_cwnd = (uint32_t)(local_cwnd > BBR_CWND_MIN_TARGET * mss ? + local_cwnd : BBR_CWND_MIN_TARGET * mss); done: *cwnd = local_cwnd; if (bbr->mode == BBR_PROBE_RTT) { - uint32_t pr_cwnd = bbr_probe_rtt_cwnd(bbr); + uint32_t pr_cwnd = bbr_probe_rtt_cwnd(bbr, mss); *cwnd = (uint32_t)(*cwnd < pr_cwnd ? *cwnd : pr_cwnd); } } @@ -768,7 +769,6 @@ done: 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; @@ -780,7 +780,7 @@ static void bbr_bound_cwnd_for_inflight_model(struct bbr* bbr, uint32_t* cwnd, u 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); + cap = (uint32_t)(cap > BBR_CWND_MIN_TARGET * mss ? cap : BBR_CWND_MIN_TARGET * mss); *cwnd = (uint32_t)(cap < *cwnd ? cap : *cwnd); } diff --git a/src/etcp_connections.c b/src/etcp_connections.c index f5e86af3..a0ce911d 100644 --- a/src/etcp_connections.c +++ b/src/etcp_connections.c @@ -791,6 +791,7 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn link->delivered_bytes = 0; link->last_ack_time_tb = get_time_tb(); link->bbr_pacing_rate = 0; + link->bbr_loss_since_ack = 0; link->bbr = u_calloc(1, sizeof(struct bbr)); if (!link->bbr) { u_free(link); diff --git a/src/etcp_connections.h b/src/etcp_connections.h index 95de809a..e62de545 100644 --- a/src/etcp_connections.h +++ b/src/etcp_connections.h @@ -230,6 +230,7 @@ struct ETCP_LINK { uint64_t delivered_bytes; // кумулятивно delivered bytes на линке (для round detection) uint64_t last_ack_time_tb; // 0.1ms timestamp последнего ACK (для interval_us) uint32_t bbr_pacing_rate; // bytes/sec output BBR → shaper + uint32_t bbr_loss_since_ack; // счётчик потерь на линке с последнего ACK (для rs->lost) etcp_udp_send_fn_t send_hook; // NULL = socket_sendto напрямую void* send_hook_ctx; diff --git a/tests/Makefile.am b/tests/Makefile.am index 35fd2c83..d75f816f 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -2,6 +2,7 @@ # All available tests (check_PROGRAMS runs via automake check-TESTS) check_PROGRAMS = \ + test_bbr_integration \ test_etcp_bbr \ test_etcp_crypto \ test_etcp_two_instances \ @@ -348,6 +349,10 @@ test_bgp_route_exchange_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_ # test_dummynet_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib # test_dummynet_LDADD = $(top_builddir)/src/utun-dummynet.o $(COMMON_LIBS) +test_bbr_integration_SOURCES = bbr_integration/test_bbr_integration.c +test_bbr_integration_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source +test_bbr_integration_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS) + bench_timeout_heap_SOURCES = bench_timeout_heap.c bench_timeout_heap_CFLAGS = -I$(top_srcdir)/lib bench_timeout_heap_LDADD = $(COMMON_LIBS) diff --git a/tests/bbr_integration/run.sh b/tests/bbr_integration/run.sh new file mode 100755 index 00000000..6960eef8 --- /dev/null +++ b/tests/bbr_integration/run.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e +cd "$(dirname "$0")/../../" +echo "=== Building BBR integration test ===" +./build.sh -j4 +echo "" +echo "=== Running BBR integration test ===" +cd build/tests +./test_bbr_integration diff --git a/tests/bbr_integration/test_bbr_integration.c b/tests/bbr_integration/test_bbr_integration.c new file mode 100644 index 00000000..36327fd7 --- /dev/null +++ b/tests/bbr_integration/test_bbr_integration.c @@ -0,0 +1,492 @@ +/** + * @file test_bbr_integration.c + * @brief BBR integration test — max-speed traffic under emulated network conditions + * + * Network: 50ms delay, 5% loss, 1 Mbit shaper, ~100KB queue + * Duration: 10 seconds, metrics every 100ms + * + * Architecture (single uasync, single thread): + * Sender(client:20000) → Dummynet(:20001) → Receiver(server:20002) + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "../lib/u_async.h" +#include "../lib/ll_queue.h" +#include "../lib/memory_pool.h" +#include "../lib/debug_config.h" +#include "../lib/platform_compat.h" +#include "../lib/mem.h" +#include "../src/dummynet.h" +#include "../src/config_parser.h" +#include "../src/utun_instance.h" +#include "../src/etcp.h" +#include "../src/etcp_api.h" +#include "../src/etcp_connections.h" +#include "../src/etcp_bbr.h" +#include "../src/secure_channel.h" +#include "../src/config_updater.h" +#include "../src/routing.h" +#include "../src/crc32.h" + +/* ===== Test constants ===== */ +#define DN_PORT 21001 +#define SRV_PORT 21002 +#define CLI_PORT 21000 + +#define PAYLOAD_SIZE 1200 +#define TEST_DURATION_MS 15000 +#define METRICS_TB 1000 /* 100ms in 0.1ms units */ +#define SEND_TIMER_TB 1 /* 0.1ms re-schedule */ +#define BURST_MAX 64 + +#define EMU_DELAY_MS 50 +#define EMU_JITTER_MS 12 +#define EMU_BW_KBPS 1000 +#define EMU_LOSS_PERM 50 /* 5% */ +#define EMU_QUEUE_PKTS 65 /* ~100KB at MTU 1400-1600 */ + +/* ===== BBR name helpers ===== */ +static const char* bbr_mode_name(uint8_t m) { + switch (m) { + case BBR_STARTUP: return "STRTUP"; + case BBR_DRAIN: return "DRAIN "; + case BBR_PROBE_BW: return "PROBW "; + case BBR_PROBE_RTT: return "PRRTT "; + default: return "??????"; + } +} + +static const char* bbr_cycle_name(uint8_t m, uint8_t c) { + if (m != BBR_PROBE_BW) return " --"; + switch (c) { + case BBR_BW_PROBE_UP: return " UP"; + case BBR_BW_PROBE_DOWN: return " DN"; + case BBR_BW_PROBE_CRUISE: return " CR"; + case BBR_BW_PROBE_REFILL: return " RF"; + default: return " ??"; + } +} + +/* ===== Test context ===== */ +struct test_ctx { + struct UASYNC* ua; + struct UTUN_INSTANCE* sender; + struct UTUN_INSTANCE* receiver; + struct dummynet* dn; + int test_done; + + uint64_t bytes_sent; + uint64_t bytes_received; + uint64_t last_bytes_received; + uint64_t start_time_us; + + FILE* log_file; + void* metrics_timer; +}; + +/* ===== Time helper ===== */ +static uint64_t now_us(void) { + struct timeval tv; + gettimeofday(&tv, NULL); + return (uint64_t)tv.tv_sec * 1000000ULL + tv.tv_usec; +} + +/* ===== Instance creation ===== */ +static struct UTUN_INSTANCE* create_instance(struct UASYNC* u, uint64_t node_id, + const char* priv_hex, const char* pub_hex) { + struct UTUN_INSTANCE* inst = u_calloc(1, sizeof(*inst)); + if (!inst) return NULL; + inst->ua = u; + inst->node_id = node_id; + if (sc_init_local_keys(&inst->my_keys, pub_hex, priv_hex) != SC_OK) { u_free(inst); return NULL; } + inst->ack_pool = memory_pool_init(sizeof(struct ACK_PACKET)); + inst->data_pool = memory_pool_init(PACKET_DATA_SIZE); + inst->pkt_pool = memory_pool_init(sizeof(struct ETCP_DGRAM) + PACKET_DATA_SIZE); + if (!inst->ack_pool || !inst->data_pool || !inst->pkt_pool) { u_free(inst); return NULL; } + struct utun_config* cfg = u_calloc(1, sizeof(*cfg)); + if (!cfg) { u_free(inst); return NULL; } + strncpy(cfg->global.my_public_key_hex, pub_hex, MAX_KEY_LEN - 1); + strncpy(cfg->global.my_private_key_hex, priv_hex, MAX_KEY_LEN - 1); + cfg->global.my_node_id = node_id; + cfg->global.mtu = 1400; + cfg->global.keepalive_timeout = 5000; + cfg->global.keepalive_interval = 500; + cfg->global.allowed_keys_allow_all = 1; + inst->config = cfg; + return inst; +} + +static int add_server(struct UTUN_INSTANCE* inst, const char* name, int port) { + struct CFG_SERVER* srv = u_calloc(1, sizeof(*srv)); + if (!srv) return -1; + strncpy(srv->name, name, MAX_CONN_NAME_LEN - 1); + srv->ip.ss_family = AF_INET; + ((struct sockaddr_in*)&srv->ip)->sin_addr.s_addr = inet_addr("127.0.0.1"); + ((struct sockaddr_in*)&srv->ip)->sin_port = htons(port); + srv->type = CFG_SERVER_TYPE_PUBLIC; + srv->next = inst->config->servers; + inst->config->servers = srv; + return 0; +} + +static int add_client(struct UTUN_INSTANCE* inst, const char* peer_pubkey) { + struct CFG_CLIENT* cli = u_calloc(1, sizeof(*cli)); + if (!cli) return -1; + strncpy(cli->name, "peer", MAX_CONN_NAME_LEN - 1); + strncpy(cli->peer_public_key_hex, peer_pubkey, MAX_KEY_LEN - 1); + cli->keepalive = 1; + cli->next = inst->config->clients; + inst->config->clients = cli; + return 0; +} + +static struct CFG_CLIENT_LINK* add_link(struct CFG_CLIENT* cli, struct CFG_SERVER* local_srv, int remote_port) { + struct CFG_CLIENT_LINK* link = u_calloc(1, sizeof(*link)); + if (!link) return NULL; + link->remote_addr.ss_family = AF_INET; + ((struct sockaddr_in*)&link->remote_addr)->sin_addr.s_addr = inet_addr("127.0.0.1"); + ((struct sockaddr_in*)&link->remote_addr)->sin_port = htons(remote_port); + link->local_srv = local_srv; + struct CFG_CLIENT_LINK** tail = &cli->links; + while (*tail) tail = &(*tail)->next; + *tail = link; + return link; +} + +/* ===== Callbacks ===== */ +static void on_recv(struct ETCP_CONN* conn, struct ll_entry* entry) { + struct test_ctx* ctx = (struct test_ctx*)conn->instance->etcp_new_conn_arg; + if (entry) { ctx->bytes_received += entry->len; queue_entry_free(entry); } +} + +static void send_burst(struct test_ctx* ctx); + +static void send_timer_cb(void* arg) { + send_burst((struct test_ctx*)arg); +} + +static void send_burst(struct test_ctx* ctx) { + if (ctx->test_done) return; + struct ETCP_CONN* conn = ctx->sender->connections; + if (!conn || !conn->initialized) return; + + int sent = 0; + while (sent < BURST_MAX) { + struct ll_entry* e = ll_alloc_lldgram(1 + PAYLOAD_SIZE); + if (!e) break; + e->dgram[0] = ETCP_ID_DATA; + e->len = 1 + PAYLOAD_SIZE; + if (etcp_send(conn, e) == 0) { ctx->bytes_sent += PAYLOAD_SIZE; sent++; } + else { queue_entry_free(e); break; } + } + if (!ctx->test_done) + uasync_set_timeout(ctx->ua, SEND_TIMER_TB, ctx, send_timer_cb, "bbr_send"); +} + +static void print_metrics(struct test_ctx* ctx) { + double time_s = (double)(now_us() - ctx->start_time_us) / 1000000.0; + uint64_t dr = ctx->bytes_received - ctx->last_bytes_received; + ctx->last_bytes_received = ctx->bytes_received; + double tput_kbps = (double)dr * 8.0 / 100.0; /* dr * 8 / (0.1s * 1000) */ + + const struct dummynet_stats* ds = dummynet_get_stats(ctx->dn, DUMMYNET_FORWARD); + int qpkt = dummynet_get_queue_size(ctx->dn, DUMMYNET_FORWARD); + uint64_t dropq = ds ? ds->dropped : 0; + uint64_t dropl = ds ? ds->lost : 0; + + const char* lk_status = " ??"; + uint32_t lk_reinit = 0; + uint32_t lk_rtrns = 0; + double bw_latest_k = 0; + double bw_lo_k = 0; + int round_start = 0; + double deliv_k = 0; + int full_bw = 0; + int loss_in_rnd = 0; + const char* mode_str = " ----"; + const char* cyc_str = " --"; + double pgain = 0; + double infl_kb = 0; + double lim_kb = 0; + double inhi_kb = 0; + double inlo_kb = 0; + double pace_kbps = 0; + double rtt_ms = 0; + + if (ctx->sender->connections && ctx->sender->connections->links) { + struct ETCP_LINK* l = ctx->sender->connections->links; + struct ETCP_CONN* c = ctx->sender->connections; + lk_status = l->link_status ? " UP" : " DN"; + lk_reinit = c->reinit_count; + lk_rtrns = l->total_retransmissions; + if (l->initialized && l->bbr) { + struct bbr* b = l->bbr; + mode_str = bbr_mode_name(b->mode); + cyc_str = bbr_cycle_name(b->mode, b->cycle_idx); + pgain = (double)b->pacing_gain / BBR_UNIT; + infl_kb = (double)l->inflight_bytes / 1024.0; + lim_kb = (double)l->inflight_lim_bytes / 1024.0; + inhi_kb = (double)b->inflight_hi / 1024.0; + inlo_kb = (double)b->inflight_lo / 1024.0; + pace_kbps = (double)l->bbr_pacing_rate / 125.0; + rtt_ms = (double)b->min_rtt_us / 1000.0; + bw_latest_k = (double)b->bw_latest / 125.0; + bw_lo_k = (double)b->bw_lo / 125.0; + round_start = b->round_start; + deliv_k = (double)b->delivered / 1024.0; + full_bw = b->full_bw_reached; + loss_in_rnd = b->loss_in_round; + } + } + + static int header_printed = 0; + if (!header_printed) { + printf("=== Legend: BWlst=bw_latest(kbps) Rnd=round_start(*) DelK=delivered(KB) BWlo=bw_lo(kbps) ===\n"); + printf("%6s %7s %4s %5s %5s %3s %4s %4s %7s %3s %6s %7s %6s %4s %5s %7s %7s %7s %6s %7s %7s %4s\n", + "Time", "TputK", "Qpkt", "DnQov", "DnQls", + "LkS", "Rein", "Rtrn", + "BWlstK", "Rnd", "DelvK", "BWloK", + "Mode", "Cyc", "Pgain", "InflKB", "LimKB", + "PaceKb", "RTTms", "InHiKB", "InLoKB", "LRnd"); + printf("------ ------- ---- ----- ----- --- ---- ---- ------- --- ------ ------- ------ ---- ----- ------- ------- ------- ------ ------- ------- ----\n"); + if (ctx->log_file) { + fprintf(ctx->log_file, "=== Legend: BWlst=bw_latest(kbps) Rnd=round_start(*) DelK=delivered(KB) BWlo=bw_lo(kbps) ===\n"); + fprintf(ctx->log_file, "%6s %7s %4s %5s %5s %3s %4s %4s %7s %3s %6s %7s %6s %4s %5s %7s %7s %7s %6s %7s %7s %4s\n", + "Time", "TputK", "Qpkt", "DnQov", "DnQls", + "LkS", "Rein", "Rtrn", + "BWlstK", "Rnd", "DelvK", "BWloK", + "Mode", "Cyc", "Pgain", "InflKB", "LimKB", + "PaceKb", "RTTms", "InHiKB", "InLoKB", "LRnd"); + fprintf(ctx->log_file, "------ ------- ---- ----- ----- --- ---- ---- ------- --- ------ ------- ------ ---- ----- ------- ------- ------- ------ ------- ------- ----\n"); + } + header_printed = 1; + } + + printf("%6.1f %7.0f %4d %5llu %5llu %3s %4u %4u %7.0f %3s %6.0f %7.1f %6s %4s %5.2f %7.1f %7.1f %7.0f %6.1f %7.1f %7.1f %4d\n", + time_s, tput_kbps, qpkt, + (unsigned long long)dropq, (unsigned long long)dropl, + lk_status, lk_reinit, lk_rtrns, + bw_latest_k, round_start ? " *" : " ", deliv_k, bw_lo_k, + mode_str, cyc_str, pgain, infl_kb, lim_kb, + pace_kbps, rtt_ms, inhi_kb, inlo_kb, loss_in_rnd); + fflush(stdout); + if (ctx->log_file) { + fprintf(ctx->log_file, "%6.1f %7.0f %4d %5llu %5llu %3s %4u %4u %7.0f %3s %6.0f %7.1f %6s %4s %5.2f %7.1f %7.1f %7.0f %6.1f %7.1f %7.1f %4d\n", + time_s, tput_kbps, qpkt, + (unsigned long long)dropq, (unsigned long long)dropl, + lk_status, lk_reinit, lk_rtrns, + bw_latest_k, round_start ? " *" : " ", deliv_k, bw_lo_k, + mode_str, cyc_str, pgain, infl_kb, lim_kb, + pace_kbps, rtt_ms, inhi_kb, inlo_kb, loss_in_rnd); + fflush(ctx->log_file); + } +} + +static void metrics_timer_cb(void* arg) { + struct test_ctx* ctx = (struct test_ctx*)arg; + if (ctx->test_done) return; + print_metrics(ctx); + ctx->metrics_timer = uasync_set_timeout(ctx->ua, METRICS_TB, ctx, metrics_timer_cb, "bbr_metrics"); +} + +/* ===== Main ===== */ +int main(void) { + printf("=== BBR Integration Test ===\n"); + printf("Network emulation: %ums delay, %u%% loss, %u kbps shaper, %u pkt queue\n\n", + EMU_DELAY_MS, EMU_LOSS_PERM / 10, EMU_BW_KBPS, EMU_QUEUE_PKTS); + + srand((unsigned)time(NULL)); + debug_config_init(); + debug_set_level(DEBUG_LEVEL_ERROR); + socket_platform_init(); + crc32_init(); + + struct test_ctx ctx; + memset(&ctx, 0, sizeof(ctx)); + + ctx.ua = uasync_create(); + if (!ctx.ua) { printf("ERROR: uasync_create failed\n"); return 1; } + + const char* s_priv = + "67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb"; + const char* s_pub = + "1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17" + "c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9"; + const char* c_priv = + "4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2"; + const char* c_pub = + "c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3" + "c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71"; + + printf("Creating instances...\n"); + ctx.sender = create_instance(ctx.ua, 0x1111111111111111ULL, c_priv, c_pub); + ctx.receiver = create_instance(ctx.ua, 0x2222222222222222ULL, s_priv, s_pub); + if (!ctx.sender || !ctx.receiver) { printf("ERROR: create_instance failed\n"); return 1; } + ctx.sender->etcp_new_conn_arg = &ctx; + ctx.receiver->etcp_new_conn_arg = &ctx; + + if (add_server(ctx.receiver, "srv", SRV_PORT) < 0 || + add_server(ctx.sender, "local", CLI_PORT) < 0 || + add_client(ctx.sender, s_pub) < 0) { + printf("ERROR: config setup failed\n"); return 1; + } + add_link(ctx.sender->config->clients, ctx.sender->config->servers, DN_PORT); + + printf("Init receiver...\n"); + if (utun_instance_init(ctx.receiver) < 0) { printf("ERROR: receiver init failed\n"); return 1; } + printf("Init sender...\n"); + if (utun_instance_init(ctx.sender) < 0) { printf("ERROR: sender init failed\n"); return 1; } + + etcp_bind(ctx.receiver, ETCP_ID_DATA, on_recv); + etcp_set_new_conn_cbk(ctx.receiver, NULL, &ctx); + + printf("Creating dummynet on port %d ...\n", DN_PORT); + ctx.dn = dummynet_create(ctx.ua, "127.0.0.1", DN_PORT); + if (!ctx.dn) { printf("ERROR: dummynet_create failed\n"); return 1; } + dummynet_set_direction(ctx.dn, DUMMYNET_FORWARD, + EMU_DELAY_MS, EMU_JITTER_MS, + EMU_BW_KBPS, EMU_QUEUE_PKTS, EMU_LOSS_PERM, + "127.0.0.1", SRV_PORT); + dummynet_set_direction(ctx.dn, DUMMYNET_BACKWARD, + 5, 1, 0, 200, 0, + "127.0.0.1", CLI_PORT); + + /* Wait for connection */ + printf("Waiting for connection...\n"); + uint64_t t0 = now_us(); + while ((now_us() - t0) < 10000000ULL) { + uasync_poll(ctx.ua, 1); + if (ctx.sender->connections && ctx.sender->connections->links) { + struct ETCP_LINK* l = ctx.sender->connections->links; + if (l->initialized && l->link_status == 1) break; + } + } + printf("Connection established, stabilizing...\n"); + t0 = now_us(); + while ((now_us() - t0) < 500000ULL) uasync_poll(ctx.ua, 1); + + /* Start test */ + printf("\n=== Starting traffic (%d seconds) ===\n\n", TEST_DURATION_MS / 1000); + ctx.log_file = fopen("test_bbr_integration.log", "w"); + if (ctx.log_file) + fprintf(ctx.log_file, "# BBR Integration Test — %ums delay %u%% loss %ukbps\n\n", + EMU_DELAY_MS, EMU_LOSS_PERM / 10, EMU_BW_KBPS); + ctx.start_time_us = now_us(); + ctx.last_bytes_received = 0; + + ctx.metrics_timer = uasync_set_timeout(ctx.ua, METRICS_TB, &ctx, metrics_timer_cb, "bbr_metrics"); + send_burst(&ctx); + + /* Main loop */ + uint64_t last_report = 0; + while (!ctx.test_done) { + uasync_poll(ctx.ua, 10); + uint64_t elapsed = (now_us() - ctx.start_time_us) / 1000; + if (elapsed >= TEST_DURATION_MS) ctx.test_done = 1; + if (elapsed - last_report >= 2000) { + last_report = elapsed; + const struct dummynet_stats* ds = dummynet_get_stats(ctx.dn, DUMMYNET_FORWARD); + const char* lks = "?"; + uint32_t rein = 0, rtrns = 0; + if (ctx.sender->connections && ctx.sender->connections->links) { + lks = ctx.sender->connections->links->link_status ? "UP" : "DN"; + rein = ctx.sender->connections->reinit_count; + rtrns = ctx.sender->connections->links->total_retransmissions; + } + printf(" t=%lus Lk=%s Rein=%u Rtrns=%u recv=%lu dn_tx=%llu dn_lost=%llu\n", + (unsigned long)elapsed, lks, rein, rtrns, (unsigned long)ctx.bytes_received, + ds ? (unsigned long long)ds->sent : 0ULL, + ds ? (unsigned long long)ds->lost : 0ULL); + } + } + + /* Drain */ + printf("\nDraining...\n"); + ctx.test_done = 1; + t0 = now_us(); + while ((now_us() - t0) < 2000000ULL) uasync_poll(ctx.ua, 10); + print_metrics(&ctx); + + /* Final summary */ + uint64_t end_us = now_us(); + double dur_s = (double)(end_us - ctx.start_time_us) / 1000000.0; + printf("\n=== BBR Integration Test Results ===\n"); + printf("Duration: %.1f s\n", dur_s); + printf("Bytes received: %lu\n", (unsigned long)ctx.bytes_received); + printf("Avg throughput: %.0f kbps\n", + (double)ctx.bytes_received * 8.0 / dur_s / 1000.0); + + const struct dummynet_stats* ds = dummynet_get_stats(ctx.dn, DUMMYNET_FORWARD); + const struct dummynet_stats* ds_bk = dummynet_get_stats(ctx.dn, DUMMYNET_BACKWARD); + if (ds) + printf("Dummynet forward: rx=%llu tx=%llu lost=%llu dropped_ovf=%llu\n", + (unsigned long long)ds->recv, (unsigned long long)ds->sent, + (unsigned long long)ds->lost, (unsigned long long)ds->dropped); + if (ds_bk) + printf("Dummynet backward: rx=%llu tx=%llu lost=%llu dropped_ovf=%llu\n", + (unsigned long long)ds_bk->recv, (unsigned long long)ds_bk->sent, + (unsigned long long)ds_bk->lost, (unsigned long long)ds_bk->dropped); + + if (ctx.sender->connections) { + struct ETCP_CONN* c = ctx.sender->connections; + printf("ETCP: reinit=%u reset=%u links_up=%u\n", + c->reinit_count, c->reset_count, c->links_up); + if (c->links) { + struct ETCP_LINK* l = c->links; + int lnum = 0; + while (l) { + lnum++; + printf(" Link%d: status=%s state=%u rKeep=%u sKeep=%u retrans=%lu" + " inflight=%u/%u lim=%u kB rtt=%u ms\n", + lnum, l->link_status ? "UP" : "DN", l->link_state, + l->recv_keepalive, l->remote_keepalive, + (unsigned long)l->total_retransmissions, + l->inflight_bytes, l->inflight_packets, l->inflight_lim_bytes / 1024, + l->rtt_last); + if (l->bbr) + printf(" BBR: mode=%d cycle=%d full_bw=%d loss_rnd=%d" + " minrtt=%.1fms bw_hi=%.0fK bw_lo=%.0fK pace=%.0fK infl_hi=%.1fK infl_lo=%.1fK\n", + l->bbr->mode, l->bbr->cycle_idx, l->bbr->full_bw_reached, l->bbr->loss_in_round, + (double)l->bbr->min_rtt_us / 1000.0, + (double)l->bbr->bw_hi[0] / 125.0, (double)l->bbr->bw_lo / 125.0, + (double)l->bbr_pacing_rate / 125.0, + (double)l->bbr->inflight_hi / 1024.0, (double)l->bbr->inflight_lo / 1024.0); + l = l->next; + } + } + } + + int pass = (ctx.bytes_received > 50000); /* at least 50KB delivered */ + printf("\n[%s]\n", pass ? "PASS" : "FAIL"); + + if (ctx.log_file) { + fprintf(ctx.log_file, "\n=== Results ===\n"); + fprintf(ctx.log_file, "Duration: %.1fs BytesRecv: %lu Throughput: %.0f kbps\n", + dur_s, (unsigned long)ctx.bytes_received, + (double)ctx.bytes_received * 8.0 / dur_s / 1000.0); + if (ds) + fprintf(ctx.log_file, "DN_FWD: rx=%llu tx=%llu lost=%llu ovf=%llu\n", + (unsigned long long)ds->recv, (unsigned long long)ds->sent, + (unsigned long long)ds->lost, (unsigned long long)ds->dropped); + if (ds_bk) + fprintf(ctx.log_file, "DN_BCK: rx=%llu tx=%llu lost=%llu ovf=%llu\n", + (unsigned long long)ds_bk->recv, (unsigned long long)ds_bk->sent, + (unsigned long long)ds_bk->lost, (unsigned long long)ds_bk->dropped); + fclose(ctx.log_file); + printf("Log written to test_bbr_integration.log\n"); + } + + /* Cleanup */ + dummynet_destroy(ctx.dn); + utun_instance_destroy(ctx.sender); + utun_instance_destroy(ctx.receiver); + uasync_destroy(ctx.ua, 1); + return pass ? 0 : 1; +} diff --git a/tools/etcpmon/etcpmon_client.c b/tools/etcpmon/etcpmon_client.c index aa842b82..7eae1e05 100644 --- a/tools/etcpmon/etcpmon_client.c +++ b/tools/etcpmon/etcpmon_client.c @@ -864,53 +864,23 @@ void etcpmon_client_add_to_history(struct etcpmon_client* client, struct etcpmon struct metrics_history* h = &client->history; int idx = h->head; -// for (int m = 0; m < GRAPH_METRICS_COUNT; m++) { -// float v = h->values[m][idx]; -// if (v < h->min_val[m]) h->min_val[m] = v; -// if (v > h->max_val[m]) h->max_val[m] = v; -// } - if (client->log_file) { - fprintf(client->log_file, "add_to_history: rtt_last=%u rtt_avg10=%u rtt_avg100=%u jitter=%u\n", - metrics->etcp.rtt_last, metrics->etcp.rtt_avg_10, metrics->etcp.rtt_avg_100, metrics->etcp.jitter); - fflush(client->log_file); + /* BBR per-link metrics (use first link as representative) */ + if (client->last_links_count > 0 && client->last_links) { + struct etcpmon_link_metrics* l = &client->last_links[0]; + h->values[GRAPH_METRIC_INFLIGHT_BYTES][idx] = l->inflight_bytes / 1024.f; + h->values[GRAPH_METRIC_INFLIGHT_LIM][idx] = l->inflight_lim_bytes / 1024.f; + h->values[GRAPH_METRIC_MIN_RTT][idx] = l->bbr_min_rtt_us / 1000.f; + h->values[GRAPH_METRIC_BW_HI][idx] = l->bbr_bw_hi / 125.f; + h->values[GRAPH_METRIC_PACING_RATE][idx] = l->bbr_pacing_rate / 125.f; + h->values[GRAPH_METRIC_BW_LO][idx] = l->bbr_bw_lo / 125.f; + h->values[GRAPH_METRIC_INFLIGHT_HI][idx] = l->bbr_inflight_hi / 1024.f; + h->values[GRAPH_METRIC_BBR_MODE][idx] = (float)l->bbr_mode; } - float scale = 0.1f; // to ms (protocol: 0.1ms units) - h->values[GRAPH_METRIC_RTT_LAST][idx] = (float)metrics->etcp.rtt_last * scale; - h->values[GRAPH_METRIC_RTT_AVG10][idx] = (float)metrics->etcp.rtt_avg_10 * scale; - h->values[GRAPH_METRIC_RTT_AVG100][idx] = (float)metrics->etcp.rtt_avg_100 * scale; - h->values[GRAPH_METRIC_JITTER][idx] = (float)metrics->etcp.jitter * scale; - - uint32_t retrans_delta = 0; - uint32_t acks_delta = 0; - uint64_t bytes_delta = 0; - - if (h->count > 0) { - if (metrics->etcp.retrans_count >= h->last_retrans) { - retrans_delta = metrics->etcp.retrans_count - h->last_retrans; - } - if (metrics->etcp.ack_count >= h->last_acks) { - acks_delta = metrics->etcp.ack_count - h->last_acks; - } - if (metrics->etcp.bytes_sent_total >= h->last_bytes_sent) { - bytes_delta = metrics->etcp.bytes_sent_total - h->last_bytes_sent; - } - } - - h->last_retrans = metrics->etcp.retrans_count; - h->last_acks = metrics->etcp.ack_count; - h->last_bytes_sent = metrics->etcp.bytes_sent_total; - - h->values[GRAPH_METRIC_RETRANS][idx] = (float)retrans_delta; - h->values[GRAPH_METRIC_ACKS][idx] = (float)acks_delta; - h->values[GRAPH_METRIC_INFLIGHT][idx] = (float)metrics->etcp.unacked_bytes; - h->values[GRAPH_METRIC_BYTES_SENT][idx] = (float)bytes_delta; - h->head = (h->head + 1) % GRAPH_HISTORY_SIZE; - if (h->count < GRAPH_HISTORY_SIZE) { - h->count++; - } + if (h->count < GRAPH_HISTORY_SIZE) h->count++; +} if (client->log_file) { fprintf(client->log_file, "history updated: count=%d head=%d\n", h->count, h->head); diff --git a/tools/etcpmon/etcpmon_client.h b/tools/etcpmon/etcpmon_client.h index a5c653fb..66fe3c5b 100644 --- a/tools/etcpmon/etcpmon_client.h +++ b/tools/etcpmon/etcpmon_client.h @@ -21,14 +21,14 @@ extern "C" { /* Graph metrics types */ typedef enum { - GRAPH_METRIC_RTT_LAST = 0, - GRAPH_METRIC_RTT_AVG10, - GRAPH_METRIC_RTT_AVG100, - GRAPH_METRIC_JITTER, - GRAPH_METRIC_RETRANS, - GRAPH_METRIC_ACKS, - GRAPH_METRIC_INFLIGHT, - GRAPH_METRIC_BYTES_SENT, + GRAPH_METRIC_INFLIGHT_BYTES = 0, + GRAPH_METRIC_INFLIGHT_LIM, + GRAPH_METRIC_MIN_RTT, + GRAPH_METRIC_BW_HI, + GRAPH_METRIC_PACING_RATE, + GRAPH_METRIC_BW_LO, + GRAPH_METRIC_INFLIGHT_HI, + GRAPH_METRIC_BBR_MODE, GRAPH_METRIC_COUNT } graph_metric_type_t; diff --git a/tools/etcpmon/etcpmon_graph.c b/tools/etcpmon/etcpmon_graph.c index 49cf12ae..c6d5a66e 100644 --- a/tools/etcpmon/etcpmon_graph.c +++ b/tools/etcpmon/etcpmon_graph.c @@ -15,12 +15,12 @@ #include #include "etcpmon_gui.h" const COLORREF graph_colors[GRAPH_METRICS_COUNT] = { - RGB(255,0,0), RGB(0,255,0), RGB(0,0,255), RGB(255,255,0), - RGB(255,0,255), RGB(0,255,255), RGB(255,128,0), RGB(128,0,255) + RGB(255,0,0), RGB(0,200,0), RGB(0,128,255), RGB(255,128,0), + RGB(160,0,200), RGB(0,180,180), RGB(255,0,128), RGB(200,200,0) }; const char* graph_metric_names[GRAPH_METRICS_COUNT] = { - "RTT Last","RTT Avg10","RTT Avg100","Jitter", - "Retrns","ACKs","Inflght","Bytes/s" + "InflB","Cwnd","minRTT","bwHi", + "Pace","bwLo","infHi","Mode" }; static void RecalculateMinMax(struct metrics_history* hist, int vis, int oldest) { @@ -165,17 +165,21 @@ void UpdateChannelValues(struct etcpmon_app* app, int history_idx) if (!app->hChannelValue[i]) continue; float val = h->values[i][history_idx]; char buf[32] = {0}; - if (i == GRAPH_METRIC_BYTES_SENT) { - if (val >= 1024*1024) snprintf(buf, sizeof(buf), "%.1f MB/s", val/(1024.*1024.)); - else if (val >= 1024) snprintf(buf, sizeof(buf), "%.1f KB/s", val/1024.); - else snprintf(buf, sizeof(buf), "%.0f B/s", val); - } else if (i == GRAPH_METRIC_INFLIGHT) { - if (val >= 1024) snprintf(buf, sizeof(buf), "%.1f KB", val/1024.); - else snprintf(buf, sizeof(buf), "%.0f B", val); - } else if (i == GRAPH_METRIC_RETRANS || i == GRAPH_METRIC_ACKS) { - snprintf(buf, sizeof(buf), "%.0f /s", val); - } else { + if (i == GRAPH_METRIC_BW_HI || i == GRAPH_METRIC_BW_LO || i == GRAPH_METRIC_PACING_RATE) { + if (val >= 1024) snprintf(buf, sizeof(buf), "%.1f MB/s", val/1024.); + else snprintf(buf, sizeof(buf), "%.1f KB/s", val); + } else if (i == GRAPH_METRIC_INFLIGHT_BYTES || i == GRAPH_METRIC_INFLIGHT_LIM || i == GRAPH_METRIC_INFLIGHT_HI) { + if (val >= 1024*1024) snprintf(buf, sizeof(buf), "%.1f MB", val/(1024.*1024.)); + else if (val >= 1024) snprintf(buf, sizeof(buf), "%.1f KB", val/1024.); + else snprintf(buf, sizeof(buf), "%.0f B", val); + } else if (i == GRAPH_METRIC_MIN_RTT) { snprintf(buf, sizeof(buf), "%.1f ms", val); + } else if (i == GRAPH_METRIC_BBR_MODE) { + static const char* mnames[] = {"STARTUP","DRAIN","PROBE_BW","PROBE_RTT"}; + int m = (int)val; if (m < 0) m = 0; if (m > 3) m = 3; + snprintf(buf, sizeof(buf), "%s", mnames[m]); + } else { + snprintf(buf, sizeof(buf), "%.1f", val); } if (strcmp(buf, last_text[i]) == 0) continue; diff --git a/tools/etcpmon/etcpmon_gui.c b/tools/etcpmon/etcpmon_gui.c index e85abd55..dcfc4ee3 100644 --- a/tools/etcpmon/etcpmon_gui.c +++ b/tools/etcpmon/etcpmon_gui.c @@ -239,8 +239,8 @@ static void CreateControls(struct etcpmon_app* app) { y += GRAPH_HEIGHT + 8; const char* channel_short_names[] = { - "RTT-L", "RTT10", "RTT100", "Jitter", - "Retrns", "ACKs", "Inflght", "Bytes/s" + "InflB", "Cwnd", "minRTT", "bwHi", + "Pace", "bwLo", "infHi", "Mode" }; int block_w = 200; @@ -1436,8 +1436,27 @@ void etcpmon_gui_update_metrics(struct etcpmon_app* app, links[i].inflight_lim_bytes, links[i].inflight_packets); + char line3[320]; + const char* mode_names[] = {"STARTUP","DRAIN","PROBE_BW","PROBE_RTT"}; + const char* cycle_names[] = {"UP","DOWN","CRUISE","REFILL"}; + snprintf(line3, sizeof(line3), + " BBR: mode=%s cycle=%s full_bw=%s loss=%s pace=%.1f KB/s rtt=%u us " + "bwHi=%.1f KB/s bwLo=%.1f KB/s infHi=%u lo=%u gain=%.2f", + mode_names[links[i].bbr_mode > 3 ? 0 : links[i].bbr_mode], + cycle_names[links[i].bbr_cycle_idx > 3 ? 0 : links[i].bbr_cycle_idx], + links[i].bbr_full_bw_reached ? "yes" : "no", + links[i].bbr_loss_in_round ? "yes" : "no", + (float)links[i].bbr_pacing_rate / 125.0f, + links[i].bbr_min_rtt_us, + (float)links[i].bbr_bw_hi / 125.0f, + (float)links[i].bbr_bw_lo / 125.0f, + links[i].bbr_inflight_hi, + links[i].bbr_inflight_lo, + (float)links[i].bbr_pacing_gain / 256.0f); + SendMessageA(app->hListLinks, LB_ADDSTRING, 0, (LPARAM)line1); SendMessageA(app->hListLinks, LB_ADDSTRING, 0, (LPARAM)line2); + SendMessageA(app->hListLinks, LB_ADDSTRING, 0, (LPARAM)line3); } InvalidateRect(app->hListLinks, NULL, FALSE); } diff --git a/tools/etcpmon/etcpmon_protocol.h b/tools/etcpmon/etcpmon_protocol.h index 08be86b8..dc2cbaef 100644 --- a/tools/etcpmon/etcpmon_protocol.h +++ b/tools/etcpmon/etcpmon_protocol.h @@ -237,6 +237,20 @@ struct etcpmon_link_metrics { uint32_t inflight_bytes; uint32_t inflight_packets; uint32_t inflight_lim_bytes; + + /* BBR congestion control metrics */ + uint8_t bbr_mode; /* 0=STARTUP, 1=DRAIN, 2=PROBE_BW, 3=PROBE_RTT */ + uint8_t bbr_cycle_idx; /* 0=UP, 1=DOWN, 2=CRUISE, 3=REFILL */ + uint8_t bbr_full_bw_reached; /* startup completed? 0/1 */ + uint8_t bbr_loss_in_round; /* loss in current round? 0/1 */ + uint32_t bbr_pacing_rate; /* bytes/sec */ + uint32_t bbr_min_rtt_us; /* min RTT (microseconds) */ + uint32_t bbr_bw_hi; /* max bandwidth estimate (bytes/sec, converted from BW_UNIT scale) */ + uint32_t bbr_bw_lo; /* conservative bandwidth estimate (bytes/sec) */ + uint32_t bbr_inflight_hi; /* upper inflight bound (bytes) */ + uint32_t bbr_inflight_lo; /* lower inflight bound (bytes) */ + uint16_t bbr_pacing_gain; /* pacing gain (BBR_UNIT fixed-point: 710=2.77x, 192=0.75x) */ + uint16_t pad_bbr; /* padding for 4-byte alignment */ }; /* TUN interface metrics */