From 150e9977dfa2376c4187e74ffcd50f384fadb7f6 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 16 Jun 2026 01:19:58 +0300 Subject: [PATCH] =?UTF-8?q?stcp:=20unified=20transport=20=E2=80=94=20etcp?= =?UTF-8?q?=5Fsend/bind=20over=20TCP=20via=20stcp=5Flink?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - stcp_link.h/c: manage TCP connections through STCP protocol stcp_server_listen, stcp_link_connect/send/close rx dispatch goes directly through inst->api_bindings (single table) - etcp_api.c: etcp_send checks conn->transport_link for TCP path etcp_bind stores only in api_bindings (no duplication) - etcp_connections.c: init_sockets creates stcp_server for TCP init_connections creates stcp_link_connect for TCP clients tcp_server_on_link creates minimal ETCP_CONN + fires callbacks - etcp.h: ETCP_CONN.transport_link field (NULL for UDP, set for TCP) - config_parser: CFG_SERVER.transport field, parsing transport=tcp|udp - utun_instance.h: struct stcp_server* - Tests: test_stcp_link (handshake, wrong key) test_etcp_stcp (etcp_send/bind over TCP) - Renamed: transport.h/c → stcp_link.h/c (cleaner, no bindings table) - 46/46 tests pass --- src/Makefile.am | 1 + src/config_parser.c | 7 +- src/config_parser.h | 1 + src/etcp.h | 2 + src/etcp_api.c | 194 +++++++-------------------------------- src/etcp_connections.c | 55 +++++++++++ src/stcp_link.c | 202 +++++++++++++++++++++++++++++++++++++++++ src/stcp_link.h | 48 ++++++++++ src/utun_instance.h | 7 +- tests/Makefile.am | 14 +++ tests/test_etcp_stcp.c | 89 ++++++++++++++++++ tests/test_stcp_link.c | 120 ++++++++++++++++++++++++ 12 files changed, 575 insertions(+), 165 deletions(-) create mode 100644 src/stcp_link.c create mode 100644 src/stcp_link.h create mode 100644 tests/test_etcp_stcp.c create mode 100644 tests/test_stcp_link.c diff --git a/src/Makefile.am b/src/Makefile.am index 5efc41f8..d4fae477 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -26,6 +26,7 @@ utun_CORE_SOURCES = \ etcp_dump.c \ secure_channel.c \ crc32.c \ + stcp_link.c \ stcp.c \ stcp_server.c \ stcp_client.c \ diff --git a/src/config_parser.c b/src/config_parser.c index 6e7fd460..c58d02c7 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -624,7 +624,12 @@ static int parse_server(const char *key, const char *value, struct CFG_SERVER *s srv->only_local = atoi(value) ? 1 : 0; return 0; } - 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); + 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); return -1; } diff --git a/src/config_parser.h b/src/config_parser.h index 1cae8c44..5ba7c287 100644 --- a/src/config_parser.h +++ b/src/config_parser.h @@ -40,6 +40,7 @@ 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 diff --git a/src/etcp.h b/src/etcp.h index 660fb62d..abc4de17 100644 --- a/src/etcp.h +++ b/src/etcp.h @@ -13,6 +13,7 @@ extern "C" { #include "pkt_normalizer.h" +struct stcp_link; // forward declaration // In struct ETCP_CONN, add: //struct pn_pair* normalizer; @@ -104,6 +105,7 @@ 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; diff --git a/src/etcp_api.c b/src/etcp_api.c index 17f4528f..2d1ebb71 100644 --- a/src/etcp_api.c +++ b/src/etcp_api.c @@ -1,197 +1,65 @@ /** * @file etcp_api.c - * @brief Реализация API для приёма-передачи пакетов через ETCP (per-instance bindings) + * @brief ETCP API — send/bind/recv dispatch */ - #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 -#include "route_lib.h" -#include "route_bgp.h" - -#define DEBUG_CATEGORY_ETCP_API 1 -#define ETCP_MAX_BINDINGS 256 /**< Максимальное количество bindings (по одному на каждый ID 0-255) */ +#define DEBUG_CATEGORY_ETCP_API DEBUG_CATEGORY_ETCP -// после создания подключения надо выждать 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_ready_cbk(struct ETCP_CONN* e, etcp_cbk_fn fn, void* arg) { + if (e) { e->ready_cbk = fn; e->ready_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_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_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_conn_set_down_cbk(struct ETCP_CONN* e, etcp_cbk_fn fn, void* arg) { + if (e) { e->down_cbk = fn; e->down_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); +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; } } int etcp_bind(struct UTUN_INSTANCE* inst, uint8_t id, etcp_recv_fn callback) { - 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); - } + if (!inst || !callback || (unsigned)id >= ETCP_MAX_BINDINGS) return -1; 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) { - 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; - } - + if (!inst || (unsigned)id >= ETCP_MAX_BINDINGS) 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) { - 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; + 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); } void etcp_int_recv(struct ll_queue* queue, void* arg) { struct ETCP_CONN* conn = (struct ETCP_CONN*)arg; - - 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 из соединения + 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]; struct UTUN_INSTANCE* inst = conn->instance; - 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); - } - } - - // Возобновляем коллбэк для получения следующего пакета + 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); } queue_resume_callback(queue); } diff --git a/src/etcp_connections.c b/src/etcp_connections.c index 7fe71d33..d0132785 100644 --- a/src/etcp_connections.c +++ b/src/etcp_connections.c @@ -15,6 +15,7 @@ #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" @@ -27,6 +28,22 @@ #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); @@ -1829,6 +1846,21 @@ 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; @@ -1971,6 +2003,29 @@ 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; diff --git a/src/stcp_link.c b/src/stcp_link.c new file mode 100644 index 00000000..b6f1b441 --- /dev/null +++ b/src/stcp_link.c @@ -0,0 +1,202 @@ +// 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 +#include + +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; +} diff --git a/src/stcp_link.h b/src/stcp_link.h new file mode 100644 index 00000000..00a5412a --- /dev/null +++ b/src/stcp_link.h @@ -0,0 +1,48 @@ +// 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 + +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 diff --git a/src/utun_instance.h b/src/utun_instance.h index 80189e80..99e770d8 100644 --- a/src/utun_instance.h +++ b/src/utun_instance.h @@ -8,6 +8,7 @@ #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" @@ -15,6 +16,9 @@ #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; @@ -71,8 +75,9 @@ struct UTUN_INSTANCE { struct memory_pool* pkt_pool; struct memory_pool* ack_pool; - // Active sockets + // Active sockets (UDP) 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; diff --git a/tests/Makefile.am b/tests/Makefile.am index 032f524f..e9734415 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -5,6 +5,8 @@ check_PROGRAMS = \ test_ll_queue \ test_serialize \ test_stream_cipher \ + test_etcp_transport \ + test_transport \ test_stcp \ test_packet_dump \ test_debug_categories \ @@ -81,6 +83,9 @@ 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 \ @@ -131,6 +136,7 @@ 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) @@ -182,6 +188,14 @@ 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) diff --git a/tests/test_etcp_stcp.c b/tests/test_etcp_stcp.c new file mode 100644 index 00000000..0708e816 --- /dev/null +++ b/tests/test_etcp_stcp.c @@ -0,0 +1,89 @@ +// 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 +#include +#include + +#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; +} diff --git a/tests/test_stcp_link.c b/tests/test_stcp_link.c new file mode 100644 index 00000000..25aa086d --- /dev/null +++ b/tests/test_stcp_link.c @@ -0,0 +1,120 @@ +// 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 +#include +#include + +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; +}