Measure owned endpoint mapping and inbound reachability separately

This commit is contained in:
evgeny committed 2026-10-12 00:01:40 +02:00
1 parent b54307a0df
commit ff69552a61
51 files changed
+2620 -2483

No files matched your search

+151 -4
View File
@@ -21,6 +21,11 @@
#include "../../src/chat/invite_link.h"
#include "../../src/stream/stream_priv.h"
#include "../../src/stream/stream_member.h"
#include "../../src/routing_layer/nat_detection.h"
#include "../../src/routing_layer/route_ping.h"
#include "../../src/routing_layer/topo_node_sqlite.h"
#include "../../src/transport_layer/node_conn_direct.h"
#include "../../src/transport_layer/etcp.h"
#include "../../src/utun_instance.h"
#define LAB_STREAMS 4
@@ -40,6 +45,9 @@ struct lab {
struct lab_stream streams[LAB_STREAMS];
struct chat_invite_build *invite;
uint64_t invite_id, group;
struct NODE_CONN_DIRECT *nat_handles[32];
uint64_t nat_records[32];
unsigned nat_handle_count, nat_record_count;
int errors;
};
static volatile sig_atomic_t stopping;
@@ -168,7 +176,47 @@ static void statistics(struct lab *lab, uint64_t id, int loads) {
stream_member_load(lab->inst, lab->group, node->node_id, &state, NULL));
}
}
printf("},\"streams\":["); separator = 0;
printf("},\"address_profiles\":["); separator = 0;
for (struct ETCP_SOCKET *socket = lab->inst->etcp_sockets; socket; socket = socket->next) {
if (socket->probe_only) continue;
struct nat_check_status status = {0}; nat_detection_get_status(lab->inst->nat_det, socket, &status);
printf("%s{\"socket\":", separator++ ? "," : ""); json_string(socket->name);
printf(",\"socket_id\":%u,\"generation\":\"%" PRIu64 "\",\"protocol\":\"%s\",\"local\":",
socket->sock_id, socket->generation, socket->is_tcp ? "tcp" : "udp");
json_string(sockaddr_storage_to_str(socket->is_tcp ? &socket->interface_addr : &socket->local_addr).str);
printf(",\"mapping\":\"%s\",\"inbound\":\"%s\",\"evidence\":\"%" PRIu64 "\",\"checked_at\":\"%" PRIu64 "\",\"external\":",
address_mapping_name(socket->address_profile.mapping), address_inbound_name(socket->address_profile.inbound),
socket->address_profile.evidence_id, socket->address_profile.checked_at);
const struct sockaddr_storage *external = &socket->nat_addr;
if (!external->ss_family && socket->address_profile.mapping == ADDRESS_MAP_UNCHANGED)
external = socket->is_tcp ? &socket->interface_addr : &socket->local_addr;
json_string(external->ss_family ? sockaddr_storage_to_str(external).str : "");
printf(",\"phase\":%u,\"reason\":\"%s\",\"observer\":\"%" PRIu64 "\",\"helper\":\"%" PRIu64 "\","
"\"rounds\":%u,\"helper_reselections\":%u,\"history_complete\":%u,\"contacts\":[", status.phase,
nat_check_reason_name(status.reason), status.observer, status.helper, status.rounds,
status.helper_reselections, status.history_complete);
uint8_t local[ROUTE_ADDR_WIRE_SIZE]; sqlite3_stmt *contacts = NULL; int contact_separator = 0;
if (!socket->is_tcp && !route_ping_addr_encode(local, &socket->local_addr) &&
sqlite3_prepare_v2(lab->inst->topo_sqlite_db, "SELECT remote FROM nat_contacts WHERE local=?",
-1, &contacts, NULL) == SQLITE_OK) {
sqlite3_bind_blob(contacts, 1, local, sizeof(local), SQLITE_TRANSIENT);
while (sqlite3_step(contacts) == SQLITE_ROW) {
const uint8_t *address = sqlite3_column_blob(contacts, 0); char text[64];
if (!address || sqlite3_column_bytes(contacts, 0) != 17 || (address[0] != 4 && address[0] != 6)) continue;
if (!inet_ntop(address[0] == 4 ? AF_INET : AF_INET6, address + 1, text, sizeof(text))) continue;
if (contact_separator++) printf(",");
json_string(text);
}
}
sqlite3_finalize(contacts); printf("]}");
}
printf("],\"direct_up\":["); separator = 0;
for (struct ll_entry *entry = lab->inst->connections->head; entry; entry = entry->next) {
const struct ETCP_CONN *connection = ((const struct conn_queue_entry *)entry->data)->conn;
if (connection && connection->links_up && !connection->close_requested && connection->state != 2)
printf("%s\"%" PRIu64 "\"", separator++ ? "," : "", connection->peer_node_id);
}
printf("],\"streams\":["); separator = 0;
for (unsigned i = 0; i < LAB_STREAMS; i++) {
struct lab_stream *stream = &lab->streams[i];
if (!stream->source) continue;
@@ -242,6 +290,100 @@ static void invite_ready(void *arg, int result, const char *link) {
json_string(link ? link : ""); printf("}\n");
}
static struct TOPO_NODE *parse_snapshot(struct lab *lab, const char *json) {
char hex[4097]; uint8_t wire[2048]; size_t length;
if (json_flat_get(json, "snapshot", hex, sizeof(hex)) || !(length = strlen(hex)) || length % 2 || length / 2 > sizeof(wire)) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "lab: invalid node snapshot size"); return NULL;
}
for (size_t i = 0; i < length / 2; i++) {
unsigned value;
if (sscanf(hex + i * 2, "%2x", &value) != 1) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "lab: invalid snapshot hex"); return NULL; }
wire[i] = (uint8_t)value;
}
struct TOPO_GROUP group = {.instance = lab->inst};
struct TOPO_NODE *node = NULL;
if (topo_node_deserialize(&group, wire, length / 2, &node, NULL, NULL, NULL, NULL) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "lab: snapshot decode failed"); return NULL;
}
if (node->node_id == lab->inst->node_id || topo_node_verify(node) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "lab: snapshot signature/identity rejected");
topo_node_destroy(lab->inst->topo_groups, node); return NULL;
}
return node;
}
/* Стенд переносит подписанные снимки через RPC только до запуска группы.
* NCD handshake, наблюдения и весь трафик всё равно проходят по настоящей сети. */
static int nat_command(struct lab *lab, const char *json, const char *operation, uint64_t id) {
if (!strcmp(operation, "chat_start")) {
int result = chat_service_start(lab->inst);
if (!result) stream_set_event_callback(lab->inst, event, lab);
reply(id, result); return 1;
}
if (!strcmp(operation, "nat_release")) {
for (unsigned i = 0; i < lab->nat_handle_count; i++) node_conn_direct_close(lab->nat_handles[i]);
for (unsigned i = 0; i < lab->nat_record_count; i++) topo_node_registry_unref(lab->inst->topo_groups, lab->nat_records[i]);
lab->nat_handle_count = lab->nat_record_count = 0; reply(id, 0); return 1;
}
if (!strcmp(operation, "nat_auto")) {
uint64_t enabled;
if (number(json, "enabled", &enabled) || enabled > 1) { reply(id, -1); return 1; }
nat_detection_set_automatic(lab->inst->nat_det, enabled); reply(id, 0); return 1;
}
if (!strcmp(operation, "nat_check")) {
char name[32]; uint64_t observer;
if (!json_flat_get(json, "socket", name, sizeof(name)) && !number(json, "observer", &observer)) {
struct ETCP_SOCKET *socket = lab->inst->etcp_sockets;
while (socket && (socket->probe_only || strcmp(socket->name, name))) socket = socket->next;
reply(id, socket ? nat_detection_request_check_socket(lab->inst->nat_det, socket, observer) : -1);
} else { nat_detection_request_check_all(lab->inst->nat_det); reply(id, 0); }
return 1;
}
if (!strcmp(operation, "snapshot")) {
uint8_t wire[2048]; struct TOPO_GROUP_NODE nq = {0};
struct TOPO_NODE *node = topo_node_registry_find(lab->inst->topo_groups, lab->inst->node_id);
int length = topo_node_serialize(node, &nq, 0, 0, wire, sizeof(wire), 0);
if (length < 0) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "lab: snapshot serialize failed"); reply(id, -1); return 1; }
printf("{\"id\":%" PRIu64 ",\"rc\":0,\"snapshot\":\"", id);
for (int i = 0; i < length; i++) printf("%02x", wire[i]);
printf("\"}\n"); return 1;
}
int store = !strcmp(operation, "nat_store"), link = !strcmp(operation, "nat_link");
if (!store && !link) return 0;
struct TOPO_NODE *node = parse_snapshot(lab, json);
if (!node) { reply(id, -1); return 1; }
int result = 0;
if (link) {
char source[32]; struct ETCP_SOCKET *socket = NULL;
if (lab->nat_handle_count < 32 && !json_flat_get(json, "socket", source, sizeof(source)))
for (socket = lab->inst->etcp_sockets; socket; socket = socket->next)
if (!socket->probe_only && !strcmp(socket->name, source)) break;
if (!socket) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "lab: native link source/handle unavailable"); result = -1; }
else {
struct NODE_CONN_DIRECT *handle = NULL;
int opened = node_conn_direct_open_node(lab->inst, node->node_id, NULL, NULL, &handle, node, socket);
if (opened == NCD_ERR || !handle) result = -1;
else lab->nat_handles[lab->nat_handle_count++] = handle;
}
topo_node_destroy(lab->inst->topo_groups, node);
} else {
if (lab->nat_record_count == 32) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "lab: imported snapshot limit reached"); result = -1;
topo_node_destroy(lab->inst->topo_groups, node);
} else {
struct TOPO_NODE *stored = topo_node_registry_store(lab->inst->topo_groups, node);
if (!stored) { topo_node_destroy(lab->inst->topo_groups, node); result = -1; }
else {
unsigned i;
for (i = 0; i < lab->nat_record_count; i++) if (lab->nat_records[i] == stored->node_id) break;
if (i == lab->nat_record_count) lab->nat_records[lab->nat_record_count++] = stored->node_id;
else topo_node_registry_unref(lab->inst->topo_groups, stored->node_id);
}
}
}
reply(id, result); return 1;
}
static void command(struct lab *lab, const char *json) {
uint64_t id = 0, source = 0, value = 0;
char operation[32];
@@ -251,6 +393,7 @@ static void command(struct lab *lab, const char *json) {
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "lab: RPC id=%" PRIu64 " op=%s", id, operation);
if (!strcmp(operation, "stats")) { number(json, "loads", &value); statistics(lab, id, value != 0); return; }
if (!strcmp(operation, "quit")) { stopping = 1; reply(id, 0); return; }
if (nat_command(lab, json, operation, id)) return;
if (!strcmp(operation, "create")) {
if (lab->group) { reply(id, -1); return; }
chat_core_create_channel_auto(lab->inst, "stream-netns");
@@ -354,7 +497,8 @@ int main(int argc, char **argv) {
for (unsigned i = 0; i < 32; i++) printf("%02x", public_key[i]);
printf("\",\"node\":\"%" PRIu64 "\"}\n", sc_derive_node_id_from_pubkey(public_key)); return 0;
}
if (argc != 3) { fprintf(stderr, "Usage: %s CONFIG LOG | --identity SEED\n", argv[0]); return 1; }
int core_only = argc == 4 && !strcmp(argv[3], "--core-only");
if (argc != 3 && !core_only) { fprintf(stderr, "Usage: %s CONFIG LOG [--core-only] | --identity SEED\n", argv[0]); return 1; }
setvbuf(stdout, NULL, _IOLBF, 0);
signal(SIGTERM, stop_signal); signal(SIGINT, stop_signal); signal(SIGPIPE, SIG_IGN);
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); debug_enable_console(0); debug_enable_file_output(argv[2], 1);
@@ -363,11 +507,12 @@ int main(int argc, char **argv) {
struct lab *lab = u_calloc(1, sizeof(*lab));
if (!async || !lab) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "lab: initialization allocation failed"); return 1; }
lab->inst = utun_instance_create(async, argv[1]);
if (!lab->inst || utun_core_start(lab->inst) || chat_service_start(lab->inst)) {
if (!lab->inst || utun_core_start(lab->inst) || (!core_only && chat_service_start(lab->inst))) {
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "lab: service startup failed"); return 1;
}
if (core_only) nat_detection_set_automatic(lab->inst->nat_det, 0);
debug_enable_console(0);
stream_set_event_callback(lab->inst, event, lab);
if (!core_only) stream_set_event_callback(lab->inst, event, lab);
if (fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "lab: stdin nonblocking failed: %s", strerror(errno)); return 1;
}
@@ -397,6 +542,8 @@ int main(int argc, char **argv) {
if (lab->streams[i].errors) lab->errors++;
}
int errors = lab->errors;
for (unsigned i = 0; i < lab->nat_handle_count; i++) node_conn_direct_force_close(lab->nat_handles[i]);
for (unsigned i = 0; i < lab->nat_record_count; i++) topo_node_registry_unref(lab->inst->topo_groups, lab->nat_records[i]);
utun_instance_destroy(lab->inst); uasync_destroy(async, 1); u_free(lab);
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "lab: stopped errors=%d", errors);
debug_disable_file_output(); return errors ? 1 : 0;