Browse Source
- 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
12 changed files with 575 additions and 165 deletions
@ -1,197 +1,65 @@ |
|||||||
/**
|
/**
|
||||||
* @file etcp_api.c |
* @file etcp_api.c |
||||||
* @brief Реализация API для приёма-передачи пакетов через ETCP (per-instance bindings) |
* @brief ETCP API — send/bind/recv dispatch |
||||||
*/ |
*/ |
||||||
|
|
||||||
#include "etcp_api.h" |
#include "etcp_api.h" |
||||||
#include "etcp.h" |
#include "etcp.h" |
||||||
#include "pkt_normalizer.h" |
#include "pkt_normalizer.h" |
||||||
#include "utun_instance.h" |
#include "utun_instance.h" |
||||||
|
#include "stcp_link.h" |
||||||
#include "../lib/debug_config.h" |
#include "../lib/debug_config.h" |
||||||
#include <string.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. только после этого можно передавать сообщения. Иначе сообщения могут потеряться.
|
void etcp_conn_set_ready_cbk(struct ETCP_CONN* e, etcp_cbk_fn fn, void* arg) { |
||||||
// conn_ready может вызваться несколько раз (после каждого переподключения - например когда удаленный узел перегрузился или возникла неустранимая ошибка).
|
if (e) { e->ready_cbk = fn; e->ready_arg = arg; } |
||||||
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) { |
||||||
void etcp_conn_set_up_cbk(struct ETCP_CONN* etcp, etcp_cbk_fn callback_fn, void* arg) { |
if (e) { e->up_cbk = fn; e->up_arg = 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) { |
||||||
void etcp_conn_set_down_cbk(struct ETCP_CONN* etcp, etcp_cbk_fn callback_fn, void* arg) { |
if (e) { e->down_cbk = fn; e->down_arg = 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) { |
||||||
// Установить callback при создании нового ETCP соединения
|
if (inst) { inst->etcp_new_conn_cbk = fn; inst->etcp_new_conn_arg = arg; } |
||||||
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) { |
int etcp_bind(struct UTUN_INSTANCE* inst, uint8_t id, etcp_recv_fn callback) { |
||||||
if (!inst) { |
if (!inst || !callback || (unsigned)id >= ETCP_MAX_BINDINGS) return -1; |
||||||
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; |
inst->api_bindings.callbacks[id] = callback; |
||||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Bound id=%u → callback=%p", id, (void*)callback); |
|
||||||
return 0; |
return 0; |
||||||
} |
} |
||||||
|
|
||||||
int etcp_unbind(struct UTUN_INSTANCE* inst, uint8_t id) { |
int etcp_unbind(struct UTUN_INSTANCE* inst, uint8_t id) { |
||||||
if (!inst) { |
if (!inst || (unsigned)id >= ETCP_MAX_BINDINGS) return -1; |
||||||
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; |
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; |
return 0; |
||||||
} |
} |
||||||
|
|
||||||
int etcp_send(struct ETCP_CONN* conn, struct ll_entry* entry) { |
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 || !entry) return -1; |
||||||
if (!conn) { |
// TCP (stcp_link) — send directly
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: NULL connection"); |
if (conn->transport_link) |
||||||
return -1; |
return stcp_link_send((struct stcp_link *)conn->transport_link, entry->dgram, entry->len); |
||||||
} |
// UDP — через normalizer
|
||||||
if (!entry) { |
if (!conn->normalizer || !conn->normalizer->input) return -1; |
||||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: NULL entry"); |
return queue_data_put(conn->normalizer->input, 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) { |
void etcp_int_recv(struct ll_queue* queue, void* arg) { |
||||||
struct ETCP_CONN* conn = (struct ETCP_CONN*)arg; |
struct ETCP_CONN* conn = (struct ETCP_CONN*)arg; |
||||||
|
if (!queue || !conn) return; |
||||||
if (!queue || !conn) { |
struct ll_entry* e = queue_data_get(queue); |
||||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Invalid arguments (queue=%p, conn=%p)",
|
if (!e) { queue_resume_callback(queue); return; } |
||||||
(void*)queue, (void*)conn); |
if (!e->dgram || e->len == 0) { queue_entry_free(e); queue_dgram_free(e); queue_resume_callback(queue); return; } |
||||||
return; |
uint8_t id = e->dgram[0]; |
||||||
} |
|
||||||
|
|
||||||
// Получаем элемент из очереди
|
|
||||||
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; |
struct UTUN_INSTANCE* inst = conn->instance; |
||||||
if (!inst) { |
if (!inst) { queue_entry_free(e); queue_dgram_free(e); queue_resume_callback(queue); return; } |
||||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Connection has no instance"); |
if (inst->api_bindings.callbacks[id]) |
||||||
queue_entry_free(entry); |
inst->api_bindings.callbacks[id](conn, e); |
||||||
queue_dgram_free(entry); |
else if (inst->api_bindings.callbacks[0]) |
||||||
queue_resume_callback(queue); |
inst->api_bindings.callbacks[0](conn, e); |
||||||
return; |
else { queue_entry_free(e); queue_dgram_free(e); } |
||||||
} |
|
||||||
|
|
||||||
// Ищем 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); |
queue_resume_callback(queue); |
||||||
} |
} |
||||||
|
|||||||
@ -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; |
||||||
|
} |
||||||
@ -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 |
||||||
@ -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; |
||||||
|
} |
||||||
@ -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…
Reference in new issue