Browse Source

fix: TCP data flow — tx_state, keepalive, last_recv_local_time

- etcp_link_enter_ready_tcp: set tx_state=DATA_WAIT (was 0, blocking sends)
- etcp_conn_input: update last_recv_local_time on every packet (keepalive liveness)
- link_rx_cb: handle ETCP_KEEPALIVE before etcp_conn_input (conflicts with MEAS_RESP=0x08)
- KEEPALIVE handler: set recv_keepalive, remote_keepalive, link_status, keepalive_recv_count
- test_stcp_traffic: etcp_send instead of raw stcp_link_send
- test_etcp_stcp: rewrite for utun_instance + etcp_connect
topo_upd
evgeny 2 months ago
parent
commit
adac854c94
  1. 2012
      src/transport_layer/etcp.c
  2. 340
      src/transport_layer/etcp.h
  3. 393
      src/transport_layer/etcp_connect.c
  4. 21
      src/transport_layer/etcp_connections.c
  5. 388
      src/transport_layer/etcp_connections.h
  6. 30
      src/transport_layer/stcp_link.c
  7. 68
      src/transport_layer/stcp_link.h
  8. 153
      tests/test_etcp_stcp.c
  9. 50
      tests/test_stcp_traffic.c

2012
src/transport_layer/etcp.c

File diff suppressed because it is too large Load Diff

340
src/transport_layer/etcp.h

@ -1,340 +0,0 @@
// etcp.h - ETCP Protocol Header (refactored based on etcp_protocol.txt)
#ifndef ETCP_H
#define ETCP_H
#include "etcp_connections.h"
#include "secure_channel.h"
#include "../lib/ll_queue.h"
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#include "pkt_normalizer.h"
struct stcp_link; // forward declaration
// In struct ETCP_CONN, add:
//struct pn_pair* normalizer;
// Forward declarations
struct UTUN_INSTANCE;
struct ETCP_CONN;
struct etcp_cbk_entry; // defined in etcp_api.h
struct UASYNC;
uint16_t get_current_timestamp(void);
// ETCP packet section types (from protocol spec)
#define ETCP_SECTION_PAYLOAD 0x00 // Data payload
#define ETCP_SECTION_ACK 0x01 // ACK section
#define ETCP_SECTION_TIMESTAMP 0x06 // Channel timestamp (example, adjust if needed)
#define ETCP_SECTION_MEAS_TS 0x07 // Measurement timestamp for bandwidth (burst packet)
#define ETCP_SECTION_MEAS_RESP 0x08 // Measurement response (burst result)
#define ETCP_SECTION_FILLER 0x09 // Filler/dummy data (discarded by receiver)
#define ETCP_SECTION_METRICS 0x0A // Metrics CSV exchange (ed25519 signed)
// Burst measurement constants
#define BURST_PACKET_COUNT 12 // число пакетов в burst
#define BURST_SKIP_COUNT 3 // сколько первых пакетов пропустить при замере gap
#define MIN_BURST_INTERVAL_TB 5000 // минимальный интервал между burst (500ms в 0.1ms)
#define BURST_RESP_TIMEOUT_TB 20000 // таймаут ожидания ответа на burst (2s в 0.1ms)
// Inflight phases
#define INFLIGHT_PHASE_SLOW_START 0
#define INFLIGHT_PHASE_CONG_AVOIDANCE 1
// Section sizes
#define MEAS_TS_SECTION_SIZE 9 // type(1) + burst_id(2) + flags(1) + seq(1) + ts_us(2) + pkt_sz(2)
#define MEAS_RESP_SECTION_SIZE 13 // type(1) + burst_id(2) + valid(1) + gap_avg(4) + gap_min(4) + pkt_count(1)
#define FILLER_HDR_SIZE 3 // type(1) + len(2)
// MEAS_TS flags
#define MEAS_FLAG_IS_FILLER 0x01 // пакет содержит FILLER вместо PAYLOAD
#define MEAS_FLAG_IS_LAST 0x02 // последний пакет в burst
#define MEAS_FLAG_IS_FIRST 0x04 // первый пакет в burst
// MEAS_RESP valid
#define MEAS_RESP_VALID 1
// Метрики: гистограммы RTT и потерь с 10-мин снимками
#define ETCP_METRICS_RTT_BUCKETS 12 // 0-1,1-2,2-3,3-5,5-8,8-13,13-21,21-34,34-55,55-90,90-150,150+ ms
#define ETCP_METRICS_LOSS_BUCKETS 11 // 0%,1%,...,10%+
#define ETCP_METRICS_INTERVAL_TB 6000000 // 10 мин в 0.1ms
#define ETCP_METRICS_MIN_PACKETS 200 // мин пакетов для обновления гистограмм
struct etcp_metrics {
uint32_t rtt_hist[ETCP_METRICS_RTT_BUCKETS]; // рабочая RTT-гистограмма
uint32_t loss_hist[ETCP_METRICS_LOSS_BUCKETS]; // рабочая loss-гистограмма
uint32_t work_samples; // RTT замеров в окне
uint32_t work_sent; // пакетов отправлено в окне
uint32_t work_rcvd; // пакетов принято в окне
uint32_t work_bytes_sent; // байт отправлено в окне
uint32_t work_bytes_rcvd; // байт принято в окне
uint32_t work_lost; // потеряно в окне
uint32_t rtt_hist_final[ETCP_METRICS_RTT_BUCKETS]; // финальная RTT-гистограмма
uint32_t loss_hist_final[ETCP_METRICS_LOSS_BUCKETS]; // финальная loss-гистограмма
uint32_t samples_final;
uint32_t sent_final;
uint32_t rcvd_final;
uint32_t bytes_sent_final;
uint32_t bytes_rcvd_final;
uint32_t lost_final;
uint64_t total_sent;
uint64_t total_rcvd;
uint64_t total_bytes_sent;
uint64_t total_bytes_rcvd;
uint64_t total_lost;
void* timer;
uint64_t last_snapshot_tb;
};
#define INFLIGHT_STATE_WAIT_ACK 0
#define INFLIGHT_STATE_WAIT_SEND 1
#define INFLIGHT_INITIAL_HASH_SIZE 1024
#define MAX_INFLIGHT_SIZE 16384 // максимальное число элементов в inflight приёмной очереди (для предотвращения атак)
#define ASM_BUF_MAX_SIZE (64 * 1024) // максимальный размер буфера сборки фрагментов
// в этот список пакет добавляется когда перемещается из input_queue в input_send_q, при этом к пакету добавляется struct INFLIGHT_PACKET из inflight_pool.
// пакет полностью удаляется когда приходит ACK (либо conn_reset/close)
struct INFLIGHT_PACKET {// выделяется из etcp->inflight_pool
struct ll_entry ll;
uint32_t seq; // packet seq (ID по документации)
struct ETCP_LINK* last_link; // Last sent link
uint64_t last_timestamp; // Last send timestamp
uint8_t send_count; // Number of sends
uint8_t retrans_req_count; // Number of retrans requests
uint8_t state; // WAIT_ACK or WAIT_SEND
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 (используем быстрый поиск с хешем)
struct ETCP_FRAGMENT {// выделяется из пула etcp->rx_pool
struct ll_entry ll;
uint32_t seq;
uint16_t timestamp;
};
struct ACK_PACKET {
struct ll_entry ll;
uint32_t seq;// sequence number
uint16_t pkt_timestamp;// timestamp пакета (часы уладенной стороны)
uint32_t recv_timestamp;// время приема (локальное)
};
#define ETCP_TX_STATE_DATA_WAIT 1
#define ETCP_TX_STATE_LINK_WAIT 2
// ETCP connection structure (refactored)
struct ETCP_CONN {
// State: 0=not ready, 1=ready (indexed in instance->connections), 2=deleted
int state; // 0=pending, 1=ready, 2=deleted (phase 1 of close done)
int ref_count; // External reference count. >0 blocks deferred resource free.
// Take/free via etcp_conn_ref_take()/etcp_conn_ref_free().
int mtu;
struct UTUN_INSTANCE* instance;
// Queue entries in instance->connections or instance->pending_connections
struct ll_entry* conn_queue_entry; // entry в очереди instance
struct ll_queue* conn_queue; // указатель на очередь где лежим (pending или connections)
// Links (channels) - linked list
struct ETCP_LINK* links;
struct ETCP_LINK* last_rr_link; // последний линк, выбранный round-robin
uint8_t tcp_link_count; // количество UP TCP линков
// Crypto and state
struct secure_channel crypto_ctx;
struct PKTNORM* normalizer;
// Peer info
uint64_t peer_node_id; // Peer node ID
uint8_t peer_ed25519_pubkey[SC_PUBKEY_SIZE]; // Ed25519 pubkey пира (из INIT)
// ============ Processing incoming data to be sent by ETCP
struct ll_queue* input_queue; // Incoming packets to send (rx_pool -> ETCP_FRAGMENT)
// Inflight очереди (2 шт) - пока пакет в статусе inflight - к нему прикрепляется struct INFLIGHT_PACKET
struct memory_pool* inflight_pool; // память для inflight очередей
struct memory_pool* io_pool; // память для rx очередей
struct ll_queue* input_send_q; // очередь на отправку (inflight_pool -> INFLIGHT_PACKET)
struct ll_queue* input_wait_ack; // очередь ожидающих подтверждение (inflight_pool -> struct INFLIGHT_PACKET)
struct ll_queue* ack_q; // неотправленные подтверждения приема пакетов (instance.ack_pool -> struct ACK_PACKET) + index [+0 SEQ]
struct ll_queue* recv_q; // очередь на сборку фрагментированных пакетов(rx_pool -> struct ETCP_FRAGMENT) + index [+0 SEQ]
void (*link_ready_for_send_fn)(struct ETCP_CONN*);// функцию которую должен вызвать драйвер линка при готовности линка принимать данные
struct ll_queue* output_queue; // Assembled outgoing packets (storage: ETCP_FRAGMENT / rx_pool)
struct ll_queue* transit_queues; // hash по (src_node_id:8, dst_node_id:8) — транзитные очереди
struct ll_queue* send_input_q; // единая входная очередь отправки (normalizer->input или tx_queue)
// IDs and state
uint32_t next_tx_id; // ID для добавления в очередь отправки (с этим id будет добавлен следующий пакет)
uint32_t last_rx_id; // Last received ID
uint32_t last_delivered_id; // Last delivered to output_queue
uint32_t rx_ack_till;// из ack пакета - по какой пакет получено и собрано на удаленной стороне
// Metrics (RTT, jitter, etc.)
// uint16_t retrans_delay; // Not used
uint16_t rtt_last;// round-trip time
uint16_t tt_last;// transmission time (время пакета до меня)
// uint16_t rxt_last;// recv relative time (бустро регаирует на изменение задержки rx и медленно на изменение задержки tx)
uint16_t rtt_avg_10;
uint16_t jitter;
uint64_t last_rtt_cb_time; // время последнего вызова ping_update_rtt (0.1ms)
uint32_t bytes_sent_total;
// uint32_t bytes_received_total; // Not used
uint32_t retransmissions_count;
uint32_t reinit_count;
uint64_t setup_start_tb; // время начала/рестарта подключения (0.1ms)
uint32_t reset_count;
// uint32_t bytes_sent_norx; // сколько отправили байт без единого ответного пакета (для детекции запроса реконнекта)
// Window and inflight management
uint32_t unacked_bytes; // Current inflight bytes
uint32_t max_inflight; // Max inflight cap from config (bbr_max_cwnd)
uint32_t optimal_inflight; // Sum over links
// Timers
void* retrans_timer; // Retrans check timer
void* ack_resp_timer; // ACK send timer
// Bandwidth measurement state
// uint8_t burst_in_progress; // Burst transmission flag - Not used
// uint16_t burst_start_id; // Start ID for burst - Not used
// Statistics counters
uint32_t ack_packets_count; // Count of ACK packets received
// uint16_t last_rx_ack_id; // Last ACK ID received - Not used
uint16_t rtt_history[10]; // RTT history for jitter calculation (RTT_HISTORY_SIZE=10)
uint8_t rtt_history_idx; // Current index in RTT history
// uint32_t total_packets_sent; // Total packets sent counter - Not used
// Flags
uint8_t routing_exchange_active; // 0-не активен, 1-надо инициировать (клиент), 2-обмен идёт, 3-завершён, 4-пропущен (нет BGP)
uint8_t got_initial_pkt; //
uint8_t initialized; // 0 - только созданный ETCP, 1 - хотя бы один линк проинициалзирован (обмен ключами произведен)
uint32_t session_id; // случайный ID сессии (генерируется клиентом) для защиты от ложного reinit
uint8_t tx_state; // 0 - n/a, 1 - data_wait (queues empty), 2 - link_wait (link busy)
uint8_t links_up; // 0 - канал не готов для передачи, 1 - канал готов для передачи (хотя бы один линк не down)
uint8_t reset_done; // 0 - рукопожатие не завершено (реинит разрешён), 1 - соединение стабильно (реинит заблокирован)
uint8_t callbacks_running; // 1 - внутри итерации колбэк-цепочек, etcp_connection_close запрещён
// Unified callback chain with event mask (init/reinit/up/down/node_changed)
struct etcp_cbk_entry* cbks;
uint8_t reinit_pending; // 1 = reinit в процессе, ждём завершения
void (*bgp_ready_cbk)(struct ETCP_CONN* conn); // вызывается когда BGP готов (завершён или пропущен)
uint8_t fin_wait : 1; // close-pending: ожидаем подтверждение закрытия от remote
void (*fin_wait_clear_cb)(struct ETCP_CONN* conn, void* arg); // вызывается при сбросе fin_wait
void* fin_wait_clear_arg;
uint32_t cnt_ack_hit_inf; // счетчик удлений из inflight
uint32_t cnt_ack_hit_sndq; // счетчик удалений inflight пакетов из sndq
uint32_t cnt_ack_miss; // счетчик не найденных ack
uint32_t cnt_link_wait; // счетчик переходов в ожидание когда link busy
uint32_t rx_dup_count; // счетчик принятых дубликатов (локальные)
uint32_t tx_dup_count; // счетчик дубликатов удаленной стороны (из ACK)
uint32_t debug[8]; // 8 значений для дебага (live watch)
struct etcp_metrics metrics; // гистограммы RTT/потерь, тотальные счётчики
// Logging identifier (format: "XXXX→XXXX [name]" - last 4 digits of local and peer node_id + optional name)
char log_name[256];
char* name; // Connection name from config (e.g., "client_test1"), or NULL/empty
};
#define RTT_CB_PERIOD_TB 600000 // 1 минута в 0.1ms
// Functions
struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* name);
void etcp_connection_close(struct ETCP_CONN* etcp);
void etcp_conn_queue_set_ready(struct ETCP_CONN* conn); // move pending->connections, fire ready cbks
/**
* @brief Take a reference on the connection (blocks deferred resource free).
* @param conn connection
* @return 0 on success, -1 if conn is NULL or already in state 2 (deleted).
*
* Increments ref_count. While ref_count > 0, etcp_connection_close()
* will detach but defer resource cleanup until all references are released.
* Always pair with etcp_conn_ref_free().
*/
int etcp_conn_ref_take(struct ETCP_CONN* conn);
/**
* @brief Release a reference. Triggers deferred resource free if last ref and state==2.
* @param conn connection
*
* Decrements ref_count. If ref_count reaches 0 and the connection has been closed
* (state == 2), schedules deferred resource cleanup via uasync_call_soon.
* Safe to call on NULL.
*/
void etcp_conn_ref_free(struct ETCP_CONN* conn);
void etcp_conn_reset(struct ETCP_CONN* etcp);
void etcp_links_reset(struct ETCP_CONN* etcp);
void etcp_conn_reinit(struct ETCP_CONN* etcp, const char* reason);
void etcp_connection_ready(struct ETCP_CONN* etcp);// вызывается когда подключение инициализировано
// Отправка: используем api ll_queue для очереди ETCP_CONN.input_queue
// Прием: используем api ll_queue для очереди ETCP_CONN.output_queue
// для очередей используется формат
// Input from etcp_connections (decrypted packet)
void etcp_conn_input(struct ETCP_DGRAM* pkt);
// Send data through ETCP connection
// Allocates memory from data_pool and places in input queue
// Returns: 0 on success, -1 on failure
int etcp_int_send(struct ETCP_CONN* etcp, const void* data, uint16_t len);
// Request next packet for load balancer
struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp);
// Process ACK receipt - remove acknowledged packet from inflight queues
void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t dts);
// Recalculate optimal_inflight and resume input queue after link inflight_lim change
void etcp_conn_on_inflight_lim_changed(struct ETCP_CONN* etcp);
// Fire connection status callback on instance's conn_status_cbks chain
void etcp_fire_conn_status(struct ETCP_CONN* conn, int status);
// Update log_name when peer_node_id becomes known
void etcp_update_log_name(struct ETCP_CONN* etcp);
// Вызывается стеком etcp когда соединение установлено (можно передавать данные)
void etcp_conn_ready(struct ETCP_CONN* conn);
void etcp_on_link_down(struct ETCP_CONN* etcp, struct ETCP_LINK* down_link);
void etcp_update_mtu(struct ETCP_CONN* etcp);
void etcp_metrics_init(struct etcp_metrics* m);
void etcp_metrics_add_rtt(struct ETCP_CONN* etcp, uint16_t rtt_tb);
void etcp_metrics_add_sent(struct ETCP_CONN* etcp, uint32_t len);
void etcp_metrics_add_rcvd(struct ETCP_CONN* etcp, uint32_t len);
void etcp_metrics_add_loss(struct ETCP_CONN* etcp, uint32_t count);
void etcp_metrics_start_timer(struct ETCP_CONN* etcp);
void etcp_metrics_stop_timer(struct ETCP_CONN* etcp);
#ifdef __cplusplus
}
#endif
#endif // ETCP_H

393
src/transport_layer/etcp_connect.c

@ -1,393 +0,0 @@
#include "etcp_api.h"
#include "etcp.h"
#include "etcp_connect.h"
#include "utun_instance.h"
#include "topo_node.h"
#include "stcp_link.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/u_async.h"
#include "../lib/socket_compat.h"
#include <string.h>
#define DEBUG_CATEGORY_ETCP_CONNECT DEBUG_CATEGORY_ETCP
struct etcp_connect_cb_node {
etcp_connect_callback_t cb;
void* arg;
uint8_t flags;
struct etcp_connect_cb_node* next;
};
struct ETCP_CONNECT {
struct ETCP_CONNECT* next;
struct UTUN_INSTANCE* instance;
uint64_t node_id;
struct ETCP_CONN* conn;
struct etcp_connect_cb_node* cb_list;
void* initial_timer;
void* settle_timer;
uint16_t min_rtt;
uint8_t early_delivered : 1;
uint8_t done : 1;
};
static void connect_init_cb(struct ETCP_CONN* conn, int event, void* arg);
static struct ETCP_CONNECT* connect_find(struct UTUN_INSTANCE* inst, uint64_t node_id) {
struct ETCP_CONNECT* ctx = inst->pending_connects;
while (ctx) { if (ctx->node_id == node_id) return ctx; ctx = ctx->next; }
return NULL;
}
static void connect_deliver(struct ETCP_CONNECT* ctx, int type, int free_nodes) {
struct etcp_connect_cb_node** pp = &ctx->cb_list;
while (*pp) {
struct etcp_connect_cb_node* node = *pp;
if (type == 0 || (node->flags & (uint8_t)type)) {
node->cb(node->arg, (type == 0) ? NULL : ctx->conn, type);
if (free_nodes) { *pp = node->next; u_free(node); } else { pp = &node->next; }
} else {
pp = &node->next;
}
}
}
static void connect_cancel(struct ETCP_CONNECT* ctx) {
if (!ctx) return;
struct UTUN_INSTANCE* inst = ctx->instance;
if (inst) {
struct ETCP_CONNECT** pp = &inst->pending_connects;
while (*pp && *pp != ctx) pp = &(*pp)->next;
if (*pp) *pp = ctx->next;
if (ctx->initial_timer) { uasync_cancel_timeout(inst->ua, ctx->initial_timer); ctx->initial_timer = NULL; }
if (ctx->settle_timer) { uasync_cancel_timeout(inst->ua, ctx->settle_timer); ctx->settle_timer = NULL; }
}
u_free(ctx);
}
static void connect_create_links_v4(struct ETCP_CONNECT* ctx, struct TOPO_GROUP_NODE* node) {
if (!node) return;
struct TOPO_NODE* ni = topo_node_registry_find(ctx->instance->topo_groups, node->node_id);
if (!ni) { DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "[etcp_connect] links_v4: ni NOT in registry for node=%016llx", (unsigned long long)node->node_id); return; }
int addr_count = 0, sock_count = 0, link_count = 0;
{ struct ETCP_SOCKET* s = ctx->instance->etcp_sockets; while (s) { sock_count++; s = s->next; } }
for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) {
addr_count++;
if (a->port == 0) continue;
{ int zero = 1; for (int j = 0; j < 4; j++) if (a->addr[j] != 0) { zero = 0; break; } if (zero) continue; }
struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET;
memcpy(&sin.sin_addr.s_addr, a->addr, 4); sin.sin_port = htons(a->port);
struct sockaddr_storage sa; memcpy(&sa, &sin, sizeof(sin));
if (!(a->protocol & TOPO_PROTO_TCP)) {
struct ETCP_SOCKET* s = ctx->instance->etcp_sockets;
while (s) { if (s->local_addr.ss_family == AF_INET && etcp_link_new(ctx->conn, s, &sa, 0)) link_count++; s = s->next; }
}
if ((a->protocol & TOPO_PROTO_TCP)) {
struct ETCP_LINK *tlink = etcp_link_new(ctx->conn, NULL, NULL, 0);
if (tlink) { tlink->is_tcp = 1; etcp_tcp_link_start_connect(tlink, &sa, a->port); link_count++; }
}
}
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "[etcp_connect] links_v4: addrs=%d socks=%d links=%d for node=%016llx",
addr_count, sock_count, link_count, (unsigned long long)node->node_id);
}
static void connect_create_links_v6(struct ETCP_CONNECT* ctx, struct TOPO_GROUP_NODE* node) {
if (!node) return;
struct TOPO_NODE* ni = topo_node_registry_find(ctx->instance->topo_groups, node->node_id);
if (!ni) return;
for (const struct TOPO_ADDR6* a = ni->v6_addrs; a; a = a->next) {
if (a->port == 0) continue;
int zero = 1; for (int j = 0; j < 16; j++) if (a->addr[j] != 0) { zero = 0; break; } if (zero) continue;
struct sockaddr_in6 sin6; memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6;
memcpy(&sin6.sin6_addr, a->addr, 16); sin6.sin6_port = htons(a->port);
int is_ll = (a->addr[0] == 0xfe && (a->addr[1] & 0xc0) == 0x80);
if (!(a->protocol & TOPO_PROTO_TCP)) {
struct ETCP_SOCKET* s = ctx->instance->etcp_sockets;
while (s) { if (s->local_addr.ss_family == AF_INET6) {
if (is_ll) sin6.sin6_scope_id = s->netif_index;
struct sockaddr_storage sa; memcpy(&sa, &sin6, sizeof(sin6));
etcp_link_new(ctx->conn, s, &sa, 0);
} s = s->next; }
}
if (a->protocol & TOPO_PROTO_TCP) {
if (is_ll) { struct ETCP_SOCKET* sv = ctx->instance->etcp_sockets;
while (sv) { if (sv->local_addr.ss_family == AF_INET6 && sv->netif_index)
{ sin6.sin6_scope_id = sv->netif_index; break; } sv = sv->next; }
}
struct sockaddr_storage sa; memcpy(&sa, &sin6, sizeof(sin6));
struct ETCP_LINK *tlink = etcp_link_new(ctx->conn, NULL, NULL, 0);
if (tlink) { tlink->is_tcp = 1; etcp_tcp_link_start_connect(tlink, &sa, a->port); }
}
}
}
static void connect_bgp_ready_cb(struct ETCP_CONN* conn) {
struct ETCP_CONNECT* ctx = connect_find(conn->instance, conn->peer_node_id);
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "[etcp_connect] bgp_ready_cb: node=%016llx ctx=%p ctx_done=%d ra=%d",
(unsigned long long)conn->peer_node_id, (void*)ctx, ctx ? ctx->done : -1, conn->routing_exchange_active);
if (!ctx || ctx->done) return;
connect_deliver(ctx, ETCP_CONNECT_BGP_READY, 1);
}
static void connect_initial_timeout_cb(void* arg) {
struct ETCP_CONNECT* ctx = (struct ETCP_CONNECT*)arg;
if (!ctx || ctx->done) return;
ctx->done = 1;
ctx->initial_timer = NULL;
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] timeout for node 0x%016llx", (unsigned long long)ctx->node_id);
struct ETCP_CONN* conn = ctx->conn; ctx->conn = NULL;
connect_deliver(ctx, 0, 1);
if (conn) etcp_connection_close(conn);
connect_cancel(ctx);
}
static void connect_settle_timeout_cb(void* arg) {
struct ETCP_CONNECT* ctx = (struct ETCP_CONNECT*)arg;
if (!ctx || ctx->done) return;
ctx->done = 1;
ctx->settle_timer = NULL;
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] settle for node 0x%016llx, min_rtt=%u",
(unsigned long long)ctx->node_id, ctx->min_rtt);
struct ETCP_LINK* link = ctx->conn->links;
while (link) {
struct ETCP_LINK* next = link->next;
if (!link->initialized) {
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] removing failed link=%p for node 0x%016llx",
(void*)link, (unsigned long long)ctx->node_id);
etcp_link_close(link);
}
link = next;
}
etcp_conn_remove_cbk(ctx->conn, connect_init_cb, ctx);
connect_deliver(ctx, ETCP_CONNECT_LATE, 1);
connect_cancel(ctx);
}
static void connect_init_cb(struct ETCP_CONN* conn, int event, void* arg) { (void)event;
struct ETCP_CONNECT* ctx = (struct ETCP_CONNECT*)arg;
if (!ctx || ctx->done) return;
int total_links = 0, up_links = 0;
struct ETCP_LINK* link = conn->links;
while (link) {
total_links++;
if (link->initialized && link->rtt_last && link->rtt_last < ctx->min_rtt)
ctx->min_rtt = link->rtt_last;
if (link->link_state == 3) up_links++;
link = link->next;
}
if (total_links == 1 || up_links == total_links || (total_links == 0 && conn->tcp_link_count > 0)) {
if (!ctx->early_delivered) {
ctx->early_delivered = 1;
if (ctx->initial_timer) { uasync_cancel_timeout(ctx->instance->ua, ctx->initial_timer); ctx->initial_timer = NULL; }
}
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] immediate LATE for node 0x%016llx (links=%d up=%d)",
(unsigned long long)ctx->node_id, total_links, up_links);
{ struct etcp_connect_cb_node* n = ctx->cb_list; while (n) { struct etcp_connect_cb_node* next = n->next; if (n->flags & ETCP_CONNECT_EARLY) n->cb(n->arg, ctx->conn, ETCP_CONNECT_EARLY); if (n->flags & ETCP_CONNECT_LATE) n->cb(n->arg, ctx->conn, ETCP_CONNECT_LATE); u_free(n); n = next; } ctx->cb_list = NULL; }
connect_cancel(ctx);
return;
}
if (ctx->early_delivered) return;
ctx->early_delivered = 1;
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] early ready for node 0x%016llx, min_rtt=%u (links=%d up=%d)",
(unsigned long long)ctx->node_id, ctx->min_rtt, total_links, up_links);
connect_deliver(ctx, ETCP_CONNECT_EARLY, 0);
if (ctx->initial_timer) { uasync_cancel_timeout(ctx->instance->ua, ctx->initial_timer); ctx->initial_timer = NULL; }
uint32_t settle_tb;
if (ctx->min_rtt == 0xFFFF) settle_tb = 20000;
else {
settle_tb = (uint32_t)ctx->min_rtt * 8;
if (settle_tb < 5000) settle_tb = 5000;
if (settle_tb > 20000) settle_tb = 20000;
}
ctx->settle_timer = uasync_set_timeout(ctx->instance->ua, (int)settle_tb, ctx,
connect_settle_timeout_cb, "etcp_connect_settle");
}
int etcp_connect(struct UTUN_INSTANCE* inst, struct TOPO_GROUP_NODE* node,
etcp_connect_callback_t cb, void* arg, uint8_t flags) {
if (!inst || !node || !cb) return -1;
struct TOPO_NODE* ni = topo_node_registry_find(inst->topo_groups, node->node_id);
if (!ni) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] node not in registry"); return -1; }
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "[etcp_connect] registry OK node=%016llx pub=%016llx v4addrs=%p v4socks=%p",
(unsigned long long)node->node_id, *(uint64_t*)ni->public_key, (void*)ni->v4_addrs, (void*)ni->v4_sock_meta);
uint64_t node_id = ni->node_id;
if (node_id == 0) {
if (!ni->public_key) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] node_id=0 and public_key is NULL");
return -1;
}
int zero = 1;
for (int i = 0; i < SC_PUBKEY_SIZE; i++)
if (ni->public_key[i]) { zero = 0; break; }
if (zero) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] node_id=0 and public_key is all zeros");
return -1;
}
node_id = sc_derive_node_id_from_pubkey(ni->public_key);
ni->node_id = node_id;
}
struct ETCP_CONN* conn = instance_find_conn(inst, node_id);
struct ETCP_CONNECT* ctx = connect_find(inst, node_id);
// Case 1: conn && ctx — already connecting
if (conn && ctx) {
if (ctx->done) {
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] node 0x%016llx: existing ctx timed out, cb(NULL,0)",
(unsigned long long)node_id);
cb(arg, NULL, 0);
return 0;
}
struct etcp_connect_cb_node* cn = u_calloc(1, sizeof(struct etcp_connect_cb_node));
if (!cn) return -1;
cn->cb = cb; cn->arg = arg; cn->flags = flags; cn->next = ctx->cb_list; ctx->cb_list = cn;
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] node 0x%016llx already connecting, added cb",
(unsigned long long)node_id);
return 0;
}
// Case 2: !conn && ctx — context exists but conn was removed (rare)
if (!conn && ctx) {
struct etcp_connect_cb_node* cn = u_calloc(1, sizeof(struct etcp_connect_cb_node));
if (!cn) return -1;
cn->cb = cb; cn->arg = arg; cn->flags = flags; cn->next = ctx->cb_list; ctx->cb_list = cn;
return 0;
}
// Case 3: conn && !ctx — existing connection, no context
if (conn && !ctx) {
if (conn->state == 0) {
// Pending conn from another path (e.g. incoming INIT before etcp_connect)
ctx = u_calloc(1, sizeof(struct ETCP_CONNECT));
if (!ctx) return -1;
ctx->instance = inst; ctx->node_id = node_id; ctx->conn = conn;
ctx->min_rtt = 0xFFFF;
struct etcp_connect_cb_node* cn = u_calloc(1, sizeof(struct etcp_connect_cb_node));
if (!cn) { u_free(ctx); return -1; }
cn->cb = cb; cn->arg = arg; cn->flags = flags; ctx->cb_list = cn;
etcp_conn_add_cbk(conn, connect_init_cb, ctx, ETCP_CBK_EVENT_INIT);
conn->bgp_ready_cbk = connect_bgp_ready_cb;
ctx->initial_timer = uasync_set_timeout(inst->ua, (int)inst->etcp_connect_timeout_tb, ctx,
connect_initial_timeout_cb, "etcp_connect_init");
ctx->next = inst->pending_connects; inst->pending_connects = ctx;
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] node 0x%016llx: existing pending conn, waiting for init",
(unsigned long long)node_id);
return 0;
}
// conn->state == 1: already ready — deliver EARLY+LATE immediately (via direct loop to deliver both phases)
ctx = u_calloc(1, sizeof(struct ETCP_CONNECT));
if (!ctx) return -1;
ctx->instance = inst; ctx->node_id = node_id; ctx->conn = conn;
ctx->early_delivered = 1;
{
struct etcp_connect_cb_node* cn = u_calloc(1, sizeof(struct etcp_connect_cb_node));
if (!cn) { u_free(ctx); return -1; }
cn->cb = cb; cn->arg = arg; cn->flags = flags;
ctx->cb_list = cn;
}
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] node 0x%016llx: existing ready conn, delivering EARLY+LATE",
(unsigned long long)node_id);
struct etcp_connect_cb_node* n = ctx->cb_list;
struct etcp_connect_cb_node* keep = NULL;
while (n) {
struct etcp_connect_cb_node* next = n->next;
if (n->flags & ETCP_CONNECT_EARLY) n->cb(n->arg, ctx->conn, ETCP_CONNECT_EARLY);
if (n->flags & ETCP_CONNECT_LATE) n->cb(n->arg, ctx->conn, ETCP_CONNECT_LATE);
if (n->flags & ETCP_CONNECT_BGP_READY) {
if (conn->routing_exchange_active >= 3) n->cb(n->arg, ctx->conn, ETCP_CONNECT_BGP_READY);
else { n->next = keep; keep = n; n = next; continue; }
}
u_free(n);
n = next;
}
ctx->cb_list = keep;
if (keep) {
conn->bgp_ready_cbk = connect_bgp_ready_cb;
ctx->next = inst->pending_connects; inst->pending_connects = ctx;
} else {
u_free(ctx);
}
return 0;
}
// Case 4: !conn && !ctx — new connection
conn = etcp_connection_create(inst, NULL);
if (!conn) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] failed to create ETCP_CONN for node 0x%016llx",
(unsigned long long)node_id);
cb(arg, NULL, 0);
return -1;
}
conn->peer_node_id = node_id;
etcp_update_log_name(conn);
if (conn->conn_queue && conn->conn_queue_entry) {
queue_remove_data(conn->conn_queue, conn->conn_queue_entry);
queue_entry_free(conn->conn_queue_entry);
}
{ struct ll_entry* qe = queue_entry_new(sizeof(struct conn_queue_entry)); if (qe) { struct conn_queue_entry* ce = (struct conn_queue_entry*)qe->data; ce->peer_node_id = node_id; ce->conn = conn; conn->conn_queue_entry = qe; conn->conn_queue = inst->connections; queue_data_put_with_index(inst->connections, qe); } }
if (sc_init_ctx(&conn->crypto_ctx, &inst->my_keys) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] sc_init_ctx failed for node 0x%016llx", (unsigned long long)node_id);
etcp_connection_close(conn); cb(arg, NULL, 0); return -1;
}
if (sc_set_peer_public_key(&conn->crypto_ctx, ni->public_key, 0) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] sc_set_peer_public_key failed for node 0x%016llx", (unsigned long long)node_id);
etcp_connection_close(conn); cb(arg, NULL, 0); return -1;
}
ctx = u_calloc(1, sizeof(struct ETCP_CONNECT));
if (!ctx) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] alloc failed");
etcp_connection_close(conn); cb(arg, NULL, 0); return -1;
}
ctx->instance = inst; ctx->node_id = node_id; ctx->conn = conn;
ctx->min_rtt = 0xFFFF;
{
struct etcp_connect_cb_node* cn = u_calloc(1, sizeof(struct etcp_connect_cb_node));
if (!cn) { u_free(ctx); etcp_connection_close(conn); cb(arg, NULL, 0); return -1; }
cn->cb = cb; cn->arg = arg; cn->flags = flags; ctx->cb_list = cn;
}
etcp_conn_add_cbk(conn, connect_init_cb, ctx, ETCP_CBK_EVENT_INIT);
conn->bgp_ready_cbk = connect_bgp_ready_cb;
connect_create_links_v4(ctx, node);
connect_create_links_v6(ctx, node);
if (!conn->links && conn->tcp_link_count == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] no links created for node 0x%016llx (links=%p tcp_links=%d)",
(unsigned long long)node_id, (void*)conn->links, conn->tcp_link_count);
connect_deliver(ctx, 0, 1);
etcp_connection_close(conn); u_free(ctx); return -1;
}
ctx->initial_timer = uasync_set_timeout(inst->ua, (int)inst->etcp_connect_timeout_tb, ctx, connect_initial_timeout_cb, "etcp_connect_init");
ctx->next = inst->pending_connects; inst->pending_connects = ctx;
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] started for node 0x%016llx", (unsigned long long)node_id);
return 0;
}
void etcp_connect_cancel_for_conn(struct UTUN_INSTANCE* inst, struct ETCP_CONN* conn) {
if (!inst || !conn) return;
struct ETCP_CONNECT** pp = &inst->pending_connects;
while (*pp) {
struct ETCP_CONNECT* ctx = *pp;
if (ctx->conn == conn) {
*pp = ctx->next;
ctx->done = 1;
ctx->conn = NULL;
if (ctx->initial_timer) { uasync_cancel_timeout(inst->ua, ctx->initial_timer); ctx->initial_timer = NULL; }
if (ctx->settle_timer) { uasync_cancel_timeout(inst->ua, ctx->settle_timer); ctx->settle_timer = NULL; }
u_free(ctx);
} else {
pp = &(*pp)->next;
}
}
}

21
src/transport_layer/etcp_connections.c

@ -52,6 +52,8 @@ static void tcp_server_on_link(struct stcp_link *link, void *arg) {
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "tcp_server_on_link: accepted TCP from peer=0x%016llx", (unsigned long long)node_id);
struct ETCP_CONN *conn = instance_find_conn(inst, node_id);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "tcp_server_on_link: %s conn=%p peer=0x%016llx pubkey=%016llx",
conn ? "existing" : "NEW", (void*)conn, (unsigned long long)node_id, *(const uint64_t*)pubkey);
if (!conn) {
conn = etcp_connection_create(inst, NULL);
if (!conn) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "tcp_server_on_link: etcp_connection_create failed"); return; }
@ -1065,11 +1067,15 @@ void etcp_link_enter_ready_tcp(struct ETCP_LINK *link) {
link->recv_keepalive = 1;
if (!link->mtu_remote) link->mtu_remote = link->mtu;
etcp->initialized = 1; etcp->links_up = 1; etcp->tcp_link_count++;
if (etcp->tx_state == 0) etcp->tx_state = ETCP_TX_STATE_DATA_WAIT;
etcp_link_send_keepalive(link);
start_keepalive_timer(link);
loadbalancer_link_ready(link);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] TCP link %d UP (mtu=%d tcp_links=%d)", etcp->log_name, link->local_link_id, link->mtu, etcp->tcp_link_count);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] TCP link %d UP (mtu=%d init=%d up=%d tcp_links=%d)",
etcp->log_name, link->local_link_id, link->mtu,
etcp->initialized, etcp->links_up, etcp->tcp_link_count);
etcp_fire_conn_status(etcp, ETCP_CONN_STATUS_UP);
etcp_cbk_fire(etcp, ETCP_CBK_EVENT_INIT);
}
static void tcp_link_reconnect_cb(void *arg) {
@ -1094,6 +1100,8 @@ void etcp_tcp_link_start_connect(struct ETCP_LINK *link, struct sockaddr_storage
if (!sl) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_tcp_link_start_connect: stcp_link_connect failed"); return; }
link->tcp_link = sl; stcp_link_set_etcp_conn(sl, link->etcp); stcp_link_set_etcp_link(sl, link);
if (link->is_server == 0) stcp_link_set_on_close(sl, tcp_link_close_cb, link);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] TCP link %d → stcp_link_connect %s:%u rc=%p",
link->etcp->log_name, link->local_link_id, addr, port, (void*)sl);
}
void etcp_tcp_link_start_reconnect(struct ETCP_LINK *link) {
@ -1117,9 +1125,14 @@ static int etcp_tcp_send(struct ETCP_DGRAM* dgram) {
struct ETCP_LINK *link = dgram->link;
link->pkt_sent_since_keepalive = 1;
dgram->flag_up = link->recv_keepalive;
if (link->send_hook) return (int)link->send_hook(0, dgram->data, dgram->data_len, NULL, 0, link, link->send_hook_ctx);
if (link->tcp_link) return stcp_link_send(link->tcp_link, dgram->data, dgram->data_len) == 0 ? (int)dgram->data_len : -1;
return -1;
int rc;
if (link->send_hook) rc = (int)link->send_hook(0, dgram->data, dgram->data_len, NULL, 0, link, link->send_hook_ctx);
else if (link->tcp_link) rc = stcp_link_send(link->tcp_link, dgram->data, dgram->data_len) == 0 ? (int)dgram->data_len : -1;
else rc = -1;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] TCP send: link=%d dlen=%d rc=%d via=%s",
link->etcp->log_name, link->local_link_id, dgram->data_len, rc,
link->send_hook ? "hook" : (link->tcp_link ? "stcp" : "NONE"));
return rc;
}

388
src/transport_layer/etcp_connections.h

@ -1,388 +0,0 @@
#ifndef ETCP_CONNECTIONS_H
#define ETCP_CONNECTIONS_H
#ifdef __cplusplus
extern "C" {
#endif
// подмодуль ETCP который обслуживает сокеты ETCP для приёма-передачи пакетов и одно ETCP подключение через несколько каналов связи (failover)
#include "secure_channel.h"
#include "utun_instance.h"
#include "etcp_bbr.h"
#include "../lib/socket_compat.h"
#include "../lib/ll_queue.h"
#include <stdint.h>
#include <stddef.h>
#define UDP_HDR_SIZE 28// размер udp header + ethernet заголовков (ipv4) [для ipv6 = 48 байт]
#define UDP_SC_HDR_SIZE (13+8+4 + 5)// 13+8+4 - sc_nonce+tag size+crc, 5 - payload hdr
#define ACK_REZERV 100// сколько байт резервировать под ack и прочие заголовки
#define SC_ENCRYPT_OVERHEAD (3 + SC_TAG_SIZE) // timestamp(2)+flag_up(1)+AES-CCM tag(16) — поверх plaintext
#define IP_UDP_OVERHEAD_V4 (20 + 8) // IP(20)+UDP(8)
#define IP_UDP_OVERHEAD_V6 (40 + 8) // IP(40)+UDP(8)
#define WIRE_OVERHEAD(fam) ((fam) == AF_INET6 ? IP_UDP_OVERHEAD_V6 + SC_ENCRYPT_OVERHEAD : IP_UDP_OVERHEAD_V4 + SC_ENCRYPT_OVERHEAD)
#define INFLIGHT_LIM_MIN 8192 // 8K
#define INFLIGHT_LIM_MAX 1048576 // 1M
#define PACKET_DATA_SIZE 1600//1536
#define PACKET_DATA_MAX_MTU 1600
#define ETCP_MAX_PAYLOAD_SIZE (PACKET_DATA_SIZE - ETCP_ACK_BASE_SIZE - 5) /* 1587 */
#define ETCP_RFC791_MIN_MTU 576
// Типы кодограмм протокола
#define ETCP_INIT_REQUEST 0x02
#define ETCP_INIT_RESPONSE 0x03
#define ETCP_INIT_REQUEST_NOINIT 0x04
#define ETCP_INIT_RESPONSE_NOINIT 0x05
#define ETCP_PING 0x06
#define ETCP_PONG 0x07
#define ETCP_KEEPALIVE 0x08
#define ETCP_PING_FLAG_WANT_RTT 0x01 // в PONG: хочу получить RTT от пингера
#define ETCP_PING_FLAG_SEND_RTT 0x02 // в PING: несу RTT для получателя
/* Адаптивный keepalive */
#define KA_PERIOD_MIN_MS 200
#define KA_PERIOD_MAX_MS 10000
#define KA_TIMEOUT_MULT 10 /* timeout = period * mult */
#pragma pack(push, 1)
struct ETCP_DGRAM {// пакет (незашифрованный)
struct ETCP_LINK* link;// откуда получена или куда отправялем
uint16_t data_len;// общий размер пакета не включая timestamp
uint16_t noencrypt_len;// число байт (с конца) которые не надо шифровать. для передачи pubkey
uint16_t timestamp;// timestamp отправляющего узла при отправке пакета
uint8_t flag_up:1;// bit0 = up/down (recv_keepalive)
uint8_t data[];// данные пакета (без timestamp)
};
#pragma pack(pop)
/* Размер зашифрованного заголовка ETCP_DGRAM (timestamp + flag_up + padding) */
#define ETCP_ENCRYPTED_HDR_SIZE (offsetof(struct ETCP_DGRAM, data) - offsetof(struct ETCP_DGRAM, timestamp))
/* Размер базового ACK: type(1) + count(1) + last_delivered_id(4) + rx_dup_count(2) */
#define ETCP_ACK_BASE_SIZE (2 + 4 + 2)
// --- INIT packet wire formats (inside encrypted payload, before padding) ---
// INIT REQUEST: client → server
struct ETCP_INIT_REQUEST_PKT {
uint8_t code; // 0: ETCP_INIT_REQUEST (0x02) или ETCP_INIT_REQUEST_NOINIT (0x04)
uint8_t node_id[8]; // 1: sender node_id (big-endian)
uint8_t session_id[4]; // 9: session (big-endian)
uint8_t mtu[2]; // 13: client MTU (big-endian)
uint8_t keepalive[2]; // 15: keepalive interval (big-endian)
uint8_t recovery[2]; // 17: recovery interval/100 (big-endian)
uint8_t link_id; // 19: client's local link id
uint8_t socket_id; // 20: client's socket id
uint8_t only_local; // 21: client only_local flag
uint8_t type; // 22: client socket type (CFG_SERVER_TYPE_*)
// V2 fields (NAT_DIRECT detection):
uint8_t src_ipv4[4]; // 23: client interface_addr IPv4 (big-endian, 0 if N/A)
uint8_t src_port[2]; // 27: client interface_addr port (big-endian)
uint8_t collision; // 29: 1 = cross-connect, remote claims master
uint8_t ed25519_pubkey[SC_PUBKEY_SIZE]; // 30: client Ed25519 pubkey (32 bytes)
} __attribute__((packed));
#define ETCP_INIT_REQ_SIZE sizeof(struct ETCP_INIT_REQUEST_PKT)
// INIT RESPONSE: server → client
struct ETCP_INIT_RESPONSE_PKT {
uint8_t code; // 0: ETCP_INIT_RESPONSE (0x03) или ETCP_INIT_RESPONSE_NOINIT (0x05)
uint8_t node_id[8]; // 1: server node_id (big-endian)
uint8_t session_id[4]; // 9: session (big-endian)
uint8_t mtu[2]; // 13: server MTU (big-endian)
uint8_t link_id; // 15: server's local link id
uint8_t remote_socket_id; // 16: echo of client's socket_id
uint8_t only_local; // 17: server only_local flag
uint8_t type; // 18: server socket type (CFG_SERVER_TYPE_*)
// V2 fields:
uint8_t peer_ipv4[4]; // 19: client's external NAT address (big-endian)
uint8_t peer_port[2]; // 23: client's external NAT port (big-endian)
// V3 fields:
uint8_t ed25519_pubkey[SC_PUBKEY_SIZE]; // 25: server Ed25519 pubkey (32 bytes)
} __attribute__((packed));
#define ETCP_INIT_RESP_SIZE sizeof(struct ETCP_INIT_RESPONSE_PKT)
typedef void (*etcp_ping_callback_t)(int success, uint16_t rtt, void* arg, uint64_t nonce,
const uint8_t* resp_data, size_t resp_data_len);
struct PING_CONTEXT {
struct PING_CONTEXT* next;
struct UTUN_INSTANCE* instance;
etcp_ping_callback_t cb;
void* arg;
uint64_t nonce;
void* timeout_timer;
uint64_t send_time; // время отправки пинга в 0.1ms
uint8_t peer_pubkey[SC_PUBKEY_SIZE];
uint8_t* user_data;
size_t user_data_len;
};
#define LINK_ADDR_KEY_SIZE 19 // port(2) + addr(16) + family(1)
struct link_queue_entry {
uint8_t key[LINK_ADDR_KEY_SIZE];
struct ETCP_LINK* link;
};
// список активных подключений которые обслуживает сокет. каждый сокет может обслуживать много подключений
struct ETCP_SOCKET {
struct ETCP_SOCKET* next; // Linked list для всех соединений
struct UTUN_INSTANCE* instance;
char name[MAX_CONN_NAME_LEN]; // Socket name from config (e.g., "lan1" from [server: lan1])
socket_t fd; // UDP socket (cross-platform)
struct sockaddr_storage local_addr; // Локальный адрес
int mtu; // MTU для этого сокета
int errorcode;
size_t pkt_format_errors;
void* socket_id; // Socket ID from uasync_add_socket
uint8_t type; // CFG_SERVER_TYPE_PUBLIC/NAT/PRIVATE
uint8_t sock_id; // unique socket id (0-255) for NAT matching
uint8_t nat_type; // NAT_TYPE_* (detected by server)
uint32_t local_defaultroute_ip; // auto-detected IPv4 for public servers (network byte order)
uint8_t local_defaultroute_ip6[16]; // auto-detected IPv6 for public servers
uint8_t only_local; // 1 = only local connections, no forwarding
uint32_t netif_index; // индекс интерфейса для sin6_scope_id (0 = не задан)
struct sockaddr_storage interface_addr; // адрес интерфейса: если bind на интерфейс - его IP, если 0.0.0.0 без интерфейса - IP интерфейса через который идет default route, иначе - адрес из конфига
struct sockaddr_storage nat_addr; // NAT адрес (network byte order), ss_family=0 = не определён
struct ll_queue* links_queue; // хеш-очередь линков, ключ = link_queue_entry.key (19 байт)
};
// NAT check status
#define NAT_CHECK_NONE 0
#define NAT_CHECK_WAITING 1
#define NAT_CHECK_IN_PROGRESS 2
#define NAT_CHECK_EIM 3
#define NAT_CHECK_STRICT 4
// NAT type (detected by server during NAT check)
#define NAT_TYPE_UNKNOWN 0
#define NAT_TYPE_EIM 1 // Endpoint-Independent Mapping
#define NAT_TYPE_STRICT 2 // Address/Restricted or Symmetric
#define NAT_TYPE_DIRECT 3 // real public IP, no NAT (detected during INIT handshake)
// Verified NAT types (published in nodeinfo after server-side NAT detection)
// Values above CFG_SERVER_TYPE_PRIVATE to avoid collision with config types
#define NAT_VERIFIED_UNKNOWN 4 // detection failed or inconclusive
#define NAT_VERIFIED_EIM 5 // EIM NAT
#define NAT_VERIFIED_STRICT 6 // strict NAT
#define NAT_VERIFIED_DIRECT 7 // real public IP, no NAT
// Тип функции-перехватчика отправки UDP.
// Если link->send_hook != NULL, etcp_udp_send вызывает её вместо socket_sendto.
struct ETCP_LINK;
typedef ssize_t (*etcp_udp_send_fn_t)(socket_t fd, const void* buf, size_t len,
const struct sockaddr* addr, socklen_t addr_len,
struct ETCP_LINK* link, void* context);
// ETCP Link - одно динамическое соединение (один путь)
struct ETCP_LINK {
struct ll_entry* link_queue_entry; // элемент в socket->links_queue
struct ETCP_LINK* next; // Linked list подключений для ETCP_CONN (каждое подключение это child для ETCP_CONN)
struct ETCP_CONN* etcp; // подключение (parent)
struct ETCP_SOCKET* conn; // сокет через который работаем
// Путь соединения
struct sockaddr_storage remote_addr; // Удалённый адрес
// Параметры соединения
uint16_t mtu; // MTU общий
uint16_t mtu_local; // MTU моего зла
uint16_t mtu_remote; // MTU удаленного узла
uint16_t keepalive_interval; // Keepalive интервал (x1ms)
uint32_t recovery_interval; // recovery интервал (x0.1ms)
uint8_t is_server; // инициирует подключение клиент
uint8_t initialized; // Флаг инициализации (1=подтверждено или получен request)
uint8_t local_link_id; // id моего линка
uint8_t remote_link_id; // id этого линка на peer (устанавливается в момент initialized)
uint8_t remote_socket_id; // socket id peer
uint8_t remote_only_local; // only_local flag from peer
uint8_t remote_type; // CFG_SERVER_TYPE_* (type of peer's socket)
uint8_t nat_type; // NAT_TYPE_* (detected for this client link)
uint8_t remote_ed25519_pubkey[SC_PUBKEY_SIZE]; // Ed25519 pubkey пира (из INIT handshake)
uint8_t recv_keepalive; // 1 - up, 0 - down (принимаются ли пакеты)
uint8_t remote_keepalive; // 1 - up, 0 - down (удаленная сторона сообщает - принимаются ли у нее пакеты)
uint8_t link_status; // 1 - up, 0 - down (итоговый статус - если есть проблемы на любой стороне - линк down)
uint8_t link_state; // 0 - just init, 1 - handshake, 2 - try reconnect, 3 - connected
// Состояние установки соединения (только для клиентов)
void* init_timer; // Таймер для повторов INIT (NULL=не подключается)
uint16_t init_timeout; // Текущий таймаут в мс
uint16_t init_retry_count; // Счетчик попыток
// uint64_t last_activity; // Время последней активности
uint64_t last_recv_local_time; // x0.1 ms
uint16_t last_recv_timestamp;
uint8_t last_recv_updated; // =1 при обновлении timestamp, =0 при отправке (чтобы не дублировать отправки при отсутствии обновлений)
uint64_t shaper_load_time_tb; // основной в 0.1 мс units
uint64_t shaper_sub_nanotime; // sub 0-999999 для 0.1 нс - 0.1 мс
uint8_t shaper_state;
void* shaper_timer;
uint32_t inflight_bytes;
uint32_t inflight_packets;
uint32_t inflight_lim_bytes;
// statistics
size_t encrypt_errors;
size_t decrypt_errors;
size_t send_errors;
size_t recv_errors;
size_t total_encrypted;
size_t total_decrypted;
uint32_t total_retransmissions;
uint64_t acked_bytes; // подтверждённых байт на этом линке
uint64_t acked_packets; // подтверждённых пакетов на этом линке
uint16_t rtt_last; // round trip (время отправки + приёма)
uint32_t jitter; // Current jitter [>>16] x0.1 ms
// rtt_avg10 удалён — заменён BBR (bbr->min_rtt_us/100)
uint32_t recv_dt_avg_tx; // дельта времени для отправленных пакетов (относительное время отправки) x256
uint32_t recv_dt_avg_rx; // дельта времени для принятых пакетов (относительное время отправки) x256
uint16_t tt_last; // transmit time (время доставки отправленных пакетов)
uint16_t rt_last; // recv time (время доставки принятых пакетов)
uint32_t bandwidth; // Link bandwidth in Kbits/sec
// NAT address tracking (from INIT_RESPONSE)
uint32_t nat_ip; // NAT IPv4 address (network byte order), 0 = not set
uint16_t nat_port; // NAT port (host byte order)
uint32_t nat_changes_count; // Counter of NAT address changes
uint32_t nat_hits_count; // Counter of NAT address matches (new init response with same IP:port)
uint8_t nat_check_status; // NAT_CHECK_*
// Keepalive state
void* keepalive_timer; // Таймер для отправки keepalive пакетов
uint32_t keepalive_timeout; // таймаут (ms)
uint16_t ka_period_ms; // адаптивный период отправки keepalive (200→10000->200)
uint8_t pkt_sent_since_keepalive; // Флаг: был ли отправлен пакет с последнего keepalive тика
uint32_t keepalive_sent_count; // Счётчик отправленных keepalive
uint32_t keepalive_recv_count; // Счётчик полученных keepalive
uint16_t handshake_minsize; // минимальный размер udp при handshake
uint16_t handshake_maxsize; // мax размер udp при handshake (выбирает рандом)
// === Burst-измерение bandwidth ===
// Sender-сторона
uint16_t burst_id; // монотонно возрастающий ID burst
uint8_t burst_active; // 1 = burst в процессе отправки
uint8_t burst_seq; // текущий номер пакета в burst (0..burst_count-1)
uint8_t burst_count; // общее число пакетов в burst
uint64_t burst_last_time_tb; // время последнего burst (0.1ms), для MIN_BURST_INTERVAL
void* burst_resp_timer; // таймер ожидания ответа на burst
uint32_t burst_target_bdp; // BDP из последнего успешного burst (байты)
uint16_t burst_pkt_size; // сохранённый размер пакета для вычисления BW
// Receiver-сторона
uint16_t burst_recv_id; // ID отслеживаемого burst
uint8_t burst_recv_count; // ожидаемое число пакетов в burst
uint8_t burst_recv_next_seq; // следующий ожидаемый seq
uint8_t burst_recv_valid; // 1 = порядок пакетов не нарушен
uint8_t burst_recv_received; // сколько пакетов уже принято
uint64_t burst_recv_times[16]; // время прихода каждого пакета (µs)
uint16_t burst_recv_pkt_size; // размер пакета из MEAS_TS (для ответа)
uint8_t burst_resp_pending; // 1 = есть готовый ответ для piggyback
uint32_t burst_resp_gap_avg; // средний inter-packet gap (µs)
uint32_t burst_resp_gap_min; // минимальный inter-packet gap (µs)
uint8_t burst_resp_pkt_count; // число пакетов в измерении
uint8_t burst_resp_valid; // валидность измерения
// === BBR congestion control ===
struct bbr* bbr; // BBR state (per-link, создаётся в etcp_link_new)
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;
uint8_t is_tcp; // 1 = TCP транспорт (STCP)
struct stcp_link *tcp_link; // STCP линк (для TCP)
void *tcp_reconnect_timer; // таймер реконнекта (TCP клиент)
uint32_t tcp_reconnect_delay_ms; // задержка реконнекта (экспоненциальный backoff)
};
// Единая точка отправки всех ETCP-пакетов.
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);
// INITIALIZATION
// Создаёт только listen-сокеты из конфига (серверы для incoming connections)
int init_sockets(struct UTUN_INSTANCE* instance);
// Создаёт listen-сокеты и client connections из конфига
int init_connections(struct UTUN_INSTANCE* instance);
// SOCKET FUNCTIONS
// добавляет новый версер (сокет для приёма и отправки кодограмм. обслуживает много подключений)
struct ETCP_SOCKET* etcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_SERVER* server);
// удаляет сокет и освобождает ресурсы (грохает все его подключения и сокет)
void etcp_socket_remove(struct ETCP_SOCKET* conn);
// TCP socket descriptor (analogous to ETCP_SOCKET for UDP)
struct TCP_SOCKET {
struct TCP_SOCKET* next;
struct UTUN_INSTANCE* instance;
char name[MAX_CONN_NAME_LEN];
uint8_t sock_id;
uint8_t type; // CFG_SERVER_TYPE_*
struct sockaddr_storage local_addr;
struct sockaddr_storage interface_addr;
struct sockaddr_storage nat_addr;
};
struct TCP_SOCKET* tcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_SERVER* server);
void tcp_socket_remove(struct TCP_SOCKET* sock);
// connection functions
// создает новый канал связи для etcp подключения (ETCP_CONN)
struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn, struct sockaddr_storage* remote_addr, uint8_t is_server);
void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim);
void etcp_link_close(struct ETCP_LINK* link);
//int etcp_input_cbk(struct packet_buffer* pkt, struct ETCP_SOCKET* conn);// получает расшифрованный пакет
int etcp_encrypt_send(struct ETCP_DGRAM* dgram);// зашифровывает и отправляет пакет
// find link by address
struct ETCP_LINK* etcp_link_find_by_addr(struct ETCP_SOCKET* e_sock, struct sockaddr_storage* addr);
// find free local_link_id for connection
// scans all links in connection, marks used ids in bit array
// returns first free id (0-255) or -1 if all occupied
int etcp_find_free_local_link_id(struct ETCP_CONN* etcp);
// Burst measurement functions
void etcp_link_burst_start(struct ETCP_LINK* link);
void etcp_link_burst_check(struct ETCP_LINK* link);
void etcp_link_burst_finish(struct ETCP_LINK* link);
void etcp_link_enter_ready_tcp(struct ETCP_LINK *link);
void etcp_tcp_link_start_connect(struct ETCP_LINK *link, struct sockaddr_storage *addr, uint16_t port);
void etcp_tcp_link_start_reconnect(struct ETCP_LINK *link);
int etcp_send_ping(struct UTUN_INSTANCE* instance, const uint8_t* peer_pubkey_bin,
const struct sockaddr_storage* addr, int timeout_ms,
etcp_ping_callback_t cb, void* user_arg,
const uint8_t* user_data, size_t user_data_len);
// шлёт пинг на addr используя pubkey. в пакет помещает user_data[user_len]. при получении ответа или таймауте вызывает callback.
// flags: ETCP_PING_FLAG_SEND_RTT — user_data содержит RTT для peer
int etcp_send_ping_to_socket(struct UTUN_INSTANCE* instance, struct ETCP_SOCKET* e_sock,
const uint8_t* peer_pubkey_bin, const struct sockaddr_storage* addr,
int timeout_ms, etcp_ping_callback_t cb, void* user_arg,
const uint8_t* user_data, size_t user_data_len,
uint8_t flags);
void etcp_connections_read_callback_socket(socket_t sock, void* arg);
#ifdef __cplusplus
}
#endif
#endif // ETCP_CONNECTIONS_H

30
src/transport_layer/stcp_link.c

@ -50,16 +50,22 @@ static void link_rx_cb(struct ll_queue *q, void *arg) {
if (!e) { queue_resume_callback(q); return; }
if (link->etcp_conn && link->etcp_link && link->cfg.inst) {
struct ETCP_DGRAM *pkt = memory_pool_alloc(link->cfg.inst->pkt_pool);
if (pkt) {
pkt->link = link->etcp_link;
pkt->data_len = (uint16_t)e->len;
pkt->noencrypt_len = 0;
if (e->len > 0) memcpy(pkt->data, e->dgram, e->len);
etcp_conn_input(pkt);
if (e->len >= 1 && e->dgram[0] == ETCP_KEEPALIVE) {
struct ETCP_LINK *l = link->etcp_link;
if (e->len >= 3) { uint16_t pp = e->dgram[1] | ((uint16_t)e->dgram[2] << 8); l->keepalive_timeout = (uint32_t)pp * KA_TIMEOUT_MULT; }
l->recv_keepalive = 1; l->remote_keepalive = 1; l->link_status = 1; l->keepalive_recv_count++;
l->last_recv_local_time = get_time_tb();
queue_dgram_free(e); queue_entry_free(e);
} else {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "stcp_link rx → etcp_conn_input len=%zu link=%p", e->len, (void*)link->etcp_link);
struct ETCP_DGRAM *pkt = memory_pool_alloc(link->cfg.inst->pkt_pool);
if (pkt) {
pkt->link = link->etcp_link; pkt->data_len = (uint16_t)e->len; pkt->noencrypt_len = 0;
if (e->len > 0) memcpy(pkt->data, e->dgram, e->len);
etcp_conn_input(pkt);
}
queue_dgram_free(e); queue_entry_free(e);
}
queue_dgram_free(e);
queue_entry_free(e);
} else {
struct UTUN_INSTANCE *inst = link->cfg.inst;
uint8_t id = (e->dgram && e->len > 0) ? e->dgram[0] : 0;
@ -110,9 +116,9 @@ static void client_ready_cb(struct stcp_conn *conn, void *arg) {
queue_set_waiter_defer(link->tx_queue, 1);
stcp_conn_set_tx_queue(conn, link->tx_queue);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "stcp_link: client connected, peer_pubkey=%02x%02x%02x%02x...",
link->cfg.peer_pubkey[0], link->cfg.peer_pubkey[1],
link->cfg.peer_pubkey[2], link->cfg.peer_pubkey[3]);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "stcp_link: client handshake OK, link=%p etcp_link=%p",
(void*)link, (void*)link->etcp_link);
if (link->etcp_link) etcp_link_enter_ready_tcp(link->etcp_link);
if (link->on_ready_cb) link->on_ready_cb(link, link->ready_arg);
}

68
src/transport_layer/stcp_link.h

@ -1,68 +0,0 @@
// stcp_link.h — STCP link management (TCP connection via STCP protocol)
#ifndef STCP_LINK_H
#define STCP_LINK_H
#ifdef __cplusplus
extern "C" {
#endif
#include "secure_channel.h"
#include "../lib/u_async.h"
#include "../lib/socket_compat.h"
#include <stdint.h>
struct stcp_link; // opaque
struct stcp_server; // opaque
struct ETCP_CONN;
struct ETCP_LINK;
struct UTUN_INSTANCE;
// ====== Link config ======
struct stcp_link_config {
struct UASYNC *ua;
struct SC_MYKEYS *my_keys; // ключи этой стороны
struct UTUN_INSTANCE *inst; // для диспатча через api_bindings
const uint8_t *peer_pubkey; // pubkey пира (клиент)
int peer_pubkey_mode; // 0=binary, 1=hex
const struct sockaddr_storage *remote_addr; // адрес пира (клиент)
uint16_t remote_port; // порт пира (клиент)
int listen_family; // AF_INET или AF_INET6 для сервера (0 = v4)
};
// ====== TCP server ======
typedef void (*stcp_server_on_link_cb)(struct stcp_link *link, void *arg);
struct stcp_server *stcp_server_listen(struct stcp_link_config *cfg, uint16_t port,
stcp_server_on_link_cb on_link, void *arg);
void stcp_link_server_destroy(struct stcp_server *srv);
/* Server list management (multiple servers per instance) */
void stcp_server_list_add(struct UTUN_INSTANCE *inst, struct stcp_server *srv);
void stcp_server_list_destroy_all(struct UTUN_INSTANCE *inst);
int stcp_server_list_count(struct UTUN_INSTANCE *inst);
// ====== TCP client link ======
struct stcp_link *stcp_link_connect(struct stcp_link_config *cfg);
void stcp_link_close(struct stcp_link *link);
int stcp_link_send(struct stcp_link *link, const uint8_t *data, size_t len);
int stcp_link_is_ready(struct stcp_link *link);
struct ETCP_CONN *stcp_link_get_etcp_conn(struct stcp_link *link);
void stcp_link_set_etcp_conn(struct stcp_link *link, struct ETCP_CONN *conn);
void stcp_link_set_etcp_link(struct stcp_link *link, struct ETCP_LINK *elink);
const struct sockaddr_storage *stcp_link_get_remote_addr(struct stcp_link *link);
typedef void (*stcp_link_cb)(struct stcp_link *link, void *arg);
void stcp_link_set_on_ready(struct stcp_link *link, stcp_link_cb cb, void *arg);
void stcp_link_set_on_close(struct stcp_link *link, void (*cb)(struct stcp_link *link, int err, void *arg), void *arg);
const uint8_t *stcp_link_get_peer_pubkey(struct stcp_link *link);
#ifdef __cplusplus
}
#endif
#endif

153
tests/test_etcp_stcp.c

@ -0,0 +1,153 @@
// test_etcp_stcp.c — integration: etcp_send + etcp_bind over STCP link
#include "stcp_link.h"
#include "etcp_api.h"
#include "etcp.h"
#include "etcp_connections.h"
#include "secure_channel.h"
#include "topo_group.h"
#include "topo_node.h"
#include "../src/utun_instance.h"
#include "../lib/u_async.h"
#include "../lib/ll_queue.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#define TEST_PORT 25678
static int test_failed = 0;
static struct SC_MYKEYS s_keys, c_keys;
static struct UTUN_INSTANCE *srv_inst, *cli_inst;
static struct ETCP_CONN *srv_conn, *cli_conn;
static int g_srv_recv_count = 0;
static uint8_t g_srv_recv_data[256];
#define TASSERT(cond) do { \
if (!(cond)) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, " FAIL: %s", #cond); test_failed = 1; return test_failed; } \
} while(0)
static void etcp_recv_cb(struct ETCP_CONN *conn, struct ll_entry *entry) {
(void)conn;
g_srv_recv_count++;
if (entry->dgram && entry->len) {
size_t n = entry->len < 256 ? entry->len : 255;
memcpy(g_srv_recv_data, entry->dgram, n);
}
queue_dgram_free(entry);
queue_entry_free(entry);
}
static void connect_cb(void *arg, struct ETCP_CONN *conn, int type) {
(void)arg;
if (!conn) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "etcp_connect failed"); test_failed = 1; return; }
if (type & ETCP_CONNECT_EARLY) {
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "etcp_connect: EARLY ready");
cli_conn = conn;
}
}
static char *build_server_config(int port) {
static char buf[512];
snprintf(buf, sizeof(buf),
"[global]\n"
"my_node_id=0xAAAAAAAAAAAAAAAA\n"
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"\n"
"[server: stcp]\n"
"addr=127.0.0.1:%d\n"
"type=public\n"
"transport=tcp\n",
port);
return buf;
}
static char *build_client_config(int port) {
static char buf[512];
snprintf(buf, sizeof(buf),
"[global]\n"
"my_node_id=0xBBBBBBBBBBBBBBBB\n"
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
"\n"
"[server: stcp]\n"
"addr=127.0.0.1:%d\n"
"type=public\n"
"transport=tcp\n",
port);
return buf;
}
int main(void) {
debug_config_init();
debug_set_level(DEBUG_LEVEL_INFO);
debug_set_categories(DEBUG_CATEGORY_GENERAL | DEBUG_CATEGORY_SOCKET | DEBUG_CATEGORY_CRYPTO);
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "=== etcp_send/bind over STCP link ===");
srand((unsigned)time(NULL));
struct UASYNC *ua = uasync_create(); TASSERT(ua);
utun_instance_set_tun_init_enabled(0);
int port = TEST_PORT + rand() % 1000;
srv_inst = utun_instance_create_from_str(ua, build_server_config(port));
TASSERT(srv_inst);
TASSERT(utun_instance_init(srv_inst) >= 0);
cli_inst = utun_instance_create_from_str(ua, build_client_config(port + 1));
TASSERT(cli_inst);
TASSERT(utun_instance_init(cli_inst) >= 0);
TASSERT(etcp_bind(srv_inst, 0, etcp_recv_cb) >= 0);
struct TOPO_GROUP_NODE *srv_node = topo_groups_get_default(srv_inst->topo_groups) ? topo_groups_get_default(srv_inst->topo_groups)->local_node : NULL;
TASSERT(srv_node);
{ struct TOPO_NODE* srv_ni = topo_node_registry_find(srv_inst->topo_groups, srv_node->node_id); TASSERT(srv_ni);
struct TOPO_NODE* cli_ni = u_calloc(1, sizeof(struct TOPO_NODE));
TASSERT(cli_ni); memcpy(cli_ni, srv_ni, sizeof(struct TOPO_NODE)); cli_ni->group_ref_count = 0;
cli_ni->v4_sock_meta = NULL; cli_ni->v4_addrs = NULL; cli_ni->v6_sock_meta = NULL; cli_ni->v6_addrs = NULL;
struct TOPO_ADDR4* a = memory_pool_alloc(cli_inst->topo_groups->v4_addr_pool);
TASSERT(a); a->addr[0]=127; a->addr[1]=0; a->addr[2]=0; a->addr[3]=1; a->port = port;
a->type = TOPO_ADDR_INTERFACE; a->socket_id = 0; a->protocol = TOPO_PROTO_TCP;
a->next = cli_ni->v4_addrs; cli_ni->v4_addrs = a;
topo_node_registry_store(cli_inst->topo_groups, cli_ni); }
cli_inst->etcp_connect_timeout_tb = 100000;
TASSERT(etcp_connect(cli_inst, srv_node, connect_cb, NULL, ETCP_CONNECT_EARLY | ETCP_CONNECT_LATE) == 0);
int ticks = 0;
while (!cli_conn && ticks < 5000) { uasync_poll(ua, 10); ticks++; }
TASSERT(cli_conn);
{ struct ll_entry* e = srv_inst->connections->head;
while (e) { struct conn_queue_entry* ce = (struct conn_queue_entry*)e->data;
struct ETCP_CONN *c = ce->conn;
struct ETCP_LINK *l = c->links; while (l) { if (l->is_tcp) { srv_conn = c; break; } l = l->next; }
if (srv_conn) break;
e = e->next; }
}
TASSERT(srv_conn);
const char *msg = "hello via etcp_send!";
struct ll_entry *e = queue_entry_new(0);
e->dgram = u_malloc(strlen(msg) + 1);
strcpy((char *)e->dgram, msg);
e->len = (uint16_t)strlen(msg);
TASSERT(etcp_send(cli_conn, e) == 0);
ticks = 0;
while (g_srv_recv_count < 1 && ticks < 2000) { uasync_poll(ua, 10); ticks++; }
TASSERT(g_srv_recv_count == 1);
TASSERT(memcmp(g_srv_recv_data, msg, strlen(msg)) == 0);
srv_inst->running = 0; cli_inst->running = 0;
utun_instance_destroy(srv_inst); srv_inst = NULL;
utun_instance_destroy(cli_inst); cli_inst = NULL;
uasync_destroy(ua, 0);
if (test_failed) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "=== FAILED ==="); return 1; }
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "=== PASSED ===");
return 0;
}

50
tests/test_stcp_traffic.c

@ -2,8 +2,8 @@
#include "stcp_link.h"
#include "etcp_api.h"
#include "etcp.h"
#include "secure_channel.h"
#include "etcp_connections.h"
#include "secure_channel.h"
#include "topo_group.h"
#include "topo_node.h"
#include "../src/utun_instance.h"
@ -27,8 +27,6 @@ static int test_failed = 0;
if (!(cond)) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, " FAIL: %s", #cond); test_failed = 1; return 1; } \
} while(0)
// ====== receive state ======
struct rx_ctx {
uint8_t *buf;
size_t len, cap;
@ -37,15 +35,11 @@ struct rx_ctx {
size_t expected_total;
};
// ====== globals ======
static struct rx_ctx srv_rx, cli_rx;
static struct UTUN_INSTANCE *srv_inst, *cli_inst;
static struct stcp_link *srv_link, *cli_link;
static struct ETCP_CONN *srv_conn, *cli_conn;
static int srv_send_ready, cli_send_ready;
// ====== receive callbacks ======
static void srv_recv_cb(struct ETCP_CONN *conn, struct ll_entry *entry) {
(void)conn;
if (!entry->dgram || entry->len < 5) { queue_dgram_free(entry); queue_entry_free(entry); return; }
@ -74,21 +68,16 @@ static void cli_recv_cb(struct ETCP_CONN *conn, struct ll_entry *entry) {
if (cli_rx.len >= cli_rx.expected_total) cli_rx.done = 1;
}
// ====== etcp_connect callback ======
static void connect_cb(void *arg, struct ETCP_CONN *conn, int type) {
(void)arg;
if (!conn) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "etcp_connect failed"); test_failed = 1; return; }
if (type & ETCP_CONNECT_EARLY) {
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "etcp_connect: EARLY ready");
struct ETCP_LINK *l = conn->links; while (l) { if (l->is_tcp && l->tcp_link) { cli_link = l->tcp_link; break; } l = l->next; }
if (!cli_link) { test_failed = 1; return; }
cli_conn = conn;
cli_send_ready = 1;
}
}
// ====== packet helpers ======
static size_t gen_packet(uint8_t *buf, int side, int seq, size_t paylen) {
buf[0] = (uint8_t)side;
buf[1] = (uint8_t)(seq >> 0);
@ -100,14 +89,18 @@ static size_t gen_packet(uint8_t *buf, int side, int seq, size_t paylen) {
return paylen + 5;
}
static int send_packet(struct stcp_link *link, int side, int seq, size_t paylen) {
static int send_etcp_packet(struct ETCP_CONN *conn, int side, int seq, size_t paylen) {
uint8_t buf[PKT_MAX + 5];
size_t tot = gen_packet(buf, side, seq, paylen);
return stcp_link_send(link, buf, tot);
struct ll_entry *e = queue_entry_new(0);
if (!e) return -1;
e->dgram = u_malloc(tot);
if (!e->dgram) { queue_entry_free(e); return -1; }
memcpy(e->dgram, buf, tot);
e->len = (uint16_t)tot;
return etcp_send(conn, e);
}
// ====== config strings ======
static char *build_server_config(int port) {
static char buf[512];
snprintf(buf, sizeof(buf),
@ -140,12 +133,10 @@ static char *build_client_config(int port) {
return buf;
}
// ====== main ======
int main(void) {
debug_config_init();
debug_set_level(DEBUG_LEVEL_INFO);
debug_set_categories(DEBUG_CATEGORY_GENERAL | DEBUG_CATEGORY_SOCKET | DEBUG_CATEGORY_CRYPTO);
debug_set_categories(DEBUG_CATEGORY_GENERAL | DEBUG_CATEGORY_SOCKET | DEBUG_CATEGORY_CRYPTO | DEBUG_CATEGORY_ETCP);
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "=== STCP Traffic Test ===");
srand((unsigned)time(NULL));
@ -178,23 +169,22 @@ int main(void) {
a->type = TOPO_ADDR_INTERFACE; a->socket_id = 0; a->protocol = TOPO_PROTO_TCP;
a->next = cli_ni->v4_addrs; cli_ni->v4_addrs = a;
topo_node_registry_store(cli_inst->topo_groups, cli_ni); }
// Increase timeout for TCP STCP handshake
cli_inst->etcp_connect_timeout_tb = 100000; // 10 seconds
cli_inst->etcp_connect_timeout_tb = 100000;
TASSERT(etcp_connect(cli_inst, srv_node, connect_cb, NULL, ETCP_CONNECT_EARLY | ETCP_CONNECT_LATE) == 0);
int ticks = 0;
while (!cli_send_ready && ticks < 5000) { uasync_poll(ua, 10); ticks++; }
TASSERT(cli_send_ready);
TASSERT(cli_link);
TASSERT(cli_conn);
{ struct ll_entry* e = srv_inst->connections->head;
while (e) { struct conn_queue_entry* ce = (struct conn_queue_entry*)e->data;
struct ETCP_CONN *c = ce->conn;
{ struct ETCP_LINK *l = c->links; while (l) { if (l->is_tcp && l->tcp_link) { srv_link = l->tcp_link; break; } l = l->next; } }
if (srv_link) break;
struct ETCP_LINK *l = c->links; while (l) { if (l->is_tcp) { srv_conn = c; break; } l = l->next; }
if (srv_conn) break;
e = e->next; }
}
TASSERT(srv_link);
TASSERT(srv_conn);
size_t remaining_s = TRAFFIC_MB, remaining_c = TRAFFIC_MB;
int seq_s = 0, seq_c = 0;
@ -207,14 +197,14 @@ int main(void) {
if (!srv_done && remaining_s > 0) {
size_t sz = PKT_MIN + (rand() % (PKT_MAX - PKT_MIN + 1));
if (sz > remaining_s) sz = remaining_s;
if (send_packet(srv_link, 2, seq_s++, sz) == 0) remaining_s -= sz;
if (send_etcp_packet(srv_conn, 2, seq_s++, sz) == 0) remaining_s -= sz;
}
if (remaining_s == 0 && seq_s > 0) srv_done = 1;
if (!cli_done && remaining_c > 0) {
size_t sz = PKT_MIN + (rand() % (PKT_MAX - PKT_MIN + 1));
if (sz > remaining_c) sz = remaining_c;
if (send_packet(cli_link, 1, seq_c++, sz) == 0) remaining_c -= sz;
if (send_etcp_packet(cli_conn, 1, seq_c++, sz) == 0) remaining_c -= sz;
}
if (remaining_c == 0 && seq_c > 0) cli_done = 1;
@ -230,8 +220,6 @@ int main(void) {
TASSERT(srv_rx.len >= TRAFFIC_MB);
TASSERT(cli_rx.len >= TRAFFIC_MB);
stcp_link_close(cli_link);
if (srv_link) stcp_link_close(srv_link);
if (srv_rx.buf) u_free(srv_rx.buf);
if (cli_rx.buf) u_free(cli_rx.buf);

Loading…
Cancel
Save