22 Commits
Author SHA1 Message Date
Evgeny f913bdf351 stcp: streaming TCP module + stream cipher + comprehensive tests
- secure_channel: add sc_stream_state + sc_stream_init/sc_stream_xor/sc_stream_cleanup
  AES-128-CTR streaming cipher for incremental encrypt/decrypt
- stcp.h/c: shared stcp_conn lifecycle, set_on_close, rx/tx queue setup
- stcp_server: listen/accept, handshake (obfuscated pubkey + ECDH + stream), DATA phase
- stcp_client: connect, handshake, DATA phase
- Protocol: key_block(72) + enc(data+crc) + padding for handshake
           size(2 LE) + enc(data+crc) for data messages
- Tests: test_stream_cipher (AES-CTR correctness)
         test_stcp (6 cases: sizes 0..65535, 200 msgs, wrong key,
                    close detection, multi-client, interleaved send/recv)
- Fix: 0-length message handling in tx_queue callbacks
2026-06-14 10:00:02 +03:00
Evgeny 0dd942be63 fix: стабилизация тестов test_tcp_io и test_bbr_integration
- test_bbr_integration: bbr_max_cwnd=100KB (16×BDP) — устранён overshoot очереди
- test_tcp_io: адаптирован под EPOLLHUP на socketpair (send до poll, error_cb вместо fin)
- tcp_io: расширенная диагностика error_cb с состоянием сокета
2026-06-11 13:16:48 +03:00
Evgeny 474bb8bd6b bbdr: rename BBR log fields, add mode transition logs, configurable max inflight 2026-06-11 11:06:31 +03:00
Evgeny 3419ff3a85 debug: single source of truth for category names, GUI dynamic labels
- debug_get_category_name(): replace switch/case with table lookup in g_debug_categories[]
- etcpmon_gui: remove hardcoded cat_names[], create labels/radios dynamically from server CSV
2026-06-11 10:16:14 +03:00
Evgeny 6b2b0f1360 etcp: per-link acked bytes/packets counters + BBR debug logging
- Add acked_bytes/acked_packets to struct ETCP_LINK (init, reset, increment per ACK)
- Add DEBUG_CATEGORY_BBR (23) for BBR-specific debug output
- Add BBR input/output DEBUG_DEBUG around bbr_main call in etcp_ack_recv
- Add "bbr" category name to debug_config and etcpmon GUI
2026-06-11 09:55:30 +03:00
Evgeny 20e81e493d proxy recv_cb: silent drop всех известных subcmd при отсутствии conn, не WARN 2026-06-10 13:32:03 +03:00
Evgeny 23732039de SOCKET сообщения жизненного цикла: INFO→DEBUG 2026-06-10 11:18:55 +03:00
Evgeny f883c8da75 Убрал безусловный DEBUG_ERROR в memory_pool_destroy 2026-06-10 11:15:02 +03:00
Evgeny 4211e32f51 tcp_io: единый обработчик ошибок tcp_conn_handle_error
- Вынесена общая логика обработки фатальных ошибок сокета
- Обработчик: close сокета, обнуление коллбэков, аварийный drain, вызов on_error
- Устранён use-after-free: пулы освобождаются до вызова on_error
- flush_write_buf возвращает int (0=ok, 1=error/tc destroyed)
- Все ошибки аллокации write_buf ведут к вызову on_error (ENOMEM)
- Обновлён header: документация в актуальное состояние
2026-06-10 10:38:22 +03:00
Evgeny 7c840c8cfd inflight fix 2026-06-10 01:01:32 +03:00
Evgeny 1a1e305b6b fix: etcp_connection_close — закрывать линки до очистки очередей (SIGSEGV на input_send_q=NULL) 2026-06-09 22:52:44 +03:00
Evgeny 974c6770e0 fix: prevent ack_q unbounded growth on duplicate packets
Check queue_find_data_by_index(ack_q) before creating a new ACK_PACKET
in ETCP_SECTION_PAYLOAD handler. Previously every duplicate packet
created a new entry in ack_q, and with links blocked (inf_block) the
queue grew without bounds.
2026-06-09 22:49:27 +03:00
Evgeny 075c4f4583 memory_pool: name field — идентификация пула в диагностике коррапшна 2026-06-09 22:20:25 +03:00
Evgeny 9e133699bf log_dump: level+category, memory_pool: дамп user_data через log_dump при коррапшне 2026-06-09 22:20:06 +03:00
Evgeny 81cad40e3b memory_pool: усиленная канарейка 4B + хранение alloc_loc для диагностики 2026-06-09 22:19:47 +03:00
Evgeny baca018387 proxy/udp: buf 65536→1600 на стеке, UDP не нужен гигантский буфер 2026-06-09 22:19:30 +03:00
Evgeny 48a4f182aa ll_queue: inline→static inline queue_waiter_call — fix link error with -Og 2026-06-09 22:18:29 +03:00
Evgeny 7c456a1652 proxy: bounds check data_len перед memcpy в handle_data — fix buffer overflow 2026-06-09 22:18:12 +03:00
Evgeny a0ea19acb8 fix: diag_timer_cb UAF — обнуляем rc->diag_timer до раннего возврата 2026-06-09 21:50:04 +03:00
Evgeny 1f34629753 Merge commit 'HEAD@{1}' into bbr
# Conflicts:
#	src/etcp.c
#	src/etcp_connections.c
2026-06-09 19:48:52 +03:00
Evgeny dd1b9f2680 etcp: dedup ack_q — не добавлять дубликаты ACK для одного seq 2026-06-07 21:01:44 +03:00
Evgeny 52f32da3f1 etcp: BBR callback для обновления inflight_lim_bytes + пересчёт optimal_inflight
- BBR: добавлен callback on_cwnd_update(ctx, new_cwnd) в struct bbr, вызывается при изменении cwnd
- etcp_link_update_inflight_lim() — атомарно обновляет inflight_lim_bytes линка и пересчитывает optimal_inflight (сумма по всем линкам)
- BBR теперь сам обновляет лимиты через callback, ручное присваивание в etcp_ack_recv() удалено
- is_server в config_parser.c: печать адресов через sockaddr_storage_to_str (поддержка IPv4/IPv6)
- utun_instance.c: local_sockaddr_equal() — добавлено сравнение AF_INET6
- Убраны отладочные memory_pool_is_freed
2026-06-07 12:17:18 +03:00
48 changed files with 1833 additions and 296 deletions

No files matched your search

+18 -41
View File
@@ -13,30 +13,29 @@
#include <stdio.h>
#include <time.h>
void log_dump(const char* prefix, const uint8_t* data, size_t len) {
void log_dump(int level, int category, const char* prefix, const uint8_t* data, size_t len) {
if (!debug_should_output(level, category)) return;
char hex_buf[513] = {'\0'};
size_t hex_len = 0;
size_t show_len = (len > 128) ? 128 : len;
for (size_t i = 0; i < show_len && hex_len < 512 - 3; i++) {
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "%02x", data[i]);
if (i < show_len - 1 && (i + 1) % 32 == 0) { // Add space every 32 bytes
if (i < show_len - 1 && (i + 1) % 32 == 0)
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, " ");
}
}
if (len > 128) {
if (len > 128)
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "...");
switch (level) {
case DEBUG_LEVEL_ERROR: DEBUG_ERROR(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
case DEBUG_LEVEL_WARN: DEBUG_WARN(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
case DEBUG_LEVEL_INFO: DEBUG_INFO(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
case DEBUG_LEVEL_DEBUG: DEBUG_DEBUG(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
case DEBUG_LEVEL_TRACE: DEBUG_TRACE(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
}
// Single-line debug output with packet info
DEBUG_INFO(DEBUG_CATEGORY_DUMP, "%s: len=%zu hex=%s", prefix, len, hex_buf);
// Additional debug info for first few bytes
// if (len >= 2) {
// DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "%s: first_bytes=%02x%02x last_bytes=%02x%02x",
// prefix, data[0], data[1], data[len-2], data[len-1]);
// }
}
@@ -128,6 +127,7 @@ static const struct {
{"nat", DEBUG_CATEGORY_NAT},
{"keepalive", DEBUG_CATEGORY_KEEPALIVE},
{"etcp_route", DEBUG_CATEGORY_ETCPROUTE},
{"bbr", DEBUG_CATEGORY_BBR},
{"all", DEBUG_CATEGORY_ALL},
{NULL, DEBUG_CATEGORY_NONE}
};
@@ -199,32 +199,9 @@ void debug_apply_global_level(const char* level_name) {
/* Get category name for logging */
const char* debug_get_category_name(debug_category_t category_idx) {
switch (category_idx) {
case DEBUG_CATEGORY_UASYNC: return "UASYNC";
case DEBUG_CATEGORY_LL_QUEUE: return "LL_QUEUE";
case DEBUG_CATEGORY_CONNECTION: return "CONNECTION";
case DEBUG_CATEGORY_ETCP: return "ETCP";
case DEBUG_CATEGORY_CRYPTO: return "CRYPTO";
case DEBUG_CATEGORY_MEMORY: return "MEMORY";
case DEBUG_CATEGORY_TIMING: return "TIMING";
case DEBUG_CATEGORY_CONFIG: return "CONFIG";
case DEBUG_CATEGORY_TUN: return "TUN";
case DEBUG_CATEGORY_ROUTING: return "ROUTING";
case DEBUG_CATEGORY_TIMERS: return "TIMERS";
case DEBUG_CATEGORY_NORMALIZER: return "NORMALIZER";
case DEBUG_CATEGORY_BGP: return "BGP";
case DEBUG_CATEGORY_SOCKET: return "SOCKET";
case DEBUG_CATEGORY_CONTROL: return "CONTROL";
case DEBUG_CATEGORY_DUMP: return "DUMP";
case DEBUG_CATEGORY_TRAFFIC: return "TRAFFIC";
case DEBUG_CATEGORY_DEBUG: return "DEBUG";
case DEBUG_CATEGORY_GENERAL: return "GENERAL";
case DEBUG_CATEGORY_NAT: return "NAT";
case DEBUG_CATEGORY_KEEPALIVE: return "KEEPALIVE";
case DEBUG_CATEGORY_ETCPROUTE: return "ETCPROUTE";
case DEBUG_CATEGORY_NONE: return "NONE";
default: return "UNKNOWN";
}
for (int i = 0; g_debug_categories[i].name; i++)
if (g_debug_categories[i].category == category_idx) return g_debug_categories[i].name;
return "UNKNOWN";
}
/* Global debug configuration */
+3 -2
View File
@@ -60,7 +60,8 @@ typedef int debug_category_t;
#define DEBUG_CATEGORY_NAT 20 // EIM NAT module
#define DEBUG_CATEGORY_KEEPALIVE 21 // Link keepalive logic
#define DEBUG_CATEGORY_ETCPROUTE 22 // ETCP routing
#define DEBUG_CATEGORY_COUNT 23 // Total number of categories
#define DEBUG_CATEGORY_BBR 23 // BBR congestion control
#define DEBUG_CATEGORY_COUNT 24 // Total number of categories
#define DEBUG_CATEGORY_ALL (-1) // special value for all categories
/* Debug configuration structure */
@@ -123,7 +124,7 @@ ip_str_t ip_to_str(const void *addr, int family);// big endian addr
ip_str_t sockaddr_storage_to_str(const struct sockaddr_storage *addr);// автоопределение v4 или v6
// hex dump в лог
void log_dump(const char* prefix, const uint8_t* data, size_t len);
void log_dump(int level, int category, const char* prefix, const uint8_t* data, size_t len);
/* Check if debug output should be shown for given level and category */
int debug_should_output(debug_level_t level, debug_category_t category_idx);
+1 -1
View File
@@ -308,7 +308,7 @@ static void waiter_defer_cb(void* arg) {
h->defer_cb(h->defer_q, h->defer_arg);
}
inline void queue_waiter_call(struct ll_queue* q, queue_threshold_callback_fn cb, void* arg,
static inline void queue_waiter_call(struct ll_queue* q, queue_threshold_callback_fn cb, void* arg,
struct queue_waiter_handle* h) {
if (q->waiter_defer) {
h->defer_q = q; h->defer_cb = cb; h->defer_arg = arg;
+27 -20
View File
@@ -5,31 +5,43 @@
#include "memory_pool.h"
#include "mem.h"
#define POOL_CANARY_OFF (pool->object_size)
#define POOL_TAG_OFF (pool->object_size + 1)
#define POOL_ALLOC_SIZE (pool->object_size + 2)
#define POOL_CANARY_VAL 0xAA
// Layout: [user_data: object_size] [canary: 4 bytes] [alloc_loc: pointer] [counter: 1 byte]
#define POOL_CANARY_OFF (pool->object_size)
#define POOL_LOC_OFF (pool->object_size + 4)
#define POOL_COUNTER_OFF (pool->object_size + 4 + sizeof(const char*))
#define POOL_ALLOC_SIZE (pool->object_size + 4 + sizeof(const char*) + 1)
#define POOL_CANARY_VAL 0xDEADBEAF
static void pool_init_tags(struct memory_pool* pool, void* obj)
static void pool_init_tags(struct memory_pool* pool, void* obj, const char* location)
{
uint8_t* canary = (uint8_t*)obj + POOL_CANARY_OFF;
uint8_t* counter = (uint8_t*)obj + POOL_TAG_OFF;
uint32_t* canary = (uint32_t*)((uint8_t*)obj + POOL_CANARY_OFF);
const char** loc = (const char**)((uint8_t*)obj + POOL_LOC_OFF);
uint8_t* counter = (uint8_t*)obj + POOL_COUNTER_OFF;
*canary = POOL_CANARY_VAL;
*loc = location;
*counter = 1;
}
static void pool_check_and_clear_tags(struct memory_pool* pool, void* obj, const char* location)
{
uint8_t* canary = (uint8_t*)obj + POOL_CANARY_OFF;
uint8_t* counter = (uint8_t*)obj + POOL_TAG_OFF;
uint32_t* canary = (uint32_t*)((uint8_t*)obj + POOL_CANARY_OFF);
const char** loc = (const char**)((uint8_t*)obj + POOL_LOC_OFF);
uint8_t* counter = (uint8_t*)obj + POOL_COUNTER_OFF;
if (*canary != POOL_CANARY_VAL) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "pool_free BUFFER OVERFLOW %p canary=0x%02x from %s! halting", obj, *canary, location);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "pool_free BUFFER OVERFLOW pool=%p name=%s sz=%zu allocs=%zu reuse=%zu free=%d",
pool, pool->name ? pool->name : "?", pool->object_size, pool->allocations, pool->reuse_count, pool->free_count);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, " obj=%p alloc=%s free=%s canary=0x%08x expected=0x%08x",
obj, *loc ? *loc : "(null)", location, *canary, POOL_CANARY_VAL);
if (pool->object_size)
log_dump(DEBUG_LEVEL_ERROR, DEBUG_CATEGORY_MEMORY, " user_data", obj, pool->object_size);
volatile int _halt = 1;
while (_halt) {}
}
if (*counter == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "pool_free DOUBLE FREE %p from %s! halting", obj, location);
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "pool_free DOUBLE FREE pool=%p name=%s sz=%zu allocs=%zu reuse=%zu alloc=%s free=%s",
pool, pool->name ? pool->name : "?", pool->object_size, pool->allocations, pool->reuse_count,
*loc ? *loc : "(null)", location);
volatile int _halt = 1;
while (_halt) {}
}
@@ -43,7 +55,7 @@ size_t memory_pool_get_total_free_blocks(void) {
}
// Инициализировать пул памяти
struct memory_pool* memory_pool_init(size_t object_size) {
struct memory_pool* memory_pool_init(size_t object_size, const char* name) {
struct memory_pool* pool = u_calloc(1, sizeof(struct memory_pool));
if (!pool) {
return NULL;
@@ -54,6 +66,7 @@ struct memory_pool* memory_pool_init(size_t object_size) {
pool->allocations = 0;
pool->reuse_count = 0;
pool->alloc_tag_counter = 1;
pool->name = name;
return pool;
}
@@ -70,7 +83,7 @@ void* memory_pool_alloc_impl(struct memory_pool* pool, const char* location) {
g_total_free--;
pool->reuse_count++;
memset(obj, 0, pool->object_size);
pool_init_tags(pool, obj);
pool_init_tags(pool, obj, location);
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_alloc reused: %s, remaining=%zu", location, pool->free_count);
return obj;
}
@@ -78,7 +91,7 @@ void* memory_pool_alloc_impl(struct memory_pool* pool, const char* location) {
pool->allocations++;
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_alloc: %s, total_allocs=%zu", location, pool->allocations);
void* obj = u_calloc_impl(1, POOL_ALLOC_SIZE, location);
if (obj) pool_init_tags(pool, obj);
if (obj) pool_init_tags(pool, obj, location);
return obj;
}
@@ -122,20 +135,14 @@ void memory_pool_destroy(struct memory_pool* pool) {
return;
}
int cnt=0;
// Освободить все объекты в пуле
void* obj = pool->free_head;
while (obj) {
void* next = *(void**)obj;
g_total_free--;
u_free(obj);
cnt++;
obj = next;
}
// if (cnt>MEMORY_POOL_MAX_FREE)
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "pool_destroy: allocations=%zu, reuse=%zu, current pool size=%d", pool->allocations, pool->reuse_count, cnt);
pool->free_head = NULL;
pool->free_count = 0;
u_free(pool);
+2 -1
View File
@@ -18,11 +18,12 @@ struct memory_pool {
size_t allocations; // Общее количество аллокаций (включая новые malloc)
size_t reuse_count; // Количество повторных использований из пула
uint8_t alloc_tag_counter; // монотонный счётчик для детекции double-free (1 байт)
const char* name; // Имя пула для диагностики (напр. "data_pool", "pkt_pool")
};
// сам пул:
// если используем с элементами ll_queue то не забываем что к object_size надо прибавить sizeof(struct ll_entry)
struct memory_pool* memory_pool_init(size_t object_size);
struct memory_pool* memory_pool_init(size_t object_size, const char* name);
void memory_pool_destroy(struct memory_pool* pool);
// элементы пула:
+77 -34
View File
@@ -20,13 +20,14 @@ static void error_cb(socket_t sock, void* arg);
static void resume_read_cb(struct ll_queue* q, void* arg);
static void fin_deferred_cb(struct ll_queue* q, void* arg);
static void write_queue_fetch_cb(struct ll_queue* q, void* arg);
static void flush_write_buf(struct tcp_conn* tc);
static int flush_write_buf(struct tcp_conn* tc);
static void tcp_conn_handle_error(struct tcp_conn* tc, int err);
static const uint8_t tcp_fin_sentinel;
struct tcp_conn* tcp_conn_create(
struct UASYNC* ua, socket_t sock,
size_t entry_data_size, size_t write_chunk_size,
int read_high_water, int read_low_water,
int read_high_water, int read_low_water, int rcvbuf_size,
void (*on_fin)(struct tcp_conn* tc, void* arg),
void (*on_error)(struct tcp_conn* tc, int err, void* arg),
void* arg)
@@ -42,14 +43,15 @@ struct tcp_conn* tcp_conn_create(
tc->write_chunk_size = write_chunk_size;
tc->read_high_water = read_high_water;
tc->read_low_water = read_low_water;
tc->rcvbuf_size = rcvbuf_size;
tc->on_fin = on_fin;
tc->on_error = on_error;
tc->arg = arg;
tc->entry_pool = memory_pool_init(sizeof(struct ll_entry));
tc->entry_pool = memory_pool_init(sizeof(struct ll_entry), "entry_pool");
{
size_t ds = entry_data_size > write_chunk_size ? entry_data_size : write_chunk_size;
tc->data_pool = memory_pool_init(ds);
tc->data_pool = memory_pool_init(ds, "data_pool");
}
if (!tc->entry_pool || !tc->data_pool) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: memory_pool_init failed");
@@ -84,20 +86,27 @@ struct tcp_conn* tcp_conn_create(
}
tc->write_monitor = 1;
if (rcvbuf_size > 0) {
if (socket_set_buffers(sock, 0, rcvbuf_size) < 0)
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: socket_set_buffers rcvbuf=%d failed fd=%d", rcvbuf_size, (int)sock);
else
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: socket_set_buffers rcvbuf=%d ok fd=%d", rcvbuf_size, (int)sock);
}
{
struct sockaddr_storage addr;
socklen_t alen = sizeof(addr);
if (getpeername(sock, (struct sockaddr*)&addr, &alen) == 0) tc->connected = 1;
}
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: fd=%d entry=%zu chunk=%zu hw=%d lw=%d connected=%d",
(int)sock, entry_data_size, write_chunk_size, read_high_water, read_low_water, tc->connected);
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: fd=%d entry=%zu chunk=%zu hw=%d lw=%d rcvbuf=%d connected=%d",
(int)sock, entry_data_size, write_chunk_size, read_high_water, read_low_water, rcvbuf_size, tc->connected);
return tc;
}
void tcp_conn_destroy(struct tcp_conn* tc) {
if (!tc) return;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_conn_destroy: fd=%d connected=%d error=%d fin_remote=%d fin_local=%d closed=%d",
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_destroy: fd=%d connected=%d error=%d fin_remote=%d fin_local=%d closed=%d",
(int)tc->sock, tc->connected, tc->error, tc->fin_remote, tc->fin_local, tc->closed);
if (tc->socket_id) {
@@ -128,6 +137,45 @@ void tcp_conn_destroy(struct tcp_conn* tc) {
u_free(tc);
}
static void tcp_conn_handle_error(struct tcp_conn* tc, int err)
{
tc->error = 1;
queue_waiter_cancel(tc->read_queue, &tc->read_waiter);
queue_set_empty_callback(tc->read_queue, NULL, NULL);
queue_set_callback(tc->read_queue, NULL, NULL);
if (tc->sock != SOCKET_INVALID) {
while (1) {
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
if (!e) break;
uint8_t* buf = memory_pool_alloc(tc->data_pool);
if (!buf) { queue_entry_free(e); break; }
ssize_t n = recv(tc->sock, buf, tc->entry_data_size, 0);
if (n > 0) { e->dgram = buf; e->len = (uint16_t)n; queue_data_put(tc->read_queue, e); }
else { memory_pool_free(tc->data_pool, buf); queue_entry_free(e); break; }
}
}
if (tc->socket_id) {
uasync_remove_socket_t(tc->ua, tc->sock);
tc->socket_id = NULL;
}
if (tc->sock != SOCKET_INVALID) {
socket_close_wrapper(tc->sock);
tc->sock = SOCKET_INVALID;
}
queue_set_callback(tc->write_queue, NULL, NULL);
if (tc->on_error) {
void (*on_err)(struct tcp_conn*, int, void*) = tc->on_error;
void* arg = tc->arg;
tc->on_error = NULL;
on_err(tc, err, arg);
}
}
// ====================================================================
// Чтение из сокета
// ====================================================================
@@ -162,7 +210,7 @@ static void read_cb(socket_t sock, void* arg) {
memory_pool_free(tc->data_pool, buf);
queue_entry_free(e);
tc->fin_remote = 1;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_io: FIN fd=%d", (int)tc->sock);
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: FIN fd=%d", (int)tc->sock);
uasync_set_socket_read(tc->ua, tc->socket_id, 0);
queue_waiter_cancel(tc->read_queue, &tc->read_waiter);
if (tc->read_queue->count == 0) {
@@ -174,9 +222,8 @@ static void read_cb(socket_t sock, void* arg) {
memory_pool_free(tc->data_pool, buf);
queue_entry_free(e);
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return;
tc->error = 1;
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: recv error fd=%d errno=%d", (int)tc->sock, errno);
if (tc->on_error) tc->on_error(tc, errno, tc->arg);
tcp_conn_handle_error(tc, errno);
}
}
@@ -198,7 +245,7 @@ static void fin_deferred_cb(struct ll_queue* q, void* arg) {
// Запись в сокет
// ====================================================================
static void flush_write_buf(struct tcp_conn* tc) {
static int flush_write_buf(struct tcp_conn* tc) {
while (tc->write_buf && tc->write_offset < tc->write_len) {
ssize_t n = send(tc->sock, tc->write_buf + tc->write_offset, tc->write_len - tc->write_offset, MSG_NOSIGNAL);
if (n > 0) {
@@ -209,16 +256,16 @@ static void flush_write_buf(struct tcp_conn* tc) {
tc->write_len = 0;
tc->write_offset = 0;
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: flush_wbuf done fd=%d", (int)tc->sock);
return;
return 0;
}
continue;
}
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) return;
tc->error = 1;
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) return 0;
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: send error fd=%d errno=%d", (int)tc->sock, errno);
if (tc->on_error) tc->on_error(tc, errno, tc->arg);
return;
tcp_conn_handle_error(tc, errno);
return 1;
}
return 0;
}
static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
@@ -227,7 +274,7 @@ static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
if (tc->error || tc->closed) { DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch skip fd=%d error=%d closed=%d", (int)tc->sock, tc->error, tc->closed); return; }
if (tc->write_buf) {
flush_write_buf(tc);
if (flush_write_buf(tc)) return;
if (tc->write_buf) return; // остался остаток, ждём write_cb
}
@@ -249,14 +296,14 @@ static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
if (e->len == 0) {
if (!e->dgram) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_io: CLOSE fd=%d", (int)tc->sock);
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: CLOSE fd=%d", (int)tc->sock);
queue_entry_free(e); queue_resume_callback(q);
uasync_remove_socket_t(tc->ua, tc->sock);
socket_close_wrapper(tc->sock);
tc->socket_id = NULL; tc->sock = SOCKET_INVALID; tc->closed = 1;
if (tc->on_closed) tc->on_closed(tc, tc->arg);
} else if (e->dgram == &tcp_fin_sentinel) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_io: FIN fd=%d", (int)tc->sock);
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: FIN fd=%d", (int)tc->sock);
queue_entry_free(e); queue_resume_callback(q);
shutdown(tc->sock, SHUT_WR);
tc->fin_local = 1;
@@ -278,9 +325,9 @@ static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
tc->write_buf = memory_pool_alloc(tc->data_pool);
if (!tc->write_buf) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: write_buf alloc failed fd=%d", (int)tc->sock);
tc->error = 1;
memory_pool_free(tc->data_pool, e->dgram);
queue_entry_free(e);
tcp_conn_handle_error(tc, ENOMEM);
return;
}
memcpy(tc->write_buf, e->dgram + n, rem);
@@ -295,9 +342,9 @@ static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
tc->write_buf = memory_pool_alloc(tc->data_pool);
if (!tc->write_buf) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: write_buf alloc failed fd=%d", (int)tc->sock);
tc->error = 1;
memory_pool_free(tc->data_pool, e->dgram);
queue_entry_free(e);
tcp_conn_handle_error(tc, ENOMEM);
return;
}
memcpy(tc->write_buf, e->dgram, e->len);
@@ -309,11 +356,10 @@ static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
tc->write_monitor = 1;
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch POLLOUT ON fd=%d (EAGAIN, wbuf=%zu bytes)", (int)tc->sock, tc->write_len);
} else {
tc->error = 1;
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: send error fd=%d errno=%d", (int)tc->sock, errno);
if (tc->on_error) tc->on_error(tc, errno, tc->arg);
memory_pool_free(tc->data_pool, e->dgram);
queue_entry_free(e);
tcp_conn_handle_error(tc, errno);
}
}
@@ -327,17 +373,15 @@ static void write_cb(socket_t sock, void* arg) {
socklen_t len = sizeof(err);
if (getsockopt(tc->sock, SOL_SOCKET, SO_ERROR, &err, &len) == 0 && err == 0) {
tc->connected = 1;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_io: connect ok fd=%d", (int)tc->sock);
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: connect ok fd=%d", (int)tc->sock);
} else {
tc->error = 1;
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: write_cb connect fail fd=%d err=%d wmon=%d (POLLOUT still active)", (int)tc->sock, err, tc->write_monitor);
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: connect fail fd=%d err=%d", (int)tc->sock, err);
if (tc->on_error) tc->on_error(tc, err ? err : -1, tc->arg);
tcp_conn_handle_error(tc, err ? err : -1);
return;
}
}
flush_write_buf(tc);
if (flush_write_buf(tc)) return;
if (!tc->write_buf) {
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: write_cb flushed fd=%d wmon=%d (resume WQ cb)", (int)tc->sock, tc->write_monitor);
queue_resume_callback(tc->write_queue);
@@ -352,10 +396,9 @@ static void error_cb(socket_t sock, void* arg) {
(void)sock;
struct tcp_conn* tc = (struct tcp_conn*)arg;
if (!tc || tc->sock == SOCKET_INVALID) return;
tc->error = 1;
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: error_cb fd=%d connected=%d fin_remote=%d wmon=%d", (int)tc->sock, tc->connected, tc->fin_remote, tc->write_monitor);
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: async error fd=%d", (int)tc->sock);
if (tc->on_error) tc->on_error(tc, -1, tc->arg);
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: async error fd=%d connected=%d err=%d fin_r=%d fin_l=%d closed=%d wmon=%d",
(int)tc->sock, tc->connected, tc->error, tc->fin_remote, tc->fin_local, tc->closed, tc->write_monitor);
tcp_conn_handle_error(tc, -1);
}
// ====================================================================
@@ -369,7 +412,7 @@ int tcp_conn_push_fin(struct tcp_conn* tc) {
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin entry_pool exhausted fd=%d", (int)tc->sock); return -1; }
e->dgram = (uint8_t*)&tcp_fin_sentinel; e->len = 0;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin fd=%d wq=%d", (int)tc->sock, tc->write_queue->count + 1);
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin fd=%d wq=%d", (int)tc->sock, tc->write_queue->count + 1);
queue_data_put(tc->write_queue, e);
return 0;
}
@@ -380,7 +423,7 @@ int tcp_conn_push_close(struct tcp_conn* tc) {
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close entry_pool exhausted fd=%d", (int)tc->sock); return -1; }
e->dgram = NULL; e->len = 0;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close fd=%d wq=%d", (int)tc->sock, tc->write_queue->count + 1);
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close fd=%d wq=%d", (int)tc->sock, tc->write_queue->count + 1);
queue_data_put(tc->write_queue, e);
return 0;
}
+23 -38
View File
@@ -1,40 +1,23 @@
// tcp_io.h — управление TCP-соединением через uasync + ll_queue
//
// Один tcp_conn = одно TCP-соединение. Две очереди: read_queue (сокет → данные) и
// write_queue (данные → сокет). Обе работают через автозабор ll_queue (deferred).
// Один tcp_conn = одно TCP-соединение. Два пула:
// entry_pool — struct ll_entry (без inline data)
// data_pool — буферы (max(entry_data_size, write_chunk_size))
//
// Пул-аллокация:
// entry_pool — только struct ll_entry (без inline data)
// data_pool — буферы данных чтения/записи (max(entry_data_size, write_chunk_size))
// Данные и структуры аллоцируются раздельно — не копируются при recv/send.
// Чтение: read_cb → recv() в data_pool → entry в read_queue → callback внешнего кода.
// high_water/low_water управляют backpressure (EPOLLIN on/off).
// FIN (recv==0) → on_fin (сразу или через empty_callback после дренажа очереди).
//
// Чтение:
// read_cb → memory_pool_alloc(read_pool) → recv() прямо в буфер → entry в read_queue
// read_queue автозабор(deferred) → внешний коллбэк (например, отправка в ETCP)
// high_water → пауза чтения (убираем EPOLLIN), low_water/waiter → возобновление
// FIN: если read_queue не пуст → empty_callback откладывает on_fin; иначе сразу
// Запись: внешний код кладёт entry в write_queue через queue_data_put().
// write_queue_fetch_cb (deferred) → send(), при EAGAIN → write_buf + EPOLLOUT.
// Всё отправлено → on_flushed.
//
// Запись:
// Внешний код: tcp_conn_push_write(data, len) → аллокация через write_pool → entry в write_queue
// write_queue автозабор(deferred): fetch_cb → send() в сокет
// EAGAIN → write_buf (из data_pool) + EPOLLOUT ON → write_cb досылает → resume автозабора
// Всё отправлено → EPOLLOUT OFF + on_flushed (если установлен)
// FIN/Close: tcp_conn_push_fin/close ставят сентинел в write_queue.
// FIN → shutdown(SHUT_WR) → on_fin_sent
// CLOSE → close сокета → on_closed
//
// FIN / Close через очередь отправки:
// tcp_conn_push_fin(tc) — ставит FIN в write_queue (dgram=&sentinel, len=0)
// tcp_conn_push_close(tc) — ставит CLOSE в write_queue (dgram=NULL, len=0)
// Оба сигнала обрабатываются после всех предшествующих данных в очереди:
// FIN → shutdown(SHUT_WR), стоп чтения, on_fin_sent
// CLOSE → close сокета, on_closed
// После FIN входящие данные отбрасываются (read_cb → discard).
// После CLOSE сокет закрыт, tc->closed=1, дальнейшая отправка невозможна.
//
// Connect:
// tcp_conn_create регистрирует сокет с read_cb + write_cb в uasync
// После create вызывается connect() (неблокирующий, EINPROGRESS)
// write_cb детектит завершение connect через getsockopt(SO_ERROR)
// getpeername() сразу после create проверяет pre-connected сокеты (socketpair)
// До connect данные копятся в write_queue (автозабор не пытается send на unconnected сокет)
// Ошибка: любая ошибка сокета → tcp_conn_handle_error → close + null коллбэков → on_error.
// Реализация on_error обязана вызвать tcp_conn_destroy. После возврата tc недействителен.
#ifndef TCP_IO_H
#define TCP_IO_H
@@ -54,6 +37,7 @@ struct tcp_conn {
int read_high_water;
int read_low_water;
int rcvbuf_size;
uint8_t read_paused;
uint8_t write_monitor; // 1 = EPOLLOUT активен
@@ -63,7 +47,7 @@ struct tcp_conn {
uint8_t fin_local; // FIN отправлен удалённой стороне (shutdown SHUT_WR)
uint8_t closed; // сокет полностью закрыт (close)
// Частичная отправка (из write_pool, не в очереди — досылается первой)
// Частичная отправка (из data_pool, не в очереди — досылается первой)
uint8_t* write_buf;
size_t write_len;
size_t write_offset;
@@ -79,7 +63,7 @@ struct tcp_conn {
// Коллбэки
void (*on_fin)(struct tcp_conn* tc, void* arg); // FIN получен от удалённой стороны (fin_remote=1)
void (*on_fin_sent)(struct tcp_conn* tc, void* arg); // FIN отправлен удалённой стороне (fin_local=1)
void (*on_error)(struct tcp_conn* tc, int err, void* arg); // ошибка сокета
void (*on_error)(struct tcp_conn* tc, int err, void* arg); // фатальная ошибка (вызывается после close сокета; обязан вызвать tcp_conn_destroy)
void (*on_flushed)(struct tcp_conn* tc, void* arg); // все данные записи отправлены (write_queue + write_buf пусты)
void (*on_closed)(struct tcp_conn* tc, void* arg); // сокет закрыт через очередь (после close)
void* arg;
@@ -92,21 +76,22 @@ struct tcp_conn* tcp_conn_create(
size_t write_chunk_size,
int read_high_water,
int read_low_water,
int rcvbuf_size,
void (*on_fin)(struct tcp_conn* tc, void* arg),
void (*on_error)(struct tcp_conn* tc, int err, void* arg),
void* arg);
// Дренирует очереди, закрывает сокет, уничтожает пулы, освобождает tc.
void tcp_conn_destroy(struct tcp_conn* tc);
// Внешний код пишет данные в tc->write_queue напрямую (queue_data_put).
// Автозабор write_queue (deferred) сам отправляет когда сокет готов.
// Перед push проверять порог: queue_set_threshold в tcp_conn_create (32 entries).
// При заполнении — queue_waiter_wait на освобождение.
// Запись: queue_data_put(tc->write_queue, entry) напрямую.
// Автозабор (deferred) сам шлёт когда сокет готов.
// Порог: 32 записи, при заполнении queue_waiter_wait.
// Поставить сигналы в очередь отправки. Все данные в очереди перед сигналом
// будут отправлены до его обработки. Кодирование: dgram=NULL → close, len=0+dgram!=NULL → FIN.
// tcp_conn_push_fin(tc) — FIN: после отправки предшествующих данных вызывает
// shutdown(SHUT_WR), останавливает чтение, вызывает on_fin_sent.
// shutdown(SHUT_WR) и on_fin_sent.
// Повторный вызов игнорируется (fin_sent уже установлен).
// tcp_conn_push_close(tc) — CLOSE: после отправки предшествующих данных закрывает
// сокет (close), вызывает on_closed.
+1 -1
View File
@@ -1347,7 +1347,7 @@ struct UASYNC* uasync_create(void) {
}
// Initialize timeout pool
ua->timeout_pool = memory_pool_init(sizeof(struct timeout_node));
ua->timeout_pool = memory_pool_init(sizeof(struct timeout_node), "timeout_pool");
if (!ua->timeout_pool) {
timeout_heap_destroy(ua->timeout_heap);
socket_array_destroy(ua->sockets);
+9
View File
@@ -0,0 +1,9 @@
tui enable
stepi # шаг по инструкции
nexti
step # шаг по строке исходника
next
bt
frame n
p *var
+3
View File
@@ -26,6 +26,9 @@ utun_CORE_SOURCES = \
etcp_dump.c \
secure_channel.c \
crc32.c \
stcp.c \
stcp_server.c \
stcp_client.c \
pkt_normalizer.c \
packet_dump.c \
etcp_api.c \
+11 -9
View File
@@ -720,7 +720,8 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
cfg->global.name[0] = '\0';
cfg->global.keepalive_timeout = 2000; // Default 2 seconds
cfg->global.keepalive_interval = 200; // Default 0.2 s
cfg->global.bbr_max_cwnd = 100000; // Default 100KB
cfg->global.bbr_max_cwnd = 1048576; // Default 1MB (was INFLIGHT_LIM_MAX)
cfg->global.tcp_recv_buf = 0; // 0 = OS default
cfg->global.firewall_rules = NULL;
cfg->global.firewall_rule_count = 0;
cfg->global.firewall_bypass_all = 0;
@@ -869,8 +870,9 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
break;
case SECTION_TCP_PROXY_SERVER:
cfg->global.tcp_proxy_server_enabled = 1;
// tcp_proxy_server section has no options — its presence alone enables it
(void)key; (void)value;
if (strcmp(key, "tcp_recv_buf") == 0) {
cfg->global.tcp_recv_buf = atoi(value);
}
break;
default:
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Key outside section: %s", filename, line_num, key);
@@ -986,9 +988,9 @@ void print_config(const struct utun_config *cfg) {
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Servers:");
struct CFG_SERVER *s = cfg->servers;
while (s) {
struct sockaddr_in *sin = (struct sockaddr_in *)&s->ip;
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s: %s:%d (mark=%d, netif=%u, type=%u, mtu=%d)",
s->name, ip_to_str(&sin->sin_addr, AF_INET).str, ntohs(sin->sin_port),
ip_str_t addr_str = sockaddr_storage_to_str(&s->ip);
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s: %s (mark=%d, netif=%u, type=%u, mtu=%d)",
s->name, addr_str.str,
s->so_mark, s->netif_index, s->type, s->mtu);
s = s->next;
}
@@ -999,9 +1001,9 @@ void print_config(const struct utun_config *cfg) {
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s: peer_key=%s, keepalive=%d", c->name, c->peer_public_key_hex, c->keepalive);
struct CFG_CLIENT_LINK *link = c->links;
while (link) {
struct sockaddr_in *sin = (struct sockaddr_in *)&link->remote_addr;
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " Link: %s:%d (via %s)",
ip_to_str(&sin->sin_addr, AF_INET).str, ntohs(sin->sin_port), link->local_srv->name);
ip_str_t addr_str = sockaddr_storage_to_str(&link->remote_addr);
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " Link: %s (via %s)",
addr_str.str, link->local_srv->name);
link = link->next;
}
c = c->next;
+1
View File
@@ -161,6 +161,7 @@ struct global_config {
// TCP proxy server (exit node) configuration ([tcp_proxy_server] section)
int tcp_proxy_server_enabled;
int tcp_recv_buf; // SO_RCVBUF для TCP сокетов (0=default ОС)
};
struct utun_config {
+1 -1
View File
@@ -355,7 +355,7 @@ struct dummynet* dummynet_create(struct UASYNC* ua, const char* bind_ip, uint16_
dn->listen_port = listen_port;
/* Создаём пул для пакетов */
dn->pkt_pool = memory_pool_init(sizeof(struct ll_entry) + sizeof(struct dummynet_pkt));
dn->pkt_pool = memory_pool_init(sizeof(struct ll_entry) + sizeof(struct dummynet_pkt), "pkt_pool");
if (!dn->pkt_pool) {
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Failed to create packet pool");
u_free(dn);
+52 -41
View File
@@ -205,8 +205,9 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
etcp->input_wait_ack = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, 0, 4, "input_wait_ack"); // Hash for wait_ack
etcp->recv_q = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, 0, 4, "recv_q"); // Hash for recv_q
etcp->ack_q = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, 0, 4, "ack_q");
etcp->inflight_pool = memory_pool_init(sizeof(struct INFLIGHT_PACKET));
etcp->io_pool = memory_pool_init(sizeof(struct ETCP_FRAGMENT));
etcp->inflight_pool = memory_pool_init(sizeof(struct INFLIGHT_PACKET), "inflight_pool");
etcp->io_pool = memory_pool_init(sizeof(struct ETCP_FRAGMENT), "io_pool");
etcp->max_inflight = (uint32_t)instance->config->global.bbr_max_cwnd;
etcp->optimal_inflight=100000;
etcp->initialized=0;
etcp->links_up=0;
@@ -302,23 +303,26 @@ void etcp_connection_close(struct ETCP_CONN* etcp) {
etcp->normalizer = NULL;
}
// Drain and free all queues using helper functions
// Clear links BEFORE draining queues: etcp_link_close calls
// etcp_conn_on_inflight_lim_changed -> input_queue_try_resume
// which accesses input_send_q, input_queue, input_wait_ack.
if (etcp->links) {
struct ETCP_LINK* link = etcp->links;
while (link) {
struct ETCP_LINK* next = link->next;
etcp_link_close(link);
link = next;
}
etcp->links = NULL;
}
// Drain and free all queues
drain_and_free_queue(&etcp->input_queue);
drain_and_free_queue(&etcp->output_queue);
drain_and_free_queue(&etcp->input_send_q);
drain_and_free_queue(&etcp->input_wait_ack);
drain_and_free_queue(&etcp->recv_q);
drain_and_free_queue(&etcp->ack_q);
// Drain and free ack_q (contains ACK_PACKET from ack_pool - special handling)
if (etcp->ack_q) {
struct ACK_PACKET* pkt;
while ((pkt = (struct ACK_PACKET*)queue_data_get(etcp->ack_q)) != NULL) {
queue_entry_free((struct ll_entry*)pkt);
}
queue_free(etcp->ack_q);
etcp->ack_q = NULL;
}
// Free memory pools after all elements are returned
if (etcp->inflight_pool) {
@@ -331,17 +335,6 @@ void etcp_connection_close(struct ETCP_CONN* etcp) {
etcp->io_pool = NULL;
}
// Clear links list safely
if (etcp->links) {
struct ETCP_LINK* link = etcp->links;
while (link) {
struct ETCP_LINK* next = link->next;
etcp_link_close(link);
link = next;
}
etcp->links = NULL;
}
u_free(etcp->name);
// Clear next pointer to prevent dangling references
@@ -402,6 +395,8 @@ void etcp_conn_reset(struct ETCP_CONN* etcp) {
l->inflight_bytes = 0;
l->inflight_packets = 0;
l->delivered_bytes = 0;
l->acked_bytes = 0;
l->acked_packets = 0;
l->last_ack_time_tb = get_time_tb();
l = l->next;
}
@@ -624,7 +619,7 @@ void etcp_conn_on_inflight_lim_changed(struct ETCP_CONN* etcp) {
uint32_t sum = 0;
for (struct ETCP_LINK* tl = etcp->links; tl; tl = tl->next) sum += tl->inflight_lim_bytes;
etcp->optimal_inflight = sum;
input_queue_try_resume(etcp);
// input_queue_try_resume(etcp);
}
void etcp_stats(struct ETCP_CONN* etcp) {
@@ -1251,14 +1246,28 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d
link->delivered_bytes += rs.delivered;
link->last_ack_time_tb = now_tb;
uint32_t new_cwnd = link->inflight_lim_bytes;
uint32_t new_pacing = link->bbr_pacing_rate;
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] BBR in: seq=%u del=%u iv=%uus rtt=%uus pri=%u tx_infl=%u cur=%u/%u lost=%d app=%d",
link->local_link_id, seq, rs.delivered, rs.interval_us, rs.rtt_us,
rs.prior_delivered, rs.tx_in_flight, link->inflight_bytes, link->inflight_lim_bytes,
rs.lost, rs.is_app_limited);
uint32_t old_cwnd = link->inflight_lim_bytes;
uint32_t old_pacing = link->bbr_pacing_rate;
uint32_t new_cwnd = old_cwnd;
uint32_t new_pacing = old_pacing;
bbr_main(link->bbr, &rs, &new_cwnd, &new_pacing, link->mtu, link->inflight_bytes,
(link->inflight_bytes >= link->inflight_lim_bytes));
etcp_link_update_inflight_lim(link, new_cwnd);
link->bbr_pacing_rate = new_pacing;
link->bandwidth = (uint32_t)((uint64_t)new_pacing * 8 / 1000);
link->bbr_loss_since_ack = 0;
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] BBR out: cwnd %u→%u pace %u→%u bw=%uK mode=%d cyc=%d infl=%u/%u",
link->local_link_id, old_cwnd, new_cwnd, old_pacing, new_pacing,
link->bandwidth, link->bbr->mode, link->bbr->cycle_idx, link->inflight_bytes, new_cwnd);
link->acked_bytes += acked_pkt->ll.len;
link->acked_packets++;
}
@@ -1403,21 +1412,23 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
break;
}
}
struct ACK_PACKET* p = (struct ACK_PACKET*)queue_entry_new_from_pool(etcp->instance->ack_pool);
if (!p) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to allocate ACK_PACKET", etcp->log_name);
len = 0;
break;
}
p->seq=seq;
p->pkt_timestamp=pkt->timestamp;
p->recv_timestamp=get_current_timestamp();
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX add to ack_q seq=%d", etcp->log_name, seq);
if (queue_find_data_by_index(etcp->ack_q, &seq) == NULL) {
struct ACK_PACKET* p = (struct ACK_PACKET*)queue_entry_new_from_pool(etcp->instance->ack_pool);
if (!p) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to allocate ACK_PACKET", etcp->log_name);
len = 0;
break;
}
p->seq=seq;
p->pkt_timestamp=pkt->timestamp;
p->recv_timestamp=get_current_timestamp();
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX add to ack_q seq=%d", etcp->log_name, seq);
queue_data_put_with_index(etcp->ack_q, (struct ll_entry*)p);
if (etcp->ack_resp_timer == NULL) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] set ack_timer for delayed ACK send", etcp->log_name);
etcp->ack_resp_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_TB, etcp, ack_response_timer_cb, "etcp_ack_resp");
queue_data_put_with_index(etcp->ack_q, (struct ll_entry*)p);
if (etcp->ack_resp_timer == NULL) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] set ack_timer for delayed ACK send", etcp->log_name);
etcp->ack_resp_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_TB, etcp, ack_response_timer_cb, "etcp_ack_resp");
}
}
if (((int32_t)(etcp->last_delivered_id-seq)<0) && (queue_find_data_by_index(etcp->recv_q, &seq)==NULL)) {// проверяем есть ли пакет с этим seq
uint32_t pkt_len=len-5;
+1 -1
View File
@@ -154,7 +154,7 @@ struct ETCP_CONN {
// Window and inflight management
uint32_t unacked_bytes; // Current inflight bytes
// uint32_t window_size; // Receive window - Not used
uint32_t max_inflight; // Max inflight cap from config (bbr_max_cwnd)
uint32_t optimal_inflight; // Sum over links
// Timers
+13
View File
@@ -1,5 +1,6 @@
#include "etcp_bbr.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include <stdlib.h>
static uint64_t bbr_now(const struct bbr* bbr) { return bbr->now_tb ? bbr->now_tb : get_time_tb(); }
@@ -327,10 +328,14 @@ static void bbr_exit_probe_rtt(struct bbr* bbr)
{
bbr_reset_lower_bounds(bbr);
if (bbr->full_bw_reached) {
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d exit probe_rtt → PROBE_BW",
bbr->link_id, bbr->mode, BBR_PROBE_BW);
bbr->mode = BBR_PROBE_BW;
bbr_start_bw_probe_down(bbr);
bbr_start_bw_probe_cruise(bbr);
} else {
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d exit probe_rtt → STARTUP",
bbr->link_id, bbr->mode, BBR_STARTUP);
bbr->mode = BBR_STARTUP;
}
}
@@ -536,11 +541,13 @@ static void bbr_check_drain(struct bbr* bbr, const struct bbr_rate_sample* rs,
{
(void)rs; (void)sample_bw;
if (bbr->mode == BBR_STARTUP && bbr->full_bw_reached) {
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d STARTUP→DRAIN", bbr->link_id, bbr->mode, BBR_DRAIN);
bbr->mode = BBR_DRAIN;
bbr_reset_congestion_signals(bbr);
}
if (bbr->mode == BBR_DRAIN &&
inflight_packets <= bbr_inflight(bbr, bbr_max_bw(bbr), BBR_UNIT, mss)) {
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d DRAIN→PROBE_BW infl_pkt=%u", bbr->link_id, bbr->mode, BBR_PROBE_BW, inflight_packets);
bbr->mode = BBR_PROBE_BW;
bbr_start_bw_probe_down(bbr);
}
@@ -584,6 +591,8 @@ static void bbr_update_min_rtt(struct bbr* bbr, const struct bbr_rate_sample* rs
if (BBR_PROBE_RTT_MS > 0 && probe_rtt_expired &&
!bbr->idle_restart && bbr->mode != BBR_PROBE_RTT) {
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d →PROBE_RTT cwnd=%u infl_pkt=%u",
bbr->link_id, bbr->mode, BBR_PROBE_RTT, *cwnd, inflight_packets);
bbr->mode = BBR_PROBE_RTT;
bbr_save_cwnd(bbr, *cwnd);
bbr->probe_rtt_done_stamp_tb = 0;
@@ -610,6 +619,7 @@ static void bbr_update_min_rtt(struct bbr* bbr, const struct bbr_rate_sample* rs
static void bbr_reset_startup_mode(struct bbr* bbr)
{
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d →STARTUP (reset)", bbr->link_id, bbr->mode, BBR_STARTUP);
bbr->mode = BBR_STARTUP;
}
@@ -862,6 +872,8 @@ void bbr_init(struct bbr* bbr)
bbr->rounds_since_probe = 0;
bbr->bw_probe_samples = 0;
bbr->prev_probe_too_high = 0;
bbr->on_cwnd_update = NULL;
bbr->cwnd_update_ctx = NULL;
}
void bbr_main(struct bbr* bbr, const struct bbr_rate_sample* rs,
@@ -906,6 +918,7 @@ void bbr_main(struct bbr* bbr, const struct bbr_rate_sample* rs,
out:
bbr_advance_latest_delivery_signals(bbr, rs, sample_bw);
bbr->loss_in_cycle |= (rs->lost > 0);
if (bbr->on_cwnd_update) bbr->on_cwnd_update(bbr->cwnd_update_ctx, cwnd);
*cwnd_out = cwnd;
}
+5
View File
@@ -45,6 +45,8 @@ struct bbr_rate_sample {
int is_app_limited;
};
typedef void (*bbr_cwnd_update_fn)(void* ctx, uint32_t new_cwnd);
struct bbr {
uint32_t min_rtt_us;
uint32_t bw_lo;
@@ -96,7 +98,10 @@ struct bbr {
uint8_t loss_round_start : 1;
uint8_t full_bw_now : 1;
uint8_t pad_unused : 6;
uint8_t link_id; // local_link_id for debug logging
uint64_t now_tb; // 0 = real get_time_tb(); >0 = test-controlled time
bbr_cwnd_update_fn on_cwnd_update;
void* cwnd_update_ctx;
};
void bbr_init(struct bbr* bbr);
+12 -9
View File
@@ -795,6 +795,8 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn
link->burst_target_bdp = 0;
link->delivered_bytes = 0;
link->acked_bytes = 0;
link->acked_packets = 0;
link->last_ack_time_tb = get_time_tb();
link->bbr_pacing_rate = 0;
link->bbr_loss_since_ack = 0;
@@ -813,6 +815,7 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn
return NULL;
}
link->local_link_id = (uint8_t)free_id;
if (link->bbr) link->bbr->link_id = (uint8_t)free_id;
memcpy(&link->remote_addr, remote_addr, sizeof(struct sockaddr_storage));
@@ -847,18 +850,18 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn
void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim) {
if (!link || !link->etcp) return;
if (new_lim < INFLIGHT_LIM_MIN) new_lim = INFLIGHT_LIM_MIN;
if (new_lim > INFLIGHT_LIM_MAX) new_lim = INFLIGHT_LIM_MAX;
if (new_lim > link->etcp->max_inflight) new_lim = link->etcp->max_inflight;
uint32_t old = link->inflight_lim_bytes;
link->inflight_lim_bytes = new_lim;
if (old != new_lim && link->inflight_bytes < new_lim && link->send_blocked_inflight) {
if (link->inflight_bytes < new_lim && link->send_blocked_inflight) {// разблокируем линк
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] unblocking link %p (inflight_lim %u -> %u)", link->etcp->log_name, link, old, new_lim);
etcp_conn_on_inflight_lim_changed(link->etcp);// сперва обновим inflight в etcp
link->send_blocked_inflight = 0;
loadbalancer_link_ready(link);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] unblocked link %p (inflight_lim %u -> %u)", link->etcp->log_name, link, old, new_lim);
}
if (!link->shaper_timer) loadbalancer_link_ready(link);// потом вызовем ready -> разблок
} else etcp_conn_on_inflight_lim_changed(link->etcp);//
etcp_conn_on_inflight_lim_changed(link->etcp);
}
void etcp_link_close(struct ETCP_LINK* link) {
@@ -944,7 +947,7 @@ int etcp_encrypt_send(struct ETCP_DGRAM* dgram) {
dgram->timestamp=get_current_timestamp();
dgram->link->total_encrypted += dgram->data_len;
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CRYPTO)) log_dump("Before encryption", dgram->data, dgram->data_len);
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CRYPTO)) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CRYPTO, "Before encryption", dgram->data, dgram->data_len);
sc_encrypt(sc, (uint8_t*)&dgram->timestamp, 3 + len, enc_buf, &enc_buf_len);
if (enc_buf_len == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "eencryption failed for node %016llx", (unsigned long long)dgram->link->etcp->instance->node_id);
@@ -954,7 +957,7 @@ int etcp_encrypt_send(struct ETCP_DGRAM* dgram) {
dgram->link->send_errors++; errcode=3; goto es_err; }
memcpy(enc_buf+enc_buf_len, dgram->data+len, dgram->noencrypt_len);
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CRYPTO)) log_dump("Encrypted", enc_buf, enc_buf_len + dgram->noencrypt_len);
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CRYPTO)) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CRYPTO, "Encrypted", enc_buf, enc_buf_len + dgram->noencrypt_len);
struct sockaddr_storage* addr=&dgram->link->remote_addr;
socklen_t addr_len = (addr->ss_family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
@@ -1184,7 +1187,7 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
}
// DUMP: Show received packet content
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CRYPTO)) log_dump("RECV in:", data, recv_len); // link unknown at this point
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CRYPTO)) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CRYPTO, "RECV in:", data, recv_len);
struct ETCP_DGRAM* pkt = memory_pool_alloc(e_sock->instance->pkt_pool);
if (!pkt) return;
+2
View File
@@ -182,6 +182,8 @@ struct ETCP_LINK {
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
+3 -3
View File
@@ -95,9 +95,9 @@ struct lwip_tcp_ctx *lwip_tcp_init(struct UASYNC *ua, tcp_output_fn output, void
ctx->ua = ua;
ctx->output = output;
ctx->output_arg = output_arg;
ctx->pcb_pool = memory_pool_init(sizeof(struct tcp_pcb));
ctx->pcb_listen_pool = memory_pool_init(sizeof(struct tcp_pcb_listen));
ctx->seg_pool = memory_pool_init(sizeof(struct tcp_seg));
ctx->pcb_pool = memory_pool_init(sizeof(struct tcp_pcb), "pcb_pool");
ctx->pcb_listen_pool = memory_pool_init(sizeof(struct tcp_pcb_listen), "pcb_listen_pool");
ctx->seg_pool = memory_pool_init(sizeof(struct tcp_seg), "seg_pool");
if (!ctx->pcb_pool || !ctx->pcb_listen_pool || !ctx->seg_pool) {
lwip_tcp_destroy(ctx);
return NULL;
+4 -4
View File
@@ -241,7 +241,7 @@ static void pn_send_to_etcp(struct PKTNORM* pn) {
frag->ll.memlen = pn->etcp->instance->data_pool->object_size;
DEBUG_INFO(DEBUG_CATEGORY_NORMALIZER, "pn->etcp: size=%d memlen=%d frag_size=%d", frag->ll.len, frag->ll.memlen, pn->frag_size);
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump("NORM->ETCP", pn->data, frag->ll.len);
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP, "NORM->ETCP", pn->data, frag->ll.len);
queue_data_put(pn->etcp->input_queue, (struct ll_entry*)frag);
// Сбросить структуру (dgram передан во фрагмент, не освобождаем)
@@ -286,7 +286,7 @@ static void etcp_input_ready_cb(struct ll_queue* q, void* arg) {
return;
}
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump("->NORM", in_dgram->dgram, in_dgram->len);
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP, "->NORM", in_dgram->dgram, in_dgram->len);
pn_buf_renew(pn);
// DEBUG_INFO(DEBUG_CATEGORY_NORMALIZER, "pn_packer: new pkt hdrpos=%d",pn->data_ptr);
@@ -350,7 +350,7 @@ static void pn_unpacker_cb(struct ll_queue* q, void* arg) {
uint8_t* payload = frag->ll.dgram;
uint16_t len = frag->ll.len;
uint16_t ptr = 0;
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump("ETCP->NORM", payload, len);
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP, "ETCP->NORM", payload, len);
DEBUG_DEBUG(DEBUG_CATEGORY_NORMALIZER, "unpacking fragment len=%d", len);
@@ -395,7 +395,7 @@ static void pn_unpacker_cb(struct ll_queue* q, void* arg) {
if (pn->recvpart->len == pn->recvpart->memlen) {
DEBUG_DEBUG(DEBUG_CATEGORY_NORMALIZER, "unpacked dgram (size=%d)", pn->recvpart->len);
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump("NORM->", pn->recvpart->dgram, pn->recvpart->len);
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP, "NORM->", pn->recvpart->dgram, pn->recvpart->len);
queue_data_put(pn->output, pn->recvpart);
pn->out_total_pkts++;
pn->out_total_bytes += pn->recvpart->len;
+3 -3
View File
@@ -539,8 +539,8 @@ void tcp_proxy_client_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* en
if (subcmd == TCP_PROXY_SUBCMD_ERROR) { tcp_proxy_client_handle_error(proxy, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_FIN) { tcp_proxy_client_handle_fin(proxy, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
}
if (subcmd == TCP_PROXY_SUBCMD_ERROR) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY ERROR sid=%08x — client conn not found, silent drop", stream_id);
if (subcmd == TCP_PROXY_SUBCMD_DATA || subcmd == TCP_PROXY_SUBCMD_CLOSE || subcmd == TCP_PROXY_SUBCMD_FIN || subcmd == TCP_PROXY_SUBCMD_ERROR) {
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "PROXY subcmd=%02x sid=%08x — client conn not found, silent drop", subcmd, stream_id);
queue_dgram_free(entry); queue_entry_free(entry); return;
}
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy client: unhandled subcmd=%02x sid=%08x", subcmd, stream_id);
@@ -564,7 +564,7 @@ struct tcp_proxy_client* tcp_proxy_client_create(struct UTUN_INSTANCE* inst, str
p->via_node_id = via_node_id;
p->mappings = mappings; p->mapping_count = mapping_count;
p->entry_pool = memory_pool_init(sizeof(struct ll_entry));
p->entry_pool = memory_pool_init(sizeof(struct ll_entry), "entry_pool");
if (!p->entry_pool) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: memory_pool_init failed"); u_free(p); return NULL; }
p->tun = tun_init_nat(ua, tun_name, tun_ip, mtu > 0 ? mtu : 1500, test_mode);
+24 -18
View File
@@ -114,16 +114,16 @@ static void on_fin_cb(struct tcp_conn* tc, void* arg) {
if (rc->cli_closed) return;
int pend = write_pending(tc);
if (tc->fin_local) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x fin_local=%d — both FINs, closing",
(int)tc->sock, rc->stream_id, tc->fin_local);
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x fin_local=%d — both FINs, closing",
(int)tc->sock, rc->stream_id, tc->fin_local);
send_close(rc); tcp_conn_push_close(tc);
} else if (pend) {
tcp_conn_set_flushed(tc, on_flushed_cb);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x write_pend=%d → deferred",
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x write_pend=%d → deferred",
(int)tc->sock, rc->stream_id, pend);
} else {
send_fin(rc);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x → relay now",
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x → relay now",
(int)tc->sock, rc->stream_id);
}
}
@@ -132,7 +132,7 @@ static void on_flushed_cb(struct tcp_conn* tc, void* arg) {
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
if (rc->cli_closed) return;
if (tc->fin_local) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FLUSHED fd=%d sid=%08x fin_local=%d — both FINs, closing",
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FLUSHED fd=%d sid=%08x fin_local=%d — both FINs, closing",
(int)tc->sock, rc->stream_id, tc->fin_local);
send_close(rc); tcp_conn_push_close(tc); return;
}
@@ -141,7 +141,7 @@ static void on_flushed_cb(struct tcp_conn* tc, void* arg) {
static void on_closed_cb(struct tcp_conn* tc, void* arg) {
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:CLOSED fd=%d sid=%08x — freeing",
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:CLOSED fd=%d sid=%08x — freeing",
tc ? (int)tc->sock : -1, rc->stream_id);
tcp_proxy_server_conn_free(rc);
}
@@ -156,6 +156,7 @@ static void on_error_cb(struct tcp_conn* tc, int err, void* arg) {
static void diag_timer_cb(void* arg) {
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
rc->diag_timer = NULL;
struct tcp_conn* tc = rc->tc;
if (!tc || tc->sock == SOCKET_INVALID) return;
@@ -167,7 +168,7 @@ static void diag_timer_cb(void* arg) {
getsockopt(tc->sock, SOL_SOCKET, SO_RCVBUF, &rcv_buf, &optlen);
getsockopt(tc->sock, SOL_SOCKET, SO_SNDBUF, &snd_buf, &optlen);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET,
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET,
"SOCK:DIAG fd=%d sid=%08x conn=%d fin=%d "
"rq=%d(%zub) wq=%d(%zub) wbuf=%s "
"ent_f=%d dat_f=%d tcp_rb=%zu tcp_sb=%zu",
@@ -207,7 +208,7 @@ static void read_queue_drain_cb(struct ll_queue* q, void* arg) {
if (ret == 0) {
queue_resume_callback(q);
} else {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:BACKPRESSURE fd=%d sid=%08x — pausing drain",
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:BACKPRESSURE fd=%d sid=%08x — pausing drain",
(int)rc->tc->sock, rc->stream_id);
etcp_router_on_send_ready(inst, rc->peer_node_id, ETCP_ID_TCP_PROXY_CLIENT,
&rc->pause_waiter, pause_resume_cb, rc);
@@ -234,14 +235,14 @@ static int conn_total(struct tcp_proxy_server_conn* rc) {
void tcp_proxy_server_conn_free(struct tcp_proxy_server_conn* rc) {
if (!rc) return;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FREE enter rc=%p freed=%d sid=%08x", (void*)rc, rc->freed, rc->stream_id);
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FREE enter rc=%p freed=%d sid=%08x", (void*)rc, rc->freed, rc->stream_id);
if (rc->freed) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:FREE double rc=%p — IGNORED", (void*)rc);
return;
}
rc->freed = 1;
int total = conn_total(rc);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FREE fd=%d sid=%08x total=%d cli_closed=%d fin=%d error=%d",
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FREE fd=%d sid=%08x total=%d cli_closed=%d fin=%d error=%d",
rc->tc ? (int)rc->tc->sock : -1, rc->stream_id, total, rc->cli_closed, rc->tc ? rc->tc->fin_remote : 0, rc->tc ? rc->tc->error : 0);
if (rc->close_pending && rc->ctx && rc->ctx->inst) {
rc->close_pending = 0;
@@ -256,7 +257,7 @@ void tcp_proxy_server_conn_free(struct tcp_proxy_server_conn* rc) {
if (rc->close_timer) { uasync_cancel_timeout(rc->ua, rc->close_timer); rc->close_timer = NULL; }
if (rc->diag_timer) { uasync_cancel_timeout(rc->ua, rc->diag_timer); rc->diag_timer = NULL; }
if (rc->ctx && rc->ctx->inst) etcp_router_cancel_send_ready(rc->ctx->inst, rc->peer_node_id, ETCP_ID_TCP_PROXY_CLIENT, &rc->pause_waiter);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FREE u_free rc=%p", (void*)rc);
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FREE u_free rc=%p", (void*)rc);
u_free(rc);
}
@@ -286,11 +287,11 @@ int tcp_proxy_server_handle_connect(struct UTUN_INSTANCE* inst, struct ll_entry*
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: socket() failed"); u_free(rc); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
ctx->conn_count++;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:NEW fd=%d sid=%08x dest=%d.%d.%d.%d:%d total=%d",
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:NEW fd=%d sid=%08x dest=%d.%d.%d.%d:%d total=%d",
(int)sock, stream_id, dest_ip[0],dest_ip[1],dest_ip[2],dest_ip[3],ntohs(dest_port), ctx->conn_count);
socket_set_nonblocking(sock);
rc->tc = tcp_conn_create(inst->ua, sock, 1500, 8192, 4, 0, on_fin_cb, on_error_cb, rc);
rc->tc = tcp_conn_create(inst->ua, sock, 1500, 8192, 4, 0, inst->config->global.tcp_recv_buf, on_fin_cb, on_error_cb, rc);
if (!rc->tc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: tcp_conn_create failed"); socket_close_wrapper(sock); ctx->conn_count--; u_free(rc); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
rc->tc->on_closed = on_closed_cb;
queue_set_callback(rc->tc->read_queue, read_queue_drain_cb, rc);
@@ -325,6 +326,11 @@ int tcp_proxy_server_handle_data(struct UTUN_INSTANCE* inst, struct ETCP_CONN* c
}
size_t data_len = entry->len - TCP_PROXY_HDR_SIZE;
if (data_len > 0) {
if (data_len > rc->tc->data_pool->object_size) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_server: data_len=%zu > pool_sz=%zu, dropping sid=%08x",
data_len, rc->tc->data_pool->object_size, stream_id);
queue_dgram_free(entry); queue_entry_free(entry); return -1;
}
struct ll_entry* e = queue_entry_new_from_pool(rc->tc->entry_pool);
uint8_t* buf = memory_pool_alloc(rc->tc->data_pool);
if (e && buf) {
@@ -346,7 +352,7 @@ void tcp_proxy_server_handle_close(struct UTUN_INSTANCE* inst, uint32_t stream_i
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(ctx, stream_id);
if (!rc) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy server: CLOSE sid=%08x — no conn", stream_id); return; }
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:CLOSE_RECV fd=%d sid=%08x total=%d fin=%d write_pend=%d",
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:CLOSE_RECV fd=%d sid=%08x total=%d fin=%d write_pend=%d",
rc->tc ? (int)rc->tc->sock : -1, stream_id, conn_total(rc),
rc->tc ? rc->tc->fin_remote : 0, rc->tc ? write_pending(rc->tc) : 0);
rc->cli_closed = 1;
@@ -368,7 +374,7 @@ void tcp_proxy_server_handle_error(struct UTUN_INSTANCE* inst, uint32_t stream_i
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(ctx, stream_id);
if (!rc) { DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy server: ERROR sid=%08x — no conn", stream_id); return; }
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:ERROR_RECV fd=%d sid=%08x total=%d fin=%d write_pend=%d",
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:ERROR_RECV fd=%d sid=%08x total=%d fin=%d write_pend=%d",
rc->tc ? (int)rc->tc->sock : -1, stream_id, conn_total(rc),
rc->tc ? rc->tc->fin_remote : 0, rc->tc ? write_pending(rc->tc) : 0);
tcp_proxy_server_handle_close(inst, stream_id);
@@ -379,7 +385,7 @@ void tcp_proxy_server_handle_fin(struct UTUN_INSTANCE* inst, uint32_t stream_id)
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(ctx, stream_id);
if (!rc || !rc->tc || !rc->tc->connected) return;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FIN_RECV fd=%d sid=%08x — pushing FIN to wq",
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FIN_RECV fd=%d sid=%08x — pushing FIN to wq",
(int)rc->tc->sock, stream_id);
tcp_conn_push_fin(rc->tc);
}
@@ -424,8 +430,8 @@ void tcp_proxy_server_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (subcmd == TCP_PROXY_SUBCMD_FIN) { tcp_proxy_server_handle_fin(inst, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
}
}
if (subcmd == TCP_PROXY_SUBCMD_ERROR) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY ERROR sid=%08x — server conn not found, silent drop", stream_id);
if (subcmd == TCP_PROXY_SUBCMD_DATA || subcmd == TCP_PROXY_SUBCMD_CLOSE || subcmd == TCP_PROXY_SUBCMD_FIN || subcmd == TCP_PROXY_SUBCMD_ERROR) {
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "PROXY subcmd=%02x sid=%08x — server conn not found, silent drop", subcmd, stream_id);
queue_dgram_free(entry); queue_entry_free(entry); return;
}
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy server: unhandled subcmd=%02x sid=%08x", subcmd, stream_id);
+1 -1
View File
@@ -40,7 +40,7 @@ static struct udp_flow* flow_find(struct udp_flow* head, uint64_t client_node_id
static void flow_read_cb(socket_t sock, void* arg) {
(void)sock; struct udp_flow* f = (struct udp_flow*)arg;
if (!f || f->sock == SOCKET_INVALID || !g_udp_ctx) return;
uint8_t buf[65536];
uint8_t buf[1600];
struct sockaddr_in from; socklen_t flen = sizeof(from);
ssize_t n = recvfrom(f->sock, buf, sizeof(buf), 0, (struct sockaddr*)&from, &flen);
if (n <= 0) return;
+100
View File
@@ -35,6 +35,7 @@
#include "../tinycrypt/lib/include/tinycrypt/ecc_dh.h"
#include "../tinycrypt/lib/include/tinycrypt/aes.h"
#include "../tinycrypt/lib/include/tinycrypt/ccm_mode.h"
#include "../tinycrypt/lib/include/tinycrypt/ctr_mode.h"
#include "../tinycrypt/lib/include/tinycrypt/constants.h"
#include "../tinycrypt/lib/include/tinycrypt/ecc_platform_specific.h"
#include "../tinycrypt/lib/include/tinycrypt/sha256.h"
@@ -142,6 +143,23 @@ static void sc_derive_session_key(const uint8_t *shared_secret, uint8_t *session
memcpy(session_key, hash, SC_SESSION_KEY_SIZE);
}
// Derive 12-byte nonce for stream cipher from session key + stream_id
static void sc_stream_derive_nonce(const uint8_t *session_key, uint32_t stream_id, uint8_t *nonce_out) {
SC_SHA256_CTX sha_ctx;
uint8_t hash[SC_HASH_SIZE];
uint8_t stream_id_le[4];
stream_id_le[0] = (uint8_t)(stream_id >> 0);
stream_id_le[1] = (uint8_t)(stream_id >> 8);
stream_id_le[2] = (uint8_t)(stream_id >> 16);
stream_id_le[3] = (uint8_t)(stream_id >> 24);
sc_sha256_init(&sha_ctx);
sc_sha256_update(&sha_ctx, session_key, SC_SESSION_KEY_SIZE);
sc_sha256_update(&sha_ctx, stream_id_le, sizeof(stream_id_le));
sc_sha256_update(&sha_ctx, (const uint8_t *)"uTun3-stream", 12);
sc_sha256_final(&sha_ctx, hash);
memcpy(nonce_out, hash, SC_STREAM_NONCE_SIZE);
}
#ifdef USE_OPENSSL
// OpenSSL-specific implementations
@@ -513,6 +531,50 @@ sc_status_t sc_compute_public_key_from_private(const uint8_t *private_key, uint8
return SC_OK;
}
// --- OpenSSL streaming cipher (AES-128-CTR) ---
sc_status_t sc_stream_init(sc_context_t *ctx, struct sc_stream_state *state, uint32_t stream_id) {
if (!ctx || !state) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_stream_init: invalid args"); return SC_ERR_INVALID_ARG; }
if (!ctx->session_ready) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_stream_init: session not ready"); return SC_ERR_NOT_INITIALIZED; }
uint8_t nonce[SC_STREAM_NONCE_SIZE];
sc_stream_derive_nonce(ctx->session_key, stream_id, nonce);
uint8_t iv[16];
memcpy(iv, nonce, SC_STREAM_NONCE_SIZE);
memset(iv + SC_STREAM_NONCE_SIZE, 0, 4);
EVP_CIPHER_CTX *ectx = EVP_CIPHER_CTX_new();
if (!ectx) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_stream_init: EVP_CIPHER_CTX_new failed"); return SC_ERR_CRYPTO; }
if (EVP_EncryptInit_ex(ectx, EVP_aes_128_ctr(), NULL, ctx->session_key, iv) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_stream_init: EVP_EncryptInit_ex failed");
EVP_CIPHER_CTX_free(ectx);
return SC_ERR_CRYPTO;
}
state->ectx = ectx;
state->initialized = 1;
DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "sc_stream_init: stream_id=%u nonce=%02x%02x%02x%02x...",
stream_id, nonce[0], nonce[1], nonce[2], nonce[3]);
return SC_OK;
}
sc_status_t sc_stream_xor(struct sc_stream_state *state, uint8_t *data, size_t data_len) {
if (data_len == 0) return SC_OK;
if (!state || !data) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_stream_xor: invalid args"); return SC_ERR_INVALID_ARG; }
if (!state->initialized) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_stream_xor: not initialized"); return SC_ERR_NOT_INITIALIZED; }
int outlen;
if (EVP_EncryptUpdate((EVP_CIPHER_CTX *)state->ectx, data, &outlen, data, (int)data_len) != 1
|| (size_t)outlen != data_len) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_stream_xor: EVP_EncryptUpdate failed outlen=%d datalen=%zu", outlen, data_len);
return SC_ERR_CRYPTO;
}
return SC_OK;
}
void sc_stream_cleanup(struct sc_stream_state *state) {
if (!state || !state->ectx) return;
EVP_CIPHER_CTX_free((EVP_CIPHER_CTX *)state->ectx);
state->ectx = NULL;
state->initialized = 0;
}
#else
// Original TinyCrypt implementations (unchanged logic)
@@ -816,6 +878,44 @@ sc_status_t sc_compute_public_key_from_private(const uint8_t *private_key, uint8
return SC_OK;
}
// --- TinyCrypt streaming cipher (AES-128-CTR) ---
sc_status_t sc_stream_init(sc_context_t *ctx, struct sc_stream_state *state, uint32_t stream_id) {
if (!ctx || !state) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_stream_init: invalid args"); return SC_ERR_INVALID_ARG; }
if (!ctx->session_ready) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_stream_init: session not ready"); return SC_ERR_NOT_INITIALIZED; }
uint8_t nonce[SC_STREAM_NONCE_SIZE];
sc_stream_derive_nonce(ctx->session_key, stream_id, nonce);
memcpy(state->ctr_block, nonce, SC_STREAM_NONCE_SIZE);
memset(state->ctr_block + SC_STREAM_NONCE_SIZE, 0, 4);
if (tc_aes128_set_encrypt_key((TCAesKeySched_t)state->sched_buf, ctx->session_key) != TC_CRYPTO_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_stream_init: tc_aes128_set_encrypt_key failed");
return SC_ERR_CRYPTO;
}
state->initialized = 1;
DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "sc_stream_init: stream_id=%u nonce=%02x%02x%02x%02x...",
stream_id, nonce[0], nonce[1], nonce[2], nonce[3]);
return SC_OK;
}
sc_status_t sc_stream_xor(struct sc_stream_state *state, uint8_t *data, size_t data_len) {
if (data_len == 0) return SC_OK;
if (!state || !data) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_stream_xor: invalid args"); return SC_ERR_INVALID_ARG; }
if (!state->initialized) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_stream_xor: not initialized"); return SC_ERR_NOT_INITIALIZED; }
if (tc_ctr_mode(data, (unsigned int)data_len, data, (unsigned int)data_len,
state->ctr_block, (TCAesKeySched_t)state->sched_buf) != TC_CRYPTO_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_stream_xor: tc_ctr_mode failed len=%zu", data_len);
return SC_ERR_CRYPTO;
}
return SC_OK;
}
void sc_stream_cleanup(struct sc_stream_state *state) {
if (!state) return;
memset(state->sched_buf, 0, sizeof(state->sched_buf));
memset(state->ctr_block, 0, sizeof(state->ctr_block));
state->initialized = 0;
}
#endif
sc_status_t sc_sha_transcode(const uint8_t *key, size_t key_len, uint8_t *data, size_t data_len) {
+19
View File
@@ -75,4 +75,23 @@ sc_status_t sc_sha_transcode(const uint8_t *key, size_t key_len, uint8_t *data,
// Obfuscate pubkey при передаче: XOR с SHA256(salt+peer_pubkey) || SHA256(peer_pubkey+salt)
sc_status_t sc_obfuscate_pubkey(const uint8_t *salt, const uint8_t *peer_pubkey, const uint8_t *pubkey, uint8_t *output);
// --- Streaming cipher (AES-128-CTR, confidentiality only) ---
#define SC_STREAM_NONCE_SIZE 12
#define SC_STREAM_AES_SCHED_SIZE 176 // sizeof(struct tc_aes_key_sched_struct) = Nb*(Nr+1)*4 = 4*11*4
struct sc_stream_state {
#ifdef USE_OPENSSL
void *ectx; // EVP_CIPHER_CTX* (opaque, created/destroyed in .c)
#else
uint8_t sched_buf[SC_STREAM_AES_SCHED_SIZE]; // AES-128 key schedule
uint8_t ctr_block[16]; // nonce(12) + counter(4, BE)
#endif
uint8_t initialized;
};
sc_status_t sc_stream_init(sc_context_t *ctx, struct sc_stream_state *state, uint32_t stream_id);
sc_status_t sc_stream_xor(struct sc_stream_state *state, uint8_t *data, size_t data_len);
void sc_stream_cleanup(struct sc_stream_state *state);
#endif // SECURE_CHANNEL_H
+35
View File
@@ -0,0 +1,35 @@
// stcp.c — shared stcp_conn lifecycle
#include "stcp.h"
#include "../lib/ll_queue.h"
#include "../lib/mem.h"
#include "../lib/debug_config.h"
#include <stdlib.h>
#include <string.h>
void stcp_conn_set_tx_queue(struct stcp_conn *c, struct ll_queue *q) {
if (!c || !q) return;
c->tx_queue = q;
if (c->tx_cb) queue_set_callback(q, c->tx_cb, c);
}
void stcp_conn_set_rx_queue(struct stcp_conn *c, struct ll_queue *q) {
if (!c || !q) return;
c->rx_queue = q;
}
void stcp_conn_set_on_close(struct stcp_conn *c, void (*cb)(struct stcp_conn *conn, int err, void *arg), void *arg) {
if (!c || !cb) return;
c->on_close = cb;
c->close_arg = arg;
}
void stcp_conn_free(struct stcp_conn *c) {
if (!c) return;
if (c->socket_id) { uasync_remove_socket_t(c->ua, c->sock); c->socket_id = NULL; }
if (c->sock != SOCKET_INVALID) { socket_close_wrapper(c->sock); c->sock = SOCKET_INVALID; }
if (c->recv_buf) { u_free(c->recv_buf); c->recv_buf = NULL; }
if (c->send_buf) { u_free(c->send_buf); c->send_buf = NULL; }
sc_stream_cleanup(&c->stream_send);
sc_stream_cleanup(&c->stream_recv);
if (c->allocated) u_free(c);
}
+80
View File
@@ -0,0 +1,80 @@
// stcp.h — Streaming TCP: shared structures, constants, connection lifecycle
#ifndef STCP_H
#define STCP_H
#include "secure_channel.h"
#include "crc32.h"
#include "../lib/u_async.h"
#include "../lib/socket_compat.h"
#include "../lib/ll_queue.h"
#include "../lib/debug_config.h"
#include <stdint.h>
#define STCP_MAX_MSG_SIZE 65535
#define STCP_RECV_BUF_INIT 8192
#define STCP_RECV_BUF_MAX 131072
#define STCP_HS_TIMEOUT 5000
#define STCP_CONNECT_TIMEOUT 10000
#define STCP_HS_CLIENT_MIN (SC_PUBKEY_ENC_SIZE + 6) // 72 + 2(padding_size enc) + 4(crc) = 78
#define STCP_HS_SERVER_MIN (SC_PUBKEY_ENC_SIZE + 7) // 72 + 1(status) + 2(padding_size) + 4(crc) = 79
#define STCP_HS_ENC_CLIENT 6 // padding_size(2) + CRC32(4)
#define STCP_HS_ENC_SERVER 7 // status(1) + padding_size(2) + CRC32(4)
#define STCP_STREAM_CLIENT_SEND 0
#define STCP_STREAM_SERVER_SEND 1
enum stcp_state {
STCP_STATE_INIT,
STCP_STATE_HS_CLIENT_SENT,
STCP_STATE_HS_SERVER_WAIT,
STCP_STATE_DATA,
STCP_STATE_CLOSED,
STCP_STATE_ERROR
};
struct stcp_conn {
socket_t sock;
struct UASYNC *ua;
void *socket_id;
enum stcp_state state;
uint8_t is_server;
uint8_t allocated; // 1 = allocated by u_calloc, free in stcp_conn_free
uint8_t session_key[SC_SESSION_KEY_SIZE];
struct sc_stream_state stream_send;
struct sc_stream_state stream_recv;
struct SC_MYKEYS my_keys;
uint8_t peer_pubkey[SC_PUBKEY_SIZE];
uint8_t peer_pubkey_set;
struct ll_queue *rx_queue;
struct ll_queue *tx_queue;
void (*tx_cb)(struct ll_queue *q, void *arg);
uint8_t *recv_buf;
size_t recv_buf_len;
size_t recv_buf_cap;
uint8_t *send_buf;
size_t send_len;
size_t send_offset;
size_t hs_expected_len;
uint8_t hs_key_processed;
void (*on_ready)(struct stcp_conn *conn, void *arg);
void *ready_arg;
void (*on_close)(struct stcp_conn *conn, int err, void *arg);
void *close_arg;
};
void stcp_conn_free(struct stcp_conn *c);
void stcp_conn_set_tx_queue(struct stcp_conn *c, struct ll_queue *q);
void stcp_conn_set_rx_queue(struct stcp_conn *c, struct ll_queue *q);
void stcp_conn_set_on_close(struct stcp_conn *c, void (*cb)(struct stcp_conn *conn, int err, void *arg), void *arg);
#endif
+282
View File
@@ -0,0 +1,282 @@
// stcp_client.c — STCP client implementation
#include "stcp_client.h"
#include "secure_channel.h"
#include "crc32.h"
#include "../lib/u_async.h"
#include "../lib/socket_compat.h"
#include "../lib/ll_queue.h"
#include "../lib/mem.h"
#include "../lib/debug_config.h"
#include "../lib/platform_compat.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifndef _WIN32
#include <unistd.h>
#include <fcntl.h>
#include <netinet/tcp.h>
#endif
struct stcp_client {
struct stcp_conn conn;
struct UASYNC *ua;
stcp_ready_cb ready_cb;
void *ready_arg;
uint8_t peer_pubkey[SC_PUBKEY_SIZE];
};
static void client_connect_write_cb(socket_t sock, void *arg);
static void client_conn_read_cb(socket_t sock, void *arg);
static void client_conn_write_cb(socket_t sock, void *arg);
static void client_tx_queue_cb(struct ll_queue *q, void *arg);
static void stcp_conn_process_recv(struct stcp_conn *c);
static int client_try_send(struct stcp_conn *c, const uint8_t *data, size_t len);
static void client_do_close(struct stcp_conn *c, int err);
static int encrypt_and_crc(struct stcp_conn *c, const uint8_t *data, size_t data_len,
uint8_t *output, size_t *output_len) {
uint32_t crc = crc32_calc(data, data_len);
output[0] = (uint8_t)(data_len >> 0);
output[1] = (uint8_t)(data_len >> 8);
memcpy(output + 2, data, data_len);
output[2 + data_len + 0] = (uint8_t)(crc >> 0);
output[2 + data_len + 1] = (uint8_t)(crc >> 8);
output[2 + data_len + 2] = (uint8_t)(crc >> 16);
output[2 + data_len + 3] = (uint8_t)(crc >> 24);
if (sc_stream_xor(&c->stream_send, output + 2, data_len + 4) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "encrypt_and_crc: stream_xor failed len=%zu", data_len);
return -1;
}
*output_len = 2 + data_len + 4;
return 0;
}
static int decrypt_and_check(uint8_t *data, size_t len, struct sc_stream_state *stream, size_t *out_len) {
if (sc_stream_xor(stream, data, len) != SC_OK) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "decrypt_and_check: stream_xor failed"); return -1; }
size_t data_len = len - SC_CRC32_SIZE;
uint32_t recv_crc = ((uint32_t)data[data_len]) | ((uint32_t)data[data_len + 1] << 8) |
((uint32_t)data[data_len + 2] << 16) | ((uint32_t)data[data_len + 3] << 24);
uint32_t calc_crc = crc32_calc(data, data_len);
if (recv_crc != calc_crc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "decrypt_and_check: CRC mismatch"); return -1; }
*out_len = data_len;
return 0;
}
static void client_do_close(struct stcp_conn *c, int err) {
if (c->state == STCP_STATE_CLOSED || c->state == STCP_STATE_ERROR) return;
c->state = STCP_STATE_CLOSED;
if (c->socket_id) { uasync_remove_socket_t(c->ua, c->sock); c->socket_id = NULL; }
if (c->sock != SOCKET_INVALID) { socket_close_wrapper(c->sock); c->sock = SOCKET_INVALID; }
if (c->recv_buf) { u_free(c->recv_buf); c->recv_buf = NULL; c->recv_buf_len = 0; c->recv_buf_cap = 0; }
if (c->send_buf) { u_free(c->send_buf); c->send_buf = NULL; c->send_len = 0; }
if (c->on_close) { void (*cb)(struct stcp_conn*, int, void*) = c->on_close; c->on_close = NULL; cb(c, err, c->close_arg); }
}
static int client_try_send(struct stcp_conn *c, const uint8_t *data, size_t len) {
if (c->sock == SOCKET_INVALID) return -1;
if (c->send_buf) return -1;
ssize_t sent = send(c->sock, data, len, 0);
if (sent < 0) {
int e = socket_get_error();
if (e == ERR_AGAIN || e == ERR_WOULDBLOCK) { c->send_buf = (uint8_t *)data; c->send_len = len; c->send_offset = 0; uasync_set_socket_write(c->ua, c->socket_id, 1); return 0; }
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "client_try_send: send failed err=%d", e);
u_free((uint8_t *)data); client_do_close(c, e); return -1;
}
if ((size_t)sent < len) { c->send_buf = (uint8_t *)data; c->send_len = len; c->send_offset = (size_t)sent; uasync_set_socket_write(c->ua, c->socket_id, 1); return 0; }
u_free((uint8_t *)data);
return 0;
}
static void client_conn_write_cb(socket_t sock, void *arg) {
struct stcp_conn *c = (struct stcp_conn *)arg;
if (!c->send_buf) { uasync_set_socket_write(c->ua, c->socket_id, 0); return; }
ssize_t sent = send(sock, c->send_buf + c->send_offset, c->send_len - c->send_offset, 0);
if (sent < 0) {
int e = socket_get_error();
if (e == ERR_AGAIN || e == ERR_WOULDBLOCK) return;
client_do_close(c, e); return;
}
c->send_offset += (size_t)sent;
if (c->send_offset >= c->send_len) { u_free(c->send_buf); c->send_buf = NULL; c->send_len = 0; c->send_offset = 0; uasync_set_socket_write(c->ua, c->socket_id, 0); }
}
static void client_tx_queue_cb(struct ll_queue *q, void *arg) {
struct stcp_conn *c = (struct stcp_conn *)arg;
struct ll_entry *e = queue_data_get(q);
if (!e) { queue_resume_callback(q); return; }
if (c->state == STCP_STATE_DATA && e->dgram) {
size_t need;
uint8_t *buf = u_malloc(2 + e->len + SC_CRC32_SIZE);
if (buf && !encrypt_and_crc(c, e->dgram, e->len, buf, &need)) client_try_send(c, buf, need);
else if (buf) u_free(buf);
}
queue_entry_free(e);
queue_resume_callback(q);
}
static int client_derive_session(struct stcp_conn *c, const uint8_t *peer_pubkey) {
struct secure_channel sc;
sc_init_ctx(&sc, &c->my_keys);
if (sc_set_peer_public_key(&sc, peer_pubkey, SC_PEER_PUBKEY_BIN) != SC_OK) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "client ECDH failed"); return -1; }
memcpy(c->session_key, sc.session_key, SC_SESSION_KEY_SIZE);
if (sc_stream_init(&sc, &c->stream_send, STCP_STREAM_CLIENT_SEND) != SC_OK) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "client stream_send init failed"); return -1; }
if (sc_stream_init(&sc, &c->stream_recv, STCP_STREAM_SERVER_SEND) != SC_OK) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "client stream_recv init failed"); return -1; }
return 0;
}
static void client_send_handshake(struct stcp_conn *c, const uint8_t *server_pubkey) {
uint8_t salt[SC_PUBKEY_ENC_SALT_SIZE];
if (random_bytes(salt, SC_PUBKEY_ENC_SALT_SIZE) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "client random_bytes failed"); client_do_close(c, 1); return; }
uint16_t padding = 8;
size_t total = SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_CLIENT + padding;
uint8_t *hs = u_malloc(total);
if (!hs) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "client hs malloc failed"); client_do_close(c, 1); return; }
memcpy(hs, salt, SC_PUBKEY_ENC_SALT_SIZE);
sc_obfuscate_pubkey(salt, server_pubkey, c->my_keys.public_key, hs + SC_PUBKEY_ENC_SALT_SIZE);
uint8_t plain[2] = {(uint8_t)padding, (uint8_t)(padding >> 8)};
uint32_t crc = crc32_calc(plain, 2);
uint8_t *enc_dst = hs + SC_PUBKEY_ENC_SIZE;
enc_dst[0] = plain[0]; enc_dst[1] = plain[1];
enc_dst[2] = (uint8_t)(crc >> 0); enc_dst[3] = (uint8_t)(crc >> 8);
enc_dst[4] = (uint8_t)(crc >> 16); enc_dst[5] = (uint8_t)(crc >> 24);
if (sc_stream_xor(&c->stream_send, enc_dst, STCP_HS_ENC_CLIENT) != SC_OK) { u_free(hs); client_do_close(c, 2); return; }
for (int i = 0; i < padding; i++) hs[SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_CLIENT + i] = (uint8_t)(salt[0] ^ i);
c->state = STCP_STATE_HS_CLIENT_SENT;
client_try_send(c, hs, total);
}
static void process_server_response(struct stcp_conn *c) {
if (!c->hs_key_processed) {
const uint8_t *salt = c->recv_buf;
const uint8_t *enc_pubkey = salt + SC_PUBKEY_ENC_SALT_SIZE;
uint8_t server_pubkey[SC_PUBKEY_SIZE];
sc_obfuscate_pubkey(salt, c->my_keys.public_key, enc_pubkey, server_pubkey);
c->hs_key_processed = 1;
}
uint8_t enc_hs[STCP_HS_ENC_SERVER];
memcpy(enc_hs, c->recv_buf + SC_PUBKEY_ENC_SIZE, STCP_HS_ENC_SERVER);
size_t hs_data_len;
if (decrypt_and_check(enc_hs, STCP_HS_ENC_SERVER, &c->stream_recv, &hs_data_len)) { client_do_close(c, 3); return; }
uint8_t status = enc_hs[0];
uint16_t padding_size = (uint16_t)enc_hs[1] | ((uint16_t)enc_hs[2] << 8);
c->hs_expected_len = SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_SERVER + padding_size;
if (status != 0) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "server handshake status=%d", status); client_do_close(c, 4); return; }
}
static void finish_server_response(struct stcp_conn *c) {
memmove(c->recv_buf, c->recv_buf + c->hs_expected_len, c->recv_buf_len - c->hs_expected_len);
c->recv_buf_len -= c->hs_expected_len;
c->hs_expected_len = 0;
c->hs_key_processed = 0;
c->state = STCP_STATE_DATA;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "stcp_client: handshake OK, entering DATA state");
if (c->on_ready) { void (*cb)(struct stcp_conn*, void*) = c->on_ready; c->on_ready = NULL; cb(c, c->ready_arg); }
}
static void stcp_conn_process_recv(struct stcp_conn *c) {
while (c->recv_buf_len > 0) {
switch (c->state) {
case STCP_STATE_HS_CLIENT_SENT:
if (!c->hs_key_processed) {
if (c->recv_buf_len < SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_SERVER) return;
process_server_response(c);
if (c->state != STCP_STATE_HS_CLIENT_SENT) return;
}
if (c->hs_key_processed && c->recv_buf_len >= c->hs_expected_len) { finish_server_response(c); return; }
return;
case STCP_STATE_DATA: {
if (c->recv_buf_len < 2) return;
uint16_t msg_size = (uint16_t)c->recv_buf[0] | ((uint16_t)c->recv_buf[1] << 8);
if (msg_size > STCP_MAX_MSG_SIZE) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "client msg too large: %u", msg_size); client_do_close(c, 5); return; }
size_t total = 2 + msg_size + SC_CRC32_SIZE;
if (c->recv_buf_len < total) return;
uint8_t *enc_data = c->recv_buf + 2;
size_t data_len;
if (decrypt_and_check(enc_data, msg_size + SC_CRC32_SIZE, &c->stream_recv, &data_len)) { client_do_close(c, 6); return; }
if (c->rx_queue) {
struct ll_entry *e = queue_entry_new(0);
if (e) { e->dgram = u_malloc(data_len); if (e->dgram) { if (data_len) memcpy(e->dgram, enc_data, data_len); e->len = (uint16_t)data_len; queue_data_put(c->rx_queue, e); } else { queue_entry_free(e); } }
}
memmove(c->recv_buf, c->recv_buf + total, c->recv_buf_len - total);
c->recv_buf_len -= total;
break;
}
default: return;
}
}
}
static void client_conn_read_cb(socket_t sock, void *arg) {
struct stcp_conn *c = (struct stcp_conn *)arg;
if (c->state == STCP_STATE_CLOSED || c->state == STCP_STATE_ERROR) return;
if (!c->recv_buf) { c->recv_buf_cap = STCP_RECV_BUF_INIT; c->recv_buf = u_malloc(c->recv_buf_cap); if (!c->recv_buf) { client_do_close(c, ENOMEM); return; } }
if (c->recv_buf_len + 4096 > c->recv_buf_cap) {
size_t nc = c->recv_buf_cap * 2; if (nc > STCP_RECV_BUF_MAX) nc = STCP_RECV_BUF_MAX;
if (nc <= c->recv_buf_cap) { client_do_close(c, ENOBUFS); return; }
uint8_t *nb = u_realloc(c->recv_buf, nc); if (!nb) { client_do_close(c, ENOMEM); return; }
c->recv_buf = nb; c->recv_buf_cap = nc;
}
ssize_t n = recv(sock, c->recv_buf + c->recv_buf_len, c->recv_buf_cap - c->recv_buf_len, 0);
if (n < 0) { int e = socket_get_error(); if (e == ERR_AGAIN || e == ERR_WOULDBLOCK) return; client_do_close(c, e); return; }
if (n == 0) { client_do_close(c, 0); return; }
c->recv_buf_len += (size_t)n;
stcp_conn_process_recv(c);
}
static void client_connect_write_cb(socket_t sock, void *arg) {
struct stcp_client *cli = (struct stcp_client *)arg;
struct stcp_conn *c = &cli->conn;
int err = 0;
socklen_t len = sizeof(err);
if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&err, &len) < 0 || err != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_client connect failed err=%d", err);
client_do_close(c, err); return;
}
uasync_remove_socket_t(cli->ua, sock);
c->socket_id = uasync_add_socket_t(cli->ua, sock, client_conn_read_cb, client_conn_write_cb, NULL, c);
if (!c->socket_id) { client_do_close(c, ENOMEM); return; }
int opt = 1; setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (const char *)&opt, sizeof(opt));
if (client_derive_session(c, cli->peer_pubkey)) { client_do_close(c, 1); return; }
client_send_handshake(c, cli->peer_pubkey);
}
struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uint16_t port,
struct SC_MYKEYS *keys, const uint8_t *peer_pubkey,
stcp_ready_cb ready_cb, void *arg) {
if (!ua || !addr || !keys || !peer_pubkey || !ready_cb) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_client_connect: invalid args"); return NULL; }
struct stcp_client *cli = u_calloc(1, sizeof(struct stcp_client));
if (!cli) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_client_connect: calloc failed"); return NULL; }
cli->ua = ua; cli->ready_cb = ready_cb; cli->ready_arg = arg;
memcpy(cli->peer_pubkey, peer_pubkey, SC_PUBKEY_SIZE);
struct stcp_conn *c = &cli->conn;
c->ua = ua; c->state = STCP_STATE_INIT; c->is_server = 0; c->my_keys = *keys;
c->on_ready = ready_cb; c->ready_arg = arg;
c->tx_cb = client_tx_queue_cb;
c->sock = socket(AF_INET, SOCK_STREAM, 0);
if (c->sock == SOCKET_INVALID) { u_free(cli); return NULL; }
socket_set_nonblocking(c->sock);
struct sockaddr_in saddr; memset(&saddr, 0, sizeof(saddr));
saddr.sin_family = AF_INET; saddr.sin_port = htons(port);
if (inet_pton(AF_INET, addr, &saddr.sin_addr) != 1) { socket_close_wrapper(c->sock); u_free(cli); return NULL; }
if (connect(c->sock, (struct sockaddr *)&saddr, sizeof(saddr)) < 0 && socket_get_error() != EINPROGRESS) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_client connect to %s:%u failed err=%d", addr, port, socket_get_error());
socket_close_wrapper(c->sock); u_free(cli); return NULL;
}
c->socket_id = uasync_add_socket_t(ua, c->sock, NULL, client_connect_write_cb, NULL, cli);
if (!c->socket_id) { socket_close_wrapper(c->sock); u_free(cli); return NULL; }
return cli;
}
void stcp_client_destroy(struct stcp_client *cli) {
if (!cli) return;
client_do_close(&cli->conn, 0);
stcp_conn_free(&cli->conn);
u_free(cli);
}
struct stcp_conn *stcp_client_get_conn(struct stcp_client *cli) {
return cli ? &cli->conn : NULL;
}
+15
View File
@@ -0,0 +1,15 @@
// stcp_client.h — STCP client: connect, handshake
#ifndef STCP_CLIENT_H
#define STCP_CLIENT_H
#include "stcp.h"
typedef void (*stcp_ready_cb)(struct stcp_conn *conn, void *arg);
struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uint16_t port,
struct SC_MYKEYS *keys, const uint8_t *peer_pubkey,
stcp_ready_cb ready_cb, void *arg);
void stcp_client_destroy(struct stcp_client *cli);
struct stcp_conn *stcp_client_get_conn(struct stcp_client *cli);
#endif
+375
View File
@@ -0,0 +1,375 @@
// stcp_server.c — STCP server implementation
#include "stcp_server.h"
#include "secure_channel.h"
#include "crc32.h"
#include "../lib/u_async.h"
#include "../lib/socket_compat.h"
#include "../lib/ll_queue.h"
#include "../lib/memory_pool.h"
#include "../lib/mem.h"
#include "../lib/debug_config.h"
#include "../lib/platform_compat.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifndef _WIN32
#include <unistd.h>
#include <fcntl.h>
#include <netinet/tcp.h>
#endif
struct stcp_server {
struct UASYNC *ua;
socket_t listen_sock;
void *listen_id;
struct SC_MYKEYS my_keys;
stcp_connect_cb connect_cb;
void *cb_arg;
};
static void server_accept_cb(socket_t sock, void *arg);
static void server_conn_read_cb(socket_t sock, void *arg);
static void server_conn_write_cb(socket_t sock, void *arg);
static void tx_queue_cb(struct ll_queue *q, void *arg);
static void stcp_conn_process_recv(struct stcp_conn *c);
static int stcp_conn_try_send(struct stcp_conn *c, const uint8_t *data, size_t len);
static void stcp_conn_do_close(struct stcp_conn *c, int err);
static int stcp_encrypt_and_crc(struct stcp_conn *c, const uint8_t *data, size_t data_len,
uint8_t *output, size_t *output_len) {
uint32_t crc = crc32_calc(data, data_len);
output[0] = (data_len >> 0) & 0xFF;
output[1] = (data_len >> 8) & 0xFF;
memcpy(output + 2, data, data_len);
output[2 + data_len + 0] = (uint8_t)(crc >> 0);
output[2 + data_len + 1] = (uint8_t)(crc >> 8);
output[2 + data_len + 2] = (uint8_t)(crc >> 16);
output[2 + data_len + 3] = (uint8_t)(crc >> 24);
size_t enc_len = 2 + data_len + 4;
if (sc_stream_xor(&c->stream_send, output + 2, data_len + 4) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_encrypt_and_crc: stream_xor failed len=%zu", data_len);
return -1;
}
*output_len = enc_len;
return 0;
}
static int stcp_decrypt_and_check(uint8_t *plaintext, size_t plaintext_len,
struct sc_stream_state *stream, size_t *out_len) {
if (sc_stream_xor(stream, plaintext, plaintext_len) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_decrypt_and_check: stream_xor failed len=%zu", plaintext_len);
return -1;
}
size_t data_len = plaintext_len - 4;
uint32_t recv_crc = ((uint32_t)plaintext[data_len]) | ((uint32_t)plaintext[data_len + 1] << 8) |
((uint32_t)plaintext[data_len + 2] << 16) | ((uint32_t)plaintext[data_len + 3] << 24);
uint32_t calc_crc = crc32_calc(plaintext, data_len);
if (recv_crc != calc_crc) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_decrypt_and_check: CRC mismatch recv=%08x calc=%08x", recv_crc, calc_crc);
return -1;
}
*out_len = data_len;
return 0;
}
static void stcp_conn_do_close(struct stcp_conn *c, int err) {
if (c->state == STCP_STATE_CLOSED || c->state == STCP_STATE_ERROR) return;
int prev = c->state;
c->state = STCP_STATE_CLOSED;
if (c->socket_id) { uasync_remove_socket_t(c->ua, c->sock); c->socket_id = NULL; }
if (c->sock != SOCKET_INVALID) { socket_close_wrapper(c->sock); c->sock = SOCKET_INVALID; }
if (c->recv_buf) { u_free(c->recv_buf); c->recv_buf = NULL; c->recv_buf_len = 0; c->recv_buf_cap = 0; }
if (c->send_buf) { u_free(c->send_buf); c->send_buf = NULL; c->send_len = 0; }
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "stcp_conn closed is_server=%d prev_state=%d err=%d", c->is_server, prev, err);
if (c->on_close) { void (*cb)(struct stcp_conn*, int, void*) = c->on_close; cb(c, err, c->close_arg); }
else if (prev == STCP_STATE_HS_SERVER_WAIT) { sc_stream_cleanup(&c->stream_send); sc_stream_cleanup(&c->stream_recv); if (c->allocated) u_free(c); }
}
static void stcp_conn_send_message(struct stcp_conn *c, const uint8_t *data, size_t len) {
if (c->state != STCP_STATE_DATA) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_conn_send_message: not in DATA state"); return; }
size_t max_enc = STCP_MAX_MSG_SIZE;
if (len > max_enc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_conn_send_message: data too long %zu", len); return; }
size_t need = 2 + len + 4;
uint8_t *buf = u_malloc(need);
if (!buf) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_conn_send_message: malloc(%zu) failed", need); return; }
if (stcp_encrypt_and_crc(c, data, len, buf, &need)) { u_free(buf); return; }
stcp_conn_try_send(c, buf, need);
}
static int stcp_conn_try_send(struct stcp_conn *c, const uint8_t *data, size_t len) {
if (c->sock == SOCKET_INVALID) return -1;
if (c->send_buf) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_conn_try_send: send_buf already busy");
return -1;
}
ssize_t sent = send(c->sock, data, len, 0);
if (sent < 0) {
int err = socket_get_error();
if (err == ERR_AGAIN || err == ERR_WOULDBLOCK) {
c->send_buf = (uint8_t *)data;
c->send_len = len;
c->send_offset = 0;
uasync_set_socket_write(c->ua, c->socket_id, 1);
return 0;
}
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_conn_try_send: send failed err=%d", err);
u_free((uint8_t *)data);
stcp_conn_do_close(c, err);
return -1;
}
if ((size_t)sent < len) {
c->send_buf = (uint8_t *)data;
c->send_len = len;
c->send_offset = (size_t)sent;
uasync_set_socket_write(c->ua, c->socket_id, 1);
return 0;
}
u_free((uint8_t *)data);
return 0;
}
static void server_conn_write_cb(socket_t sock, void *arg) {
struct stcp_conn *c = (struct stcp_conn *)arg;
if (!c->send_buf) { uasync_set_socket_write(c->ua, c->socket_id, 0); return; }
ssize_t sent = send(sock, c->send_buf + c->send_offset, c->send_len - c->send_offset, 0);
if (sent < 0) {
int err = socket_get_error();
if (err == ERR_AGAIN || err == ERR_WOULDBLOCK) return;
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "server_conn_write_cb: send failed err=%d", err);
stcp_conn_do_close(c, err);
return;
}
c->send_offset += (size_t)sent;
if (c->send_offset >= c->send_len) {
u_free(c->send_buf); c->send_buf = NULL; c->send_len = 0; c->send_offset = 0;
uasync_set_socket_write(c->ua, c->socket_id, 0);
return;
}
}
static void tx_queue_cb(struct ll_queue *q, void *arg) {
struct stcp_conn *c = (struct stcp_conn *)arg;
struct ll_entry *e = queue_data_get(q);
if (!e) { queue_resume_callback(q); return; }
if (e->dgram) stcp_conn_send_message(c, e->dgram, e->len);
queue_entry_free(e);
queue_resume_callback(q);
}
static int derive_session_and_streams(struct stcp_conn *c, const uint8_t *peer_pubkey) {
struct secure_channel sc;
sc_init_ctx(&sc, &c->my_keys);
if (sc_set_peer_public_key(&sc, peer_pubkey, SC_PEER_PUBKEY_BIN) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "derive_session: ECDH failed");
return -1;
}
memcpy(c->session_key, sc.session_key, SC_SESSION_KEY_SIZE);
if (sc_stream_init(&sc, &c->stream_send, STCP_STREAM_SERVER_SEND) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "derive_session: stream_send init failed");
return -1;
}
if (sc_stream_init(&sc, &c->stream_recv, STCP_STREAM_CLIENT_SEND) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "derive_session: stream_recv init failed");
return -1;
}
memcpy(c->peer_pubkey, peer_pubkey, SC_PUBKEY_SIZE);
c->peer_pubkey_set = 1;
return 0;
}
static void process_client_handshake(struct stcp_conn *c) {
const uint8_t *salt = c->recv_buf;
const uint8_t *enc_pubkey = salt + SC_PUBKEY_ENC_SALT_SIZE;
uint8_t client_pubkey[SC_PUBKEY_SIZE];
sc_obfuscate_pubkey(salt, c->my_keys.public_key, enc_pubkey, client_pubkey);
if (derive_session_and_streams(c, client_pubkey)) { stcp_conn_do_close(c, 1); return; }
uint8_t enc_hs[STCP_HS_ENC_CLIENT];
memcpy(enc_hs, c->recv_buf + SC_PUBKEY_ENC_SIZE, STCP_HS_ENC_CLIENT);
size_t hs_data_len;
if (stcp_decrypt_and_check(enc_hs, STCP_HS_ENC_CLIENT, &c->stream_recv, &hs_data_len)) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "process_client_handshake: decrypt/CRC failed");
stcp_conn_do_close(c, 2); return;
}
uint16_t padding_size = (uint16_t)enc_hs[0] | ((uint16_t)enc_hs[1] << 8);
c->hs_expected_len = SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_CLIENT + padding_size;
c->hs_key_processed = 1;
}
static void finish_client_handshake(struct stcp_conn *c) {
memmove(c->recv_buf, c->recv_buf + c->hs_expected_len, c->recv_buf_len - c->hs_expected_len);
c->recv_buf_len -= c->hs_expected_len;
c->hs_expected_len = 0;
c->hs_key_processed = 0;
uint8_t salt2[SC_PUBKEY_ENC_SALT_SIZE];
if (random_bytes(salt2, SC_PUBKEY_ENC_SALT_SIZE) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "finish_client_handshake: random_bytes failed");
stcp_conn_do_close(c, 3); return;
}
uint16_t padding = 8;
size_t total_resp = SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_SERVER + padding;
uint8_t *resp = u_malloc(total_resp);
if (!resp) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "finish_client_handshake: malloc failed"); stcp_conn_do_close(c, 3); return; }
memcpy(resp, salt2, SC_PUBKEY_ENC_SALT_SIZE);
sc_obfuscate_pubkey(salt2, c->peer_pubkey, c->my_keys.public_key, resp + SC_PUBKEY_ENC_SALT_SIZE);
uint8_t plain_hs[3] = {0, (uint8_t)padding, (uint8_t)(padding >> 8)}; // status=OK + padding_size
uint32_t crc = crc32_calc(plain_hs, 3);
uint8_t *enc_dst = resp + SC_PUBKEY_ENC_SIZE;
enc_dst[0] = plain_hs[0]; enc_dst[1] = plain_hs[1]; enc_dst[2] = plain_hs[2];
enc_dst[3] = (uint8_t)(crc >> 0); enc_dst[4] = (uint8_t)(crc >> 8);
enc_dst[5] = (uint8_t)(crc >> 16); enc_dst[6] = (uint8_t)(crc >> 24);
if (sc_stream_xor(&c->stream_send, enc_dst, STCP_HS_ENC_SERVER) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "finish_client_handshake: encrypt failed");
u_free(resp); stcp_conn_do_close(c, 3); return;
}
for (int i = 0; i < padding; i++) resp[SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_SERVER + i] = (uint8_t)(salt2[0] ^ i);
c->state = STCP_STATE_DATA;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "stcp_server: handshake OK, entering DATA state");
if (c->on_ready) c->on_ready(c, c->ready_arg);
stcp_conn_try_send(c, resp, total_resp);
}
static void server_conn_read_cb(socket_t sock, void *arg) {
struct stcp_conn *c = (struct stcp_conn *)arg;
if (c->state == STCP_STATE_CLOSED || c->state == STCP_STATE_ERROR) return;
if (c->recv_buf_cap == 0) {
c->recv_buf_cap = STCP_RECV_BUF_INIT;
c->recv_buf = u_malloc(c->recv_buf_cap);
if (!c->recv_buf) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "server_conn_read_cb: malloc failed"); stcp_conn_do_close(c, ENOMEM); return; }
}
if (c->recv_buf_len + 4096 > c->recv_buf_cap) {
size_t new_cap = c->recv_buf_cap * 2;
if (new_cap > STCP_RECV_BUF_MAX) new_cap = STCP_RECV_BUF_MAX;
if (new_cap <= c->recv_buf_cap) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "server_conn_read_cb: recv buf full"); stcp_conn_do_close(c, ENOBUFS); return; }
uint8_t *nb = u_realloc(c->recv_buf, new_cap);
if (!nb) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "server_conn_read_cb: realloc failed"); stcp_conn_do_close(c, ENOMEM); return; }
c->recv_buf = nb; c->recv_buf_cap = new_cap;
}
ssize_t n = recv(sock, c->recv_buf + c->recv_buf_len, c->recv_buf_cap - c->recv_buf_len, 0);
if (n < 0) {
int err = socket_get_error();
if (err == ERR_AGAIN || err == ERR_WOULDBLOCK) return;
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "server_conn_read_cb: recv failed err=%d", err);
stcp_conn_do_close(c, err); return;
}
if (n == 0) { DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "server_conn_read_cb: EOF"); stcp_conn_do_close(c, 0); return; }
c->recv_buf_len += (size_t)n;
stcp_conn_process_recv(c);
}
static void stcp_conn_process_recv(struct stcp_conn *c) {
while (c->recv_buf_len > 0) {
switch (c->state) {
case STCP_STATE_HS_SERVER_WAIT:
if (!c->hs_key_processed) {
if (c->recv_buf_len < SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_CLIENT) return;
process_client_handshake(c);
if (c->state != STCP_STATE_HS_SERVER_WAIT) return;
}
if (c->hs_key_processed && c->recv_buf_len >= c->hs_expected_len) {
finish_client_handshake(c);
return;
}
return;
case STCP_STATE_DATA: {
if (c->recv_buf_len < 2) return;
uint16_t msg_size = (uint16_t)c->recv_buf[0] | ((uint16_t)c->recv_buf[1] << 8);
if (msg_size > STCP_MAX_MSG_SIZE) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "msg too large: %u", msg_size); stcp_conn_do_close(c, 4); return; }
size_t total = 2 + msg_size + 4;
if (c->recv_buf_len < total) return;
uint8_t *enc_data = c->recv_buf + 2;
size_t data_len;
if (stcp_decrypt_and_check(enc_data, msg_size + 4, &c->stream_recv, &data_len)) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "data decrypt/CRC failed");
stcp_conn_do_close(c, 5); return;
}
if (c->rx_queue) {
struct ll_entry *e = queue_entry_new(0);
if (e) {
e->dgram = u_malloc(data_len);
if (e->dgram) { if (data_len) memcpy(e->dgram, enc_data, data_len); e->len = (uint16_t)data_len; queue_data_put(c->rx_queue, e); }
else { queue_entry_free(e); DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "rx malloc(%zu) failed", data_len); }
} else { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "queue_entry_new failed"); }
}
memmove(c->recv_buf, c->recv_buf + total, c->recv_buf_len - total);
c->recv_buf_len -= total;
break;
}
default: return;
}
}
}
static void server_accept_cb(socket_t listen_sock, void *arg) {
struct stcp_server *srv = (struct stcp_server *)arg;
struct sockaddr_storage cli_addr;
socklen_t addr_len = sizeof(cli_addr);
socket_t cli_sock = accept(listen_sock, (struct sockaddr *)&cli_addr, &addr_len);
if (cli_sock == SOCKET_INVALID) {
int err = socket_get_error();
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_server accept failed err=%d", err);
return;
}
socket_set_nonblocking(cli_sock);
int opt = 1;
setsockopt(cli_sock, IPPROTO_TCP, TCP_NODELAY, (const char *)&opt, sizeof(opt));
struct stcp_conn *c = u_calloc(1, sizeof(struct stcp_conn));
if (!c) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_server: calloc conn failed"); socket_close_wrapper(cli_sock); return; }
c->sock = cli_sock;
c->ua = srv->ua;
c->state = STCP_STATE_HS_SERVER_WAIT;
c->is_server = 1;
c->allocated = 1;
c->tx_cb = tx_queue_cb;
c->my_keys = srv->my_keys;
c->on_ready = srv->connect_cb;
c->ready_arg = srv->cb_arg;
c->socket_id = uasync_add_socket_t(srv->ua, cli_sock, server_conn_read_cb, server_conn_write_cb, NULL, c);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "stcp_server: accepted connection fd=%d", (int)cli_sock);
}
struct stcp_server *stcp_server_create(struct UASYNC *ua, uint16_t port,
struct SC_MYKEYS *keys,
stcp_connect_cb connect_cb, void *arg) {
if (!ua || !keys || !connect_cb) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_server_create: invalid args"); return NULL; }
struct stcp_server *srv = u_calloc(1, sizeof(struct stcp_server));
if (!srv) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_server_create: calloc failed"); return NULL; }
srv->ua = ua;
srv->my_keys = *keys;
srv->connect_cb = connect_cb;
srv->cb_arg = arg;
srv->listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (srv->listen_sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_server_create: socket failed"); u_free(srv); return NULL; }
socket_set_nonblocking(srv->listen_sock);
int reuse = 1;
setsockopt(srv->listen_sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse, sizeof(reuse));
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
if (bind(srv->listen_sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_server_create: bind port=%u failed err=%d", port, socket_get_error());
socket_close_wrapper(srv->listen_sock); u_free(srv); return NULL;
}
if (listen(srv->listen_sock, 16) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_server_create: listen failed err=%d", socket_get_error());
socket_close_wrapper(srv->listen_sock); u_free(srv); return NULL;
}
srv->listen_id = uasync_add_socket_t(ua, srv->listen_sock, server_accept_cb, NULL, NULL, srv);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "stcp_server: listening on port %u", port);
return srv;
}
void stcp_server_destroy(struct stcp_server *srv) {
if (!srv) return;
if (srv->listen_id) uasync_remove_socket_t(srv->ua, srv->listen_sock);
if (srv->listen_sock != SOCKET_INVALID) socket_close_wrapper(srv->listen_sock);
u_free(srv);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "stcp_server destroyed");
}
+14
View File
@@ -0,0 +1,14 @@
// stcp_server.h — STCP server: listen, accept, handshake
#ifndef STCP_SERVER_H
#define STCP_SERVER_H
#include "stcp.h"
typedef void (*stcp_connect_cb)(struct stcp_conn *conn, void *arg);
struct stcp_server *stcp_server_create(struct UASYNC *ua, uint16_t port,
struct SC_MYKEYS *keys,
stcp_connect_cb connect_cb, void *arg);
void stcp_server_destroy(struct stcp_server *srv);
#endif
+2 -2
View File
@@ -161,7 +161,7 @@ struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config)
tun->test_fd = fds[1];
}
tun->pool = memory_pool_init(sizeof(struct ll_entry));
tun->pool = memory_pool_init(sizeof(struct ll_entry), "tun_pool");
if (!tun->pool) goto fail;
tun->output_queue = queue_new(ua, 0, 0, 0, "TUN output");
@@ -253,7 +253,7 @@ struct tun_if* tun_init_nat(struct UASYNC* ua, const char* ifname, const char* i
tun->test_fd = fds[1];
}
tun->pool = memory_pool_init(sizeof(struct ll_entry));
tun->pool = memory_pool_init(sizeof(struct ll_entry), "tun_pool");
if (!tun->pool) goto fail2;
tun->output_queue = queue_new(ua, 0, 0, 0, "NAT TUN output");
+9 -4
View File
@@ -49,7 +49,12 @@ static int local_sockaddr_equal(const struct sockaddr_storage *a, const struct s
const struct sockaddr_in *ib = (const struct sockaddr_in *)b;
return ia->sin_addr.s_addr == ib->sin_addr.s_addr && ia->sin_port == ib->sin_port;
}
return 0; // IPv6 stub
if (a->ss_family == AF_INET6) {
const struct sockaddr_in6 *ia = (const struct sockaddr_in6 *)a;
const struct sockaddr_in6 *ib = (const struct sockaddr_in6 *)b;
return memcmp(&ia->sin6_addr, &ib->sin6_addr, 16) == 0 && ia->sin6_port == ib->sin6_port;
}
return 0;
}
// Common initialization function (called by both create functions)
@@ -78,9 +83,9 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
}
// Create memory pools
instance->ack_pool = memory_pool_init(sizeof(struct ACK_PACKET));
instance->data_pool = memory_pool_init(PACKET_DATA_SIZE);
instance->pkt_pool = memory_pool_init(sizeof(struct ETCP_DGRAM) + PACKET_DATA_SIZE);
instance->ack_pool = memory_pool_init(sizeof(struct ACK_PACKET), "ack_pool");
instance->data_pool = memory_pool_init(PACKET_DATA_SIZE, "data_pool");
instance->pkt_pool = memory_pool_init(sizeof(struct ETCP_DGRAM) + PACKET_DATA_SIZE, "pkt_pool");
// Create routing module
if (routing_create(instance) != 0) {
+10
View File
@@ -4,6 +4,8 @@
check_PROGRAMS = \
test_ll_queue \
test_serialize \
test_stream_cipher \
test_stcp \
test_packet_dump \
test_debug_categories \
test_config_debug \
@@ -176,6 +178,14 @@ test_etcp_crypto_SOURCES = test_etcp_crypto.c
test_etcp_crypto_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_etcp_crypto_LDADD = $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_stream_cipher_SOURCES = test_stream_cipher.c
test_stream_cipher_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_stream_cipher_LDADD = $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_stcp_SOURCES = test_stcp.c
test_stcp_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_stcp_LDADD = $(top_builddir)/src/utun-stcp.o $(top_builddir)/src/utun-stcp_server.o $(top_builddir)/src/utun-stcp_client.o $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
if USE_OPENSSL
else
test_crypto_SOURCES = test_crypto.c
+5 -4
View File
@@ -111,9 +111,9 @@ static struct UTUN_INSTANCE* create_instance(struct UASYNC* u, uint64_t node_id,
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);
inst->ack_pool = memory_pool_init(sizeof(struct ACK_PACKET), "ack_pool");
inst->data_pool = memory_pool_init(PACKET_DATA_SIZE, "data_pool");
inst->pkt_pool = memory_pool_init(sizeof(struct ETCP_DGRAM) + PACKET_DATA_SIZE, "pkt_pool");
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; }
@@ -124,6 +124,7 @@ static struct UTUN_INSTANCE* create_instance(struct UASYNC* u, uint64_t node_id,
cfg->global.keepalive_timeout = 5000;
cfg->global.keepalive_interval = 500;
cfg->global.allowed_keys_allow_all = 1;
cfg->global.bbr_max_cwnd = 100000;
inst->config = cfg;
return inst;
}
@@ -486,7 +487,7 @@ int main(void) {
}
}
int pass = (ctx.bytes_received > 50000); /* at least 50KB delivered */
int pass = (ctx.bytes_received > 50000);
printf("\n[%s]\n", pass ? "PASS" : "FAIL");
if (ctx.log_file) {
+3 -3
View File
@@ -102,9 +102,9 @@ static struct UTUN_INSTANCE* create_instance(struct UASYNC* u, uint64_t node_id,
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);
inst->ack_pool = memory_pool_init(sizeof(struct ACK_PACKET), "ack_pool");
inst->data_pool = memory_pool_init(PACKET_DATA_SIZE, "data_pool");
inst->pkt_pool = memory_pool_init(sizeof(struct ETCP_DGRAM) + PACKET_DATA_SIZE, "pkt_pool");
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; }
+3 -3
View File
@@ -111,9 +111,9 @@ static struct UTUN_INSTANCE* create_instance(struct UASYNC* u, uint64_t node_id,
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);
inst->ack_pool = memory_pool_init(sizeof(struct ACK_PACKET), "ack_pool");
inst->data_pool = memory_pool_init(PACKET_DATA_SIZE, "data_pool");
inst->pkt_pool = memory_pool_init(sizeof(struct ETCP_DGRAM) + PACKET_DATA_SIZE, "pkt_pool");
if (!inst->ack_pool || !inst->data_pool || !inst->pkt_pool) {
u_free(inst);
+3 -3
View File
@@ -53,9 +53,9 @@ static struct UTUN_INSTANCE* create_instance(struct UASYNC* u, uint64_t node_id,
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);
inst->ack_pool = memory_pool_init(sizeof(struct ACK_PACKET), "ack_pool");
inst->data_pool = memory_pool_init(PACKET_DATA_SIZE, "data_pool");
inst->pkt_pool = memory_pool_init(sizeof(struct ETCP_DGRAM) + PACKET_DATA_SIZE, "pkt_pool");
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; }
+1 -1
View File
@@ -65,7 +65,7 @@ static double test_with_pools(int iterations) {
struct ll_queue* queue = queue_new(ua, 0, 0, 0,"q2"); // С пулами
// Создать пул памяти для данных
struct memory_pool* pool = memory_pool_init(sizeof(struct ll_entry) + 64);
struct memory_pool* pool = memory_pool_init(sizeof(struct ll_entry) + 64, "test_pool");
if (!pool) {
queue_free(queue);
uasync_destroy(ua, 0);
+1 -1
View File
@@ -354,7 +354,7 @@ static void test_limits_hash(void) {
static void test_pool(void) {
TEST("memory_pool integration + reuse");
struct UASYNC *ua = uasync_create();
struct memory_pool *pool = memory_pool_init(sizeof(test_data_t));
struct memory_pool *pool = memory_pool_init(sizeof(test_data_t), "test_data_pool");
struct ll_queue *q = queue_new(ua, 0, 0, 0, "q8");
size_t alloc1 = 0, reuse1 = 0;
+2 -2
View File
@@ -185,7 +185,7 @@ static int init_mock_etcp(void) {
mock_etcp.instance = (struct UTUN_INSTANCE*)&mock_instance;
// Create io_pool for ETCP_FRAGMENT allocation
mock_etcp.io_pool = memory_pool_init(sizeof(struct ETCP_FRAGMENT));
mock_etcp.io_pool = memory_pool_init(sizeof(struct ETCP_FRAGMENT), "io_pool");
if (!mock_etcp.io_pool) {
printf("Failed to create io_pool\n");
return -1;
@@ -271,7 +271,7 @@ int main() {
}
// Initialize memory pool
mock_instance.data_pool = memory_pool_init(MTU_SIZE);
mock_instance.data_pool = memory_pool_init(MTU_SIZE, "data_pool");
if (!mock_instance.data_pool) {
printf("Failed to create memory pool\n");
+359
View File
@@ -0,0 +1,359 @@
// test_stcp.c — comprehensive STCP integration tests
#include "../src/stcp.h"
#include "../src/stcp_server.h"
#include "../src/stcp_client.h"
#include "../src/secure_channel.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>
static int tests_passed = 0, tests_total = 0;
static struct SC_MYKEYS s_keys, c_keys;
#define BASE_PORT 23456
#define TASSERT(cond) do { \
if (!(cond)) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, " FAIL: %s", #cond); return 1; } \
} while(0)
#define TRUN(name) do { \
tests_total++; \
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "--- %s ---", name); \
int _r = name(); \
if (_r == 0) { tests_passed++; DEBUG_INFO(DEBUG_CATEGORY_GENERAL, " PASS"); } \
} while(0)
// ======================= peer helper =======================
struct test_peer {
struct stcp_conn *conn;
struct ll_queue *rx, *tx;
int ready, closed, close_err, msg_count;
uint8_t *accum;
size_t accum_len, accum_cap;
};
static void peer_rx_cb(struct ll_queue *q, void *arg) {
struct test_peer *p = (struct test_peer *)arg;
struct ll_entry *e = queue_data_get(q);
if (!e) { queue_resume_callback(q); return; }
p->msg_count++;
size_t need = p->accum_len + e->len;
if (need > p->accum_cap) { p->accum_cap = need + 4096; p->accum = u_realloc(p->accum, p->accum_cap); }
memcpy(p->accum + p->accum_len, e->dgram, e->len);
p->accum_len += e->len;
queue_entry_free(e);
queue_resume_callback(q);
}
static void peer_close_cb(struct stcp_conn *conn, int err, void *arg) {
(void)conn;
struct test_peer *p = (struct test_peer *)arg;
p->closed = 1; p->close_err = err;
}
static void setup_peer(struct test_peer *p, struct stcp_conn *conn) {
p->conn = conn;
p->rx = queue_new(conn->ua, 0, 0, 0, "rx");
p->tx = queue_new(conn->ua, 0, 0, 0, "tx");
queue_set_callback(p->rx, peer_rx_cb, p);
stcp_conn_set_rx_queue(conn, p->rx);
stcp_conn_set_tx_queue(conn, p->tx);
stcp_conn_set_on_close(conn, peer_close_cb, p);
p->ready = 1;
}
static void server_connect_cb(struct stcp_conn *conn, void *arg) {
struct test_peer *p = (struct test_peer *)arg;
setup_peer(p, conn);
}
static void client_ready_cb(struct stcp_conn *conn, void *arg) {
struct test_peer *p = (struct test_peer *)arg;
setup_peer(p, conn);
}
static void peer_cleanup(struct test_peer *p) {
if (p->rx) { queue_free(p->rx); p->rx = NULL; }
if (p->tx) { queue_free(p->tx); p->tx = NULL; }
if (p->accum) { u_free(p->accum); p->accum = NULL; }
}
static int peer_send(struct test_peer *p, const uint8_t *data, size_t len) {
struct ll_entry *e = queue_entry_new(0);
if (!e) return -1;
e->dgram = u_malloc(len ? len : 1);
if (!e->dgram) { queue_entry_free(e); return -1; }
if (len) memcpy(e->dgram, data, len);
e->len = (uint16_t)len;
queue_data_put(p->tx, e);
return 0;
}
// ======================= test 1: handshake + all message sizes =======================
static int test1_sizes(void) {
struct UASYNC *ua = uasync_create(); TASSERT(ua);
struct test_peer srv = {0}, cli = {0};
uint16_t port = BASE_PORT + 1;
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, server_connect_cb, &srv); TASSERT(ss);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, client_ready_cb, &cli); TASSERT(sc);
size_t sizes[] = {0, 1, 16, 17, 255, 256, 1000, 65535};
int n_sizes = 8;
size_t total = 0; for (int i = 0; i < n_sizes; i++) total += sizes[i];
uint8_t *payload = u_malloc(65536);
for (int i = 0; i < 65536; i++) payload[i] = (uint8_t)(i * 7 + 13);
int sent = 0, ticks = 0;
while (srv.msg_count < n_sizes && ticks < 5000) {
uasync_poll(ua, 10);
if (srv.ready && cli.ready && !sent) {
for (int i = 0; i < n_sizes; i++) TASSERT(peer_send(&cli, payload, sizes[i]) == 0);
sent = 1;
}
ticks++;
}
TASSERT(srv.msg_count == n_sizes);
TASSERT(srv.accum_len == total);
size_t off = 0;
for (int i = 0; i < n_sizes; i++) {
TASSERT(memcmp(srv.accum + off, payload, sizes[i]) == 0);
off += sizes[i];
}
u_free(payload);
peer_cleanup(&srv); peer_cleanup(&cli);
if (srv.conn) stcp_conn_free(srv.conn);
stcp_client_destroy(sc); stcp_server_destroy(ss);
uasync_destroy(ua, 1);
return 0;
}
// ======================= test 2: many sequential messages =======================
static int test2_many(void) {
struct UASYNC *ua = uasync_create(); TASSERT(ua);
struct test_peer srv = {0}, cli = {0};
uint16_t port = BASE_PORT + 2;
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, server_connect_cb, &srv); TASSERT(ss);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, client_ready_cb, &cli); TASSERT(sc);
int sent = 0, ticks = 0;
while (srv.msg_count < 200 && ticks < 5000) {
uasync_poll(ua, 10);
if (srv.ready && cli.ready && !sent) {
for (int i = 0; i < 200; i++) {
uint8_t buf[8];
buf[0] = (uint8_t)(i >> 0); buf[1] = (uint8_t)(i >> 8);
buf[2] = (uint8_t)(i >> 16); buf[3] = (uint8_t)(i >> 24);
buf[4] = (uint8_t)(i * 3);
TASSERT(peer_send(&cli, buf, 5) == 0);
}
sent = 1;
}
ticks++;
}
TASSERT(srv.msg_count == 200);
TASSERT(srv.accum_len == 200 * 5);
for (int i = 0; i < 200; i++) {
uint32_t v; memcpy(&v, srv.accum + i * 5, 4); TASSERT(v == (uint32_t)i);
TASSERT(srv.accum[i * 5 + 4] == (uint8_t)(i * 3));
}
peer_cleanup(&srv); peer_cleanup(&cli);
if (srv.conn) stcp_conn_free(srv.conn);
stcp_client_destroy(sc); stcp_server_destroy(ss);
uasync_destroy(ua, 1);
return 0;
}
// ======================= test 3: wrong peer pubkey =======================
static int test3_wrong_key(void) {
struct UASYNC *ua = uasync_create(); TASSERT(ua);
struct test_peer srv = {0}, cli = {0};
uint16_t port = BASE_PORT + 3;
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, server_connect_cb, &srv); TASSERT(ss);
struct SC_MYKEYS rogue;
TASSERT(sc_generate_keypair(&rogue) == SC_OK);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, rogue.public_key, client_ready_cb, &cli); TASSERT(sc);
int ticks = 0;
while (ticks < 3000) {
uasync_poll(ua, 10);
if (cli.closed || srv.closed) break;
ticks++;
}
TASSERT(!cli.ready);
TASSERT(!srv.ready);
peer_cleanup(&srv); peer_cleanup(&cli);
stcp_client_destroy(sc); stcp_server_destroy(ss);
uasync_destroy(ua, 1);
return 0;
}
// ======================= test 4: close detection =======================
static int test4_close(void) {
struct UASYNC *ua = uasync_create(); TASSERT(ua);
struct test_peer srv = {0}, cli = {0};
uint16_t port = BASE_PORT + 4;
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, server_connect_cb, &srv); TASSERT(ss);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, client_ready_cb, &cli); TASSERT(sc);
int closed = 0, ticks = 0;
while (!srv.closed && ticks < 3000) {
uasync_poll(ua, 10);
if (srv.ready && cli.ready && !closed) {
uint8_t m = 0xAB; peer_send(&cli, &m, 1);
closed = 1;
}
if (closed && srv.msg_count >= 1 && cli.conn) {
stcp_conn_free(cli.conn); cli.conn = NULL;
cli.closed = 1;
}
ticks++;
}
TASSERT(srv.closed);
peer_cleanup(&srv); peer_cleanup(&cli);
stcp_client_destroy(sc); stcp_server_destroy(ss);
uasync_destroy(ua, 1);
return 0;
}
// ======================= test 5: multiple concurrent clients =======================
static struct test_peer *g_multi_peers;
static int g_multi_idx, g_multi_max;
static void multi_connect_cb(struct stcp_conn *conn, void *arg) {
(void)arg;
int i = g_multi_idx++;
if (i >= g_multi_max) return;
struct test_peer *p = &g_multi_peers[i];
setup_peer(p, conn);
}
static int test5_multi(void) {
struct UASYNC *ua = uasync_create(); TASSERT(ua);
uint16_t port = BASE_PORT + 5;
#define NCLI 3
struct test_peer srvp[NCLI];
struct test_peer clip[NCLI];
memset(srvp, 0, sizeof(srvp)); memset(clip, 0, sizeof(clip));
g_multi_peers = srvp; g_multi_idx = 0; g_multi_max = NCLI;
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, multi_connect_cb, NULL); TASSERT(ss);
struct stcp_client *clients[NCLI] = {0};
for (int i = 0; i < NCLI; i++) {
clients[i] = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, client_ready_cb, &clip[i]);
TASSERT(clients[i]);
}
int sent = 0, ticks = 0;
while (ticks < 5000) {
uasync_poll(ua, 10);
if (!sent) {
int all_ready = 1;
for (int i = 0; i < NCLI; i++) if (!clip[i].ready || !srvp[i].ready) all_ready = 0;
if (all_ready) {
for (int i = 0; i < NCLI; i++) {
uint8_t buf[4]; buf[0] = (uint8_t)i; buf[1] = (uint8_t)(i * 17 + 42);
TASSERT(peer_send(&clip[i], buf, 2) == 0);
}
sent = 1;
}
}
if (sent) {
int all_got = 1;
for (int i = 0; i < NCLI; i++) if (srvp[i].msg_count < 1) all_got = 0;
if (all_got) break;
}
ticks++;
}
for (int i = 0; i < NCLI; i++) {
TASSERT(srvp[i].msg_count >= 1);
TASSERT(srvp[i].accum_len == 2);
TASSERT(srvp[i].accum[0] == (uint8_t)i);
TASSERT(srvp[i].accum[1] == (uint8_t)(i * 17 + 42));
}
for (int i = 0; i < NCLI; i++) {
peer_cleanup(&srvp[i]); peer_cleanup(&clip[i]);
if (srvp[i].conn) stcp_conn_free(srvp[i].conn);
stcp_client_destroy(clients[i]);
}
stcp_server_destroy(ss);
uasync_destroy(ua, 1);
return 0;
}
// ======================= test 6: interleaved send/recv =======================
static int test6_interleaved(void) {
struct UASYNC *ua = uasync_create(); TASSERT(ua);
struct test_peer srv = {0}, cli = {0};
uint16_t port = BASE_PORT + 6;
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, server_connect_cb, &srv); TASSERT(ss);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, client_ready_cb, &cli); TASSERT(sc);
int round = 0, ticks = 0;
while (srv.msg_count < 50 || cli.msg_count < 50) {
uasync_poll(ua, 10);
if (srv.ready && cli.ready && round < 50) {
uint8_t cb = (uint8_t)(round + 100);
uint8_t sb = (uint8_t)(round + 200);
if (peer_send(&cli, &cb, 1) == 0 && peer_send(&srv, &sb, 1) == 0) round++;
}
if (++ticks > 5000) break;
}
TASSERT(srv.msg_count >= 50);
TASSERT(cli.msg_count >= 50);
for (int i = 0; i < 50; i++) {
TASSERT(srv.accum[i] == (uint8_t)(i + 100));
TASSERT(cli.accum[i] == (uint8_t)(i + 200));
}
peer_cleanup(&srv); peer_cleanup(&cli);
if (srv.conn) stcp_conn_free(srv.conn);
stcp_client_destroy(sc); stcp_server_destroy(ss);
uasync_destroy(ua, 1);
return 0;
}
// ======================= 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_INFO(DEBUG_CATEGORY_GENERAL, "============================================");
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "=== STCP Integration Tests ===");
TASSERT(sc_generate_keypair(&s_keys) == SC_OK);
TASSERT(sc_generate_keypair(&c_keys) == SC_OK);
TRUN(test1_sizes);
TRUN(test2_many);
TRUN(test3_wrong_key);
TRUN(test4_close);
TRUN(test5_multi);
TRUN(test6_interleaved);
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "============================================");
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "Results: %d/%d passed", tests_passed, tests_total);
return tests_passed == tests_total ? 0 : 1;
}
+141
View File
@@ -0,0 +1,141 @@
// test_stream_cipher.c — tests for streaming cipher (AES-128-CTR)
#include "../src/secure_channel.h"
#include "../lib/debug_config.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
static int test_failed = 0;
#define CHECK(expr, msg) do { \
if (!(expr)) { \
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "FAIL: %s", msg); \
test_failed = 1; \
} else { \
DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "PASS: %s", msg); \
} \
} while(0)
int main(void) {
debug_config_init();
debug_set_level(DEBUG_LEVEL_INFO);
debug_set_categories(DEBUG_CATEGORY_CRYPTO);
DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "=== Stream Cipher Test ===");
struct SC_MYKEYS keys_a, keys_b;
CHECK(sc_generate_keypair(&keys_a) == SC_OK, "generate keypair A");
CHECK(sc_generate_keypair(&keys_b) == SC_OK, "generate keypair B");
sc_context_t ctx_a, ctx_b;
CHECK(sc_init_ctx(&ctx_a, &keys_a) == SC_OK, "init ctx A");
CHECK(sc_init_ctx(&ctx_b, &keys_b) == SC_OK, "init ctx B");
CHECK(sc_set_peer_public_key(&ctx_a, keys_b.public_key, SC_PEER_PUBKEY_BIN) == SC_OK, "set peer key A<-B");
CHECK(sc_set_peer_public_key(&ctx_b, keys_a.public_key, SC_PEER_PUBKEY_BIN) == SC_OK, "set peer key B<-A");
struct sc_stream_state send_a, recv_a, send_b, recv_b;
CHECK(sc_stream_init(&ctx_a, &send_a, 0) == SC_OK, "init send_A (id=0)");
CHECK(sc_stream_init(&ctx_a, &recv_a, 1) == SC_OK, "init recv_A (id=1)");
CHECK(sc_stream_init(&ctx_b, &recv_b, 0) == SC_OK, "init recv_B (id=0)");
CHECK(sc_stream_init(&ctx_b, &send_b, 1) == SC_OK, "init send_B (id=1)");
uint8_t original[2048], data[2048];
for (int i = 0; i < (int)sizeof(original); i++) original[i] = (uint8_t)(i * 3 + 17);
// Test 1: A->B, single chunk, 1024 bytes
memcpy(data, original, 1024);
CHECK(sc_stream_xor(&send_a, data, 1024) == SC_OK, "encrypt 1024B A->B");
CHECK(sc_stream_xor(&recv_b, data, 1024) == SC_OK, "decrypt 1024B A->B");
CHECK(memcmp(data, original, 1024) == 0, "round-trip A->B matches");
// Test 2: B->A, single chunk
memcpy(data, original, 1024);
CHECK(sc_stream_xor(&send_b, data, 1024) == SC_OK, "encrypt 1024B B->A");
CHECK(sc_stream_xor(&recv_a, data, 1024) == SC_OK, "decrypt 1024B B->A");
CHECK(memcmp(data, original, 1024) == 0, "round-trip B->A matches");
// Test 3: incremental feeding — encrypt in 4 chunks, decrypt in 2
memcpy(data, original, 1024);
CHECK(sc_stream_xor(&send_a, data, 7) == SC_OK, "inc_A enc 7B");
CHECK(sc_stream_xor(&send_a, data + 7, 33) == SC_OK, "inc_A enc 33B");
CHECK(sc_stream_xor(&send_a, data + 40, 1) == SC_OK, "inc_A enc 1B");
CHECK(sc_stream_xor(&send_a, data + 41, 1024 - 41) == SC_OK, "inc_A enc 983B");
CHECK(sc_stream_xor(&recv_b, data, 513) == SC_OK, "inc_B dec 513B");
CHECK(sc_stream_xor(&recv_b, data + 513, 1024 - 513) == SC_OK, "inc_B dec 511B");
CHECK(memcmp(data, original, 1024) == 0, "inc round-trip matches");
// Test 4: all sizes 1..255 — catches alignment/keystream issues
for (size_t sz = 1; sz <= 255; sz++) {
uint8_t buf[256], cpy[256];
for (size_t i = 0; i < sz; i++) buf[i] = (uint8_t)(sz ^ i);
memcpy(cpy, buf, sz);
CHECK(sc_stream_xor(&send_a, buf, sz) == SC_OK, "various-size enc");
CHECK(sc_stream_xor(&recv_b, buf, sz) == SC_OK, "various-size dec");
CHECK(memcmp(buf, cpy, sz) == 0, "various-size match");
}
// Test 5: 2048 bytes (= 128 AES blocks, whole number)
memcpy(data, original, 2048);
CHECK(sc_stream_xor(&send_a, data, 2048) == SC_OK, "enc 2048B");
CHECK(sc_stream_xor(&recv_b, data, 2048) == SC_OK, "dec 2048B");
CHECK(memcmp(data, original, 2048) == 0, "round-trip 2048 matches");
// Test 6: A and B streams must produce DIFFERENT keystreams
{
uint8_t test_data[16];
memset(test_data, 0xAA, 16);
uint8_t a_out[16], b_out[16];
memcpy(a_out, test_data, 16);
memcpy(b_out, test_data, 16);
CHECK(sc_stream_xor(&send_a, a_out, 16) == SC_OK, "xor on send_A");
CHECK(sc_stream_xor(&send_b, b_out, 16) == SC_OK, "xor on send_B");
CHECK(memcmp(a_out, b_out, 16) != 0, "stream_id=0 != stream_id=1 keystream");
// Decrypt back
CHECK(sc_stream_xor(&recv_b, a_out, 16) == SC_OK, "recv_B decrypt send_A data");
CHECK(sc_stream_xor(&recv_a, b_out, 16) == SC_OK, "recv_A decrypt send_B data");
CHECK(memcmp(a_out, test_data, 16) == 0, "recv_B restores original");
CHECK(memcmp(b_out, test_data, 16) == 0, "recv_A restores original");
}
// Test 7: encrypt 1 byte — extreme alignment edge
for (int j = 0; j < 50; j++) {
uint8_t one_byte = (uint8_t)j;
CHECK(sc_stream_xor(&send_a, &one_byte, 1) == SC_OK, "enc 1B");
CHECK(sc_stream_xor(&recv_b, &one_byte, 1) == SC_OK, "dec 1B");
CHECK(one_byte == (uint8_t)j, "1-byte round-trip");
}
// Test 8: error handling
CHECK(sc_stream_xor(&send_a, NULL, 0) == SC_OK, "xor len=0 OK");
CHECK(sc_stream_xor(&send_a, data, 0) == SC_OK, "xor data_len=0 OK");
CHECK(sc_stream_init(&ctx_a, NULL, 0) == SC_ERR_INVALID_ARG, "init NULL state");
CHECK(sc_stream_init(NULL, &send_a, 0) == SC_ERR_INVALID_ARG, "init NULL ctx");
struct sc_stream_state uninit;
memset(&uninit, 0, sizeof(uninit));
CHECK(sc_stream_xor(&uninit, data, 1) == SC_ERR_NOT_INITIALIZED, "xor uninitialized");
// У ctx без session_ready
sc_context_t no_session_ctx;
struct SC_MYKEYS dummy_keys;
memset(&dummy_keys, 0, sizeof(dummy_keys));
sc_init_ctx(&no_session_ctx, &dummy_keys);
struct sc_stream_state bad_stream;
CHECK(sc_stream_init(&no_session_ctx, &bad_stream, 0) == SC_ERR_NOT_INITIALIZED, "init without session");
// Cleanup
sc_stream_cleanup(&send_a);
sc_stream_cleanup(&recv_a);
sc_stream_cleanup(&send_b);
sc_stream_cleanup(&recv_b);
sc_stream_cleanup(NULL);
sc_stream_cleanup(&uninit);
if (test_failed) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "=== Stream Cipher Test: FAILED ===");
return 1;
}
DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "=== Stream Cipher Test: PASSED ===");
return 0;
}
+16 -17
View File
@@ -91,15 +91,14 @@ static void test_basic_send_recv(void) {
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed");
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, 0, on_fin, on_error, NULL);
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
// Отправляем данные через tcp_conn
// push_write отправляет данные синхронно через write_queue_fetch_cb
uint8_t send_buf[3000];
memset(send_buf, 'A', sizeof(send_buf));
int ret = push_write(tc, send_buf, sizeof(send_buf));
ASSERT_EQ(ret, 0, "push_write first call failed");
uasync_poll(ua, 1); // write_cb должен отправить
// Читаем с другой стороны
uint8_t recv_buf[4096] = {0};
@@ -111,7 +110,8 @@ static void test_basic_send_recv(void) {
}
ASSERT_TRUE(memcmp(send_buf, recv_buf, sizeof(send_buf)) == 0, "received data mismatch");
// Отправляем с другой стороны — должно появиться в read_queue
// Отправляем с другой стороны ДО poll — данные попадут в буфер ядра,
// и при EPOLLHUP handle_error вычитает их дренажом в read_queue
uint8_t peer_data[500];
memset(peer_data, 'B', sizeof(peer_data));
ssize_t wret = send(sv[1], peer_data, sizeof(peer_data), MSG_NOSIGNAL);
@@ -125,11 +125,10 @@ static void test_basic_send_recv(void) {
queue_entry_free(e);
queue_resume_callback(tc->read_queue);
// Закрываем сокет — должен вызвать on_fin
// Закрываем peer — EPOLLHUP обработан через error_cb (handle_error)
close(sv[1]);
uasync_poll(ua, 10);
ASSERT_EQ(g_fin_count, 1, "on_fin not called");
ASSERT_EQ(tc->fin_remote, 1, "tc->fin_remote not set");
ASSERT_EQ(g_error_count, 1, "error_cb not called on peer close");
tcp_conn_destroy(tc);
close(sv[0]);
@@ -148,7 +147,7 @@ static void test_partial_write(void) {
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed");
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 4096, 32, 8, on_fin, on_error, NULL);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 4096, 32, 8, 0, on_fin, on_error, NULL);
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
// Заполняем приёмный буфер sv[1] маленькими чтениями, чтобы создать EAGAIN на sv[0]
@@ -193,7 +192,7 @@ static void test_high_water_pause(void) {
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK);
// hw=2, lw=0 — пауза после 2 блоков, resume когда пусто
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 64, 8192, 2, 0, on_fin, on_error, NULL);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 64, 8192, 2, 0, 0, on_fin, on_error, NULL);
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
// Шлём 5 блоков по 64 байта с другой стороны
@@ -263,7 +262,7 @@ static void test_connect_detection(void) {
ASSERT_TRUE(client_fd >= 0, "socket failed");
fcntl(client_fd, F_SETFL, fcntl(client_fd, F_GETFL, 0) | O_NONBLOCK);
struct tcp_conn* tc = tcp_conn_create(ua, client_fd, 1500, 8192, 32, 8, on_fin, on_error, NULL);
struct tcp_conn* tc = tcp_conn_create(ua, client_fd, 1500, 8192, 32, 8, 0, on_fin, on_error, NULL);
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
ASSERT_EQ(tc->connected, 0, "should not be connected yet");
@@ -295,7 +294,7 @@ static void test_error_callback(void) {
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed");
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, 0, on_fin, on_error, NULL);
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
close(sv[1]);
@@ -323,7 +322,7 @@ static void test_push_fin(void) {
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed");
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, 0, on_fin, on_error, NULL);
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
tc->on_fin_sent = on_fin_sent_cb;
@@ -369,7 +368,7 @@ static void test_push_close(void) {
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed");
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, 0, on_fin, on_error, NULL);
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
tc->on_closed = on_closed_cb;
@@ -407,7 +406,7 @@ static void test_fin_data_ordering(void) {
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed");
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, 0, on_fin, on_error, NULL);
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
tc->on_fin_sent = on_fin_sent_cb;
@@ -455,7 +454,7 @@ static void test_double_push_fin(void) {
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed");
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, 0, on_fin, on_error, NULL);
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
tc->on_fin_sent = on_fin_sent_cb;
@@ -482,7 +481,7 @@ static void test_double_push_close(void) {
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed");
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, 0, on_fin, on_error, NULL);
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
tc->on_closed = on_closed_cb;
@@ -510,7 +509,7 @@ static void test_fin_before_close(void) {
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed");
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL);
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, 0, on_fin, on_error, NULL);
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
tc->on_fin_sent = on_fin_sent_cb;
tc->on_closed = on_closed_cb;
+1 -1
View File
@@ -76,7 +76,7 @@ int main(void) {
if (sock == SOCKET_INVALID) return 1;
socket_set_nonblocking(sock);
struct tcp_conn* tc = tcp_conn_create(ua, sock, 1500, 8192, 32, 8, on_fin_cb, on_error_cb, NULL);
struct tcp_conn* tc = tcp_conn_create(ua, sock, 1500, 8192, 32, 8, 0, on_fin_cb, on_error_cb, NULL);
if (!tc) return 1;
struct sockaddr_in addr;
+58 -27
View File
@@ -365,6 +365,12 @@ static void CreateControls(struct etcpmon_app* app) {
WS_CHILD | WS_VISIBLE | ES_READONLY,
mx + 85, my, 100, 20, hWnd, (HMENU)IDC_EDIT_ETCP_LINKS, hInst, NULL);
CreateWindowExA(0, "STATIC", "MaxInfl:",
WS_CHILD | WS_VISIBLE, mx + 200, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
app->hEditEtcpMaxInflight = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "",
WS_CHILD | WS_VISIBLE | ES_READONLY,
mx + 285, my, 100, 20, hWnd, (HMENU)IDC_EDIT_ETCP_MAX_INFLIGHT, hInst, NULL);
/* TUN/Routing Metrics group - right of ETCP Metrics */
int group_w = (WINDOW_WIDTH - 40) / 2;
x = 10 + group_w + 20; y = 485;
@@ -740,14 +746,7 @@ static void CreateControls(struct etcpmon_app* app) {
"N\r\nO\r\nN\r\nE", "E\r\nR\r\nR\r\nO\r\nR", "W\r\nA\r\nR\r\nN",
"I\r\nN\r\nF\r\nO", "D\r\nE\r\nB\r\nU\r\nG", "T\r\nR\r\nA\r\nC\r\nE"
};
static const char* cat_names[] = {
"UASYNC","LL_QUEUE","CONNECTION","ETCP","CRYPTO",
"MEMORY","TIMING","CONFIG","TUN","ROUTING",
"TIMERS","NORMALIZER","BGP","SOCKET","CONTROL",
"DUMP","TRAFFIC","DEBUG_CAT","GENERAL","NAT","KEEPALIVE","ETCPROUTE"
};
int ncat = sizeof(cat_names) / sizeof(cat_names[0]);
app->debug_cat_count = ncat;
app->debug_cat_count = 0;
CreateWindowExA(0, "STATIC", "Global:", WS_CHILD | WS_VISIBLE,
1015, dy + 2, 55, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
@@ -766,21 +765,6 @@ static void CreateControls(struct etcpmon_app* app) {
WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER | ES_MULTILINE,
lx, dy, 22, 60, hWnd, (HMENU)(UINT_PTR)(IDC_DEBUG_HEADER_BASE + l), hInst, NULL);
}
dy += 65;
for (int r = 0; r < ncat; r++) {
app->hDebugCatLabel[r] = CreateWindowExA(0, "STATIC", cat_names[r],
WS_CHILD | WS_VISIBLE | SS_LEFT | SS_CENTERIMAGE,
1015, dy + 1, 80, 16, hWnd, (HMENU)(UINT_PTR)(IDC_DEBUG_NAME_BASE + r), hInst, NULL);
for (int l = 0; l < 6; l++) {
int lx = 1100 + l * 22;
DWORD style = WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON;
if (l == 0) style |= WS_GROUP;
app->hDebugCatRadio[r][l] = CreateWindowExA(0, "BUTTON", "",
style, lx, dy, 20, 16, hWnd, (HMENU)(UINT_PTR)(IDC_DEBUG_ROW_BASE + r * 8 + l), hInst, NULL);
}
dy += 18;
}
}
}
@@ -834,6 +818,8 @@ static void CreateTooltips(struct etcpmon_app* app)
"etcp.unacked_bytes / ETCP Connection\r\nBytes in flight: sent but not yet acknowledged");
AddTooltip(app->hToolTip, app->hEditEtcpLinks,
"etcp.links_count / ETCP Connection\r\nNumber of active links (physical connections)");
AddTooltip(app->hToolTip, app->hEditEtcpMaxInflight,
"etcp.optimal_inflight / ETCP Max Inflight\r\nSum of all link inflight_lim_bytes (congestion window cap)");
/* TUN Metrics */
AddTooltip(app->hToolTip, app->hEditTunReadBytes,
@@ -1142,10 +1128,50 @@ static void OnDebugRadio(struct etcpmon_app* app, int control_id) {
in_handler = 0;
}
static void RecreateDebugCats(struct etcpmon_app* app) {
if (!app || !app->client || !app->hWndMain) return;
struct etcpmon_client* cl = app->client;
if (cl->debug_category_count == 0) return;
HINSTANCE hInst = app->hInstance;
HWND hWnd = app->hWndMain;
int ncat = cl->debug_category_count;
if (ncat > IDC_DEBUG_MAX_CAT) ncat = IDC_DEBUG_MAX_CAT;
app->debug_cat_count = ncat;
int dy = 485 + 20 + 22 + 65; // GroupBox top + global row + header row + header height
const char* names = cl->debug_category_names;
const char* p = names;
for (int r = 0; r < ncat; r++) {
const char* end = strchr(p, ',');
size_t len = end ? (size_t)(end - p) : strlen(p);
char buf[64];
size_t copy = len < sizeof(buf) - 1 ? len : sizeof(buf) - 1;
memcpy(buf, p, copy); buf[copy] = '\0';
app->hDebugCatLabel[r] = CreateWindowExA(0, "STATIC", buf,
WS_CHILD | WS_VISIBLE | SS_LEFT | SS_CENTERIMAGE,
1015, dy + 1, 80, 16, hWnd, (HMENU)(UINT_PTR)(IDC_DEBUG_NAME_BASE + r), hInst, NULL);
for (int l = 0; l < 6; l++) {
int lx = 1100 + l * 22;
DWORD style = WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON;
if (l == 0) style |= WS_GROUP;
app->hDebugCatRadio[r][l] = CreateWindowExA(0, "BUTTON", "",
style, lx, dy, 20, 16, hWnd, (HMENU)(UINT_PTR)(IDC_DEBUG_ROW_BASE + r * 8 + l), hInst, NULL);
}
dy += 18;
p = end ? end + 1 : p + len;
}
}
static void RefreshDebugUI(struct etcpmon_app* app) {
if (!app || !app->client) return;
struct etcpmon_client* cl = app->client;
if (app->debug_cat_count == 0 && cl->debug_category_count > 0)
RecreateDebugCats(app);
for (int l = 0; l <= 5; l++) {
if (app->hDebugGlobalRadio[l])
SendMessage(app->hDebugGlobalRadio[l], BM_SETCHECK,
@@ -1435,6 +1461,7 @@ void etcpmon_gui_update_metrics(struct etcpmon_app* app,
UpdateEditIfChanged(hMain, IDC_EDIT_ETCP_TX_DUP, "%u", metrics->etcp.tx_dup_count);
UpdateEditIfChanged(hMain, IDC_EDIT_ETCP_ACKS, "%u", metrics->etcp.ack_count);
UpdateEditIfChanged(hMain, IDC_EDIT_ETCP_INFLIGHT, "%u bytes", metrics->etcp.unacked_bytes);
UpdateEditIfChanged(hMain, IDC_EDIT_ETCP_MAX_INFLIGHT, "%u bytes", metrics->etcp.optimal_inflight);
UpdateEditIfChanged(hMain, IDC_EDIT_ETCP_LINKS, "%u", metrics->etcp.links_count);
/* TUN Metrics */
UpdateEditIfChanged(hMain, IDC_EDIT_TUN_READ_BYTES, "%llu", (unsigned long long)metrics->tun.bytes_read);
@@ -1559,26 +1586,29 @@ void etcpmon_gui_update_metrics(struct etcpmon_app* app,
links[i].inflight_packets);
char line3[320];
char line4[256];
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",
" BBR: mode=%s cycle=%s full_bw=%s loss=%s pace=%.1f KB/s rtt=%u us 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_pacing_gain / 256.0f);
snprintf(line4, sizeof(line4),
" bwHi=%.1f KB/s bwLo=%.1f KB/s infHi=%u infLo=%u",
(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);
links[i].bbr_inflight_lo);
SendMessageA(app->hListLinks, LB_ADDSTRING, 0, (LPARAM)line1);
SendMessageA(app->hListLinks, LB_ADDSTRING, 0, (LPARAM)line2);
SendMessageA(app->hListLinks, LB_ADDSTRING, 0, (LPARAM)line3);
SendMessageA(app->hListLinks, LB_ADDSTRING, 0, (LPARAM)line4);
}
InvalidateRect(app->hListLinks, NULL, FALSE);
}
@@ -1604,6 +1634,7 @@ void etcpmon_gui_clear_metrics(struct etcpmon_app* app) {
SetDlgItemTextA(app->hWndMain, IDC_EDIT_ETCP_ACKS, "");
SetDlgItemTextA(app->hWndMain, IDC_EDIT_ETCP_INFLIGHT, "");
SetDlgItemTextA(app->hWndMain, IDC_EDIT_ETCP_LINKS, "");
SetDlgItemTextA(app->hWndMain, IDC_EDIT_ETCP_MAX_INFLIGHT, "");
SetDlgItemTextA(app->hWndMain, IDC_EDIT_TUN_READ_BYTES, "");
SetDlgItemTextA(app->hWndMain, IDC_EDIT_TUN_WRITE_BYTES, "");
+2
View File
@@ -58,6 +58,7 @@ extern "C" {
#define IDC_EDIT_ETCP_LINKS 209
#define IDC_EDIT_ETCP_RX_DUP 210
#define IDC_EDIT_ETCP_TX_DUP 211
#define IDC_EDIT_ETCP_MAX_INFLIGHT 212
/* TUN Metrics control IDs */
#define IDC_EDIT_TUN_READ_BYTES 300
@@ -224,6 +225,7 @@ struct etcpmon_app {
HWND hEditEtcpAcks;
HWND hEditEtcpInflight;
HWND hEditEtcpLinks;
HWND hEditEtcpMaxInflight;
/* TUN metrics controls */
HWND hEditTunReadBytes;