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 |
||||
* @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); |
||||
} |
||||
|
||||
@ -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