Cover socket reuse and TCP LAN recovery and document lifecycle
This commit is contained in:
1 parent
8dd28fb90a
commit
199839b273
3 files changed
+74
-6
No files matched your search
@@ -98,6 +98,34 @@ NAT-профиль и обязательные метаданные REALITY. IPv
|
||||
`node_conn_direct_open_node` сверяет переданную запись с реестром/БД. Более
|
||||
свежая подписанная запись принимается целиком, старая уступает уже известной.
|
||||
Bootstrap-адреса не перекрывают подписанную запись даже при DOWN.
|
||||
LAN discovery предоставляет отдельные адреса подключения и не меняет подписанный снимок.
|
||||
Открытое объявление рабочих сокетов и ответ на проверку имеют Ed25519-подпись;
|
||||
публичные ключи в этих пакетах отсутствуют. Получатель использует известный ключ
|
||||
из registry/SQLite, а неизвестное устройство остаётся только в списке discovery.
|
||||
|
||||
Подписываются весь заголовок local_transport (magic/version/type, длина, IDs/эпохи,
|
||||
request=0) и тело: nonce(16), family(4/6), IP(16), количество сокетов,
|
||||
затем socket_id/protocol/port(BE)/generation(BE) каждого сокета. IPv4 дополнен
|
||||
нулевыми байтами. Подписанный IP обязан совпасть с источником UDP; рабочий порт
|
||||
отдельный от discovery-порта. Публикуются принимающие IP/interface обычные сокеты,
|
||||
без PRIVATE, probe и SOCKS UDP; до 32 endpoint на один IP, без фрагментации.
|
||||
|
||||
Объявление само не разрешает NCD использовать адрес. Только для узла без прямого UP
|
||||
и живого BGP-пути во всех группах получатель отправляет случайный nonce;
|
||||
подписанный ответ связывает его с обоими node_id и эпохами discovery. Ответ принимается
|
||||
однократно в течение 3 секунд. Это проверка свежести без часов/NTP: старые подписи,
|
||||
эпохи или случайные поколения сокетов сами по себе свежесть не доказывают.
|
||||
После проверки адрес действует 30 секунд по монотонным часам и только на интерфейсе
|
||||
приёма (включая IPv6 scope). Повтор объявления/ответа не продлевает срок без нового nonce.
|
||||
|
||||
NCD использует эти кандидаты при проверке совместимости, открытии/обновлении
|
||||
и появлении локального сокета. Если сохранены только ключи, идентичность загружается
|
||||
из nodes без требования старого адреса. LAN не создаёт NCD handle автоматически.
|
||||
Неподтверждённые ETCP-пробы очищаются после истечения кандидата/остановки discovery;
|
||||
после успешного handshake линк живёт по обычным правилам транспорта. REALITY требует
|
||||
метаданных из известного снимка; прямые LAN TCP-пробы при глобальном SOCKS выключены.
|
||||
Диагностика: discovery для подписи/nonce/адресов, etcp для NCD-проб и их удаления.
|
||||
|
||||
ID узла проверяется по public_key. Ошибка запуска возвращает NULL handle и не снимает
|
||||
прежний FIN_WAIT. Отдельно принятый snapshot и уже добавленные линки при частичной
|
||||
ошибке могут сохраниться; новый handle публикуется после подготовки ресурсов.
|
||||
|
||||
@@ -49,7 +49,7 @@ struct NODE_CONN_DIRECT;
|
||||
|
||||
/* Рабочий ETCP, совместимая исходящая пара либо возможность reverse на наш опубликованный адрес.
|
||||
* Без линков/handles/таймеров/проб. Исходящие пары — тот же обход, что open; reverse — sock_match/публикуемые адреса.
|
||||
* Учитывает IPv6 и исходящий TCP fallback; 1 — кандидат есть, 0 — нет/ошибка. SQLite не загружает.
|
||||
* Учитывает IPv6 и исходящий TCP fallback; 1 — кандидат есть, 0 — нет/ошибка. Обычный snapshot ищется только в registry.
|
||||
* Для подтверждённого LAN-кандидата может прочитать идентичность из SQLite без регистрации.
|
||||
* LAN TCP соблюдает глобальную SOCKS-политику: при включённом SOCKS прямые LAN TCP-пробы не создаются.
|
||||
* Возможность попытки не доказывает достижимость и не подтверждает готовность группы. */
|
||||
|
||||
@@ -78,15 +78,42 @@ ssize_t __wrap_socket_sendto(socket_t fd, const void* data, size_t size, const s
|
||||
/* Объявление идёт с отдельного discovery-порта, пригодный ETCP-порт берётся из подписи. */
|
||||
static int route(struct local_route* out) { return local_transport_route(a->local_transport, b->node_id, 0, out); }
|
||||
|
||||
static struct UTUN_INSTANCE* create_core(struct UASYNC* ua) {
|
||||
struct UTUN_INSTANCE* inst = utun_instance_create_from_str(ua,
|
||||
static struct UTUN_INSTANCE* create_core(struct UASYNC* ua, int tcp) {
|
||||
char config[512];
|
||||
snprintf(config, sizeof(config),
|
||||
"[global]\nclient_type=1\nauto_sockets=no\nkeepalive_interval=100\n"
|
||||
"[server: lan]\naddr=127.0.0.1:0\ntype=local\n[allowed_keys]\nallow_all=1\n");
|
||||
"[server: lan]\naddr=127.0.0.1:0\ntype=local\ntransport=%s\n[allowed_keys]\nallow_all=1\n", tcp ? "tcp" : "udp");
|
||||
struct UTUN_INSTANCE* inst = utun_instance_create_from_str(ua, config);
|
||||
REQUIRE(inst && !utun_core_start(inst));
|
||||
REQUIRE(inst->etcp_sockets && inst->etcp_sockets->generation);
|
||||
return inst;
|
||||
}
|
||||
|
||||
/* Один и тот же подписанный формат адресов обслуживает STCP без публичных ключей на wire. */
|
||||
static void tcp_routes(struct UASYNC* ua) {
|
||||
a = create_core(ua, 1); b = create_core(ua, 1);
|
||||
REQUIRE(!topo_node_sqlite_node_update_verified(a->topo_sqlite_db, b->node_id, "B", b->my_keys.public_key,
|
||||
b->my_ed25519_pubkey, 1, 1));
|
||||
REQUIRE(!topo_node_sqlite_node_update_verified(b->topo_sqlite_db, a->node_id, "A", a->my_keys.public_key,
|
||||
a->my_ed25519_pubkey, 1, 1));
|
||||
REQUIRE(!chat_local_start(a) && !chat_local_start(b)); test_clock_run_for(ua, 10000);
|
||||
struct local_route found; REQUIRE(route(&found) && found.socket.protocol == LOCAL_ROUTE_TCP);
|
||||
a->config->global.socks_enabled = 1;
|
||||
REQUIRE(!node_conn_direct_compatible(a, b->node_id));
|
||||
a->config->global.socks_enabled = 0;
|
||||
REQUIRE(node_conn_direct_compatible(a, b->node_id));
|
||||
struct NODE_CONN_DIRECT* handle = NULL;
|
||||
REQUIRE(node_conn_direct_open(a, b->node_id, NULL, NULL, &handle, NULL) == NCD_NEW);
|
||||
test_clock_run_for(ua, 30000);
|
||||
struct ETCP_CONN* conn = node_conn_direct_get_conn(handle);
|
||||
REQUIRE(conn && conn->links_up && conn->links->is_tcp);
|
||||
chat_local_stop(a); test_clock_run_for(ua, 10000);
|
||||
REQUIRE(conn->links_up);
|
||||
node_conn_direct_force_close(handle);
|
||||
chat_local_stop(b); utun_instance_destroy(a); a = NULL; utun_instance_destroy(b); b = NULL;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "PASS: LAN TCP/STCP handshake, SOCKS policy and discovery stop preserve live transport");
|
||||
}
|
||||
|
||||
/* Сбрасываем только discovery у получателя: ключи и ядро переживают его перезапуск. */
|
||||
static void restart_discovery(struct UASYNC* ua) {
|
||||
chat_local_stop(a); REQUIRE(!chat_local_start(a)); test_clock_run_for(ua, 10000);
|
||||
@@ -127,11 +154,11 @@ static void bgp_gate(struct UASYNC* ua) {
|
||||
int main(void) {
|
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN);
|
||||
debug_set_category_level(DEBUG_CATEGORY_DISCOVERY, DEBUG_LEVEL_DEBUG);
|
||||
debug_set_category_level(DEBUG_CATEGORY_ETCP, DEBUG_LEVEL_DEBUG);
|
||||
debug_set_category_level(DEBUG_CATEGORY_ETCP, DEBUG_LEVEL_INFO);
|
||||
debug_set_category_level(DEBUG_CATEGORY_GENERAL, DEBUG_LEVEL_INFO);
|
||||
test_clock_init(10000); interface_index = if_nametoindex("lo"); REQUIRE(interface_index);
|
||||
struct UASYNC* ua = uasync_create(); REQUIRE(ua);
|
||||
a = create_core(ua); b = create_core(ua);
|
||||
a = create_core(ua, 0); b = create_core(ua, 0);
|
||||
REQUIRE(!chat_local_start(a) && !chat_local_start(b));
|
||||
test_clock_run_for(ua, 10000);
|
||||
struct local_route found;
|
||||
@@ -158,6 +185,18 @@ int main(void) {
|
||||
REQUIRE(node_conn_direct_compatible(a, b->node_id) && !instance_find_conn(a, b->node_id));
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "PASS: unknown key, bad signature/IP/nonce/destination, late proof and keyless wire format");
|
||||
|
||||
/* Реально пересоздаём рабочий сокет: тот же ID, новое случайное поколение и новый порт. */
|
||||
uint8_t old_id = b->etcp_sockets->sock_id;
|
||||
uint64_t old_generation = b->etcp_sockets->generation;
|
||||
struct CFG_SERVER server = *b->config->servers;
|
||||
etcp_socket_remove(b->etcp_sockets); b->next_socket_id = old_id;
|
||||
REQUIRE(etcp_socket_add(b, &server) && b->etcp_sockets->generation != old_generation);
|
||||
REQUIRE(!local_transport_set_name(b->local_transport, "Socket replaced"));
|
||||
test_clock_run_for(ua, 10000);
|
||||
REQUIRE(route(&found) && found.socket.socket_id == old_id && found.socket.generation == b->etcp_sockets->generation);
|
||||
REQUIRE(found.socket.port == ss_get_port(&b->etcp_sockets->local_addr));
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "PASS: socket ID reuse replaces generation and endpoint after a fresh nonce");
|
||||
|
||||
bgp_gate(ua);
|
||||
block_transport = 1;
|
||||
struct NODE_CONN_DIRECT* handle = NULL;
|
||||
@@ -192,6 +231,7 @@ int main(void) {
|
||||
node_conn_direct_force_close(handle);
|
||||
chat_local_stop(a); chat_local_stop(b);
|
||||
utun_instance_destroy(a); a = NULL; utun_instance_destroy(b); b = NULL;
|
||||
tcp_routes(ua);
|
||||
uasync_destroy(ua, 0); test_clock_finish();
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "PASS: signed LAN route integration challenges=%d proofs=%d", challenges, proofs);
|
||||
return 0;
|
||||
|
||||
Reference in new issue
Block a user