202 lines
8.5 KiB
C
202 lines
8.5 KiB
C
// test_stcp_link.c — STCP link layer: server listen/accept (slim config) + client connect (raw stcp_client)
|
|
#include "stcp_link.h"
|
|
#include "stcp_client.h"
|
|
#include "stcp_server.h"
|
|
#include "secure_channel.h"
|
|
#include "utun_instance.h"
|
|
#include "etcp.h"
|
|
#include "test_clock.h"
|
|
#include "../lib/u_async.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 TASSERT(cond) do { \
|
|
if (!(cond)) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, " FAIL: %s", #cond); test_failed = 1; return test_failed; } \
|
|
} while(0)
|
|
|
|
static struct stcp_link *g_on_srv_link = NULL;
|
|
static void on_srv_link(struct stcp_link *link, struct ETCP_SOCKET *tcp_sock) {
|
|
(void)tcp_sock; g_on_srv_link = link;
|
|
}
|
|
|
|
static void on_cli_ready(struct stcp_conn *conn, void *arg) {
|
|
(void)conn;
|
|
int *ready = (int *)arg;
|
|
*ready = 1;
|
|
}
|
|
|
|
static struct UTUN_INSTANCE *make_inst(struct UASYNC *ua, struct SC_MYKEYS *keys) {
|
|
struct UTUN_INSTANCE *inst = u_calloc(1, sizeof(struct UTUN_INSTANCE));
|
|
if (!inst) return NULL;
|
|
inst->ua = ua;
|
|
inst->my_keys = *keys;
|
|
inst->keepalive_interval = 200;
|
|
return inst;
|
|
}
|
|
|
|
static int test1_basic(void) {
|
|
struct UASYNC *ua = uasync_create(); TASSERT(ua);
|
|
struct UTUN_INSTANCE *inst = make_inst(ua, &s_keys); TASSERT(inst);
|
|
|
|
struct stcp_link_config cfg = {.inst = inst, .listen_family = AF_INET};
|
|
g_on_srv_link = NULL;
|
|
struct stcp_server *ss = stcp_server_listen(&cfg, 0, on_srv_link, NULL);
|
|
TASSERT(ss);
|
|
|
|
int cli_ready = 0;
|
|
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", stcp_link_server_get_port(ss), &c_keys,
|
|
s_keys.public_key, NULL,
|
|
0, NULL,
|
|
0, 200, 0, on_cli_ready, &cli_ready, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL);
|
|
TASSERT(sc);
|
|
|
|
int ticks = 0;
|
|
while ((!g_on_srv_link || !cli_ready) && ticks < 5000) { test_clock_poll(ua, 10); ticks++; }
|
|
TASSERT(g_on_srv_link != NULL); TASSERT(cli_ready);
|
|
|
|
stcp_client_destroy(sc);
|
|
stcp_link_close(g_on_srv_link);
|
|
stcp_link_server_destroy(ss);
|
|
uasync_drain_immediate(ua);
|
|
u_free(inst);
|
|
uasync_destroy(ua, 1);
|
|
return 0;
|
|
}
|
|
|
|
static int test2_wrong_key(void) {
|
|
struct UASYNC *ua = uasync_create(); TASSERT(ua);
|
|
struct UTUN_INSTANCE *inst = make_inst(ua, &s_keys); TASSERT(inst);
|
|
|
|
struct stcp_link_config cfg = {.inst = inst, .listen_family = AF_INET};
|
|
g_on_srv_link = NULL;
|
|
struct stcp_server *ss = stcp_server_listen(&cfg, 0, on_srv_link, NULL);
|
|
TASSERT(ss);
|
|
|
|
struct SC_MYKEYS rogue; TASSERT(sc_generate_keypair(&rogue) == SC_OK);
|
|
int cli_ready = 0;
|
|
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", stcp_link_server_get_port(ss), &c_keys,
|
|
rogue.public_key, NULL,
|
|
0, NULL,
|
|
0, 200, 0, on_cli_ready, &cli_ready, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL);
|
|
TASSERT(sc);
|
|
|
|
// Неверный ключ должен завершить handshake закрытием, без создания серверного линка.
|
|
struct stcp_conn* conn = stcp_client_get_conn(sc); TASSERT(conn);
|
|
uint64_t start = get_time_tb(), deadline = start + 20000;
|
|
while (conn->state != STCP_STATE_CLOSED && get_time_tb() < deadline) test_clock_poll(ua, 10);
|
|
TASSERT(conn->state == STCP_STATE_CLOSED);
|
|
TASSERT(!cli_ready);
|
|
TASSERT(!g_on_srv_link); // should NOT connect with wrong key
|
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "STCP wrong-key test: handshake closed without READY/link elapsed_tb=%llu",
|
|
(unsigned long long)(get_time_tb() - start));
|
|
|
|
stcp_client_destroy(sc);
|
|
stcp_link_server_destroy(ss);
|
|
uasync_drain_immediate(ua);
|
|
u_free(inst);
|
|
uasync_destroy(ua, 1);
|
|
return 0;
|
|
}
|
|
|
|
struct failure_case {
|
|
struct stcp_link *link;
|
|
unsigned closed, ready;
|
|
int error, close_inside;
|
|
};
|
|
|
|
static void adapter_ready(struct stcp_link *link, void *arg) {
|
|
(void)link;
|
|
((struct failure_case *)arg)->ready++;
|
|
}
|
|
|
|
static void adapter_closed(struct stcp_link *link, int err, void *arg) {
|
|
struct failure_case *result = arg;
|
|
result->closed++; result->error = err;
|
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "adapter close: count=%u err=%d close_inside=%d", result->closed, err, result->close_inside);
|
|
if (result->close_inside) {
|
|
stcp_link_close(link);
|
|
stcp_link_close(link); /* Повторный close внутри того же callback не планирует второй free. */
|
|
result->link = NULL;
|
|
}
|
|
}
|
|
|
|
/* 0: refused; 1: отмена до poll; 2: неверный ключ; 3: молчащий сервер; 4: close из close-callback. */
|
|
static int test_adapter_failure(unsigned scenario) {
|
|
size_t before = u_get_allocated_count();
|
|
struct UASYNC *ua = uasync_create(); TASSERT(ua);
|
|
struct UTUN_INSTANCE *inst = make_inst(ua, &c_keys); TASSERT(inst);
|
|
struct ETCP_CONN parent = {.instance = inst};
|
|
struct ETCP_LINK path = {.etcp = &parent, .is_tcp = 1};
|
|
struct failure_case result = {.close_inside = scenario == 4};
|
|
struct sockaddr_storage address = {0};
|
|
struct sockaddr_in *v4 = (struct sockaddr_in *)&address;
|
|
v4->sin_family = AF_INET; v4->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
struct stcp_server *server = NULL;
|
|
socket_t reserved = SOCKET_INVALID;
|
|
int server_ready = 0;
|
|
if (scenario == 2) {
|
|
server = stcp_server_create(ua, 0, &s_keys, NULL, NULL, on_cli_ready, &server_ready, NULL, NULL, AF_INET);
|
|
TASSERT(server);
|
|
v4->sin_port = htons(stcp_server_get_port(server));
|
|
struct SC_MYKEYS rogue; TASSERT(sc_generate_keypair(&rogue) == SC_OK);
|
|
memcpy(parent.crypto_ctx.peer_public_key, rogue.public_key, SC_PUBKEY_SIZE);
|
|
} else {
|
|
reserved = socket(AF_INET, SOCK_STREAM, 0); TASSERT(reserved != SOCKET_INVALID);
|
|
TASSERT(bind(reserved, (struct sockaddr *)v4, sizeof(*v4)) == 0);
|
|
socklen_t length = sizeof(*v4);
|
|
TASSERT(getsockname(reserved, (struct sockaddr *)v4, &length) == 0);
|
|
if (scenario == 3) TASSERT(listen(reserved, 1) == 0);
|
|
memcpy(parent.crypto_ctx.peer_public_key, s_keys.public_key, SC_PUBKEY_SIZE);
|
|
}
|
|
result.link = stcp_link_connect(&path, &address, ntohs(v4->sin_port)); TASSERT(result.link);
|
|
path.tcp_link = result.link;
|
|
stcp_link_set_on_ready(result.link, adapter_ready, &result);
|
|
stcp_link_set_on_close(result.link, adapter_closed, &result);
|
|
if (scenario == 1) { stcp_link_close(result.link); result.link = NULL; }
|
|
uint64_t deadline = get_time_tb() + (scenario == 3 ? STCP_CONNECT_TIMEOUT + 1000 : 20000);
|
|
while (!result.closed && get_time_tb() < deadline) test_clock_poll(ua, 100);
|
|
TASSERT(!result.ready && !server_ready);
|
|
TASSERT(result.closed == (scenario == 1 ? 0 : 1));
|
|
if (scenario == 0 || scenario == 4) TASSERT(result.error == ECONNREFUSED);
|
|
if (scenario == 3) TASSERT(result.error == ETIMEDOUT);
|
|
test_clock_run_for(ua, 1000);
|
|
TASSERT(result.closed == (scenario == 1 ? 0 : 1));
|
|
if (result.link) stcp_link_close(result.link);
|
|
if (server) stcp_server_destroy(server);
|
|
if (reserved != SOCKET_INVALID) socket_close_wrapper(reserved);
|
|
uasync_drain_immediate(ua);
|
|
TASSERT(ua->timer_alloc_count == ua->timer_free_count && ua->socket_alloc_count == ua->socket_free_count);
|
|
u_free(inst); uasync_destroy(ua, 0);
|
|
TASSERT(u_get_allocated_count() == before);
|
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS adapter failure scenario=%u callbacks=%u", scenario, result.closed);
|
|
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);
|
|
debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_INFO);
|
|
test_clock_init(100);
|
|
|
|
TASSERT(sc_generate_keypair(&s_keys) == SC_OK);
|
|
TASSERT(sc_generate_keypair(&c_keys) == SC_OK);
|
|
|
|
tests_total++; if (!test1_basic()) tests_passed++;
|
|
tests_total++; if (!test2_wrong_key()) tests_passed++;
|
|
for (unsigned scenario = 0; scenario < 5; scenario++) {
|
|
tests_total++; if (!test_adapter_failure(scenario)) tests_passed++;
|
|
}
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "Results: %d/%d passed", tests_passed, tests_total);
|
|
test_clock_finish();
|
|
return test_failed ? 1 : 0;
|
|
}
|