Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f651f1df0 | ||
|
|
cff81a70da | ||
|
|
4cfaaec3b0 | ||
|
|
b5d49926a9 | ||
|
|
d7c84a94e2 | ||
|
|
9af10a208f | ||
|
|
677a5cdcf5 | ||
|
|
54c92f423f | ||
|
|
b37e7001b0 | ||
|
|
ce37db137b |
No files matched your search
@@ -108,4 +108,3 @@ cl
|
||||
build_win.log
|
||||
/wintun.dll
|
||||
src/lwip_orig/
|
||||
lib/timeout/
|
||||
+2
-2
@@ -7,8 +7,8 @@ libuasync_a_SOURCES = \
|
||||
ll_queue.c \
|
||||
debug_config.c \
|
||||
debug_config.h \
|
||||
timeout_heap.c \
|
||||
timeout_heap.h \
|
||||
twheel.c \
|
||||
twheel.h \
|
||||
memory_pool.c \
|
||||
memory_pool.h \
|
||||
sha256.c \
|
||||
|
||||
+26
-4
@@ -127,7 +127,6 @@ 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,9 +198,32 @@ 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) {
|
||||
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";
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
/* Global debug configuration */
|
||||
|
||||
+1
-2
@@ -60,8 +60,7 @@ 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_BBR 23 // BBR congestion control
|
||||
#define DEBUG_CATEGORY_COUNT 24 // Total number of categories
|
||||
#define DEBUG_CATEGORY_COUNT 23 // Total number of categories
|
||||
#define DEBUG_CATEGORY_ALL (-1) // special value for all categories
|
||||
|
||||
/* Debug configuration structure */
|
||||
|
||||
@@ -135,14 +135,20 @@ 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);
|
||||
|
||||
+32
-75
@@ -20,14 +20,13 @@ 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 int flush_write_buf(struct tcp_conn* tc);
|
||||
static void tcp_conn_handle_error(struct tcp_conn* tc, int err);
|
||||
static void flush_write_buf(struct tcp_conn* tc);
|
||||
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 rcvbuf_size,
|
||||
int read_high_water, int read_low_water,
|
||||
void (*on_fin)(struct tcp_conn* tc, void* arg),
|
||||
void (*on_error)(struct tcp_conn* tc, int err, void* arg),
|
||||
void* arg)
|
||||
@@ -43,7 +42,6 @@ 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;
|
||||
@@ -86,27 +84,20 @@ 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_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);
|
||||
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);
|
||||
return tc;
|
||||
}
|
||||
|
||||
void tcp_conn_destroy(struct tcp_conn* tc) {
|
||||
if (!tc) return;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_destroy: fd=%d connected=%d error=%d fin_remote=%d fin_local=%d closed=%d",
|
||||
DEBUG_INFO(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) {
|
||||
@@ -137,45 +128,6 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Чтение из сокета
|
||||
// ====================================================================
|
||||
@@ -210,7 +162,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_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: FIN fd=%d", (int)tc->sock);
|
||||
DEBUG_INFO(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) {
|
||||
@@ -222,8 +174,9 @@ 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);
|
||||
tcp_conn_handle_error(tc, errno);
|
||||
if (tc->on_error) tc->on_error(tc, errno, tc->arg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +198,7 @@ static void fin_deferred_cb(struct ll_queue* q, void* arg) {
|
||||
// Запись в сокет
|
||||
// ====================================================================
|
||||
|
||||
static int flush_write_buf(struct tcp_conn* tc) {
|
||||
static void 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) {
|
||||
@@ -256,16 +209,16 @@ static int 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 0;
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) return 0;
|
||||
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) return;
|
||||
tc->error = 1;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: send error fd=%d errno=%d", (int)tc->sock, errno);
|
||||
tcp_conn_handle_error(tc, errno);
|
||||
return 1;
|
||||
if (tc->on_error) tc->on_error(tc, errno, tc->arg);
|
||||
return;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
|
||||
@@ -274,7 +227,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) {
|
||||
if (flush_write_buf(tc)) return;
|
||||
flush_write_buf(tc);
|
||||
if (tc->write_buf) return; // остался остаток, ждём write_cb
|
||||
}
|
||||
|
||||
@@ -296,14 +249,14 @@ static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
|
||||
|
||||
if (e->len == 0) {
|
||||
if (!e->dgram) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: CLOSE fd=%d", (int)tc->sock);
|
||||
DEBUG_INFO(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_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: FIN fd=%d", (int)tc->sock);
|
||||
DEBUG_INFO(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;
|
||||
@@ -325,9 +278,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);
|
||||
@@ -342,9 +295,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);
|
||||
@@ -356,10 +309,11 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,15 +327,17 @@ 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_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: connect ok fd=%d", (int)tc->sock);
|
||||
DEBUG_INFO(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);
|
||||
tcp_conn_handle_error(tc, err ? err : -1);
|
||||
if (tc->on_error) tc->on_error(tc, err ? err : -1, tc->arg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (flush_write_buf(tc)) return;
|
||||
flush_write_buf(tc);
|
||||
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);
|
||||
@@ -396,9 +352,10 @@ 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;
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
@@ -412,7 +369,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_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin fd=%d wq=%d", (int)tc->sock, tc->write_queue->count + 1);
|
||||
DEBUG_INFO(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;
|
||||
}
|
||||
@@ -423,7 +380,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_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close fd=%d wq=%d", (int)tc->sock, tc->write_queue->count + 1);
|
||||
DEBUG_INFO(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;
|
||||
}
|
||||
|
||||
+38
-23
@@ -1,23 +1,40 @@
|
||||
// tcp_io.h — управление TCP-соединением через uasync + ll_queue
|
||||
//
|
||||
// Один tcp_conn = одно TCP-соединение. Два пула:
|
||||
// entry_pool — struct ll_entry (без inline data)
|
||||
// data_pool — буферы (max(entry_data_size, write_chunk_size))
|
||||
// Один tcp_conn = одно TCP-соединение. Две очереди: read_queue (сокет → данные) и
|
||||
// write_queue (данные → сокет). Обе работают через автозабор ll_queue (deferred).
|
||||
//
|
||||
// Чтение: read_cb → recv() в data_pool → entry в read_queue → callback внешнего кода.
|
||||
// high_water/low_water управляют backpressure (EPOLLIN on/off).
|
||||
// FIN (recv==0) → on_fin (сразу или через empty_callback после дренажа очереди).
|
||||
// Пул-аллокация:
|
||||
// entry_pool — только struct ll_entry (без inline data)
|
||||
// data_pool — буферы данных чтения/записи (max(entry_data_size, write_chunk_size))
|
||||
// Данные и структуры аллоцируются раздельно — не копируются при recv/send.
|
||||
//
|
||||
// Запись: внешний код кладёт entry в write_queue через queue_data_put().
|
||||
// write_queue_fetch_cb (deferred) → send(), при EAGAIN → write_buf + EPOLLOUT.
|
||||
// Всё отправлено → on_flushed.
|
||||
// Чтение:
|
||||
// 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; иначе сразу
|
||||
//
|
||||
// FIN/Close: tcp_conn_push_fin/close ставят сентинел в write_queue.
|
||||
// FIN → shutdown(SHUT_WR) → on_fin_sent
|
||||
// CLOSE → close сокета → on_closed
|
||||
// Запись:
|
||||
// Внешний код: 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 (если установлен)
|
||||
//
|
||||
// Ошибка: любая ошибка сокета → tcp_conn_handle_error → close + null коллбэков → on_error.
|
||||
// Реализация on_error обязана вызвать tcp_conn_destroy. После возврата tc недействителен.
|
||||
// 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 сокет)
|
||||
|
||||
#ifndef TCP_IO_H
|
||||
#define TCP_IO_H
|
||||
@@ -37,7 +54,6 @@ struct tcp_conn {
|
||||
|
||||
int read_high_water;
|
||||
int read_low_water;
|
||||
int rcvbuf_size;
|
||||
uint8_t read_paused;
|
||||
|
||||
uint8_t write_monitor; // 1 = EPOLLOUT активен
|
||||
@@ -47,7 +63,7 @@ struct tcp_conn {
|
||||
uint8_t fin_local; // FIN отправлен удалённой стороне (shutdown SHUT_WR)
|
||||
uint8_t closed; // сокет полностью закрыт (close)
|
||||
|
||||
// Частичная отправка (из data_pool, не в очереди — досылается первой)
|
||||
// Частичная отправка (из write_pool, не в очереди — досылается первой)
|
||||
uint8_t* write_buf;
|
||||
size_t write_len;
|
||||
size_t write_offset;
|
||||
@@ -63,7 +79,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); // фатальная ошибка (вызывается после close сокета; обязан вызвать tcp_conn_destroy)
|
||||
void (*on_error)(struct tcp_conn* tc, int err, void* arg); // ошибка сокета
|
||||
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;
|
||||
@@ -76,22 +92,21 @@ 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);
|
||||
|
||||
// Запись: queue_data_put(tc->write_queue, entry) напрямую.
|
||||
// Автозабор (deferred) сам шлёт когда сокет готов.
|
||||
// Порог: 32 записи, при заполнении queue_waiter_wait.
|
||||
// Внешний код пишет данные в tc->write_queue напрямую (queue_data_put).
|
||||
// Автозабор write_queue (deferred) сам отправляет когда сокет готов.
|
||||
// Перед push проверять порог: queue_set_threshold в tcp_conn_create (32 entries).
|
||||
// При заполнении — 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,193 +0,0 @@
|
||||
// timeout_heap.c
|
||||
|
||||
#include "timeout_heap.h"
|
||||
#include "debug_config.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h> // For potential error printing, optional
|
||||
#include "mem.h"
|
||||
|
||||
// Helper macros for 1-based indices
|
||||
#define PARENT(i) ((i) / 2)
|
||||
#define LEFT_CHILD(i) (2 * (i))
|
||||
#define RIGHT_CHILD(i) (2 * (i) + 1)
|
||||
|
||||
TimeoutHeap *timeout_heap_create(size_t initial_capacity) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH1...");
|
||||
TimeoutHeap *h = u_malloc(sizeof(TimeoutHeap));
|
||||
if (!h) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "TH0 error...");
|
||||
return NULL;
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH2...");
|
||||
h->heap = u_malloc(sizeof(TimeoutEntry) * initial_capacity);
|
||||
if (!h->heap) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "TH1 error...");
|
||||
u_free(h);
|
||||
return NULL;
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH3...");
|
||||
h->size = 0;
|
||||
h->capacity = initial_capacity;
|
||||
h->freed_count = 0;
|
||||
h->user_data = NULL;
|
||||
h->free_callback = NULL;
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
void timeout_heap_destroy(TimeoutHeap *h) {
|
||||
if (!h) return;
|
||||
|
||||
// Free all remaining data (deleted or not)
|
||||
for (size_t i = 0; i < h->size; i++) {
|
||||
if (h->free_callback) {
|
||||
h->free_callback(h->user_data, h->heap[i].data);
|
||||
}
|
||||
}
|
||||
|
||||
u_free(h->heap);
|
||||
u_free(h);
|
||||
}
|
||||
|
||||
|
||||
static void update_index(TimeoutHeap *h, size_t idx) {
|
||||
if (h->heap[idx].index_ptr)
|
||||
*h->heap[idx].index_ptr = idx;
|
||||
}
|
||||
|
||||
static void swap_entries(TimeoutHeap *h, size_t a, size_t b) {
|
||||
TimeoutEntry temp = h->heap[a];
|
||||
h->heap[a] = h->heap[b];
|
||||
h->heap[b] = temp;
|
||||
update_index(h, a);
|
||||
update_index(h, b);
|
||||
}
|
||||
|
||||
static void bubble_up(TimeoutHeap *h, size_t i) {
|
||||
// i is 1-based
|
||||
while (i > 1 && h->heap[PARENT(i) - 1].expiration > h->heap[i - 1].expiration) {
|
||||
swap_entries(h, PARENT(i) - 1, i - 1);
|
||||
i = PARENT(i);
|
||||
}
|
||||
}
|
||||
|
||||
int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data, size_t *index_ptr) {
|
||||
if (h->size == h->capacity) {
|
||||
size_t new_cap = h->capacity ? h->capacity * 2 : 1;
|
||||
TimeoutEntry *new_heap = u_realloc(h->heap, sizeof(TimeoutEntry) * new_cap);
|
||||
if (!new_heap) return -1; // Allocation failed
|
||||
h->heap = new_heap;
|
||||
h->capacity = new_cap;
|
||||
}
|
||||
|
||||
// Insert at end (0-based)
|
||||
size_t idx = h->size++;
|
||||
h->heap[idx].expiration = expiration;
|
||||
h->heap[idx].data = data;
|
||||
h->heap[idx].index_ptr = index_ptr;
|
||||
h->heap[idx].deleted = 0;
|
||||
if (index_ptr)
|
||||
*index_ptr = idx;
|
||||
|
||||
// Bubble up (1-based)
|
||||
bubble_up(h, idx + 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void heapify_down(TimeoutHeap *h, size_t i) {
|
||||
// i is 1-based
|
||||
while (1) {
|
||||
size_t smallest = i;
|
||||
size_t left = LEFT_CHILD(i);
|
||||
size_t right = RIGHT_CHILD(i);
|
||||
|
||||
if (left <= h->size && h->heap[left - 1].expiration < h->heap[smallest - 1].expiration) {
|
||||
smallest = left;
|
||||
}
|
||||
if (right <= h->size && h->heap[right - 1].expiration < h->heap[smallest - 1].expiration) {
|
||||
smallest = right;
|
||||
}
|
||||
if (smallest == i) break;
|
||||
|
||||
swap_entries(h, smallest - 1, i - 1);
|
||||
i = smallest;
|
||||
}
|
||||
}
|
||||
|
||||
static void remove_root(TimeoutHeap *h) {
|
||||
if (h->size == 0) return;
|
||||
|
||||
// Move last to root
|
||||
h->heap[0] = h->heap[--h->size];
|
||||
update_index(h, 0);
|
||||
|
||||
// Heapify down (1-based)
|
||||
if (h->size > 0) {
|
||||
heapify_down(h, 1);
|
||||
}
|
||||
}
|
||||
|
||||
int timeout_heap_peek(TimeoutHeap *h, TimeoutEntry *out) {
|
||||
if (h->size == 0) return -1;
|
||||
|
||||
while (h->size > 0 && h->heap[0].deleted) {
|
||||
void* data_to_free = h->heap[0].data;
|
||||
remove_root(h);
|
||||
if (h->free_callback) {
|
||||
h->free_callback(h->user_data, data_to_free);
|
||||
}
|
||||
}
|
||||
if (h->size == 0) return -1;
|
||||
|
||||
*out = h->heap[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out) {
|
||||
if (h->size == 0) return -1;
|
||||
|
||||
while (h->size > 0 && h->heap[0].deleted) {
|
||||
void* data_to_free = h->heap[0].data;
|
||||
remove_root(h);
|
||||
if (h->free_callback) {
|
||||
h->free_callback(h->user_data, data_to_free);
|
||||
}
|
||||
}
|
||||
if (h->size == 0) return -1;
|
||||
|
||||
*out = h->heap[0];
|
||||
remove_root(h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data) {
|
||||
for (size_t i = 0; i < h->size; ++i) {
|
||||
if (h->heap[i].expiration == expiration && h->heap[i].data == data) {
|
||||
h->heap[i].deleted = 1;
|
||||
h->heap[i].expiration = 0;
|
||||
bubble_up(h, i + 1);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1; // Not found
|
||||
}
|
||||
|
||||
int timeout_heap_cancel_at(TimeoutHeap *h, size_t index, void *data) {
|
||||
if (index >= h->size || h->heap[index].data != data) return -1;
|
||||
|
||||
h->heap[index].deleted = 1;
|
||||
h->heap[index].expiration = 0;
|
||||
bubble_up(h, index + 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void timeout_heap_set_free_callback(TimeoutHeap *h, void* user_data, void (*callback)(void* user_data, void* data)) {
|
||||
if (!h) return;
|
||||
h->user_data = user_data;
|
||||
h->free_callback = callback;
|
||||
}
|
||||
|
||||
size_t timeout_heap_get_size(TimeoutHeap *h) {
|
||||
if (!h) return 0;
|
||||
return h->size;
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
// timeout_heap.h
|
||||
|
||||
#ifndef TIMEOUT_HEAP_H
|
||||
#define TIMEOUT_HEAP_H
|
||||
|
||||
#include <stdint.h> // For uint64_t
|
||||
#include <stddef.h> // For size_t
|
||||
|
||||
typedef uint64_t TimeoutTime; // e.g., milliseconds since epoch or from now
|
||||
|
||||
typedef struct {
|
||||
TimeoutTime expiration; // Sort key (smaller = earlier)
|
||||
void *data; // User data (e.g., callback or ID)
|
||||
size_t *index_ptr; // Pointer to node's heap_index (NULL = no tracking)
|
||||
int deleted; // 0 = active, 1 = deleted
|
||||
} TimeoutEntry;
|
||||
|
||||
typedef struct TimeoutHeap TimeoutHeap;
|
||||
|
||||
struct TimeoutHeap {
|
||||
TimeoutEntry *heap; // Dynamic array
|
||||
size_t size; // Current number of elements
|
||||
size_t capacity; // Allocated size
|
||||
size_t freed_count; // Number of freed timer nodes
|
||||
void* user_data; // User data for free callback
|
||||
void (*free_callback)(void* user_data, void* data); // Callback to free data
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new timeout heap with initial capacity.
|
||||
* @param initial_capacity Starting capacity (will grow as needed).
|
||||
* @return Pointer to the heap, or NULL on failure.
|
||||
*/
|
||||
TimeoutHeap *timeout_heap_create(size_t initial_capacity);
|
||||
|
||||
/**
|
||||
* Destroy the timeout heap and free resources.
|
||||
* @param h The heap to destroy.
|
||||
*/
|
||||
void timeout_heap_destroy(TimeoutHeap *h);
|
||||
|
||||
/**
|
||||
* Set a callback function to free data when deleted nodes are removed.
|
||||
* @param h The heap.
|
||||
* @param user_data User data passed to callback.
|
||||
* @param callback Callback function (if NULL, data is freed with free()).
|
||||
*/
|
||||
void timeout_heap_set_free_callback(TimeoutHeap *h, void* user_data, void (*callback)(void* user_data, void* data));
|
||||
|
||||
/**
|
||||
* Insert a new timeout into the heap.
|
||||
* @param h The heap.
|
||||
* @param expiration The expiration time.
|
||||
* @param data User data associated with the timeout.
|
||||
* @return 0 on success, -1 on allocation failure.
|
||||
*/
|
||||
int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data, size_t *index_ptr);
|
||||
|
||||
/**
|
||||
* Peek at the earliest non-deleted timeout without removing it.
|
||||
* @param h The heap.
|
||||
* @param out Where to store the entry.
|
||||
* @return 0 on success, -1 if empty.
|
||||
*/
|
||||
int timeout_heap_peek(TimeoutHeap *h, TimeoutEntry *out);
|
||||
|
||||
/**
|
||||
* Pop the earliest non-deleted timeout from the heap.
|
||||
* @param h The heap.
|
||||
* @param out Where to store the entry.
|
||||
* @return 0 on success, -1 if empty.
|
||||
*/
|
||||
int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out);
|
||||
|
||||
/**
|
||||
* Cancel a timeout by matching expiration and data.
|
||||
* Scans the heap linearly, so O(n) time.
|
||||
* Assumes combinations are unique; cancels the first match.
|
||||
* @param h The heap.
|
||||
* @param expiration The expiration time to match.
|
||||
* @param data The data to match.
|
||||
* @return 0 if found and canceled, -1 if not found.
|
||||
*/
|
||||
int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data);
|
||||
|
||||
int timeout_heap_cancel_at(TimeoutHeap *h, size_t index, void *data);
|
||||
|
||||
/**
|
||||
* Get the number of freed timer nodes.
|
||||
* @param h The heap.
|
||||
* @return Count of freed timer nodes.
|
||||
*/
|
||||
size_t timeout_heap_get_freed_count(TimeoutHeap *h);
|
||||
|
||||
size_t timeout_heap_get_size(TimeoutHeap *h);
|
||||
|
||||
#endif // TIMEOUT_HEAP_H
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
// twheel.c — Tickless hierarchical timing wheel (adapted from timeout.c)
|
||||
// Original: Copyright (c) 2013-2014 William Ahern, MIT license.
|
||||
|
||||
#include <limits.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/queue.h>
|
||||
|
||||
#include "twheel.h"
|
||||
#include "mem.h"
|
||||
#include "debug_config.h"
|
||||
|
||||
#define WHEEL_BIT 6
|
||||
#define WHEEL_NUM 4
|
||||
|
||||
#define WHEEL_LEN (1U << WHEEL_BIT)
|
||||
#define WHEEL_MAX (WHEEL_LEN - 1)
|
||||
#define WHEEL_MASK (WHEEL_LEN - 1)
|
||||
|
||||
#include "twheel_bitops.c"
|
||||
|
||||
#define ctz(n) ctz64(n)
|
||||
#define clz(n) clz64(n)
|
||||
#define fls(n) ((int)(64 - clz64(n)))
|
||||
|
||||
typedef uint64_t wheel_t;
|
||||
#define WHEEL_C(n) UINT64_C(n)
|
||||
|
||||
#define countof(a) (sizeof(a) / sizeof *(a))
|
||||
|
||||
#ifndef TAILQ_CONCAT
|
||||
#define TAILQ_CONCAT(head1, head2, field) do { \
|
||||
if (!TAILQ_EMPTY(head2)) { \
|
||||
*(head1)->tqh_last = (head2)->tqh_first; \
|
||||
(head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
|
||||
(head1)->tqh_last = (head2)->tqh_last; \
|
||||
TAILQ_INIT((head2)); \
|
||||
} \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#ifndef TAILQ_FOREACH_SAFE
|
||||
#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
|
||||
for ((var) = TAILQ_FIRST(head); \
|
||||
(var) && ((tvar) = TAILQ_NEXT(var, field), 1); \
|
||||
(var) = (tvar))
|
||||
#endif
|
||||
|
||||
#ifndef MAX
|
||||
#define MAX(a,b) (((a)>(b))?(a):(b))
|
||||
#endif
|
||||
#ifndef MIN
|
||||
#define MIN(a,b) (((a)<(b))?(a):(b))
|
||||
#endif
|
||||
|
||||
static inline wheel_t rotl(const wheel_t v, int c) {
|
||||
if (!(c &= (sizeof v * CHAR_BIT - 1))) return v;
|
||||
return (v << c) | (v >> (sizeof v * CHAR_BIT - c));
|
||||
}
|
||||
|
||||
static inline wheel_t rotr(const wheel_t v, int c) {
|
||||
if (!(c &= (sizeof v * CHAR_BIT - 1))) return v;
|
||||
return (v >> c) | (v << (sizeof v * CHAR_BIT - c));
|
||||
}
|
||||
|
||||
static struct twheels *twheels_init(struct twheels *T, twheel_t hz) {
|
||||
for (unsigned i = 0; i < WHEEL_NUM; i++) {
|
||||
for (unsigned j = 0; j < WHEEL_LEN; j++) {
|
||||
TAILQ_INIT(&T->wheel[i][j]);
|
||||
}
|
||||
}
|
||||
TAILQ_INIT(&T->expired);
|
||||
for (unsigned i = 0; i < WHEEL_NUM; i++) T->pending[i] = 0;
|
||||
T->curtime = 0;
|
||||
T->hertz = hz ? hz : TWHEEL_mHZ;
|
||||
return T;
|
||||
}
|
||||
|
||||
struct twheels *twheels_open(twheel_t hz) {
|
||||
struct twheels *T = u_malloc(sizeof *T);
|
||||
if (!T) { DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "twheels_open: malloc failed"); return NULL; }
|
||||
return twheels_init(T, hz);
|
||||
}
|
||||
|
||||
void twheels_close(struct twheels *T) {
|
||||
if (!T) return;
|
||||
struct twheel_list reset;
|
||||
TAILQ_INIT(&reset);
|
||||
for (unsigned i = 0; i < WHEEL_NUM; i++)
|
||||
for (unsigned j = 0; j < WHEEL_LEN; j++)
|
||||
TAILQ_CONCAT(&reset, &T->wheel[i][j], tqe);
|
||||
TAILQ_CONCAT(&reset, &T->expired, tqe);
|
||||
struct twheel *to;
|
||||
TAILQ_FOREACH(to, &reset, tqe) to->pending = NULL;
|
||||
u_free(T);
|
||||
}
|
||||
|
||||
twheel_t twheels_hz(struct twheels *T) { return T->hertz; }
|
||||
|
||||
void twheels_del(struct twheels *T, struct twheel *to) {
|
||||
if (to->pending) {
|
||||
TAILQ_REMOVE(to->pending, to, tqe);
|
||||
if (to->pending != &T->expired && TAILQ_EMPTY(to->pending)) {
|
||||
ptrdiff_t index = to->pending - &T->wheel[0][0];
|
||||
int w = index / WHEEL_LEN, slot = index % WHEEL_LEN;
|
||||
T->pending[w] &= ~(WHEEL_C(1) << slot);
|
||||
}
|
||||
to->pending = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static inline int timeout_wheel(twheel_t timeout) {
|
||||
return (fls(MIN(timeout, TWHEEL_MAX)) - 1) / WHEEL_BIT;
|
||||
}
|
||||
|
||||
static inline int timeout_slot(int wheel, twheel_t expires) {
|
||||
return WHEEL_MASK & ((expires >> (wheel * WHEEL_BIT)) - !!wheel);
|
||||
}
|
||||
|
||||
static void twheels_sched(struct twheels *T, struct twheel *to, twheel_t expires) {
|
||||
twheels_del(T, to);
|
||||
to->expires = expires;
|
||||
if (expires > T->curtime) {
|
||||
twheel_t rem = expires - T->curtime;
|
||||
int wheel = timeout_wheel(rem);
|
||||
int slot = timeout_slot(wheel, to->expires);
|
||||
to->pending = &T->wheel[wheel][slot];
|
||||
TAILQ_INSERT_TAIL(to->pending, to, tqe);
|
||||
T->pending[wheel] |= WHEEL_C(1) << slot;
|
||||
} else {
|
||||
to->pending = &T->expired;
|
||||
TAILQ_INSERT_TAIL(to->pending, to, tqe);
|
||||
}
|
||||
}
|
||||
|
||||
void twheels_add(struct twheels *T, struct twheel *to, twheel_t timeout) {
|
||||
if (to->flags & TWHEEL_INT) to->interval = MAX(1, timeout);
|
||||
if (to->flags & TWHEEL_ABS) twheels_sched(T, to, timeout);
|
||||
else twheels_sched(T, to, T->curtime + timeout);
|
||||
}
|
||||
|
||||
void twheels_update(struct twheels *T, twheel_t curtime) {
|
||||
twheel_t elapsed = curtime - T->curtime;
|
||||
struct twheel_list todo;
|
||||
TAILQ_INIT(&todo);
|
||||
|
||||
for (int wheel = 0; wheel < WHEEL_NUM; wheel++) {
|
||||
wheel_t pending;
|
||||
if ((elapsed >> (wheel * WHEEL_BIT)) > WHEEL_MAX) {
|
||||
pending = (wheel_t)~WHEEL_C(0);
|
||||
} else {
|
||||
wheel_t _elapsed = WHEEL_MASK & (elapsed >> (wheel * WHEEL_BIT));
|
||||
int oslot = WHEEL_MASK & (T->curtime >> (wheel * WHEEL_BIT));
|
||||
pending = rotl(((UINT64_C(1) << _elapsed) - 1), oslot);
|
||||
int nslot = WHEEL_MASK & (curtime >> (wheel * WHEEL_BIT));
|
||||
pending |= rotr(rotl(((WHEEL_C(1) << _elapsed) - 1), nslot), _elapsed);
|
||||
pending |= WHEEL_C(1) << nslot;
|
||||
}
|
||||
while (pending & T->pending[wheel]) {
|
||||
int slot = ctz(pending & T->pending[wheel]);
|
||||
TAILQ_CONCAT(&todo, &T->wheel[wheel][slot], tqe);
|
||||
T->pending[wheel] &= ~(UINT64_C(1) << slot);
|
||||
}
|
||||
if (!(0x1 & pending)) break;
|
||||
elapsed = MAX(elapsed, (WHEEL_LEN << (wheel * WHEEL_BIT)));
|
||||
}
|
||||
T->curtime = curtime;
|
||||
|
||||
struct twheel *to;
|
||||
while (!TAILQ_EMPTY(&todo)) {
|
||||
to = TAILQ_FIRST(&todo);
|
||||
TAILQ_REMOVE(&todo, to, tqe);
|
||||
to->pending = NULL;
|
||||
twheels_sched(T, to, to->expires);
|
||||
}
|
||||
}
|
||||
|
||||
bool twheels_pending(struct twheels *T) {
|
||||
wheel_t pending = 0;
|
||||
for (int wheel = 0; wheel < WHEEL_NUM; wheel++) pending |= T->pending[wheel];
|
||||
return !!pending;
|
||||
}
|
||||
|
||||
bool twheels_expired(struct twheels *T) { return !TAILQ_EMPTY(&T->expired); }
|
||||
|
||||
static twheel_t twheels_int(struct twheels *T) {
|
||||
twheel_t timeout = ~TWHEEL_C(0), _timeout, relmask = 0;
|
||||
for (int wheel = 0; wheel < WHEEL_NUM; wheel++) {
|
||||
if (T->pending[wheel]) {
|
||||
int slot = WHEEL_MASK & (T->curtime >> (wheel * WHEEL_BIT));
|
||||
_timeout = (ctz(rotr(T->pending[wheel], slot)) + !!wheel) << (wheel * WHEEL_BIT);
|
||||
_timeout -= relmask & T->curtime;
|
||||
timeout = MIN(_timeout, timeout);
|
||||
}
|
||||
relmask <<= WHEEL_BIT;
|
||||
relmask |= WHEEL_MASK;
|
||||
}
|
||||
return timeout;
|
||||
}
|
||||
|
||||
twheel_t twheels_timeout(struct twheels *T) {
|
||||
if (!TAILQ_EMPTY(&T->expired)) return 0;
|
||||
return twheels_int(T);
|
||||
}
|
||||
|
||||
struct twheel *twheels_get(struct twheels *T) {
|
||||
if (!TAILQ_EMPTY(&T->expired)) {
|
||||
struct twheel *to = TAILQ_FIRST(&T->expired);
|
||||
TAILQ_REMOVE(&T->expired, to, tqe);
|
||||
to->pending = NULL;
|
||||
return to;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct twheel *twheel_init(struct twheel *to, int flags) {
|
||||
memset(to, 0, sizeof *to);
|
||||
to->flags = flags;
|
||||
return to;
|
||||
}
|
||||
|
||||
bool twheel_pending(struct twheel *to) { return to->pending != NULL; }
|
||||
@@ -0,0 +1,57 @@
|
||||
// twheel.h — Tickless hierarchical timing wheel (adapted from timeout.h)
|
||||
// Original: Copyright (c) 2013-2014 William Ahern, MIT license.
|
||||
|
||||
#ifndef TWHEEL_H
|
||||
#define TWHEEL_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/queue.h>
|
||||
|
||||
#define TWHEEL_VERSION 0x20160226
|
||||
|
||||
typedef uint64_t twheel_t;
|
||||
#define TWHEEL_C(n) UINT64_C(n)
|
||||
#define TWHEEL_mHZ TWHEEL_C(1000)
|
||||
#define TWHEEL_MAX ((TWHEEL_C(1) << 24) - 1)
|
||||
|
||||
enum twheel_flags {
|
||||
TWHEEL_INT = 0x01,
|
||||
TWHEEL_ABS = 0x02,
|
||||
};
|
||||
|
||||
#define TWHEEL_INITIALIZER(flags) { (flags) }
|
||||
|
||||
struct twheel {
|
||||
int flags;
|
||||
twheel_t expires;
|
||||
struct twheel_list *pending;
|
||||
TAILQ_ENTRY(twheel) tqe;
|
||||
twheel_t interval;
|
||||
};
|
||||
|
||||
TAILQ_HEAD(twheel_list, twheel);
|
||||
|
||||
struct twheels {
|
||||
struct twheel_list wheel[4][64], expired;
|
||||
uint64_t pending[4];
|
||||
twheel_t curtime;
|
||||
twheel_t hertz;
|
||||
};
|
||||
|
||||
struct twheel *twheel_init(struct twheel *, int flags);
|
||||
bool twheel_pending(struct twheel *);
|
||||
|
||||
struct twheels *twheels_open(twheel_t hz);
|
||||
void twheels_close(struct twheels *);
|
||||
twheel_t twheels_hz(struct twheels *);
|
||||
void twheels_update(struct twheels *, twheel_t curtime);
|
||||
twheel_t twheels_timeout(struct twheels *);
|
||||
void twheels_add(struct twheels *, struct twheel *, twheel_t timeout);
|
||||
void twheels_del(struct twheels *, struct twheel *);
|
||||
struct twheel *twheels_get(struct twheels *);
|
||||
bool twheels_pending(struct twheels *);
|
||||
bool twheels_expired(struct twheels *);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,35 @@
|
||||
// twheel_bitops.c — ctz/clz for timing wheel (included by twheel.c)
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(__GNUC__) && !defined(TWHEEL_DISABLE_GNUC_BITOPS)
|
||||
#define ctz64(n) __builtin_ctzll(n)
|
||||
#define clz64(n) __builtin_clzll(n)
|
||||
#if LONG_BITS == 32
|
||||
#define ctz32(n) __builtin_ctzl(n)
|
||||
#define clz32(n) __builtin_clzl(n)
|
||||
#else
|
||||
#define ctz32(n) __builtin_ctz(n)
|
||||
#define clz32(n) __builtin_clz(n)
|
||||
#endif
|
||||
#elif defined(_MSC_VER) && !defined(TWHEEL_DISABLE_MSVC_BITOPS)
|
||||
#include <intrin.h>
|
||||
static __inline int ctz32(unsigned long val) { DWORD z=0; _BitScanForward(&z,val); return z; }
|
||||
static __inline int clz32(unsigned long val) { DWORD z=0; _BitScanReverse(&z,val); return z; }
|
||||
#ifdef _WIN64
|
||||
static __inline int ctz64(uint64_t val) { DWORD z=0; _BitScanForward64(&z,val); return z; }
|
||||
static __inline int clz64(uint64_t val) { DWORD z=0; _BitScanReverse64(&z,val); return z; }
|
||||
#else
|
||||
static __inline int ctz64(uint64_t val) { uint32_t lo=(uint32_t)val,hi=(uint32_t)(val>>32); return lo?ctz32(lo):32+ctz32(hi); }
|
||||
static __inline int clz64(uint64_t val) { uint32_t lo=(uint32_t)val,hi=(uint32_t)(val>>32); return hi?clz32(hi):32+clz32(lo); }
|
||||
#endif
|
||||
#else
|
||||
#define process_(one, cz_bits, bits) if (x < ( one << (cz_bits - bits))) { rv += bits; x <<= bits; }
|
||||
static inline int clz64(uint64_t x) { int rv=0; process_(UINT64_C(1),64,32); process_(UINT64_C(1),64,16); process_(UINT64_C(1),64,8); process_(UINT64_C(1),64,4); process_(UINT64_C(1),64,2); process_(UINT64_C(1),64,1); return rv; }
|
||||
static inline int clz32(uint32_t x) { int rv=0; process_(UINT32_C(1),32,16); process_(UINT32_C(1),32,8); process_(UINT32_C(1),32,4); process_(UINT32_C(1),32,2); process_(UINT32_C(1),32,1); return rv; }
|
||||
#undef process_
|
||||
#define process_(one, bits) if ((x & ((one << (bits))-1)) == 0) { rv += bits; x >>= bits; }
|
||||
static inline int ctz64(uint64_t x) { int rv=0; process_(UINT64_C(1),32); process_(UINT64_C(1),16); process_(UINT64_C(1),8); process_(UINT64_C(1),4); process_(UINT64_C(1),2); process_(UINT64_C(1),1); return rv; }
|
||||
static inline int ctz32(uint32_t x) { int rv=0; process_(UINT32_C(1),16); process_(UINT32_C(1),8); process_(UINT32_C(1),4); process_(UINT32_C(1),2); process_(UINT32_C(1),1); return rv; }
|
||||
#undef process_
|
||||
#endif
|
||||
+200
-386
@@ -4,7 +4,8 @@
|
||||
#include "platform_compat.h"
|
||||
#include "debug_config.h"
|
||||
#include "mem.h"
|
||||
#include "memory_pool.h"
|
||||
#include "memory_pool.h"
|
||||
#include "twheel.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
@@ -32,13 +33,12 @@
|
||||
|
||||
// Timeout node with safe cancellation
|
||||
struct timeout_node {
|
||||
struct twheel to; // MUST be first — embedded timing wheel entry
|
||||
char name[16];
|
||||
void* arg;
|
||||
timeout_callback_t callback;
|
||||
uint64_t expiration_ms; // absolute expiration time in milliseconds
|
||||
struct UASYNC* ua; // Pointer back to uasync instance for counter updates
|
||||
struct timeout_node* next; // For immediate queue (FIFO)
|
||||
size_t heap_index; // Position in timeout_heap (SIZE_MAX if not in heap)
|
||||
};
|
||||
|
||||
// Socket node with array-based storage
|
||||
@@ -287,19 +287,52 @@ static struct socket_node* socket_array_get_by_sock(struct socket_array* sa, soc
|
||||
if (index == -1 || !sa->sockets[index].active) return NULL;
|
||||
if (sa->sockets[index].type != SOCKET_NODE_TYPE_SOCK) return NULL;
|
||||
|
||||
return &sa->sockets[index];
|
||||
}
|
||||
|
||||
// Callback to u_free timeout node and update counters
|
||||
static void timeout_node_free_callback(void* user_data, void* data) {
|
||||
struct UASYNC* ua = (struct UASYNC*)user_data;
|
||||
struct timeout_node* node = (struct timeout_node*)data;
|
||||
(void)node; // Not used directly, but keep for consistency
|
||||
ua->timer_free_count++;
|
||||
memory_pool_free(ua->timeout_pool, data);
|
||||
}
|
||||
|
||||
// Helper to get current time
|
||||
return &sa->sockets[index];
|
||||
}
|
||||
|
||||
// ------ socket_node dispatch helpers (eliminate FD/SOCK type duplication) ------
|
||||
|
||||
static inline int socket_node_fd(const struct socket_node* n) {
|
||||
if (n->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
#ifdef _WIN32
|
||||
return (int)(intptr_t)n->sock;
|
||||
#else
|
||||
return n->sock;
|
||||
#endif
|
||||
}
|
||||
return n->fd;
|
||||
}
|
||||
|
||||
static inline void socket_node_dispatch_read(const struct socket_node* n) {
|
||||
if (n->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (n->read_cbk_sock) n->read_cbk_sock(n->sock, n->user_data);
|
||||
} else {
|
||||
if (n->read_cbk) n->read_cbk(n->fd, n->user_data);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void socket_node_dispatch_write(const struct socket_node* n) {
|
||||
if (n->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (n->write_cbk_sock) n->write_cbk_sock(n->sock, n->user_data);
|
||||
} else {
|
||||
if (n->write_cbk) n->write_cbk(n->fd, n->user_data);
|
||||
}
|
||||
}
|
||||
|
||||
static inline short socket_node_events(const struct socket_node* n) {
|
||||
short events = 0;
|
||||
if (n->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (n->read_cbk_sock && n->enable_read) events |= POLLIN;
|
||||
if (n->write_cbk_sock && n->enable_write) events |= POLLOUT;
|
||||
} else {
|
||||
if (n->read_cbk && n->enable_read) events |= POLLIN;
|
||||
if (n->write_cbk && n->enable_write) events |= POLLOUT;
|
||||
}
|
||||
if (n->except_cbk) events |= POLLPRI;
|
||||
return events;
|
||||
}
|
||||
|
||||
// Simplified timeout handling without reference counting
|
||||
static void get_current_time(struct timeval* tv) {
|
||||
#ifdef _WIN32
|
||||
// Для Windows используем GetTickCount или другие механизмы
|
||||
@@ -358,17 +391,6 @@ uint64_t get_time_us(void) {
|
||||
|
||||
|
||||
|
||||
// Drain wakeup pipe - read all available bytes
|
||||
static void drain_wakeup_pipe(struct UASYNC* ua) {
|
||||
if (!ua || !ua->wakeup_initialized) return;
|
||||
|
||||
char buf[64];
|
||||
while (1) {
|
||||
ssize_t n = read(ua->wakeup_pipe[0], buf, sizeof(buf));
|
||||
if (n <= 0) break;
|
||||
}
|
||||
}
|
||||
|
||||
// Process posted tasks (lock-u_free during execution)
|
||||
static void process_posted_tasks(struct UASYNC* ua) {
|
||||
if (!ua) return;
|
||||
@@ -390,7 +412,7 @@ static void process_posted_tasks(struct UASYNC* ua) {
|
||||
#endif
|
||||
|
||||
while (list) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "POSTed task get");
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "POSTed task get");
|
||||
struct posted_task* t = list;
|
||||
list = list->next;
|
||||
|
||||
@@ -435,8 +457,8 @@ static uint64_t timeval_to_ms(const struct timeval* tv) {
|
||||
|
||||
|
||||
|
||||
// Simplified timeout handling without reference counting
|
||||
|
||||
// Simplified timeout handling without reference counting
|
||||
|
||||
// Process expired timeouts with safe cancellation
|
||||
static void process_timeouts(struct UASYNC* ua) {
|
||||
if (!ua) return;
|
||||
@@ -460,63 +482,40 @@ static void process_timeouts(struct UASYNC* ua) {
|
||||
memory_pool_free(ua->timeout_pool, node);
|
||||
}
|
||||
|
||||
if (!ua->timeout_heap) return;
|
||||
if (!ua->twheel) return;
|
||||
|
||||
struct timeval now_tv;
|
||||
get_current_time(&now_tv);
|
||||
uint64_t now_ms = timeval_to_ms(&now_tv);
|
||||
|
||||
while (1) {
|
||||
TimeoutEntry entry;
|
||||
if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) break;
|
||||
if (entry.expiration > now_ms) break;
|
||||
|
||||
// Pop the expired timeout
|
||||
timeout_heap_pop(ua->timeout_heap, &entry);
|
||||
struct timeout_node* node = (struct timeout_node*)entry.data;
|
||||
// Update timing wheel to current time — moves expired to expired queue
|
||||
twheels_update(ua->twheel, now_ms);
|
||||
|
||||
// Drain all expired timeouts
|
||||
struct twheel *to;
|
||||
while ((to = twheels_get(ua->twheel))) {
|
||||
struct timeout_node* node = (struct timeout_node*)to;
|
||||
|
||||
if (node && node->callback) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "timer→%s expired", node->name[0] ? node->name : "");
|
||||
node->callback(node->arg);
|
||||
}
|
||||
|
||||
// Always u_free the node after processing
|
||||
if (node && node->ua) {
|
||||
node->ua->timer_free_count++;
|
||||
}
|
||||
memory_pool_free(ua->timeout_pool, node);
|
||||
continue; // Process next expired timeout
|
||||
}
|
||||
}
|
||||
|
||||
// Compute time to next timeout
|
||||
static void get_next_timeout(struct UASYNC* ua, struct timeval* tv) {
|
||||
if (!ua || !ua->timeout_heap) {
|
||||
tv->tv_sec = 0;
|
||||
tv->tv_usec = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
TimeoutEntry entry;
|
||||
if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) {
|
||||
tv->tv_sec = 0;
|
||||
tv->tv_usec = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
struct timeval now_tv;
|
||||
get_current_time(&now_tv);
|
||||
uint64_t now_ms = timeval_to_ms(&now_tv);
|
||||
|
||||
if (entry.expiration <= now_ms) {
|
||||
tv->tv_sec = 0;
|
||||
tv->tv_usec = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t delta_ms = entry.expiration - now_ms;
|
||||
tv->tv_sec = delta_ms / 1000;
|
||||
tv->tv_usec = (delta_ms % 1000) * 1000;
|
||||
// Compute time to next timeout in milliseconds
|
||||
// Returns: 0 = timer already expired/no timers, ~0 = no pending timers, >0 = ms to wait
|
||||
static uint64_t get_next_timeout_ms(struct UASYNC* ua) {
|
||||
if (!ua || !ua->twheel) return 0;
|
||||
twheel_t t = twheels_timeout(ua->twheel);
|
||||
if (t == 0) return 0; // expired timer exists
|
||||
if (t == ~TWHEEL_C(0)) return ~TWHEEL_C(0); // no pending timers
|
||||
return (uint64_t)t;
|
||||
}
|
||||
|
||||
|
||||
@@ -524,9 +523,7 @@ static void get_next_timeout(struct UASYNC* ua, struct timeval* tv) {
|
||||
// Instance version
|
||||
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_callback_t callback, const char* name) {
|
||||
if (!ua || timeout_tb < 0 || !callback) return NULL;
|
||||
if (!ua->timeout_heap) return NULL;
|
||||
|
||||
// DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: timeout=%d.%d ms, arg=%p, callback=%p", timeout_tb/10, timeout_tb%10, arg, callback);
|
||||
if (!ua->twheel) return NULL;
|
||||
|
||||
struct timeout_node* node = memory_pool_alloc(ua->timeout_pool);
|
||||
if (!node) {
|
||||
@@ -544,24 +541,19 @@ void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_c
|
||||
node->arg = arg;
|
||||
node->callback = callback;
|
||||
node->ua = ua;
|
||||
node->heap_index = SIZE_MAX;
|
||||
|
||||
// Calculate expiration time in milliseconds
|
||||
// Calculate expiration time in milliseconds (absolute monotonic)
|
||||
struct timeval now;
|
||||
get_current_time(&now);
|
||||
timeval_add_tb(&now, timeout_tb);
|
||||
node->expiration_ms = timeval_to_ms(&now);
|
||||
uint64_t expiration_ms = timeval_to_ms(&now);
|
||||
|
||||
// Initialize wheel entry and add to timing wheel
|
||||
twheel_init(&node->to, TWHEEL_ABS);
|
||||
twheels_add(ua->twheel, &node->to, expiration_ms);
|
||||
|
||||
// Add to heap
|
||||
if (timeout_heap_push(ua->timeout_heap, node->expiration_ms, node, &node->heap_index) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to push to heap");
|
||||
memory_pool_free(ua->timeout_pool, node);
|
||||
ua->timer_free_count++; // Balance the alloc counter
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
// Immediate execution in next mainloop (FIFO order)
|
||||
void* uasync_call_soon(struct UASYNC* ua, void* user_arg, timeout_callback_t callback) {
|
||||
@@ -579,9 +571,8 @@ void* uasync_call_soon(struct UASYNC* ua, void* user_arg, timeout_callback_t cal
|
||||
node->arg = user_arg;
|
||||
node->callback = callback;
|
||||
node->ua = ua;
|
||||
node->expiration_ms = 0;
|
||||
node->next = NULL;
|
||||
node->heap_index = SIZE_MAX;
|
||||
memset(&node->to, 0, sizeof(node->to));
|
||||
|
||||
// FIFO: добавляем в конец очереди
|
||||
if (ua->immediate_queue_tail) {
|
||||
@@ -609,30 +600,26 @@ err_t uasync_call_soon_cancel(struct UASYNC* ua, void* t_id) {
|
||||
|
||||
|
||||
|
||||
// Instance version
|
||||
// Instance version
|
||||
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id) {
|
||||
if (!ua || !t_id || !ua->timeout_heap) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: invalid parameters ua=%p, t_id=%p, heap=%p",
|
||||
ua, t_id, ua ? ua->timeout_heap : NULL);
|
||||
if (!ua || !t_id || !ua->twheel) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: invalid parameters ua=%p, t_id=%p, twheel=%p",
|
||||
ua, t_id, ua ? ua->twheel : NULL);
|
||||
return ERR_FAIL;
|
||||
}
|
||||
|
||||
struct timeout_node* node = (struct timeout_node*)t_id;
|
||||
|
||||
if (node->heap_index == SIZE_MAX || node->heap_index >= ua->timeout_heap->size ||
|
||||
ua->timeout_heap->heap[node->heap_index].data != node) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: not found in heap: ua=%p, t_id=%p, node=%p, expires=%llu ms",
|
||||
ua, t_id, node, (unsigned long long)node->expiration_ms);
|
||||
if (!twheel_pending(&node->to)) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: not pending: ua=%p, t_id=%p", ua, t_id);
|
||||
return ERR_FAIL;
|
||||
}
|
||||
|
||||
if (timeout_heap_cancel_at(ua->timeout_heap, node->heap_index, node) == 0) {
|
||||
node->heap_index = SIZE_MAX;
|
||||
node->callback = NULL;
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
return ERR_FAIL;
|
||||
twheels_del(ua->twheel, &node->to);
|
||||
node->callback = NULL;
|
||||
ua->timer_free_count++;
|
||||
memory_pool_free(ua->timeout_pool, node);
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -765,36 +752,27 @@ err_t uasync_remove_socket_t(struct UASYNC* ua, socket_t sock) {
|
||||
return ERR_FAIL;
|
||||
}
|
||||
|
||||
err_t uasync_set_socket_read(struct UASYNC* ua, void* s_id, int enable) {
|
||||
static err_t socket_set_event(struct UASYNC* ua, void* s_id, int is_read, int enable) {
|
||||
if (!ua || !s_id) return ERR_FAIL;
|
||||
|
||||
struct socket_node* node = (struct socket_node*)s_id;
|
||||
if (!node->active || node->fd < 0) return ERR_FAIL;
|
||||
|
||||
int val = enable ? 1 : 0;
|
||||
if (node->enable_read == val) return ERR_OK;
|
||||
node->enable_read = val;
|
||||
int* field = is_read ? &node->enable_read : &node->enable_write;
|
||||
if (*field == val) return ERR_OK;
|
||||
*field = val;
|
||||
|
||||
#if HAS_EPOLL
|
||||
if (ua->use_epoll && ua->epoll_fd >= 0) {
|
||||
struct epoll_event ev;
|
||||
ev.events = 0;
|
||||
if (node->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (node->read_cbk_sock && node->enable_read) ev.events |= EPOLLIN;
|
||||
if (node->write_cbk_sock && node->enable_write) ev.events |= EPOLLOUT;
|
||||
ev.data.fd = node->sock;
|
||||
} else {
|
||||
if (node->read_cbk && node->enable_read) ev.events |= EPOLLIN;
|
||||
if (node->write_cbk && node->enable_write) ev.events |= EPOLLOUT;
|
||||
ev.data.fd = node->fd;
|
||||
}
|
||||
if (node->except_cbk) ev.events |= EPOLLPRI;
|
||||
ev.events = socket_node_events(node);
|
||||
ev.data.fd = socket_node_fd(node);
|
||||
#ifdef _WIN32
|
||||
int efd = (int)(intptr_t)ev.data.fd;
|
||||
epoll_ctl(ua->epoll_fd, EPOLL_CTL_MOD, (int)(intptr_t)ev.data.fd, &ev);
|
||||
#else
|
||||
int efd = ev.data.fd;
|
||||
epoll_ctl(ua->epoll_fd, EPOLL_CTL_MOD, ev.data.fd, &ev);
|
||||
#endif
|
||||
epoll_ctl(ua->epoll_fd, EPOLL_CTL_MOD, efd, &ev);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -802,41 +780,12 @@ err_t uasync_set_socket_read(struct UASYNC* ua, void* s_id, int enable) {
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
err_t uasync_set_socket_read(struct UASYNC* ua, void* s_id, int enable) {
|
||||
return socket_set_event(ua, s_id, 1, enable);
|
||||
}
|
||||
|
||||
err_t uasync_set_socket_write(struct UASYNC* ua, void* s_id, int enable) {
|
||||
if (!ua || !s_id) return ERR_FAIL;
|
||||
|
||||
struct socket_node* node = (struct socket_node*)s_id;
|
||||
if (!node->active || node->fd < 0) return ERR_FAIL;
|
||||
|
||||
int val = enable ? 1 : 0;
|
||||
if (node->enable_write == val) return ERR_OK;
|
||||
node->enable_write = val;
|
||||
|
||||
#if HAS_EPOLL
|
||||
if (ua->use_epoll && ua->epoll_fd >= 0) {
|
||||
struct epoll_event ev;
|
||||
ev.events = 0;
|
||||
if (node->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (node->read_cbk_sock && node->enable_read) ev.events |= EPOLLIN;
|
||||
if (node->write_cbk_sock && node->enable_write) ev.events |= EPOLLOUT;
|
||||
ev.data.fd = node->sock;
|
||||
} else {
|
||||
if (node->read_cbk && node->enable_read) ev.events |= EPOLLIN;
|
||||
if (node->write_cbk && node->enable_write) ev.events |= EPOLLOUT;
|
||||
ev.data.fd = node->fd;
|
||||
}
|
||||
if (node->except_cbk) ev.events |= EPOLLPRI;
|
||||
#ifdef _WIN32
|
||||
int efd = (int)(intptr_t)ev.data.fd;
|
||||
#else
|
||||
int efd = ev.data.fd;
|
||||
#endif
|
||||
epoll_ctl(ua->epoll_fd, EPOLL_CTL_MOD, efd, &ev);
|
||||
}
|
||||
#endif
|
||||
|
||||
ua->poll_fds_dirty = 1;
|
||||
return ERR_OK;
|
||||
return socket_set_event(ua, s_id, 0, enable);
|
||||
}
|
||||
|
||||
// Helper function to rebuild cached pollfd array
|
||||
@@ -869,37 +818,16 @@ static void rebuild_poll_fds(struct UASYNC* ua) {
|
||||
idx++;
|
||||
}
|
||||
|
||||
// Add socket fds using active_indices for O(1) traversal
|
||||
for (int i = 0; i < socket_count; i++) {
|
||||
int socket_array_idx = ua->sockets->active_indices[i];
|
||||
struct socket_node* cur = &ua->sockets->sockets[socket_array_idx];
|
||||
|
||||
// Handle socket_t vs int fd
|
||||
if (cur->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
// socket_t - cast to int for pollfd
|
||||
#ifdef _WIN32
|
||||
ua->poll_fds[idx].fd = (int)(intptr_t)cur->sock;
|
||||
#else
|
||||
ua->poll_fds[idx].fd = cur->sock;
|
||||
#endif
|
||||
} else {
|
||||
// Regular fd
|
||||
ua->poll_fds[idx].fd = cur->fd;
|
||||
}
|
||||
ua->poll_fds[idx].events = 0;
|
||||
ua->poll_fds[idx].revents = 0;
|
||||
|
||||
if (cur->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (cur->read_cbk_sock && cur->enable_read) ua->poll_fds[idx].events |= POLLIN;
|
||||
if (cur->write_cbk_sock && cur->enable_write) ua->poll_fds[idx].events |= POLLOUT;
|
||||
} else {
|
||||
if (cur->read_cbk && cur->enable_read) ua->poll_fds[idx].events |= POLLIN;
|
||||
if (cur->write_cbk && cur->enable_write) ua->poll_fds[idx].events |= POLLOUT;
|
||||
}
|
||||
if (cur->write_cbk && cur->enable_write) ua->poll_fds[idx].events |= POLLOUT;
|
||||
if (cur->except_cbk) ua->poll_fds[idx].events |= POLLPRI;
|
||||
|
||||
idx++;
|
||||
// Add socket fds using active_indices for O(1) traversal
|
||||
for (int i = 0; i < socket_count; i++) {
|
||||
int socket_array_idx = ua->sockets->active_indices[i];
|
||||
struct socket_node* cur = &ua->sockets->sockets[socket_array_idx];
|
||||
|
||||
ua->poll_fds[idx].fd = socket_node_fd(cur);
|
||||
ua->poll_fds[idx].events = socket_node_events(cur);
|
||||
ua->poll_fds[idx].revents = 0;
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
ua->poll_fds_count = total_fds;
|
||||
@@ -913,7 +841,7 @@ static void process_epoll_events(struct UASYNC* ua, struct epoll_event* events,
|
||||
// Check if this is the wakeup fd (data.fd is -1)
|
||||
if (events[i].data.fd < 0) {
|
||||
if (events[i].events & EPOLLIN) {
|
||||
drain_wakeup_pipe(ua);
|
||||
handle_wakeup(ua);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -936,99 +864,59 @@ static void process_epoll_events(struct UASYNC* ua, struct epoll_event* events,
|
||||
|
||||
/* Check for error conditions first */
|
||||
if (events[i].events & (EPOLLERR | EPOLLHUP)) {
|
||||
if (local_except) {
|
||||
local_except(local_fd, local_ud);
|
||||
}
|
||||
if (local_except) local_except(local_fd, local_ud);
|
||||
}
|
||||
|
||||
/* Read readiness - use appropriate callback based on socket type */
|
||||
|
||||
/* Read readiness */
|
||||
if (events[i].events & EPOLLIN) {
|
||||
if (local_type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (local_read_sock) {
|
||||
local_read_sock(local_sock, local_ud);
|
||||
}
|
||||
} else {
|
||||
if (local_read) {
|
||||
local_read(local_fd, local_ud);
|
||||
}
|
||||
}
|
||||
if (local_read_sock) local_read_sock(local_sock, local_ud);
|
||||
} else if (local_read) local_read(local_fd, local_ud);
|
||||
}
|
||||
|
||||
/* Write readiness - use appropriate callback based on socket type */
|
||||
|
||||
/* Write readiness */
|
||||
if (events[i].events & EPOLLOUT) {
|
||||
if (local_type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (local_write_sock) {
|
||||
local_write_sock(local_sock, local_ud);
|
||||
}
|
||||
} else {
|
||||
if (local_write) {
|
||||
local_write(local_fd, local_ud);
|
||||
}
|
||||
}
|
||||
if (local_write_sock) local_write_sock(local_sock, local_ud);
|
||||
} else if (local_write) local_write(local_fd, local_ud);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Instance version
|
||||
void uasync_poll(struct UASYNC* ua, int timeout_tb) {
|
||||
if (!ua) return;
|
||||
if (!ua->sockets || !ua->timeout_heap) return;
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "poll(%d sockets, %zu timers, timeout=%d.%dms)",
|
||||
ua->sockets->count, ua->timeout_heap->size, timeout_tb >= 0 ? timeout_tb / 10000 : -1,
|
||||
timeout_tb >= 0 ? (timeout_tb % 10000) / 10 : 0);
|
||||
|
||||
// Handle negative or zero timeout
|
||||
if (timeout_tb < 0) timeout_tb = -1; // Infinite wait
|
||||
else if (timeout_tb == 0) timeout_tb = 0; // No wait
|
||||
|
||||
// Get next timeout
|
||||
struct timeval next_timeout;
|
||||
get_next_timeout(ua, &next_timeout);
|
||||
|
||||
// Convert requested timeout to timeval
|
||||
struct timeval req_timeout = {0};
|
||||
if (timeout_tb >= 0) {
|
||||
req_timeout.tv_sec = timeout_tb / 10000;
|
||||
req_timeout.tv_usec = (timeout_tb % 10000) * 100;
|
||||
}
|
||||
|
||||
struct timeval poll_timeout;
|
||||
// Instance version
|
||||
void uasync_poll(struct UASYNC* ua, int timeout_tb) {
|
||||
if (!ua) return;
|
||||
if (!ua->sockets || !ua->twheel) return;
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "poll(%d sockets, timeout=%d.%dms)",
|
||||
ua->sockets->count, timeout_tb >= 0 ? timeout_tb / 10000 : -1,
|
||||
timeout_tb >= 0 ? (timeout_tb % 10000) / 10 : 0);
|
||||
|
||||
uint64_t next_ms = get_next_timeout_ms(ua); // 0=expired, ~0=no timers, >0=ms to wait
|
||||
|
||||
int timeout_ms;
|
||||
if (timeout_tb < 0) {
|
||||
poll_timeout = next_timeout;
|
||||
// Infinite wait or until next timer
|
||||
if (next_ms == 0) timeout_ms = 0; // Expired timer, don't wait
|
||||
else if (next_ms != ~TWHEEL_C(0)) timeout_ms = (int)next_ms; // Wait for next timer
|
||||
else timeout_ms = -1; // No timers, infinite wait
|
||||
} else {
|
||||
if (next_timeout.tv_sec < req_timeout.tv_sec ||
|
||||
(next_timeout.tv_sec == req_timeout.tv_sec && next_timeout.tv_usec < req_timeout.tv_usec)) {
|
||||
poll_timeout = next_timeout;
|
||||
} else {
|
||||
poll_timeout = req_timeout;
|
||||
}
|
||||
int req_ms = timeout_tb / 10; // timebase to ms
|
||||
if (next_ms == 0) timeout_ms = 0; // Expired timer, don't wait
|
||||
else if (next_ms != ~TWHEEL_C(0)) timeout_ms = (int)(next_ms < (uint64_t)req_ms ? next_ms : (uint64_t)req_ms);
|
||||
else timeout_ms = req_ms;
|
||||
}
|
||||
if (poll_timeout.tv_sec == 0 && poll_timeout.tv_usec == 0 && timeout_tb > 0) poll_timeout = req_timeout;
|
||||
|
||||
|
||||
int timeout_ms;
|
||||
if (timeout_tb < 0 && (next_timeout.tv_sec > 0 || next_timeout.tv_usec > 0)) {
|
||||
timeout_ms = (poll_timeout.tv_sec * 1000) + (poll_timeout.tv_usec / 1000);
|
||||
} else if (timeout_tb < 0) {
|
||||
timeout_ms = -1; // Infinite
|
||||
} else {
|
||||
timeout_ms = (poll_timeout.tv_sec * 1000) + (poll_timeout.tv_usec / 1000);
|
||||
}
|
||||
|
||||
// Count active sockets
|
||||
int socket_count = ua->sockets->count;
|
||||
if (socket_count == 0 && timeout_ms == -1) {
|
||||
// No sockets and infinite wait - but we have timers? Wait for timer
|
||||
if (ua->timeout_heap->size > 0) {
|
||||
timeout_ms = (poll_timeout.tv_sec * 1000) + (poll_timeout.tv_usec / 1000);
|
||||
} else {
|
||||
// Nothing to do - return immediately
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int socket_count = ua->sockets->count;
|
||||
if (socket_count == 0 && timeout_ms == -1) {
|
||||
// No sockets and infinite wait — but we might have timers
|
||||
if (twheels_pending(ua->twheel) || twheels_expired(ua->twheel))
|
||||
timeout_ms = 0; // Let process_timeouts handle them
|
||||
else
|
||||
return; // Nothing to do
|
||||
}
|
||||
|
||||
#if HAS_EPOLL
|
||||
// Use epoll on Linux if available
|
||||
if (ua->use_epoll && ua->epoll_fd >= 0) {
|
||||
@@ -1143,35 +1031,12 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
|
||||
if (!has_read && !has_write && !has_except) continue;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "select→fd=%d r=%d w=%d e=%d", (int)s, has_read, has_write, has_except);
|
||||
|
||||
if (has_except) {
|
||||
if (node->except_cbk) {
|
||||
node->except_cbk(node->fd, node->user_data);
|
||||
}
|
||||
}
|
||||
|
||||
if (has_read) {
|
||||
if (node->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (node->read_cbk_sock) {
|
||||
node->read_cbk_sock(node->sock, node->user_data);
|
||||
}
|
||||
} else {
|
||||
if (node->read_cbk) {
|
||||
node->read_cbk(node->fd, node->user_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (has_write) {
|
||||
if (node->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (node->write_cbk_sock) {
|
||||
node->write_cbk_sock(node->sock, node->user_data);
|
||||
}
|
||||
} else {
|
||||
if (node->write_cbk) {
|
||||
node->write_cbk(node->fd, node->user_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (has_except) {
|
||||
if (node->except_cbk) node->except_cbk(node->fd, node->user_data);
|
||||
}
|
||||
|
||||
if (has_read) socket_node_dispatch_read(node);
|
||||
if (has_write) socket_node_dispatch_write(node);
|
||||
}
|
||||
}
|
||||
#else
|
||||
@@ -1202,14 +1067,6 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
|
||||
if (ua->poll_fds[i].revents == 0) continue;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "poll→fd=%d rev=0x%x", ua->poll_fds[i].fd, ua->poll_fds[i].revents);
|
||||
|
||||
/* Handle wakeup fd separately */
|
||||
if (wakeup_fd_present && i == 0) {
|
||||
if (ua->poll_fds[i].revents & POLLIN) {
|
||||
drain_wakeup_pipe(ua);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Socket event - lookup by fd */
|
||||
struct socket_node* node = socket_array_get(ua->sockets, ua->poll_fds[i].fd);
|
||||
if (!node) { // Try by socket_t (in case this is a socket)
|
||||
@@ -1218,46 +1075,18 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
|
||||
}
|
||||
if (!node) continue; // Socket may have been removed
|
||||
|
||||
/* Check for error conditions first */
|
||||
if (ua->poll_fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
|
||||
/* Treat as exceptional condition */
|
||||
if (node->except_cbk) {
|
||||
node->except_cbk(node->fd, node->user_data);
|
||||
}
|
||||
}
|
||||
|
||||
/* Exceptional data (out-of-band) */
|
||||
if (ua->poll_fds[i].revents & POLLPRI) {
|
||||
if (node->except_cbk) {
|
||||
node->except_cbk(node->fd, node->user_data);
|
||||
}
|
||||
}
|
||||
|
||||
/* Read readiness - use appropriate callback based on socket type */
|
||||
if (ua->poll_fds[i].revents & POLLIN) {
|
||||
if (node->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (node->read_cbk_sock) {
|
||||
node->read_cbk_sock(node->sock, node->user_data);
|
||||
}
|
||||
} else {
|
||||
if (node->read_cbk) {
|
||||
node->read_cbk(node->fd, node->user_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Write readiness - use appropriate callback based on socket type */
|
||||
if (ua->poll_fds[i].revents & POLLOUT) {
|
||||
if (node->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (node->write_cbk_sock) {
|
||||
node->write_cbk_sock(node->sock, node->user_data);
|
||||
}
|
||||
} else {
|
||||
if (node->write_cbk) {
|
||||
node->write_cbk(node->fd, node->user_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Check for error conditions first */
|
||||
if (ua->poll_fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
|
||||
if (node->except_cbk) node->except_cbk(node->fd, node->user_data);
|
||||
}
|
||||
|
||||
/* Exceptional data (out-of-band) */
|
||||
if (ua->poll_fds[i].revents & POLLPRI) {
|
||||
if (node->except_cbk) node->except_cbk(node->fd, node->user_data);
|
||||
}
|
||||
|
||||
if (ua->poll_fds[i].revents & POLLIN) socket_node_dispatch_read(node);
|
||||
if (ua->poll_fds[i].revents & POLLOUT) socket_node_dispatch_write(node);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1267,9 +1096,6 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
|
||||
}
|
||||
|
||||
|
||||
// Put this near the top of u_async.c, after includes and before uasync_create
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
static void wakeup_read_callback_win(socket_t sock, void* arg) {
|
||||
(void)sock; // не нужен
|
||||
@@ -1330,8 +1156,8 @@ struct UASYNC* uasync_create(void) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ua->timeout_heap = timeout_heap_create(16);
|
||||
if (!ua->timeout_heap) {
|
||||
ua->twheel = twheels_open(TWHEEL_mHZ);
|
||||
if (!ua->twheel) {
|
||||
socket_array_destroy(ua->sockets);
|
||||
if (ua->wakeup_initialized) {
|
||||
#ifdef _WIN32
|
||||
@@ -1349,7 +1175,7 @@ struct UASYNC* uasync_create(void) {
|
||||
// Initialize timeout pool
|
||||
ua->timeout_pool = memory_pool_init(sizeof(struct timeout_node), "timeout_pool");
|
||||
if (!ua->timeout_pool) {
|
||||
timeout_heap_destroy(ua->timeout_heap);
|
||||
twheels_close(ua->twheel);
|
||||
socket_array_destroy(ua->sockets);
|
||||
if (ua->wakeup_initialized) {
|
||||
#ifdef _WIN32
|
||||
@@ -1364,10 +1190,7 @@ struct UASYNC* uasync_create(void) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Set callback to u_free timeout nodes and update counters
|
||||
timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback);
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH1...");
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH1...");
|
||||
// Initialize epoll on Linux
|
||||
ua->epoll_fd = -1;
|
||||
ua->use_epoll = 0;
|
||||
@@ -1479,29 +1302,11 @@ void uasync_print_resources(struct UASYNC* ua, const char* prefix) {
|
||||
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
|
||||
|
||||
// Показать активные таймеры
|
||||
if (ua->timeout_heap) {
|
||||
struct timeval now_tv;
|
||||
get_current_time(&now_tv);
|
||||
uint64_t now_ms = timeval_to_ms(&now_tv);
|
||||
size_t active_timers = 0;
|
||||
size_t deleted_timers = 0;
|
||||
for (size_t i = 0; i < ua->timeout_heap->size; i++) {
|
||||
uint64_t exp = ua->timeout_heap->heap[i].expiration;
|
||||
int64_t remain = (int64_t)(exp - now_ms);
|
||||
if (ua->timeout_heap->heap[i].deleted) {
|
||||
deleted_timers++;
|
||||
struct timeout_node* node = (struct timeout_node*)ua->timeout_heap->heap[i].data;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Timer DELETED: node=%p name='%s' exp=%llu remain=%lldms",
|
||||
node, node ? node->name : "null", (unsigned long long)exp, (long long)remain);
|
||||
} else {
|
||||
active_timers++;
|
||||
struct timeout_node* node = (struct timeout_node*)ua->timeout_heap->heap[i].data;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Timer ACTIVE: node=%p name='%s' exp=%llu remain=%lldms",
|
||||
node, node ? node->name : "null", (unsigned long long)exp, (long long)remain);
|
||||
}
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Active timers in heap: %zu, deleted: %zu, total: %zu",
|
||||
active_timers, deleted_timers, ua->timeout_heap->size);
|
||||
if (ua->twheel) {
|
||||
bool has_pending = twheels_pending(ua->twheel);
|
||||
bool has_expired = twheels_expired(ua->twheel);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Timing wheel: pending=%d expired=%d curtime=%llu",
|
||||
has_pending, has_expired, (unsigned long long)ua->twheel->curtime);
|
||||
}
|
||||
|
||||
// Показать активные сокеты
|
||||
@@ -1560,21 +1365,30 @@ void uasync_destroy(struct UASYNC* ua, int close_fds) {
|
||||
}
|
||||
ua->immediate_queue_tail = NULL;
|
||||
|
||||
// Очистить heap
|
||||
if (ua->timeout_heap) {
|
||||
while (1) {
|
||||
TimeoutEntry entry;
|
||||
if (timeout_heap_pop(ua->timeout_heap, &entry) != 0) break;
|
||||
struct timeout_node* node = (struct timeout_node*)entry.data;
|
||||
|
||||
// Free all timer nodes (avoid double-u_free bug)
|
||||
if (node) {
|
||||
ua->timer_free_count++;
|
||||
memory_pool_free(ua->timeout_pool, node);
|
||||
// Очистить timing wheel
|
||||
if (ua->twheel) {
|
||||
for (int w = 0; w < 4; w++) {
|
||||
for (int s = 0; s < 64; s++) {
|
||||
struct twheel *to;
|
||||
while (!TAILQ_EMPTY(&ua->twheel->wheel[w][s])) {
|
||||
to = TAILQ_FIRST(&ua->twheel->wheel[w][s]);
|
||||
TAILQ_REMOVE(&ua->twheel->wheel[w][s], to, tqe);
|
||||
struct timeout_node* node = (struct timeout_node*)to;
|
||||
ua->timer_free_count++;
|
||||
memory_pool_free(ua->timeout_pool, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
timeout_heap_destroy(ua->timeout_heap);
|
||||
ua->timeout_heap = NULL;
|
||||
struct twheel *to;
|
||||
while (!TAILQ_EMPTY(&ua->twheel->expired)) {
|
||||
to = TAILQ_FIRST(&ua->twheel->expired);
|
||||
TAILQ_REMOVE(&ua->twheel->expired, to, tqe);
|
||||
struct timeout_node* node = (struct timeout_node*)to;
|
||||
ua->timer_free_count++;
|
||||
memory_pool_free(ua->timeout_pool, node);
|
||||
}
|
||||
twheels_close(ua->twheel);
|
||||
ua->twheel = NULL;
|
||||
}
|
||||
|
||||
// Destroy timeout pool
|
||||
|
||||
+9
-11
@@ -6,9 +6,9 @@
|
||||
#define UASYNC_H
|
||||
|
||||
#include "platform_compat.h"
|
||||
#include "memory_pool.h"
|
||||
#include <stddef.h>
|
||||
#include <signal.h>
|
||||
#include "timeout_heap.h"
|
||||
#include "socket_compat.h"
|
||||
|
||||
typedef void (*timeout_callback_t)(void* user_arg);// передаёт user_arg из uasync_set_timeout
|
||||
@@ -26,16 +26,15 @@ typedef int err_t;
|
||||
#define SOCKET_NODE_TYPE_FD 0 // Regular file descriptor (pipe, file)
|
||||
#define SOCKET_NODE_TYPE_SOCK 1 // Socket (socket_t)
|
||||
|
||||
typedef void (*uasync_post_callback_t)(void* user_arg);
|
||||
|
||||
#include "memory_pool.h"
|
||||
typedef void (*uasync_post_callback_t)(void* user_arg);
|
||||
|
||||
struct timeout_node; // Forward declaration
|
||||
struct timeout_node;
|
||||
struct twheels;
|
||||
|
||||
struct posted_task {
|
||||
uasync_post_callback_t callback;
|
||||
void* arg;
|
||||
struct posted_task* next;
|
||||
struct posted_task {
|
||||
uasync_post_callback_t callback;
|
||||
void* arg;
|
||||
struct posted_task* next;
|
||||
};
|
||||
|
||||
// Uasync instance structure
|
||||
@@ -43,7 +42,7 @@ struct UASYNC {
|
||||
struct memory_pool* timeout_pool; // Pool for timeout_node allocation
|
||||
struct timeout_node* immediate_queue_head; // FIFO queue for immediate execution
|
||||
struct timeout_node* immediate_queue_tail;
|
||||
TimeoutHeap* timeout_heap; // Heap for timeout management
|
||||
struct twheels* twheel; // Timing wheel for timeout management
|
||||
struct socket_array* sockets; // Array-based socket management
|
||||
// Debug counters for memory allocation tracking
|
||||
size_t timer_alloc_count;
|
||||
@@ -74,7 +73,6 @@ struct UASYNC {
|
||||
|
||||
// Type definitions
|
||||
typedef struct UASYNC uasync_t;
|
||||
typedef struct UASYNC UASYNC_t;
|
||||
|
||||
// Instance API - основной API для работы с uasync
|
||||
struct UASYNC* uasync_create(void);
|
||||
|
||||
@@ -26,10 +26,6 @@ utun_CORE_SOURCES = \
|
||||
etcp_dump.c \
|
||||
secure_channel.c \
|
||||
crc32.c \
|
||||
stcp_link.c \
|
||||
stcp.c \
|
||||
stcp_server.c \
|
||||
stcp_client.c \
|
||||
pkt_normalizer.c \
|
||||
packet_dump.c \
|
||||
etcp_api.c \
|
||||
|
||||
+4
-11
@@ -624,12 +624,7 @@ static int parse_server(const char *key, const char *value, struct CFG_SERVER *s
|
||||
srv->only_local = atoi(value) ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "transport") == 0) {
|
||||
if (strcmp(value, "tcp") == 0) srv->transport = 1;
|
||||
else srv->transport = 0; // default udp
|
||||
return 0;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown server option '%s'. Valid: addr, so_mark, fib, netif, type, mtu, only_local, transport", filename, line_num, key);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown server option '%s'. Valid: addr, so_mark, fib, netif, type, mtu, only_local", filename, line_num, key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -725,8 +720,7 @@ 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 = 1048576; // Default 1MB (was INFLIGHT_LIM_MAX)
|
||||
cfg->global.tcp_recv_buf = 0; // 0 = OS default
|
||||
cfg->global.bbr_max_cwnd = 100000; // Default 100KB
|
||||
cfg->global.firewall_rules = NULL;
|
||||
cfg->global.firewall_rule_count = 0;
|
||||
cfg->global.firewall_bypass_all = 0;
|
||||
@@ -875,9 +869,8 @@ 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;
|
||||
if (strcmp(key, "tcp_recv_buf") == 0) {
|
||||
cfg->global.tcp_recv_buf = atoi(value);
|
||||
}
|
||||
// tcp_proxy_server section has no options — its presence alone enables it
|
||||
(void)key; (void)value;
|
||||
break;
|
||||
default:
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Key outside section: %s", filename, line_num, key);
|
||||
|
||||
@@ -40,7 +40,6 @@ struct CFG_SERVER {
|
||||
int so_mark;
|
||||
int fib; // FreeBSD FIB (routing table), -1 = don't set
|
||||
uint8_t type; // public/nat/private
|
||||
uint8_t transport; // 0=udp (default), 1=tcp
|
||||
uint8_t ipv6_mode; // CFG_IPV6_MODE_*
|
||||
int mtu;
|
||||
uint8_t only_local; // 1 = only local connections, no forwarding
|
||||
@@ -162,7 +161,6 @@ 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 {
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "pkt_normalizer.h"
|
||||
#include "../tools/etcpmon/etcpmon_protocol.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/timeout_heap.h"
|
||||
#include "../lib/memory_pool.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
@@ -1028,11 +1027,7 @@ static void send_metrics(struct control_server* server, struct control_client* c
|
||||
}
|
||||
|
||||
/* System resources */
|
||||
if (instance->ua && instance->ua->timeout_heap) {
|
||||
rsp->etcp.active_timeouts = (uint32_t)timeout_heap_get_size(instance->ua->timeout_heap);
|
||||
} else {
|
||||
rsp->etcp.active_timeouts = 0;
|
||||
}
|
||||
rsp->etcp.active_timeouts = 0;
|
||||
|
||||
rsp->etcp.busy_memory_blocks = (uint32_t)(u_get_allocated_count() - memory_pool_get_total_free_blocks());
|
||||
|
||||
|
||||
+32
-62
@@ -207,7 +207,6 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
|
||||
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), "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;
|
||||
@@ -303,26 +302,23 @@ void etcp_connection_close(struct ETCP_CONN* etcp) {
|
||||
etcp->normalizer = NULL;
|
||||
}
|
||||
|
||||
// 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 all queues using helper functions
|
||||
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) {
|
||||
@@ -335,6 +331,17 @@ 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
|
||||
@@ -384,19 +391,12 @@ void etcp_conn_reset(struct ETCP_CONN* etcp) {
|
||||
clear_queue(etcp->recv_q);
|
||||
clear_queue(etcp->ack_q);
|
||||
|
||||
// clear_queue leaves callback_suspended=1, resume to prevent deadlock
|
||||
queue_resume_callback(etcp->input_queue);
|
||||
queue_resume_callback(etcp->input_send_q);
|
||||
queue_resume_callback(etcp->input_wait_ack);
|
||||
|
||||
// В etcp_conn_reset(), после очистки очередей добавьте:
|
||||
struct ETCP_LINK* l = etcp->links;
|
||||
while (l) {
|
||||
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;
|
||||
}
|
||||
@@ -612,16 +612,6 @@ static void input_queue_try_resume(struct ETCP_CONN* etcp) {// при ACK
|
||||
}
|
||||
}
|
||||
|
||||
// Called from link-level etcp_link_update_inflight_lim() after inflight_lim_bytes changes.
|
||||
// Recalculates connection-level optimal_inflight and resumes input_queue if room opened up.
|
||||
void etcp_conn_on_inflight_lim_changed(struct ETCP_CONN* etcp) {
|
||||
if (!etcp) return;
|
||||
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);
|
||||
}
|
||||
|
||||
void etcp_stats(struct ETCP_CONN* etcp) {
|
||||
if (!etcp) return;
|
||||
|
||||
@@ -762,6 +752,7 @@ static void ack_timeout_check(struct ETCP_CONN* etcp) {
|
||||
// shedule timer
|
||||
int64_t next_timeout=timeout - elapsed;
|
||||
if (next_timeout<0) next_timeout=0;
|
||||
if (next_timeout>timeout) next_timeout=timeout;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] retransmission timer set for %llu units", etcp->log_name, next_timeout);
|
||||
etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, next_timeout+10, etcp, ack_timeout_cb, "etcp_retrans");
|
||||
return;
|
||||
@@ -934,8 +925,7 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
|
||||
bbr_note_loss(inf_pkt->last_link->bbr);
|
||||
inf_pkt->last_link->inflight_bytes -= inf_pkt->ll.len;
|
||||
inf_pkt->last_link->inflight_packets--;
|
||||
if (inf_pkt->last_link->send_blocked_inflight && inf_pkt->last_link->inflight_bytes < inf_pkt->last_link->inflight_lim_bytes)
|
||||
loadbalancer_link_ready(inf_pkt->last_link);
|
||||
if (link->send_blocked_inflight && link->inflight_bytes < link->inflight_lim_bytes) loadbalancer_link_ready(link);
|
||||
}
|
||||
|
||||
// Always add to the CURRENT link (first send or retransmission)
|
||||
@@ -1246,28 +1236,18 @@ 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;
|
||||
|
||||
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;
|
||||
uint32_t new_cwnd = link->inflight_lim_bytes;
|
||||
uint32_t new_pacing = link->bbr_pacing_rate;
|
||||
bbr_main(link->bbr, &rs, &new_cwnd, &new_pacing, link->mtu, link->inflight_bytes,
|
||||
(link->inflight_bytes >= link->inflight_lim_bytes));
|
||||
etcp_link_update_inflight_lim(link, new_cwnd);
|
||||
link->bbr_pacing_rate = new_pacing;
|
||||
link->bandwidth = (uint32_t)((uint64_t)new_pacing * 8 / 1000);
|
||||
|
||||
if (link->send_blocked_inflight && link->inflight_bytes < new_cwnd) {
|
||||
link->send_blocked_inflight = 0;
|
||||
loadbalancer_link_ready(link);
|
||||
}
|
||||
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++;
|
||||
}
|
||||
|
||||
|
||||
@@ -1298,10 +1278,6 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d
|
||||
void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "");
|
||||
if (!pkt) return;
|
||||
if (memory_pool_is_freed(pkt->link->etcp->instance->pkt_pool, pkt)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_conn_input: pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt);
|
||||
volatile int _halt = 1; while (_halt) {}
|
||||
}
|
||||
if (!pkt->data_len) {
|
||||
memory_pool_free(pkt->link->etcp->instance->pkt_pool, pkt);
|
||||
return;
|
||||
@@ -1429,7 +1405,7 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
} else DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX ack dedup: seq=%d already in ack_q", etcp->log_name, seq);
|
||||
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;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] adding packet seq=%u to recv_q (last_delivered_id=%u)", etcp->log_name, seq, etcp->last_delivered_id);
|
||||
@@ -1571,12 +1547,6 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
|
||||
}
|
||||
|
||||
if (memory_pool_is_freed(pkt->link->etcp->instance->pkt_pool, pkt)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_conn_input: pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt);
|
||||
volatile int _halt = 1; while (_halt) {}
|
||||
}
|
||||
|
||||
|
||||
memory_pool_free(etcp->instance->pkt_pool, pkt); // Free the incoming dgram
|
||||
}
|
||||
|
||||
|
||||
+1
-6
@@ -13,7 +13,6 @@ extern "C" {
|
||||
|
||||
#include "pkt_normalizer.h"
|
||||
|
||||
struct stcp_link; // forward declaration
|
||||
// In struct ETCP_CONN, add:
|
||||
//struct pn_pair* normalizer;
|
||||
|
||||
@@ -105,7 +104,6 @@ struct ETCP_CONN {
|
||||
// Links (channels) - linked list
|
||||
struct ETCP_LINK* links;
|
||||
struct ETCP_LINK* last_rr_link; // последний линк, выбранный round-robin
|
||||
struct stcp_link *transport_link; // non-NULL for TCP connections
|
||||
|
||||
// Crypto and state
|
||||
struct secure_channel crypto_ctx;
|
||||
@@ -156,7 +154,7 @@ struct ETCP_CONN {
|
||||
|
||||
// Window and inflight management
|
||||
uint32_t unacked_bytes; // Current inflight bytes
|
||||
uint32_t max_inflight; // Max inflight cap from config (bbr_max_cwnd)
|
||||
// uint32_t window_size; // Receive window - Not used
|
||||
uint32_t optimal_inflight; // Sum over links
|
||||
|
||||
// Timers
|
||||
@@ -232,9 +230,6 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp);
|
||||
// Process ACK receipt - remove acknowledged packet from inflight queues
|
||||
void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t dts);
|
||||
|
||||
// Recalculate optimal_inflight and resume input queue after link inflight_lim change
|
||||
void etcp_conn_on_inflight_lim_changed(struct ETCP_CONN* etcp);
|
||||
|
||||
// Update log_name when peer_node_id becomes known
|
||||
void etcp_update_log_name(struct ETCP_CONN* etcp);
|
||||
|
||||
|
||||
+163
-31
@@ -1,65 +1,197 @@
|
||||
/**
|
||||
* @file etcp_api.c
|
||||
* @brief ETCP API — send/bind/recv dispatch
|
||||
* @brief Реализация API для приёма-передачи пакетов через ETCP (per-instance bindings)
|
||||
*/
|
||||
|
||||
#include "etcp_api.h"
|
||||
#include "etcp.h"
|
||||
#include "pkt_normalizer.h"
|
||||
#include "utun_instance.h"
|
||||
#include "stcp_link.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include <string.h>
|
||||
#include "route_lib.h"
|
||||
#include "route_bgp.h"
|
||||
|
||||
#define DEBUG_CATEGORY_ETCP_API DEBUG_CATEGORY_ETCP
|
||||
#define DEBUG_CATEGORY_ETCP_API 1
|
||||
|
||||
void etcp_conn_set_ready_cbk(struct ETCP_CONN* e, etcp_cbk_fn fn, void* arg) {
|
||||
if (e) { e->ready_cbk = fn; e->ready_arg = arg; }
|
||||
#define ETCP_MAX_BINDINGS 256 /**< Максимальное количество bindings (по одному на каждый ID 0-255) */
|
||||
|
||||
// после создания подключения надо выждать conn_ready. только после этого можно передавать сообщения. Иначе сообщения могут потеряться.
|
||||
// conn_ready может вызваться несколько раз (после каждого переподключения - например когда удаленный узел перегрузился или возникла неустранимая ошибка).
|
||||
void etcp_conn_set_ready_cbk(struct ETCP_CONN* etcp, etcp_cbk_fn callback_fn, void* arg) {
|
||||
if (!etcp) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "NULL conn"); return; }
|
||||
etcp->ready_cbk = callback_fn;
|
||||
etcp->ready_arg = arg;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "conn=%s cbk=%p arg=%p", etcp->log_name, (void*)callback_fn, arg);
|
||||
}
|
||||
void etcp_conn_set_up_cbk(struct ETCP_CONN* e, etcp_cbk_fn fn, void* arg) {
|
||||
if (e) { e->up_cbk = fn; e->up_arg = arg; }
|
||||
|
||||
void etcp_conn_set_up_cbk(struct ETCP_CONN* etcp, etcp_cbk_fn callback_fn, void* arg) {
|
||||
if (!etcp) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "NULL conn"); return; }
|
||||
etcp->up_cbk = callback_fn;
|
||||
etcp->up_arg = arg;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "conn=%s cbk=%p arg=%p", etcp->log_name, (void*)callback_fn, arg);
|
||||
}
|
||||
void etcp_conn_set_down_cbk(struct ETCP_CONN* e, etcp_cbk_fn fn, void* arg) {
|
||||
if (e) { e->down_cbk = fn; e->down_arg = arg; }
|
||||
|
||||
void etcp_conn_set_down_cbk(struct ETCP_CONN* etcp, etcp_cbk_fn callback_fn, void* arg) {
|
||||
if (!etcp) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "NULL conn"); return; }
|
||||
etcp->down_cbk = callback_fn;
|
||||
etcp->down_arg = arg;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "conn=%s cbk=%p arg=%p", etcp->log_name, (void*)callback_fn, arg);
|
||||
}
|
||||
void etcp_set_new_conn_cbk(struct UTUN_INSTANCE* inst, etcp_cbk_fn fn, void* arg) {
|
||||
if (inst) { inst->etcp_new_conn_cbk = fn; inst->etcp_new_conn_arg = arg; }
|
||||
|
||||
// Установить callback при создании нового ETCP соединения
|
||||
void etcp_set_new_conn_cbk(struct UTUN_INSTANCE* instance, etcp_cbk_fn callback_fn, void* arg) {
|
||||
if (!instance) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_set_new_conn_cbk: NULL instance");
|
||||
return;
|
||||
}
|
||||
instance->etcp_new_conn_cbk = callback_fn;
|
||||
instance->etcp_new_conn_arg = arg;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "etcp_set_new_conn_cbk: instance=%p cbk=%p arg=%p",
|
||||
(void*)instance, (void*)callback_fn, arg);
|
||||
}
|
||||
|
||||
int etcp_bind(struct UTUN_INSTANCE* inst, uint8_t id, etcp_recv_fn callback) {
|
||||
if (!inst || !callback || (unsigned)id >= ETCP_MAX_BINDINGS) return -1;
|
||||
if (!inst) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_bind: NULL instance");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((unsigned)id >= ETCP_MAX_BINDINGS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Invalid ID %u (max=%d)", id, ETCP_MAX_BINDINGS);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!callback) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_bind: NULL callback for id=%u", id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (inst->api_bindings.callbacks[id] != NULL) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Overwriting existing callback for id=%u", id);
|
||||
}
|
||||
inst->api_bindings.callbacks[id] = callback;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Bound id=%u → callback=%p", id, (void*)callback);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int etcp_unbind(struct UTUN_INSTANCE* inst, uint8_t id) {
|
||||
if (!inst || (unsigned)id >= ETCP_MAX_BINDINGS) return -1;
|
||||
if (!inst) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: NULL instance");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((unsigned)id >= ETCP_MAX_BINDINGS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: Invalid ID %u", id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (inst->api_bindings.callbacks[id] == NULL) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: ID %u not bound", id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
inst->api_bindings.callbacks[id] = NULL;
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: Unbound ID %u for instance %p", id, (void*)inst);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int etcp_send(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
if (!conn || !entry) return -1;
|
||||
// TCP (stcp_link) — send directly
|
||||
if (conn->transport_link)
|
||||
return stcp_link_send((struct stcp_link *)conn->transport_link, entry->dgram, entry->len);
|
||||
// UDP — через normalizer
|
||||
if (!conn->normalizer || !conn->normalizer->input) return -1;
|
||||
return queue_data_put(conn->normalizer->input, entry);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "etcp_send enter conn=%p entry=%p", conn, entry);
|
||||
if (!conn) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: NULL connection");
|
||||
return -1;
|
||||
}
|
||||
if (!entry) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: NULL entry");
|
||||
return -1;
|
||||
}
|
||||
if (!conn->normalizer) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: Connection has no normalizer");
|
||||
return -1;
|
||||
}
|
||||
if (conn->initialized==0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: Connection is not initialized");
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct PKTNORM* pn = conn->normalizer;
|
||||
if (!pn || !pn->input) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: pn=%p or input null", pn);
|
||||
return -1;
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_send: queued to [%s]", conn->log_name);
|
||||
int result = queue_data_put(pn->input, entry);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_NORMALIZER, "etcp_send after put result=%d", result);
|
||||
if (result != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_send: queue_data_put failed");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void etcp_int_recv(struct ll_queue* queue, void* arg) {
|
||||
struct ETCP_CONN* conn = (struct ETCP_CONN*)arg;
|
||||
if (!queue || !conn) return;
|
||||
struct ll_entry* e = queue_data_get(queue);
|
||||
if (!e) { queue_resume_callback(queue); return; }
|
||||
if (!e->dgram || e->len == 0) { queue_entry_free(e); queue_dgram_free(e); queue_resume_callback(queue); return; }
|
||||
uint8_t id = e->dgram[0];
|
||||
|
||||
if (!queue || !conn) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Invalid arguments (queue=%p, conn=%p)",
|
||||
(void*)queue, (void*)conn);
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем элемент из очереди
|
||||
struct ll_entry* entry = queue_data_get(queue);
|
||||
if (!entry) {
|
||||
// Очередь пуста - это нормально, просто возобновляем коллбэк
|
||||
queue_resume_callback(queue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем что есть данные
|
||||
if (!entry->dgram || entry->len == 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Empty packet received");
|
||||
queue_entry_free(entry);
|
||||
queue_dgram_free(entry);
|
||||
queue_resume_callback(queue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем ID пакета (первый байт кодограммы)
|
||||
uint8_t id = entry->dgram[0];
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Received packet with ID %u from conn [%s] %p",
|
||||
id, conn->log_name, (void*)conn);
|
||||
|
||||
// Получаем instance из соединения
|
||||
struct UTUN_INSTANCE* inst = conn->instance;
|
||||
if (!inst) { queue_entry_free(e); queue_dgram_free(e); queue_resume_callback(queue); return; }
|
||||
if (inst->api_bindings.callbacks[id])
|
||||
inst->api_bindings.callbacks[id](conn, e);
|
||||
else if (inst->api_bindings.callbacks[0])
|
||||
inst->api_bindings.callbacks[0](conn, e);
|
||||
else { queue_entry_free(e); queue_dgram_free(e); }
|
||||
if (!inst) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Connection has no instance");
|
||||
queue_entry_free(entry);
|
||||
queue_dgram_free(entry);
|
||||
queue_resume_callback(queue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ищем binding для этого ID в instance
|
||||
if (inst->api_bindings.callbacks[id] != NULL) {
|
||||
// Вызываем коллбэк
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Delivering packet ID %u to callback %p",
|
||||
id, (void*)inst->api_bindings.callbacks[id]);
|
||||
inst->api_bindings.callbacks[id](conn, entry);
|
||||
} else {
|
||||
// Нет binding для этого ID - по умолчанию считаем что это данные (ID=0)
|
||||
if (inst->api_bindings.callbacks[0] != NULL) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_recv: No binding for ID %u, routing to default (ID=0)", id);
|
||||
inst->api_bindings.callbacks[0](conn, entry);
|
||||
} else {
|
||||
// Нет default binding - освобождаем пакет
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_recv: No binding for ID %u and no default handler, dropping packet", id);
|
||||
queue_entry_free(entry);
|
||||
queue_dgram_free(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Возобновляем коллбэк для получения следующего пакета
|
||||
queue_resume_callback(queue);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
#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(); }
|
||||
@@ -328,14 +327,10 @@ 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;
|
||||
}
|
||||
}
|
||||
@@ -541,13 +536,11 @@ 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);
|
||||
}
|
||||
@@ -591,8 +584,6 @@ 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;
|
||||
@@ -619,7 +610,6 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,6 @@ 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;
|
||||
|
||||
+8
-87
@@ -15,7 +15,6 @@
|
||||
#include "config_parser.h"
|
||||
#include "crc32.h"
|
||||
#include "etcp.h"
|
||||
#include "stcp_link.h"
|
||||
#include "route_node.h"
|
||||
#include "route_bgp.h"
|
||||
#include "route_ping.h"
|
||||
@@ -28,22 +27,6 @@
|
||||
#include "../lib/mem.h"
|
||||
#include "etcp.h"
|
||||
|
||||
// TCP server: on new incoming connection → create minimal ETCP_CONN
|
||||
static void tcp_server_on_link(struct stcp_link *link, void *arg) {
|
||||
struct UTUN_INSTANCE *inst = (struct UTUN_INSTANCE *)arg;
|
||||
struct ETCP_CONN *conn = u_calloc(1, sizeof(struct ETCP_CONN));
|
||||
if (!conn) return;
|
||||
conn->instance = inst;
|
||||
conn->transport_link = link;
|
||||
snprintf(conn->log_name, sizeof(conn->log_name), "tcp-[%p]", (void*)link);
|
||||
conn->next = inst->connections;
|
||||
inst->connections = conn;
|
||||
inst->connections_count++;
|
||||
if (inst->etcp_new_conn_cbk) inst->etcp_new_conn_cbk(conn, inst->etcp_new_conn_arg);
|
||||
if (conn->ready_cbk) conn->ready_cbk(conn, conn->ready_arg);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "TCP server new conn=%p total=%d", (void*)conn, inst->connections_count);
|
||||
}
|
||||
|
||||
// Forward declaration
|
||||
void etcp_connections_read_callback_socket(socket_t sock, void* arg);
|
||||
static void etcp_link_remove_from_connections(struct ETCP_SOCKET* conn, struct ETCP_LINK* link);
|
||||
@@ -753,9 +736,9 @@ void etcp_socket_remove(struct ETCP_SOCKET* conn) {
|
||||
size_t i = 0;
|
||||
while (i < conn->num_channels) {
|
||||
struct ETCP_LINK* l = conn->links[i];
|
||||
int had_conn = (l->conn != NULL);
|
||||
etcp_link_close(l);
|
||||
conn->links[i] = NULL; // обнулить после — etcp_link_close обращается к массиву через remove_link
|
||||
i++;
|
||||
if (!had_conn) i++;
|
||||
}
|
||||
u_free(conn->links);
|
||||
|
||||
@@ -804,7 +787,6 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn
|
||||
link->keepalive_sent_count = 0;
|
||||
link->keepalive_recv_count = 0;
|
||||
link->ka_period_ms = KA_PERIOD_MIN_MS;
|
||||
link->inflight_lim_bytes = link->mtu * 4; // BBR init_cwnd (~4 packets)
|
||||
link->bandwidth = 10000; // начальная оценка 10 Mbps для шейпера
|
||||
link->burst_id = 0;
|
||||
link->burst_active = 0;
|
||||
@@ -812,8 +794,6 @@ 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;
|
||||
@@ -832,7 +812,6 @@ 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));
|
||||
|
||||
@@ -852,7 +831,12 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn
|
||||
while (l && l->next) l=l->next;
|
||||
if (l) l->next = link; else etcp->links = link;
|
||||
|
||||
etcp_link_update_inflight_lim(link, link->inflight_lim_bytes);
|
||||
link->inflight_lim_bytes = link->mtu * 4; // BBR init_cwnd (~4 packets)
|
||||
|
||||
// пересчитать connection-level optimal_inflight
|
||||
{ uint32_t sum = 0;
|
||||
for (struct ETCP_LINK* tl = etcp->links; tl; tl = tl->next) sum += tl->inflight_lim_bytes;
|
||||
etcp->optimal_inflight = sum; }
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "NEW link initialized on etcp=[%s] link=%p socket=%s id=%d is_server=%d mtu=%d", etcp->log_name, link, conn->name, link->local_link_id, link->is_server, link->mtu);
|
||||
|
||||
@@ -864,23 +848,6 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn
|
||||
return link;
|
||||
}
|
||||
|
||||
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 > link->etcp->max_inflight) new_lim = link->etcp->max_inflight;
|
||||
|
||||
uint32_t old = link->inflight_lim_bytes;
|
||||
link->inflight_lim_bytes = new_lim;
|
||||
|
||||
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;
|
||||
if (!link->shaper_timer) loadbalancer_link_ready(link);// потом вызовем ready -> разблок
|
||||
} else etcp_conn_on_inflight_lim_changed(link->etcp);//
|
||||
|
||||
}
|
||||
|
||||
void etcp_link_close(struct ETCP_LINK* link) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_CONNECTION, "");
|
||||
if (!link) return;
|
||||
@@ -897,7 +864,6 @@ void etcp_link_close(struct ETCP_LINK* link) {
|
||||
if (link->init_timer) uasync_cancel_timeout(link->etcp->instance->ua, link->init_timer);
|
||||
if (link->shaper_timer) uasync_cancel_timeout(link->etcp->instance->ua, link->shaper_timer);
|
||||
if (link->keepalive_timer) uasync_cancel_timeout(link->etcp->instance->ua, link->keepalive_timer);
|
||||
etcp_conn_on_inflight_lim_changed(link->etcp);
|
||||
u_free(link);
|
||||
return;
|
||||
}
|
||||
@@ -933,7 +899,6 @@ void etcp_link_close(struct ETCP_LINK* link) {
|
||||
|
||||
remove_link(link->conn, link->ip_port_hash);
|
||||
|
||||
etcp_conn_on_inflight_lim_changed(link->etcp);
|
||||
u_free(link->bbr);
|
||||
u_free(link);
|
||||
}
|
||||
@@ -1563,8 +1528,6 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
|
||||
pkt->noencrypt_len=0;
|
||||
pkt->link=link;
|
||||
link->recv_keepalive = 1;
|
||||
link->last_recv_local_time = get_time_tb();
|
||||
link->last_recv_timestamp = pkt->timestamp;
|
||||
|
||||
int xoffset=sizeof(*ack_repl_hdr);
|
||||
// padding
|
||||
@@ -1769,10 +1732,6 @@ process_decrypted:
|
||||
// log_dump("RECV decrypted:", pkt->data, pkt->data_len, link);
|
||||
|
||||
if (link->link_state == 3) {
|
||||
if (memory_pool_is_freed(e_sock->instance->pkt_pool, pkt)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_conn_input: pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt);
|
||||
volatile int _halt = 1; while (_halt) {}
|
||||
}
|
||||
etcp_conn_input(pkt);
|
||||
} else memory_pool_free(e_sock->instance->pkt_pool, pkt);
|
||||
return;
|
||||
@@ -1848,21 +1807,6 @@ int init_sockets(struct UTUN_INSTANCE* instance) {
|
||||
}
|
||||
}
|
||||
|
||||
// TCP transport: create stcp_server instead of UDP socket
|
||||
if (server->transport) {
|
||||
uint16_t port = ntohs(((struct sockaddr_in*)&server->ip)->sin_port);
|
||||
struct stcp_link_config scfg = {.ua = instance->ua, .my_keys = &instance->my_keys, .inst = instance};
|
||||
if (!stcp_server_listen(&scfg, port, tcp_server_on_link, instance)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to create TCP server for %s", server->name);
|
||||
fail_count++;
|
||||
} else {
|
||||
success_count++;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "TCP server %s on port %u", server->name, port);
|
||||
}
|
||||
server = server->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
struct ETCP_SOCKET* e_sock = etcp_socket_add(instance, server);
|
||||
if (e_sock && default_ip != 0) {
|
||||
e_sock->local_defaultroute_ip = default_ip;
|
||||
@@ -2005,29 +1949,6 @@ int init_connections(struct UTUN_INSTANCE* instance) {
|
||||
sock = sock->next;
|
||||
}
|
||||
|
||||
if (local_server->transport) {
|
||||
if (strlen(client->peer_public_key_hex) == 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "TCP client %s has no peer key", client->name);
|
||||
client_link = client_link->next; continue;
|
||||
}
|
||||
// TCP transport — use stcp_link instead of etcp_link_new
|
||||
uint16_t rport = ntohs(((struct sockaddr_in*)&client_link->remote_addr)->sin_port);
|
||||
struct stcp_link_config scfg = {
|
||||
.ua = instance->ua, .my_keys = &instance->my_keys, .inst = instance,
|
||||
.peer_pubkey = (const uint8_t*)client->peer_public_key_hex,
|
||||
.peer_pubkey_mode = 1, // hex
|
||||
.remote_addr = &client_link->remote_addr, .remote_port = rport
|
||||
};
|
||||
struct stcp_link *slink = stcp_link_connect(&scfg);
|
||||
if (!slink) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to create TCP link for client %s", client->name);
|
||||
client_link = client_link->next; continue;
|
||||
}
|
||||
etcp_conn->transport_link = slink;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "TCP link created for client %s", client->name);
|
||||
client_link = client_link->next; continue;
|
||||
}
|
||||
|
||||
if (!e_sock) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "No socket found for client %s link", client->name);
|
||||
client_link = client_link->next;
|
||||
|
||||
@@ -14,9 +14,6 @@
|
||||
#define UDP_SC_HDR_SIZE (13+8+4 + 5)// 13+8+4 - sc_nonce+tag size+crc, 5 - payload hdr
|
||||
#define ACK_REZERV 100// сколько байт резервировать под ack и прочие заголовки
|
||||
|
||||
#define INFLIGHT_LIM_MIN 8192 // 8K
|
||||
#define INFLIGHT_LIM_MAX 1048576 // 1M
|
||||
|
||||
#define PACKET_DATA_SIZE 1600//1536
|
||||
#define PACKET_DATA_MAX_MTU 1600
|
||||
#define ETCP_MAX_PAYLOAD_SIZE (PACKET_DATA_SIZE - ETCP_ACK_BASE_SIZE - 5) /* 1587 */
|
||||
@@ -182,8 +179,6 @@ 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
|
||||
@@ -267,7 +262,6 @@ void etcp_socket_remove(struct ETCP_SOCKET* conn);
|
||||
// connection functions
|
||||
// создает новый канал связи для etcp подключения (ETCP_CONN)
|
||||
struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn, struct sockaddr_storage* remote_addr, uint8_t is_server);
|
||||
void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim);
|
||||
void etcp_link_close(struct ETCP_LINK* link);
|
||||
//int etcp_input_cbk(struct packet_buffer* pkt, struct ETCP_SOCKET* conn);// получает расшифрованный пакет
|
||||
int etcp_encrypt_send(struct ETCP_DGRAM* dgram);// зашифровывает и отправляет пакет
|
||||
|
||||
@@ -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_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);
|
||||
if (subcmd == TCP_PROXY_SUBCMD_ERROR) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY ERROR sid=%08x — client conn not found, silent drop", 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);
|
||||
|
||||
@@ -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_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);
|
||||
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);
|
||||
send_close(rc); tcp_conn_push_close(tc);
|
||||
} else if (pend) {
|
||||
tcp_conn_set_flushed(tc, on_flushed_cb);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x write_pend=%d → deferred",
|
||||
DEBUG_INFO(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_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x → relay now",
|
||||
DEBUG_INFO(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_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FLUSHED fd=%d sid=%08x fin_local=%d — both FINs, closing",
|
||||
DEBUG_INFO(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_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:CLOSED fd=%d sid=%08x — freeing",
|
||||
DEBUG_INFO(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,7 +156,6 @@ 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;
|
||||
|
||||
@@ -168,7 +167,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_DEBUG(DEBUG_CATEGORY_SOCKET,
|
||||
DEBUG_INFO(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",
|
||||
@@ -208,7 +207,7 @@ static void read_queue_drain_cb(struct ll_queue* q, void* arg) {
|
||||
if (ret == 0) {
|
||||
queue_resume_callback(q);
|
||||
} else {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:BACKPRESSURE fd=%d sid=%08x — pausing drain",
|
||||
DEBUG_INFO(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);
|
||||
@@ -235,14 +234,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_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FREE enter rc=%p freed=%d sid=%08x", (void*)rc, rc->freed, rc->stream_id);
|
||||
DEBUG_INFO(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_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FREE fd=%d sid=%08x total=%d cli_closed=%d fin=%d error=%d",
|
||||
DEBUG_INFO(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;
|
||||
@@ -257,7 +256,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_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FREE u_free rc=%p", (void*)rc);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FREE u_free rc=%p", (void*)rc);
|
||||
u_free(rc);
|
||||
}
|
||||
|
||||
@@ -287,11 +286,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_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:NEW fd=%d sid=%08x dest=%d.%d.%d.%d:%d total=%d",
|
||||
DEBUG_INFO(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, inst->config->global.tcp_recv_buf, on_fin_cb, on_error_cb, rc);
|
||||
rc->tc = tcp_conn_create(inst->ua, sock, 1500, 8192, 4, 0, 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);
|
||||
@@ -352,7 +351,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_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:CLOSE_RECV fd=%d sid=%08x total=%d fin=%d write_pend=%d",
|
||||
DEBUG_INFO(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;
|
||||
@@ -374,7 +373,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_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:ERROR_RECV fd=%d sid=%08x total=%d fin=%d write_pend=%d",
|
||||
DEBUG_INFO(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);
|
||||
@@ -385,7 +384,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_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FIN_RECV fd=%d sid=%08x — pushing FIN to wq",
|
||||
DEBUG_INFO(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);
|
||||
}
|
||||
@@ -430,8 +429,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_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);
|
||||
if (subcmd == TCP_PROXY_SUBCMD_ERROR) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY ERROR sid=%08x — server conn not found, silent drop", 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);
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
#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"
|
||||
@@ -143,23 +142,6 @@ 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
|
||||
@@ -531,50 +513,6 @@ 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)
|
||||
@@ -878,44 +816,6 @@ 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) {
|
||||
|
||||
@@ -75,23 +75,4 @@ 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
@@ -1,35 +0,0 @@
|
||||
// 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
@@ -1,80 +0,0 @@
|
||||
// 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
|
||||
@@ -1,282 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
// 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
|
||||
-202
@@ -1,202 +0,0 @@
|
||||
// stcp_link.c — STCP link management implementation
|
||||
#include "stcp_link.h"
|
||||
#include "stcp.h"
|
||||
#include "stcp_server.h"
|
||||
#include "stcp_client.h"
|
||||
#include "secure_channel.h"
|
||||
#include "etcp.h"
|
||||
#include "utun_instance.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/mem.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct stcp_server {
|
||||
struct stcp_server *srv; // stcp_server from stcp_server.h
|
||||
struct stcp_link_config cfg;
|
||||
stcp_server_on_link_cb on_link;
|
||||
void *on_link_arg;
|
||||
};
|
||||
|
||||
struct stcp_link {
|
||||
struct stcp_link_config cfg;
|
||||
struct stcp_client *cli; // only 1 client link for now
|
||||
struct stcp_conn *conn; // STCP connection
|
||||
|
||||
uint8_t ready;
|
||||
uint8_t peer_pubkey[SC_PUBKEY_SIZE];
|
||||
|
||||
stcp_link_cb on_ready_cb;
|
||||
void *ready_arg;
|
||||
void (*on_close_cb)(struct stcp_link *link, int err, void *arg);
|
||||
void *close_arg;
|
||||
|
||||
struct ll_queue *tx_queue; // owned by this link
|
||||
struct ETCP_CONN etcp_conn; // lightweight, for etcp_recv_fn compat
|
||||
};
|
||||
|
||||
// ====== rx dispatch through inst->api_bindings ======
|
||||
|
||||
static void link_rx_cb(struct ll_queue *q, void *arg) {
|
||||
struct stcp_link *link = (struct stcp_link *)arg;
|
||||
struct ll_entry *e = queue_data_get(q);
|
||||
if (!e) { queue_resume_callback(q); return; }
|
||||
|
||||
struct UTUN_INSTANCE *inst = link->cfg.inst;
|
||||
uint8_t id = (e->dgram && e->len > 0) ? e->dgram[0] : 0;
|
||||
|
||||
if (inst && inst->api_bindings.callbacks[id]) {
|
||||
inst->api_bindings.callbacks[id](&link->etcp_conn, e);
|
||||
} else if (inst && inst->api_bindings.callbacks[0]) {
|
||||
inst->api_bindings.callbacks[0](&link->etcp_conn, e);
|
||||
} else {
|
||||
queue_dgram_free(e);
|
||||
queue_entry_free(e);
|
||||
}
|
||||
queue_resume_callback(q);
|
||||
}
|
||||
|
||||
// ====== server accept → link ======
|
||||
|
||||
static void server_accept_cb(struct stcp_conn *conn, void *arg) {
|
||||
struct stcp_server *ss = (struct stcp_server *)arg;
|
||||
struct stcp_link *link = u_calloc(1, sizeof(struct stcp_link));
|
||||
if (!link) { stcp_conn_free(conn); return; }
|
||||
link->cfg = ss->cfg;
|
||||
link->ready = 1;
|
||||
link->conn = conn;
|
||||
link->etcp_conn.instance = ss->cfg.inst;
|
||||
link->etcp_conn.transport_link = (void *)link; // backpointer for stcp_link
|
||||
|
||||
struct ll_queue *rx = queue_new(conn->ua, 0, 0, 0, "srx");
|
||||
queue_set_callback(rx, link_rx_cb, link);
|
||||
stcp_conn_set_rx_queue(conn, rx);
|
||||
link->tx_queue = queue_new(conn->ua, 0, 0, 0, "stx");
|
||||
stcp_conn_set_tx_queue(conn, link->tx_queue);
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "stcp_link: server accepted connection");
|
||||
if (link->on_ready_cb) link->on_ready_cb(link, link->ready_arg);
|
||||
if (ss->on_link) ss->on_link(link, ss->on_link_arg);
|
||||
}
|
||||
|
||||
// ====== client connect → link ======
|
||||
|
||||
static void client_ready_cb(struct stcp_conn *conn, void *arg) {
|
||||
struct stcp_link *link = (struct stcp_link *)arg;
|
||||
if (!conn) return;
|
||||
link->ready = 1;
|
||||
link->conn = conn;
|
||||
link->etcp_conn.instance = link->cfg.inst;
|
||||
link->etcp_conn.transport_link = (void *)link;
|
||||
|
||||
struct ll_queue *rx = queue_new(conn->ua, 0, 0, 0, "crx");
|
||||
queue_set_callback(rx, link_rx_cb, link);
|
||||
stcp_conn_set_rx_queue(conn, rx);
|
||||
link->tx_queue = queue_new(conn->ua, 0, 0, 0, "ctx");
|
||||
stcp_conn_set_tx_queue(conn, link->tx_queue);
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "stcp_link: client connected");
|
||||
if (link->on_ready_cb) link->on_ready_cb(link, link->ready_arg);
|
||||
}
|
||||
|
||||
// ====== API ======
|
||||
|
||||
struct stcp_server *stcp_server_listen(struct stcp_link_config *cfg, uint16_t port,
|
||||
stcp_server_on_link_cb on_link, void *arg) {
|
||||
if (!cfg || !cfg->ua || !cfg->my_keys) return NULL;
|
||||
struct stcp_server *ss = u_calloc(1, sizeof(struct stcp_server));
|
||||
if (!ss) return NULL;
|
||||
ss->cfg = *cfg;
|
||||
ss->on_link = on_link;
|
||||
ss->on_link_arg = arg;
|
||||
ss->srv = stcp_server_create(cfg->ua, port, cfg->my_keys, server_accept_cb, ss);
|
||||
if (!ss->srv) { u_free(ss); return NULL; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "stcp_server_listen: port=%u", port);
|
||||
return ss;
|
||||
}
|
||||
|
||||
void stcp_link_server_destroy(struct stcp_server *ss) {
|
||||
if (!ss) return;
|
||||
if (ss->srv) stcp_server_destroy(ss->srv);
|
||||
u_free(ss);
|
||||
}
|
||||
|
||||
struct stcp_link *stcp_link_connect(struct stcp_link_config *cfg) {
|
||||
if (!cfg || !cfg->ua || !cfg->my_keys || !cfg->peer_pubkey || !cfg->remote_addr)
|
||||
return NULL;
|
||||
|
||||
char addr_str[64];
|
||||
struct sockaddr_in *sa = (struct sockaddr_in *)cfg->remote_addr;
|
||||
inet_ntop(AF_INET, &sa->sin_addr, addr_str, sizeof(addr_str));
|
||||
uint16_t port = cfg->remote_port ? cfg->remote_port : ntohs(sa->sin_port);
|
||||
|
||||
struct stcp_link *link = u_calloc(1, sizeof(struct stcp_link));
|
||||
if (!link) return NULL;
|
||||
link->cfg = *cfg;
|
||||
|
||||
// Convert hex pubkey to binary if needed
|
||||
if (cfg->peer_pubkey_mode) {
|
||||
struct secure_channel sc_tmp;
|
||||
sc_init_ctx(&sc_tmp, cfg->my_keys);
|
||||
if (sc_set_peer_public_key(&sc_tmp, cfg->peer_pubkey, 1) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_link_connect: invalid peer pubkey hex");
|
||||
u_free(link); return NULL;
|
||||
}
|
||||
memcpy(link->peer_pubkey, sc_tmp.peer_public_key, SC_PUBKEY_SIZE);
|
||||
} else {
|
||||
memcpy(link->peer_pubkey, cfg->peer_pubkey, SC_PUBKEY_SIZE);
|
||||
}
|
||||
|
||||
link->cli = stcp_client_connect(cfg->ua, addr_str, port, cfg->my_keys, link->peer_pubkey,
|
||||
client_ready_cb, link);
|
||||
if (!link->cli) { u_free(link); return NULL; }
|
||||
|
||||
link->etcp_conn.instance = cfg->inst;
|
||||
link->etcp_conn.transport_link = (void *)link;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "stcp_link_connect: connecting to %s:%u", addr_str, port);
|
||||
return link;
|
||||
}
|
||||
|
||||
void stcp_link_close(struct stcp_link *link) {
|
||||
if (!link) return;
|
||||
if (link->conn) {
|
||||
if (link->conn->rx_queue) queue_free(link->conn->rx_queue);
|
||||
if (link->tx_queue) queue_free(link->tx_queue);
|
||||
stcp_conn_free(link->conn);
|
||||
}
|
||||
if (link->cli) stcp_client_destroy(link->cli);
|
||||
u_free(link);
|
||||
}
|
||||
|
||||
int stcp_link_send(struct stcp_link *link, const uint8_t *data, size_t len) {
|
||||
if (!link || !link->ready) return -1;
|
||||
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(link->conn->tx_queue, e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int stcp_link_is_ready(struct stcp_link *link) {
|
||||
return link ? link->ready : 0;
|
||||
}
|
||||
|
||||
struct ETCP_CONN *stcp_link_get_etcp_conn(struct stcp_link *link) {
|
||||
return link ? &link->etcp_conn : NULL;
|
||||
}
|
||||
|
||||
void stcp_link_set_on_ready(struct stcp_link *link, stcp_link_cb cb, void *arg) {
|
||||
if (!link) return;
|
||||
link->on_ready_cb = cb;
|
||||
link->ready_arg = arg;
|
||||
}
|
||||
|
||||
void stcp_link_set_on_close(struct stcp_link *link, void (*cb)(struct stcp_link *link, int err, void *arg), void *arg) {
|
||||
if (!link) return;
|
||||
link->on_close_cb = cb;
|
||||
link->close_arg = arg;
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// stcp_link.h — STCP link management (TCP connection via STCP protocol)
|
||||
#ifndef STCP_LINK_H
|
||||
#define STCP_LINK_H
|
||||
|
||||
#include "secure_channel.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/socket_compat.h"
|
||||
#include <stdint.h>
|
||||
|
||||
struct stcp_link; // opaque
|
||||
struct stcp_server; // opaque
|
||||
struct ETCP_CONN;
|
||||
struct UTUN_INSTANCE;
|
||||
|
||||
// ====== Link config ======
|
||||
|
||||
struct stcp_link_config {
|
||||
struct UASYNC *ua;
|
||||
struct SC_MYKEYS *my_keys; // ключи этой стороны
|
||||
struct UTUN_INSTANCE *inst; // для диспатча через api_bindings
|
||||
const uint8_t *peer_pubkey; // pubkey пира (клиент)
|
||||
int peer_pubkey_mode; // 0=binary, 1=hex
|
||||
const struct sockaddr_storage *remote_addr; // адрес пира (клиент)
|
||||
uint16_t remote_port; // порт пира (клиент)
|
||||
};
|
||||
|
||||
// ====== TCP server ======
|
||||
|
||||
typedef void (*stcp_server_on_link_cb)(struct stcp_link *link, void *arg);
|
||||
|
||||
struct stcp_server *stcp_server_listen(struct stcp_link_config *cfg, uint16_t port,
|
||||
stcp_server_on_link_cb on_link, void *arg);
|
||||
void stcp_link_server_destroy(struct stcp_server *srv);
|
||||
|
||||
// ====== TCP client link ======
|
||||
|
||||
struct stcp_link *stcp_link_connect(struct stcp_link_config *cfg);
|
||||
void stcp_link_close(struct stcp_link *link);
|
||||
int stcp_link_send(struct stcp_link *link, const uint8_t *data, size_t len);
|
||||
int stcp_link_is_ready(struct stcp_link *link);
|
||||
|
||||
struct ETCP_CONN *stcp_link_get_etcp_conn(struct stcp_link *link);
|
||||
|
||||
typedef void (*stcp_link_cb)(struct stcp_link *link, void *arg);
|
||||
void stcp_link_set_on_ready(struct stcp_link *link, stcp_link_cb cb, void *arg);
|
||||
void stcp_link_set_on_close(struct stcp_link *link, void (*cb)(struct stcp_link *link, int err, void *arg), void *arg);
|
||||
|
||||
#endif
|
||||
@@ -1,375 +0,0 @@
|
||||
// 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");
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
// 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
|
||||
+1
-4
@@ -284,7 +284,6 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
|
||||
|
||||
// Диагностика ресурсов ДО cleanup
|
||||
utun_instance_diagnose_leaks(instance, "BEFORE_CLEANUP");
|
||||
if (instance->ua) uasync_print_resources(instance->ua, "INSTANCE_DESTROY_BEFORE");
|
||||
|
||||
// Stop running if not already
|
||||
instance->running = 0;
|
||||
@@ -403,9 +402,7 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
|
||||
memory_pool_destroy(instance->data_pool);
|
||||
instance->data_pool = NULL;
|
||||
}
|
||||
|
||||
if (instance->ua) uasync_print_resources(instance->ua, "INSTANCE_DESTROY_AFTER");
|
||||
|
||||
|
||||
// Note: uasync is NOT destroyed here - caller must destroy it separately
|
||||
// This allows sharing uasync between multiple instances
|
||||
instance->ua = NULL;
|
||||
|
||||
+1
-6
@@ -8,7 +8,6 @@
|
||||
#include "secure_channel.h"
|
||||
#include "etcp_api.h"
|
||||
#include "config_parser.h"
|
||||
#include "stcp_link.h"
|
||||
#include "firewall.h"
|
||||
#include "eim_nat.h"
|
||||
#include "nat_transport.h"
|
||||
@@ -16,9 +15,6 @@
|
||||
#include "etcp_router.h"
|
||||
#include "proxy/tcp_proxy_server.h"
|
||||
|
||||
#include "etcp_api.h"
|
||||
#include "config_parser.h"
|
||||
|
||||
// Forward declarations
|
||||
struct utun_config;
|
||||
struct uasync_s;
|
||||
@@ -75,9 +71,8 @@ struct UTUN_INSTANCE {
|
||||
struct memory_pool* pkt_pool;
|
||||
struct memory_pool* ack_pool;
|
||||
|
||||
// Active sockets (UDP)
|
||||
// Active sockets
|
||||
struct ETCP_SOCKET* etcp_sockets;// linked-list
|
||||
struct stcp_server *stcp_server; // TCP server (single for now)
|
||||
|
||||
// Pending one-shot pings (for callback on PONG or timeout)
|
||||
struct PING_CONTEXT* pending_pings;
|
||||
|
||||
@@ -4,10 +4,6 @@
|
||||
check_PROGRAMS = \
|
||||
test_ll_queue \
|
||||
test_serialize \
|
||||
test_stream_cipher \
|
||||
test_etcp_transport \
|
||||
test_transport \
|
||||
test_stcp \
|
||||
test_packet_dump \
|
||||
test_debug_categories \
|
||||
test_config_debug \
|
||||
@@ -46,7 +42,6 @@ check_PROGRAMS = \
|
||||
test_bbr_integration \
|
||||
test_intensive_memory_pool \
|
||||
test_tcp_io \
|
||||
bench_timeout_heap \
|
||||
bench_uasync_timeouts
|
||||
|
||||
# Долгие тесты: запускаются только вручную, не включаются в make check
|
||||
@@ -83,9 +78,6 @@ TINYCRYPT_OBJS = \
|
||||
# Secure channel and CRC objects (built in src directory)
|
||||
SECURE_CHANNEL_OBJS = $(top_builddir)/src/utun-secure_channel.o $(top_builddir)/src/utun-crc32.o
|
||||
|
||||
# Transport objects (needed by etcp_api.c)
|
||||
STCP_LINK_OBJS = $(top_builddir)/src/utun-stcp_link.o $(top_builddir)/src/utun-stcp.o $(top_builddir)/src/utun-stcp_server.o $(top_builddir)/src/utun-stcp_client.o
|
||||
|
||||
# ETCP core objects
|
||||
ETCP_CORE_OBJS = \
|
||||
$(top_builddir)/src/utun-etcp.o \
|
||||
@@ -136,7 +128,6 @@ ETCP_FULL_OBJS = \
|
||||
$(top_builddir)/src/lwip_tcp/utun-lwip_tcp.o \
|
||||
$(top_builddir)/src/lwip_tcp/utun-lwip_tcp_in.o \
|
||||
$(top_builddir)/src/lwip_tcp/utun-lwip_tcp_out.o \
|
||||
$(STCP_LINK_OBJS) \
|
||||
$(ETCP_CORE_OBJS)
|
||||
|
||||
# Windows-specific libraries (advapi32 for CryptGenRandom, ws2_32 for sockets)
|
||||
@@ -184,22 +175,6 @@ 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_transport_SOURCES = test_stcp_link.c
|
||||
test_transport_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
test_transport_LDADD = $(STCP_LINK_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_etcp_transport_SOURCES = test_etcp_stcp.c
|
||||
test_etcp_transport_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
test_etcp_transport_LDADD = $(STCP_LINK_OBJS) $(top_builddir)/src/utun-etcp_api.o $(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
|
||||
|
||||
@@ -124,7 +124,6 @@ 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;
|
||||
}
|
||||
@@ -487,7 +486,7 @@ int main(void) {
|
||||
}
|
||||
}
|
||||
|
||||
int pass = (ctx.bytes_received > 50000);
|
||||
int pass = (ctx.bytes_received > 50000); /* at least 50KB delivered */
|
||||
printf("\n[%s]\n", pass ? "PASS" : "FAIL");
|
||||
|
||||
if (ctx.log_file) {
|
||||
|
||||
@@ -71,7 +71,7 @@ static void benchmark_without_sockets(void) {
|
||||
printf("Set %d zero timeouts:\n", NUM_TIMEOUTS);
|
||||
printf(" Total time: %.3f ms\n", set_time);
|
||||
printf(" Average per timeout: %.3f us\n", (set_time * 1000.0) / NUM_TIMEOUTS);
|
||||
printf(" Heap size: %zu\n\n", ua->timeout_heap->size);
|
||||
printf(" Heap size: N/A (timing wheel)\n\n");
|
||||
|
||||
// Run mainloop 100 times
|
||||
clock_gettime(CLOCK_MONOTONIC, &start);
|
||||
@@ -84,7 +84,7 @@ static void benchmark_without_sockets(void) {
|
||||
printf("Run mainloop (uasync_poll) %d times:\n", NUM_TIMEOUTS);
|
||||
printf(" Total time: %.3f ms\n", mainloop_time);
|
||||
printf(" Average per iteration: %.3f us\n", (mainloop_time * 1000.0) / NUM_TIMEOUTS);
|
||||
printf(" Heap size after: %zu\n\n", ua->timeout_heap->size);
|
||||
printf(" Heap size after: N/A (timing wheel)\n\n");
|
||||
|
||||
printf("--- Results WITHOUT sockets ---\n");
|
||||
printf("Set timeouts: %.3f ms (%.3f us/op)\n", set_time, (set_time * 1000.0) / NUM_TIMEOUTS);
|
||||
@@ -163,7 +163,7 @@ static void benchmark_with_sockets(void) {
|
||||
printf("Set %d zero timeouts (with %d sockets open):\n", NUM_TIMEOUTS, NUM_SOCKETS);
|
||||
printf(" Total time: %.3f ms\n", set_time);
|
||||
printf(" Average per timeout: %.3f us\n", (set_time * 1000.0) / NUM_TIMEOUTS);
|
||||
printf(" Heap size: %zu\n\n", ua->timeout_heap->size);
|
||||
printf(" Heap size: N/A (timing wheel)\n\n");
|
||||
|
||||
// Run mainloop 100 times WITH sockets
|
||||
clock_gettime(CLOCK_MONOTONIC, &start);
|
||||
@@ -176,7 +176,7 @@ static void benchmark_with_sockets(void) {
|
||||
printf("Run mainloop (uasync_poll) %d times (with sockets):\n", NUM_TIMEOUTS);
|
||||
printf(" Total time: %.3f ms\n", mainloop_time);
|
||||
printf(" Average per iteration: %.3f us\n", (mainloop_time * 1000.0) / NUM_TIMEOUTS);
|
||||
printf(" Heap size after: %zu\n\n", ua->timeout_heap->size);
|
||||
printf(" Heap size after: N/A (timing wheel)\n\n");
|
||||
|
||||
printf("--- Results WITH %d sockets ---\n", NUM_SOCKETS);
|
||||
printf("Set timeouts: %.3f ms (%.3f us/op)\n", set_time, (set_time * 1000.0) / NUM_TIMEOUTS);
|
||||
|
||||
@@ -117,7 +117,11 @@ static int is_connection_established(struct UTUN_INSTANCE* inst) {
|
||||
if (!inst) return 0;
|
||||
struct ETCP_CONN* conn = inst->connections;
|
||||
while (conn) {
|
||||
if (conn->initialized) return 1;
|
||||
struct ETCP_LINK* link = conn->links;
|
||||
while (link) {
|
||||
if (link->initialized) return 1;
|
||||
link = link->next;
|
||||
}
|
||||
conn = conn->next;
|
||||
}
|
||||
return 0;
|
||||
@@ -207,9 +211,6 @@ static void monitor(void* arg) {
|
||||
return;
|
||||
}
|
||||
printf("Server recreated (node_id=%llx)\n", (unsigned long long)server_instance->node_id);
|
||||
// Force client to reinit — otherwise conn_ok stays true on old initialized=1
|
||||
{ struct ETCP_CONN* c = client_instance->connections;
|
||||
while (c) { etcp_conn_reinit(c); c = c->next; } }
|
||||
restart_action_done = 1;
|
||||
}
|
||||
drain_received(0);
|
||||
@@ -230,8 +231,7 @@ static void monitor(void* arg) {
|
||||
packets_received = 0;
|
||||
drain_received(0);
|
||||
}
|
||||
if (is_connection_established(client_instance))
|
||||
send_packets();
|
||||
send_packets();
|
||||
drain_received(1);
|
||||
if (packets_received >= TOTAL_PACKETS) {
|
||||
printf("=== Phase 4 done: sent=%d received=%d ===\n", packets_sent, packets_received);
|
||||
@@ -293,8 +293,7 @@ static void monitor(void* arg) {
|
||||
packets_received = 0;
|
||||
drain_received(0);
|
||||
}
|
||||
if (is_connection_established(client_instance))
|
||||
send_packets();
|
||||
send_packets();
|
||||
drain_received(1);
|
||||
if (packets_received >= TOTAL_PACKETS) {
|
||||
printf("=== Phase 7 done: sent=%d received=%d ===\n", packets_sent, packets_received);
|
||||
@@ -321,6 +320,8 @@ int main(void) {
|
||||
if (create_temp_configs() != 0) return 1;
|
||||
|
||||
debug_config_init();
|
||||
debug_set_level(DEBUG_LEVEL_ERROR);
|
||||
debug_set_categories(DEBUG_CATEGORY_NONE);
|
||||
|
||||
printf("=== ETCP Reconnect Test ===\n");
|
||||
printf("Server port: %d, Client port: %d\n", server_port, client_port);
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
// test_etcp_stcp.c — integration: etcp_send + etcp_bind over STCP link
|
||||
#include "../src/stcp_link.h"
|
||||
#include "../src/etcp_api.h"
|
||||
#include "../src/etcp.h"
|
||||
#include "../src/secure_channel.h"
|
||||
#include "../src/utun_instance.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define TEST_PORT 25678
|
||||
|
||||
static int test_failed = 0;
|
||||
static struct SC_MYKEYS s_keys, c_keys;
|
||||
|
||||
#define TASSERT(cond) do { \
|
||||
if (!(cond)) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, " FAIL: %s", #cond); test_failed = 1; return; } \
|
||||
} while(0)
|
||||
|
||||
static int g_srv_recv_count = 0;
|
||||
static uint8_t g_srv_recv_data[256];
|
||||
|
||||
static void etcp_recv_cb(struct ETCP_CONN *conn, struct ll_entry *entry) {
|
||||
(void)conn;
|
||||
g_srv_recv_count++;
|
||||
if (entry->dgram && entry->len) memcpy(g_srv_recv_data, entry->dgram, entry->len < 256 ? entry->len : 255);
|
||||
queue_dgram_free(entry);
|
||||
queue_entry_free(entry);
|
||||
}
|
||||
|
||||
static struct stcp_link *g_srv_link;
|
||||
static void on_srv_link_ready(struct stcp_link *link, void *arg) { (void)arg; g_srv_link = link; }
|
||||
|
||||
int main(void) {
|
||||
debug_config_init();
|
||||
debug_set_level(DEBUG_LEVEL_INFO);
|
||||
debug_set_categories(DEBUG_CATEGORY_GENERAL | DEBUG_CATEGORY_SOCKET | DEBUG_CATEGORY_CRYPTO);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "=== etcp_send/bind over STCP link ===");
|
||||
|
||||
TASSERT(sc_generate_keypair(&s_keys) == SC_OK);
|
||||
TASSERT(sc_generate_keypair(&c_keys) == SC_OK);
|
||||
struct UASYNC *ua = uasync_create(); TASSERT(ua);
|
||||
|
||||
struct UTUN_INSTANCE inst; memset(&inst, 0, sizeof(inst));
|
||||
inst.ua = ua;
|
||||
TASSERT(etcp_bind(&inst, 0, etcp_recv_cb) == 0);
|
||||
|
||||
struct stcp_link_config cfg = {.ua = ua, .my_keys = &s_keys, .inst = &inst};
|
||||
struct stcp_server *ss = stcp_server_listen(&cfg, TEST_PORT, on_srv_link_ready, NULL);
|
||||
TASSERT(ss);
|
||||
|
||||
struct sockaddr_in raddr; memset(&raddr, 0, sizeof(raddr));
|
||||
raddr.sin_family = AF_INET; raddr.sin_port = htons(TEST_PORT);
|
||||
raddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
|
||||
struct stcp_link_config cli_cfg = {.ua = ua, .my_keys = &c_keys, .inst = &inst,
|
||||
.peer_pubkey = s_keys.public_key, .remote_addr = (struct sockaddr_storage *)&raddr};
|
||||
struct stcp_link *cli_link = stcp_link_connect(&cli_cfg); TASSERT(cli_link);
|
||||
struct ETCP_CONN *cli_conn = stcp_link_get_etcp_conn(cli_link);
|
||||
|
||||
int ticks = 0;
|
||||
while ((!g_srv_link || !stcp_link_is_ready(cli_link)) && ticks < 5000) { uasync_poll(ua, 10); ticks++; }
|
||||
TASSERT(g_srv_link != NULL);
|
||||
TASSERT(stcp_link_is_ready(cli_link));
|
||||
|
||||
const char *msg = "hello via etcp_send!";
|
||||
struct ll_entry *e = queue_entry_new(0);
|
||||
e->dgram = u_malloc(strlen(msg) + 1);
|
||||
strcpy((char *)e->dgram, msg);
|
||||
e->len = (uint16_t)strlen(msg);
|
||||
TASSERT(etcp_send(cli_conn, e) == 0);
|
||||
|
||||
ticks = 0;
|
||||
while (g_srv_recv_count < 1 && ticks < 2000) { uasync_poll(ua, 10); ticks++; }
|
||||
TASSERT(g_srv_recv_count == 1);
|
||||
TASSERT(memcmp(g_srv_recv_data, msg, strlen(msg)) == 0);
|
||||
|
||||
stcp_link_close(cli_link);
|
||||
stcp_link_server_destroy(ss);
|
||||
uasync_destroy(ua, 1);
|
||||
|
||||
if (test_failed) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "=== FAILED ==="); return 1; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "=== PASSED ===");
|
||||
return 0;
|
||||
}
|
||||
@@ -1,359 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
// test_stcp_link.c — integration test for stcp_link API (TCP via STCP)
|
||||
#include "../src/stcp_link.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;
|
||||
static int test_failed = 0;
|
||||
|
||||
#define BASE_PORT 24567
|
||||
#define TASSERT(cond) do { \
|
||||
if (!(cond)) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, " FAIL: %s", #cond); test_failed = 1; return; } \
|
||||
} while(0)
|
||||
|
||||
struct recv_ctx { uint8_t *data; size_t len, cap; int count; };
|
||||
|
||||
static void recv_cb(struct ll_queue *q, void *arg) {
|
||||
struct recv_ctx *r = (struct recv_ctx *)arg;
|
||||
struct ll_entry *e = queue_data_get(q);
|
||||
if (!e) { queue_resume_callback(q); return; }
|
||||
r->count++;
|
||||
size_t need = r->len + e->len;
|
||||
if (need > r->cap) { r->cap = need + 256; r->data = u_realloc(r->data, r->cap); }
|
||||
if (e->dgram && e->len) memcpy(r->data + r->len, e->dgram, e->len);
|
||||
r->len += e->len;
|
||||
queue_entry_free(e);
|
||||
queue_resume_callback(q);
|
||||
}
|
||||
|
||||
static void on_srv_link(struct stcp_link *link, void *arg) {
|
||||
struct stcp_link **out = (struct stcp_link **)arg;
|
||||
*out = link;
|
||||
}
|
||||
|
||||
static void on_cli_ready(struct stcp_link *link, void *arg) {
|
||||
int *ready = (int *)arg;
|
||||
*ready = 1;
|
||||
(void)link;
|
||||
}
|
||||
|
||||
static int test1_basic(void) {
|
||||
struct UASYNC *ua = uasync_create(); TASSERT(ua);
|
||||
|
||||
struct stcp_link_config cfg = {.ua = ua, .my_keys = &s_keys, .inst = NULL};
|
||||
struct stcp_link *srv_link = NULL;
|
||||
struct stcp_server *ss = stcp_server_listen(&cfg, BASE_PORT + 1, on_srv_link, &srv_link);
|
||||
TASSERT(ss);
|
||||
|
||||
struct sockaddr_in raddr; memset(&raddr, 0, sizeof(raddr));
|
||||
raddr.sin_family = AF_INET; raddr.sin_port = htons(BASE_PORT + 1);
|
||||
raddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
|
||||
struct stcp_link_config cli_cfg = {.ua = ua, .my_keys = &c_keys, .inst = NULL,
|
||||
.peer_pubkey = s_keys.public_key, .remote_addr = (struct sockaddr_storage *)&raddr};
|
||||
int cli_ready = 0;
|
||||
struct stcp_link *cli_link = stcp_link_connect(&cli_cfg); TASSERT(cli_link);
|
||||
stcp_link_set_on_ready(cli_link, on_cli_ready, &cli_ready);
|
||||
|
||||
int ticks = 0;
|
||||
while ((!srv_link || !cli_ready) && ticks < 5000) { uasync_poll(ua, 10); ticks++; }
|
||||
TASSERT(srv_link != NULL); TASSERT(cli_ready);
|
||||
|
||||
// Verify send works (no crash, data goes through TCP)
|
||||
const char *msg = "hello link!";
|
||||
int r = stcp_link_send(cli_link, (const uint8_t *)msg, strlen(msg));
|
||||
TASSERT(r == 0);
|
||||
|
||||
stcp_link_close(cli_link);
|
||||
stcp_link_server_destroy(ss);
|
||||
uasync_destroy(ua, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test2_wrong_key(void) {
|
||||
struct UASYNC *ua = uasync_create(); TASSERT(ua);
|
||||
|
||||
struct stcp_link_config cfg = {.ua = ua, .my_keys = &s_keys, .inst = NULL};
|
||||
struct stcp_link *srv_link = NULL;
|
||||
struct stcp_server *ss = stcp_server_listen(&cfg, BASE_PORT + 2, on_srv_link, &srv_link);
|
||||
TASSERT(ss);
|
||||
|
||||
struct SC_MYKEYS rogue; TASSERT(sc_generate_keypair(&rogue) == SC_OK);
|
||||
struct sockaddr_in raddr; memset(&raddr, 0, sizeof(raddr));
|
||||
raddr.sin_family = AF_INET; raddr.sin_port = htons(BASE_PORT + 2);
|
||||
raddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
|
||||
struct stcp_link_config cli_cfg = {.ua = ua, .my_keys = &c_keys, .inst = NULL,
|
||||
.peer_pubkey = rogue.public_key, .remote_addr = (struct sockaddr_storage *)&raddr};
|
||||
struct stcp_link *cli_link = stcp_link_connect(&cli_cfg); TASSERT(cli_link);
|
||||
|
||||
int ticks = 0;
|
||||
while (!srv_link && ticks < 2000) { uasync_poll(ua, 10); ticks++; }
|
||||
TASSERT(!srv_link); // should NOT connect with wrong key
|
||||
|
||||
stcp_link_close(cli_link);
|
||||
stcp_link_server_destroy(ss);
|
||||
uasync_destroy(ua, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
debug_config_init();
|
||||
debug_set_level(DEBUG_LEVEL_INFO);
|
||||
debug_set_categories(DEBUG_CATEGORY_GENERAL | DEBUG_CATEGORY_SOCKET | DEBUG_CATEGORY_CRYPTO);
|
||||
|
||||
TASSERT(sc_generate_keypair(&s_keys) == SC_OK);
|
||||
TASSERT(sc_generate_keypair(&c_keys) == SC_OK);
|
||||
|
||||
test1_basic();
|
||||
test2_wrong_key();
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "Results: %d/%d passed", 2 - (test_failed ? 1 : 0), 2);
|
||||
return test_failed ? 1 : 0;
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
+17
-16
@@ -91,14 +91,15 @@ 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, 0, on_fin, on_error, NULL);
|
||||
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL);
|
||||
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
|
||||
|
||||
// push_write отправляет данные синхронно через write_queue_fetch_cb
|
||||
// Отправляем данные через tcp_conn
|
||||
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};
|
||||
@@ -110,8 +111,7 @@ static void test_basic_send_recv(void) {
|
||||
}
|
||||
ASSERT_TRUE(memcmp(send_buf, recv_buf, sizeof(send_buf)) == 0, "received data mismatch");
|
||||
|
||||
// Отправляем с другой стороны ДО poll — данные попадут в буфер ядра,
|
||||
// и при EPOLLHUP handle_error вычитает их дренажом в read_queue
|
||||
// Отправляем с другой стороны — должно появиться в 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,10 +125,11 @@ static void test_basic_send_recv(void) {
|
||||
queue_entry_free(e);
|
||||
queue_resume_callback(tc->read_queue);
|
||||
|
||||
// Закрываем peer — EPOLLHUP обработан через error_cb (handle_error)
|
||||
// Закрываем сокет — должен вызвать on_fin
|
||||
close(sv[1]);
|
||||
uasync_poll(ua, 10);
|
||||
ASSERT_EQ(g_error_count, 1, "error_cb not called on peer close");
|
||||
ASSERT_EQ(g_fin_count, 1, "on_fin not called");
|
||||
ASSERT_EQ(tc->fin_remote, 1, "tc->fin_remote not set");
|
||||
|
||||
tcp_conn_destroy(tc);
|
||||
close(sv[0]);
|
||||
@@ -147,7 +148,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, 0, on_fin, on_error, NULL);
|
||||
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 4096, 32, 8, on_fin, on_error, NULL);
|
||||
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
|
||||
|
||||
// Заполняем приёмный буфер sv[1] маленькими чтениями, чтобы создать EAGAIN на sv[0]
|
||||
@@ -192,7 +193,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, 0, on_fin, on_error, NULL);
|
||||
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 64, 8192, 2, 0, on_fin, on_error, NULL);
|
||||
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
|
||||
|
||||
// Шлём 5 блоков по 64 байта с другой стороны
|
||||
@@ -262,7 +263,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, 0, on_fin, on_error, NULL);
|
||||
struct tcp_conn* tc = tcp_conn_create(ua, client_fd, 1500, 8192, 32, 8, on_fin, on_error, NULL);
|
||||
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
|
||||
ASSERT_EQ(tc->connected, 0, "should not be connected yet");
|
||||
|
||||
@@ -294,7 +295,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, 0, on_fin, on_error, NULL);
|
||||
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL);
|
||||
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
|
||||
|
||||
close(sv[1]);
|
||||
@@ -322,7 +323,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, 0, on_fin, on_error, NULL);
|
||||
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL);
|
||||
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
|
||||
tc->on_fin_sent = on_fin_sent_cb;
|
||||
|
||||
@@ -368,7 +369,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, 0, on_fin, on_error, NULL);
|
||||
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL);
|
||||
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
|
||||
tc->on_closed = on_closed_cb;
|
||||
|
||||
@@ -406,7 +407,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, 0, on_fin, on_error, NULL);
|
||||
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL);
|
||||
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
|
||||
tc->on_fin_sent = on_fin_sent_cb;
|
||||
|
||||
@@ -454,7 +455,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, 0, on_fin, on_error, NULL);
|
||||
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL);
|
||||
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
|
||||
tc->on_fin_sent = on_fin_sent_cb;
|
||||
|
||||
@@ -481,7 +482,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, 0, on_fin, on_error, NULL);
|
||||
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, on_fin, on_error, NULL);
|
||||
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed");
|
||||
tc->on_closed = on_closed_cb;
|
||||
|
||||
@@ -509,7 +510,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, 0, on_fin, on_error, NULL);
|
||||
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, 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;
|
||||
|
||||
@@ -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, 0, on_fin_cb, on_error_cb, NULL);
|
||||
struct tcp_conn* tc = tcp_conn_create(ua, sock, 1500, 8192, 32, 8, on_fin_cb, on_error_cb, NULL);
|
||||
if (!tc) return 1;
|
||||
|
||||
struct sockaddr_in addr;
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "../lib/platform_compat.h"
|
||||
|
||||
#include "u_async.h"
|
||||
#include "timeout_heap.h"
|
||||
#include "debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
|
||||
|
||||
+23
-41
@@ -746,7 +746,14 @@ 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"
|
||||
};
|
||||
app->debug_cat_count = 0;
|
||||
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;
|
||||
|
||||
CreateWindowExA(0, "STATIC", "Global:", WS_CHILD | WS_VISIBLE,
|
||||
1015, dy + 2, 55, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
|
||||
@@ -765,6 +772,21 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1128,50 +1150,10 @@ 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,
|
||||
|
||||
Reference in new issue
Block a user