Cover UDP accept cancellation with surviving paths and NCD owners

This commit is contained in:
evgeny committed 2026-10-23 23:50:39 +02:00
1 parent 942736d604
commit fb995db489
1 file changed
+80 -10
+80 -10
View File
@@ -6,6 +6,8 @@
#include "etcp_api.h"
#include "etcp_connections.h"
#include "etcp_keepalive.h"
#include "node_conn_direct.h"
#include "routing_layer/topo_node.h"
#include "utun_instance.h"
#include "test_clock.h"
#include "test_net.h"
@@ -87,6 +89,18 @@ static struct ETCP_LINK* first_link(unsigned side) {
static void run_for(unsigned tb) { test_clock_run_for(ua, tb); }
static void start_source(unsigned count) {
ep[0].conn = etcp_connection_create(ep[0].inst, "liveness"); CHECK(ep[0].conn);
ep[0].conn->peer_node_id = ep[1].inst->node_id;
CHECK(sc_init_ctx(&ep[0].conn->crypto_ctx, &ep[0].inst->my_keys) == SC_OK);
CHECK(sc_set_peer_public_key(&ep[0].conn->crypto_ctx, ep[1].inst->my_keys.public_key, SC_PEER_PUBKEY_BIN) == SC_OK);
etcp_conn_add_cbk(ep[0].conn, conn_event, &ep[0], ETCP_CBK_EVENT_UP | ETCP_CBK_EVENT_DOWN | ETCP_CBK_EVENT_REINIT);
for (unsigned i = 0; i < count; i++) {
struct ETCP_SOCKET* socket = i ? test_etcp_bound_socket(ep[0].inst, "extra") : ep[0].socket;
CHECK(etcp_link_new(ep[0].conn, socket, &ep[1].socket->interface_addr, 0));
}
}
/* Только ядро и одна явно выбранная UDP-пара: BGP/NCD не создают обходных путей. */
static void setup(unsigned swapped, unsigned count) {
memset(ep, 0, sizeof(ep)); close_action = close_fired = late_handshakes = 0;
@@ -105,16 +119,7 @@ static void setup(unsigned swapped, unsigned count) {
CHECK(etcp_bind(ep[side].inst, 253, receive) == 0);
}
if (!count) return; /* Отдельная проверка входящего INIT до создания транспорта. */
ep[0].conn = etcp_connection_create(ep[0].inst, "liveness"); CHECK(ep[0].conn);
ep[0].conn->peer_node_id = ep[1].inst->node_id;
CHECK(sc_init_ctx(&ep[0].conn->crypto_ctx, &ep[0].inst->my_keys) == SC_OK);
CHECK(sc_set_peer_public_key(&ep[0].conn->crypto_ctx, ep[1].inst->my_keys.public_key, SC_PEER_PUBKEY_BIN) == SC_OK);
etcp_conn_add_cbk(ep[0].conn, conn_event, &ep[0], ETCP_CBK_EVENT_UP | ETCP_CBK_EVENT_DOWN | ETCP_CBK_EVENT_REINIT);
for (unsigned i = 0; i < count; i++) {
struct ETCP_SOCKET* socket = ep[0].socket;
if (i) socket = test_etcp_bound_socket(ep[0].inst, "extra");
CHECK(etcp_link_new(ep[0].conn, socket, &ep[1].socket->interface_addr, 0));
}
start_source(count);
uint64_t end = get_time_tb() + 30000;
while (get_time_tb() < end) {
test_clock_poll(ua, 10);
@@ -225,6 +230,69 @@ static void send_init_request_body(const struct ETCP_INIT_REQUEST_PKT *request,
etcp_connections_read_callback_socket(ep[1].socket->fd, ep[1].socket);
}
struct accept_case {
struct NODE_CONN_DIRECT *owner;
struct ETCP_LINK *replacement;
unsigned with_owner, with_link, fired, indexed, closed;
int open_result;
};
static void accept_owner_event(struct NODE_CONN_DIRECT *h, enum ncd_event event, void *arg) {
(void)h;
struct accept_case *test = arg;
if (event == NCD_EVENT_CLOSED) test->closed++;
}
static void cancel_accept(struct ETCP_CONN *conn, struct ETCP_LINK *link, int old_state, int old_status, void *arg) {
(void)old_status;
struct accept_case *test = arg;
if (test->fired || old_state != -1 || !link->is_server) return;
test->fired++;
ep[1].conn = conn; CHECK(etcp_conn_ref_take(conn) == 0);
test->indexed = instance_find_conn(conn->instance, conn->peer_node_id) == conn;
struct ETCP_SOCKET *socket = link->conn;
struct sockaddr_storage remote = link->remote_addr;
etcp_link_close(link);
if (test->with_link) { test->replacement = etcp_link_new(conn, socket, &remote, 0); CHECK(test->replacement); }
if (test->with_owner) {
/* Адресов пока нет: владелец удерживает входящий транспорт и ждёт будущего линка. */
struct TOPO_NODE peer = {.node_id = ep[0].inst->node_id};
memcpy(peer.public_key, ep[0].inst->my_keys.public_key, SC_PUBKEY_SIZE);
test->open_result = node_conn_direct_open_node(conn->instance, peer.node_id, accept_owner_event, test,
&test->owner, &peer, socket);
CHECK(test->open_result != NCD_ERR && test->owner);
}
}
static void cancelled_accept(unsigned swapped, unsigned with_owner, unsigned with_link) {
setup(swapped, 0);
struct accept_case test = {.with_owner = with_owner, .with_link = with_link};
etcp_add_link_status_cbk(ep[1].inst, cancel_accept, &test);
struct ETCP_INIT_REQUEST_PKT request = {.code = ETCP_INIT_REQUEST, .link_id = 1};
for (unsigned i = 0; i < 8; i++) request.node_id[i] = ep[0].inst->node_id >> (56 - 8 * i);
request.mtu[0] = ep[0].socket->mtu >> 8; request.mtu[1] = ep[0].socket->mtu;
send_init_request_body(&request, sizeof(request), 0);
etcp_remove_link_status_cbk(ep[1].inst, cancel_accept, &test);
CHECK(test.fired == 1 && ep[1].conn);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "UDP cancelled accept swapped=%u other_link=%u owner=%u: indexed=%u closing=%u owner_conn=%p conn=%p",
swapped, with_link, with_owner, test.indexed, ep[1].conn->close_requested,
(void*)node_conn_direct_get_conn(test.owner), (void*)ep[1].conn);
CHECK(test.indexed && !late_handshakes);
if (with_owner || with_link) {
CHECK(!ep[1].conn->close_requested && instance_find_conn(ep[1].inst, ep[0].inst->node_id) == ep[1].conn);
CHECK(ep[1].inst->connections->count == 1 && !test.closed);
if (with_owner) CHECK(test.open_result == NCD_REUSED && node_conn_direct_get_conn(test.owner) == ep[1].conn);
if (with_link) CHECK(ep[1].conn->links == test.replacement && !test.replacement->closing);
start_source(1); run_for(10000);
CHECK(ep[0].conn->links_up && ep[1].conn->links_up && !test.closed);
for (unsigned side = 0; side < 2; side++) {
struct ll_entry *entry = ll_alloc_lldgram(2); CHECK(entry);
entry->len = 2; entry->dgram[0] = 253; entry->dgram[1] = 17;
CHECK(etcp_send(ep[side].conn, entry) == 0);
}
run_for(10000); CHECK(ep[0].received == 1 && ep[1].received == 1);
} else CHECK(ep[1].conn->close_requested);
node_conn_direct_force_close(test.owner);
teardown();
}
static void request_boundaries(unsigned established) {
setup(0, established);
unsigned drained = 0; uint8_t pending;
@@ -481,6 +549,8 @@ int main(void) {
debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_DEBUG);
}
size_t baseline = u_get_allocated_count();
for (unsigned swapped = 0; swapped < 2; swapped++) for (unsigned owner = 0; owner < 2; owner++)
for (unsigned link = 0; link < 2; link++) { cancelled_accept(swapped, owner, link); check_memory(baseline); }
for (unsigned swapped = 0; swapped < 2; swapped++) for (unsigned stage = 0; stage < 3; stage++)
for (unsigned action = 0; action < 3; action++) { transition_callbacks(swapped, stage, action); check_memory(baseline); }
for (unsigned established = 0; established < 2; established++) { request_boundaries(established); check_memory(baseline); }