diff --git a/doc/nat_detection.txt b/doc/nat_detection.txt index 8e2316d9..fdaf4abf 100644 --- a/doc/nat_detection.txt +++ b/doc/nat_detection.txt @@ -5,6 +5,13 @@ при обращении к разным IP и проходят ли к нему пакеты от незнакомого IP. Проверяется именно сокет, через который работает приложение. +Есть быстрый путь без помощников: если рабочий сокет принял подключение +и локальный IP и фактический IP подключившегося узла публичные, +ему сразу ставится direct/open (UNCHANGED + OPEN). Это правило действует +для UDP и TCP, даже если с IP пира раньше уже был исходящий обмен. +Локальные подключения и SOCKS интернет-вход не подтверждают. Для TCP +фактический локальный IP должен совпадать с IP интерфейса сокета. + Нужны три узла: A проверяет себя, B наблюдает его внешний адрес, C помогает проверить входящие пакеты. Команды и результаты передаются по уже работающей сети uTun, в том числе через TCP или промежуточные узлы. @@ -96,5 +103,7 @@ TCP-пира: по готовому защищённому соединению IP совпали — трансляция IP не обнаружена; отличаются — NAT есть, тип UNKNOWN. Порты при сравнении не учитываются. Публичный внешний IP сохраняется с портом 0: входящий порт неизвестен, поэтому NCD не использует эту запись для подключения. -Тип NAT и доступность TCP-входа не проверяются. Через SOCKS внешний IP прокси +Активных проб типа NAT и TCP-входа нет; OPEN устанавливается по принятому +публичному подключению и сохраняется при последующих совпавших наблюдениях IP. +Через SOCKS внешний IP прокси не записывается как собственный адрес. diff --git a/tests/test_nat_detection.c b/tests/test_nat_detection.c index fb3e200a..f4c6431f 100644 --- a/tests/test_nat_detection.c +++ b/tests/test_nat_detection.c @@ -48,7 +48,41 @@ static void check_snapshot(struct UTUN_INSTANCE *owner, struct ETCP_SOCKET *sock sqlite3_finalize(statement); } -/* Один TCP-пир даёт только наличие NAT и IP:0; локальный IP сам по себе достаточен для NAT. */ +/* Настоящий входящий UDP/STCP публикует direct/open только у получателя; LAN не подтверждает интернет-вход. */ +static void test_incoming_connections(void) { + struct nat_fixture fixture; nat_fixture_init(&fixture); + const char *tcp_config = "[server:tcp]\naddr=127.0.0.2:0\ntype=public\ntransport=tcp\n"; + struct UTUN_INSTANCE *client = nat_fixture_node(&fixture, "127.0.0.2", NULL, tcp_config); + struct UTUN_INSTANCE *server = nat_fixture_node(&fixture, "127.0.0.3", NULL, + "[server:tcp]\naddr=127.0.0.3:0\ntype=public\ntransport=tcp\n"); + struct UTUN_INSTANCE *lan = nat_fixture_node(&fixture, "127.0.0.4", NULL, + "[server:tcp]\naddr=127.0.0.4:0\ntype=public\ntransport=tcp\n"); + nat_detection_set_automatic(client->nat_det, 0); nat_detection_set_automatic(server->nat_det, 0); + nat_detection_set_automatic(lan->nat_det, 0); nat_detection_set_allow_local(lan->nat_det, 0); + struct ETCP_SOCKET *udp = test_etcp_bound_socket(server, "udp"), *tcp = test_etcp_bound_socket(server, "tcp"); + nat_fixture_connect(&fixture, client, "udp", server); + assert(udp->address_profile.mapping == ADDRESS_MAP_UNCHANGED && udp->address_profile.inbound == ADDRESS_IN_OPEN); + assert(test_etcp_bound_socket(client, "udp")->address_profile.inbound == ADDRESS_IN_UNKNOWN); + check_snapshot(server, udp); + nat_fixture_connect(&fixture, client, "tcp", server); + assert(tcp->address_profile.mapping == ADDRESS_MAP_UNCHANGED && tcp->address_profile.inbound == ADDRESS_IN_OPEN); + assert(route_ping_addr_equal(&tcp->nat_addr, &tcp->interface_addr, 0)); + assert(!((struct sockaddr_in *)&tcp->nat_addr)->sin_port); + assert(test_etcp_bound_socket(client, "tcp")->address_profile.inbound == ADDRESS_IN_UNKNOWN); + check_snapshot(server, tcp); + /* Отчёт пира уже принят по настоящему TCP: OPEN у listener сохранён, исходящий порт остаётся нулём. */ + struct nat_check_status status; + assert(!nat_detection_get_status(server->nat_det, tcp, &status) && !status.rounds && !status.helper && !status.reason); + nat_fixture_connect(&fixture, client, "udp", lan); + nat_fixture_connect(&fixture, client, "tcp", lan); + assert(test_etcp_bound_socket(lan, "udp")->address_profile.inbound == ADDRESS_IN_UNKNOWN); + assert(test_etcp_bound_socket(lan, "tcp")->address_profile.inbound == ADDRESS_IN_UNKNOWN); + assert(tcp->address_profile.inbound == ADDRESS_IN_OPEN && udp->address_profile.inbound == ADDRESS_IN_OPEN); + nat_fixture_cleanup(&fixture); + DEBUG_INFO(DEBUG_CATEGORY_NAT, "PASS incoming UDP/TCP: receiving socket DIRECT/OPEN, outbound unchanged, IP:0, signed profile, LAN ignored"); +} + +/* Исходящий TCP даёт только наличие NAT и IP:0; локальный IP сам по себе достаточен для NAT. */ static void test_tcp_presence(void) { struct nat_fixture fixture; nat_fixture_init(&fixture); struct UTUN_INSTANCE *owner = nat_fixture_node(&fixture, "127.0.0.2", NULL, @@ -249,6 +283,10 @@ int main(void) { nat_fixture_connect(&fixture, observer, "udp", owner); owner->ntp.reachable = 0; /* ни измерение, ни публикация не требуют интернет-NTP */ nat_detection_request_check_all(owner->nat_det); + struct nat_check_status initial_status; + uint64_t initial_end = get_time_tb() + 180000; + do { uasync_poll(fixture.ua, 10); assert(!nat_detection_get_status(owner->nat_det, socket, &initial_status)); } + while (get_time_tb() < initial_end && (!initial_status.rounds || initial_status.phase || initial_status.cycling)); wait_profile(&fixture, socket, ADDRESS_MAP_UNCHANGED, ADDRESS_IN_OPEN); wait_profile(&fixture, second_socket, ADDRESS_MAP_UNCHANGED, ADDRESS_IN_OPEN); @@ -302,5 +340,6 @@ int main(void) { nat_fixture_cleanup(&fixture); test_all_groups_tcp(); test_tcp_presence(); + test_incoming_connections(); return 0; }