Browse Source

stcp: unified transport — etcp_send/bind over TCP via stcp_link

- 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
stcp
Evgeny 4 months ago
parent
commit
150e9977df
  1. 1
      src/Makefile.am
  2. 7
      src/config_parser.c
  3. 1
      src/config_parser.h
  4. 2
      src/etcp.h
  5. 194
      src/etcp_api.c
  6. 55
      src/etcp_connections.c
  7. 202
      src/stcp_link.c
  8. 48
      src/stcp_link.h
  9. 7
      src/utun_instance.h
  10. 14
      tests/Makefile.am
  11. 89
      tests/test_etcp_stcp.c
  12. 120
      tests/test_stcp_link.c

1
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 \

7
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;
}

1
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

2
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;

194
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 <string.h>
#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);
}

55
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;

202
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 <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;
}

48
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 <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

7
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;

14
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)

89
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 <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;
}

120
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 <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;
}
Loading…
Cancel
Save