Reproduce NCD startup failures stale events and malformed control
This commit is contained in:
1 parent
10d6ddee38
commit
2cc5a6df91
2 files changed
+182
No files matched your search
@@ -103,6 +103,7 @@ check_PROGRAMS += \
|
||||
test_ntp_regressions \
|
||||
test_etcp_windows \
|
||||
test_dummynet_channels \
|
||||
test_ncd_faults \
|
||||
test_stcp_sigpipe \
|
||||
test_firewall \
|
||||
test_mem \
|
||||
@@ -715,6 +716,14 @@ test_node_conn_direct_SOURCES = test_node_conn_direct.c
|
||||
test_node_conn_direct_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_node_conn_direct_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
if OS_LINUX
|
||||
test_ncd_faults_SOURCES = test_ncd_faults.c $(test_clock_sources)
|
||||
test_ncd_faults_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_ncd_faults_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
test_ncd_faults_LDFLAGS = $(test_clock_ldflags) -Wl,--wrap=etcp_conn_add_cbk -Wl,--wrap=uasync_call_soon \
|
||||
-Wl,--wrap=uasync_set_timeout -Wl,--wrap=u_calloc_impl
|
||||
endif
|
||||
|
||||
test_group_ownership_SOURCES = test_group_ownership.c
|
||||
test_group_ownership_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_group_ownership_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
/* Реальные ядро/ETCP/таймеры: атомарный запуск NCD, отказы ресурсов и повторные события из callback. */
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "utun_instance.h"
|
||||
#include "etcp.h"
|
||||
#include "etcp_api.h"
|
||||
#include "node_conn_direct.h"
|
||||
#include "test_clock.h"
|
||||
#include "../lib/mem.h"
|
||||
|
||||
#define CHECK(x) do { if (!(x)) { DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "FAIL %s:%d: %s", __func__, __LINE__, #x); exit(1); } } while (0)
|
||||
static struct UASYNC* ua;
|
||||
static struct UTUN_INSTANCE* inst;
|
||||
static size_t allocations;
|
||||
static int fail_callback, fail_soon, fail_connect, fail_alloc;
|
||||
|
||||
void __real_etcp_conn_add_cbk(struct ETCP_CONN*, etcp_cbk_fn, void*, int);
|
||||
void __wrap_etcp_conn_add_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg, int events) {
|
||||
if (!fail_callback) __real_etcp_conn_add_cbk(conn, fn, arg, events);
|
||||
}
|
||||
|
||||
void* __real_uasync_call_soon(struct UASYNC*, void*, timeout_callback_t);
|
||||
void* __wrap_uasync_call_soon(struct UASYNC* loop, void* arg, timeout_callback_t fn) {
|
||||
if (fail_soon) { fail_soon--; return NULL; }
|
||||
return __real_uasync_call_soon(loop, arg, fn);
|
||||
}
|
||||
|
||||
void* __real_uasync_set_timeout(struct UASYNC*, int, void*, timeout_callback_t, const char*);
|
||||
void* __wrap_uasync_set_timeout(struct UASYNC* loop, int delay, void* arg, timeout_callback_t fn, const char* name) {
|
||||
if (fail_connect && !strncmp(name, "ncd_connect", 11)) return NULL;
|
||||
return __real_uasync_set_timeout(loop, delay, arg, fn, name);
|
||||
}
|
||||
|
||||
void* __real_u_calloc_impl(uint32_t, uint32_t, const char*);
|
||||
void* __wrap_u_calloc_impl(uint32_t count, uint32_t size, const char* location) {
|
||||
if (fail_alloc && strstr(location, "node_conn_direct.c")) return NULL;
|
||||
return __real_u_calloc_impl(count, size, location);
|
||||
}
|
||||
|
||||
static void setup(void) {
|
||||
allocations = u_get_allocated_count();
|
||||
ua = uasync_create(); CHECK(ua);
|
||||
inst = utun_instance_create_from_str(ua,
|
||||
"[global]\nauto_sockets=no\n"
|
||||
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
|
||||
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
|
||||
"[ntp]\nenabled=no\n");
|
||||
CHECK(inst); inst->etcp_connect_timeout_tb = 10000;
|
||||
}
|
||||
|
||||
static void teardown(void) {
|
||||
CHECK(!fail_callback && !fail_soon && !fail_connect && !fail_alloc);
|
||||
utun_instance_destroy(inst);
|
||||
uasync_poll(ua, 0);
|
||||
CHECK(ua->timer_alloc_count == ua->timer_free_count);
|
||||
CHECK(ua->socket_alloc_count == ua->socket_free_count);
|
||||
uasync_destroy(ua, 0);
|
||||
CHECK(u_get_allocated_count() == allocations);
|
||||
}
|
||||
|
||||
static struct ETCP_CONN* connection(int ready) {
|
||||
struct ETCP_CONN* conn = etcp_connection_create(inst, "NCD fault fixture"); CHECK(conn);
|
||||
conn->peer_node_id = 42;
|
||||
queue_set_callback(conn->send_input_q, NULL, NULL);
|
||||
CHECK(etcp_conn_index_peer(conn) == 0);
|
||||
if (ready) { conn->links_up = 1; etcp_conn_ready(conn); CHECK(conn->state == 1); }
|
||||
return conn;
|
||||
}
|
||||
|
||||
struct events { struct ETCP_CONN* conn; unsigned up, down, closed; int nested_down; };
|
||||
static void changed(struct NODE_CONN_DIRECT* handle, enum ncd_event event, void* arg) {
|
||||
struct events* state = arg; (void)handle;
|
||||
if (event == NCD_EVENT_UP) {
|
||||
state->up++;
|
||||
if (state->nested_down) {
|
||||
state->nested_down = 0; state->conn->links_up = 0;
|
||||
etcp_cbk_fire(state->conn, ETCP_CBK_EVENT_DOWN);
|
||||
}
|
||||
} else if (event == NCD_EVENT_DOWN) state->down++;
|
||||
else if (event == NCD_EVENT_CLOSED) state->closed++;
|
||||
}
|
||||
|
||||
static void callback_failure(void) {
|
||||
setup(); struct ETCP_CONN* conn = connection(1);
|
||||
struct NODE_CONN_DIRECT* handle = NULL; struct events state = {0};
|
||||
fail_callback = 1;
|
||||
int rc = node_conn_direct_open(inst, 42, changed, &state, &handle, NULL);
|
||||
fail_callback = 0;
|
||||
CHECK(rc == NCD_ERR && !handle && !inst->ncd_registry && !conn->close_requested && conn->ref_count == 0);
|
||||
uasync_drain_immediate(ua); CHECK(!state.up && !state.closed);
|
||||
teardown();
|
||||
}
|
||||
|
||||
static void up_failure(void) {
|
||||
setup(); struct ETCP_CONN* conn = connection(1);
|
||||
struct NODE_CONN_DIRECT *owner = NULL, *extra = NULL; struct events state = {0};
|
||||
CHECK(node_conn_direct_open(inst, 42, NULL, NULL, &owner, NULL) == NCD_REUSED);
|
||||
uasync_drain_immediate(ua);
|
||||
fail_soon = 1;
|
||||
int rc = node_conn_direct_open(inst, 42, changed, &state, &extra, NULL);
|
||||
CHECK(rc == NCD_ERR && !extra && !fail_soon);
|
||||
CHECK(node_conn_direct_handle_count(conn) == 1 && !conn->close_requested);
|
||||
uasync_drain_immediate(ua); CHECK(!state.up && !state.closed);
|
||||
node_conn_direct_force_close(owner); teardown();
|
||||
}
|
||||
|
||||
static void timer_failure(void) {
|
||||
setup(); struct ETCP_CONN* conn = connection(0);
|
||||
struct NODE_CONN_DIRECT* handle = NULL; struct events state = {0};
|
||||
fail_connect = 1;
|
||||
int rc = node_conn_direct_open(inst, 42, changed, &state, &handle, NULL);
|
||||
fail_connect = 0;
|
||||
CHECK(rc == NCD_ERR && !handle && !inst->ncd_registry && !conn->close_requested && conn->ref_count == 0);
|
||||
teardown();
|
||||
}
|
||||
|
||||
static void fin_wait_failure(void) {
|
||||
setup(); struct ETCP_CONN* conn = connection(1);
|
||||
struct NODE_CONN_DIRECT* handle = NULL;
|
||||
CHECK(node_conn_direct_open(inst, 42, NULL, NULL, &handle, NULL) == NCD_REUSED);
|
||||
node_conn_direct_close(handle); CHECK(conn->fin_wait);
|
||||
fail_alloc = 1;
|
||||
int rc = node_conn_direct_open(inst, 42, NULL, NULL, &handle, NULL);
|
||||
fail_alloc = 0;
|
||||
CHECK(rc == NCD_ERR && !handle && conn->fin_wait && node_conn_direct_handle_count(conn) == 0);
|
||||
test_clock_run_for(ua, 51000); CHECK(!inst->ncd_registry);
|
||||
teardown();
|
||||
}
|
||||
|
||||
static void nested_events(void) {
|
||||
setup(); struct ETCP_CONN* conn = connection(1);
|
||||
struct NODE_CONN_DIRECT *first = NULL, *second = NULL;
|
||||
struct events a = {.conn = conn}, b = {.conn = conn};
|
||||
CHECK(node_conn_direct_open(inst, 42, changed, &a, &first, NULL) == NCD_REUSED);
|
||||
CHECK(node_conn_direct_open(inst, 42, changed, &b, &second, NULL) == NCD_REUSED);
|
||||
uasync_drain_immediate(ua);
|
||||
conn->links_up = 0; etcp_cbk_fire(conn, ETCP_CBK_EVENT_DOWN);
|
||||
a.up = a.down = b.up = b.down = 0; b.nested_down = 1;
|
||||
conn->links_up = 1; etcp_cbk_fire(conn, ETCP_CBK_EVENT_UP);
|
||||
CHECK(b.up == 1 && b.down == 1 && a.down == 1 && a.up == 0);
|
||||
node_conn_direct_force_close(first); node_conn_direct_force_close(second); teardown();
|
||||
}
|
||||
|
||||
static void control_length(void) {
|
||||
setup(); struct ETCP_CONN* conn = connection(1);
|
||||
struct NODE_CONN_DIRECT* handle = NULL;
|
||||
CHECK(node_conn_direct_open(inst, 42, NULL, NULL, &handle, NULL) == NCD_REUSED);
|
||||
node_conn_direct_close(handle); CHECK(conn->fin_wait);
|
||||
struct ncd_control_msg message = { ETCP_ID_NCD_CONTROL, NCD_SUBCMD_KEEP_ALIVE, 42 };
|
||||
struct ll_entry* packet = ll_alloc_lldgram(sizeof(message) + 1); CHECK(packet);
|
||||
memcpy(packet->dgram, &message, sizeof(message)); packet->dgram[sizeof(message)] = 0;
|
||||
packet->len = sizeof(message) + 1;
|
||||
inst->api_bindings.callbacks[ETCP_ID_NCD_CONTROL](conn, packet);
|
||||
CHECK(conn->fin_wait);
|
||||
test_clock_run_for(ua, 51000); CHECK(!inst->ncd_registry); teardown();
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_INFO);
|
||||
if (getenv("UTUN_TEST_DEBUG")) debug_set_category_level(DEBUG_CATEGORY_ETCP, DEBUG_LEVEL_DEBUG);
|
||||
test_clock_init(1000);
|
||||
struct { const char* name; void (*run)(void); } cases[] = {
|
||||
{"callback", callback_failure}, {"up", up_failure}, {"timer", timer_failure},
|
||||
{"fin_wait", fin_wait_failure}, {"nested", nested_events}, {"control", control_length}
|
||||
};
|
||||
unsigned selected = 0;
|
||||
for (unsigned i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
|
||||
if (argc > 1 && strcmp(argv[1], cases[i].name)) continue;
|
||||
selected++; cases[i].run();
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS NCD fault scenario=%s", cases[i].name);
|
||||
}
|
||||
CHECK(selected && argc <= 2); test_clock_finish(); return 0;
|
||||
}
|
||||
Reference in new issue
Block a user