diff --git a/tests/test_nat_detection.c b/tests/test_nat_detection.c index ad214325..1d148a29 100644 --- a/tests/test_nat_detection.c +++ b/tests/test_nat_detection.c @@ -48,6 +48,75 @@ static void check_snapshot(struct UTUN_INSTANCE *owner, struct ETCP_SOCKET *sock sqlite3_finalize(statement); } +/* Один 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, + "[server:tcp]\naddr=127.0.0.2:0\ntype=public\ntransport=tcp\n"); + struct UTUN_INSTANCE *peer = nat_fixture_node(&fixture, "127.0.0.3", NULL, + "[server:tcp]\naddr=127.0.0.3:0\ntype=public\ntransport=tcp\n"); + nat_detection_set_automatic(owner->nat_det, 0); nat_detection_set_automatic(peer->nat_det, 0); + struct ETCP_SOCKET *socket = test_etcp_bound_socket(owner, "tcp"); + struct ETCP_CONN *connection = nat_fixture_connect(&fixture, owner, "tcp", peer); + wait_profile(&fixture, socket, ADDRESS_MAP_UNCHANGED, ADDRESS_IN_UNKNOWN); + struct ETCP_LINK *link = connection->links; + while (link && !link->is_tcp) link = link->next; + assert(link && link->conn == socket && link->local_as_seen_by_peer.ss_family == AF_INET); + assert(!((struct sockaddr_in *)&link->local_as_seen_by_peer)->sin_port); + assert(route_ping_addr_equal(&socket->nat_addr, &socket->local_addr, 0)); + assert(!((struct sockaddr_in *)&socket->nat_addr)->sin_port); + struct nat_check_status status; + assert(!nat_detection_get_status(owner->nat_det, socket, &status) && !status.rounds && !status.helper); + + /* В обычном режиме loopback — непубличный IP: NAT известен без второго пира и без пробы listener. */ + nat_detection_set_allow_local(owner->nat_det, 0); + nat_detection_socket_removed(owner->nat_det, socket); + wait_profile(&fixture, socket, ADDRESS_MAP_UNKNOWN, ADDRESS_IN_UNKNOWN); + assert(!socket->nat_addr.ss_family); + struct sockaddr_storage remote = link->remote_addr, local = socket->interface_addr, observed = {0}; + ((struct sockaddr_in *)&link->remote_addr)->sin_addr.s_addr = htonl(UINT32_C(0xc633640b)); + struct sockaddr_in *external = (struct sockaddr_in *)&observed; + external->sin_family = AF_INET; external->sin_addr.s_addr = htonl(UINT32_C(0xcb00712c)); external->sin_port = htons(5555); + nat_detection_tcp_observed(owner->nat_det, link, &local, &observed); + assert(socket->address_profile.mapping == ADDRESS_MAP_UNKNOWN && socket->address_profile.source == ADDRESS_SOURCE_ETCP); + assert(route_ping_addr_equal(&socket->nat_addr, &observed, 0) && !((struct sockaddr_in *)&socket->nat_addr)->sin_port); + check_snapshot(owner, socket); + struct TOPO_NODE *snapshot = topo_node_registry_find(owner->topo_groups, owner->node_id); + int found = 0; + for (const struct TOPO_ADDR4 *a = snapshot->v4_addrs; a; a = a->next) { + if (a->socket_id != socket->sock_id || a->type != TOPO_ADDR_NAT) continue; + assert(!a->port && (a->protocol & TOPO_PROTO_TCP) && !memcmp(a->addr, &external->sin_addr, 4)); found++; + } + assert(found == 1); + sqlite3_stmt *statement = NULL; + assert(sqlite3_prepare_v2(owner->topo_sqlite_db, + "SELECT port,addr_type FROM node_addresses WHERE node_id=? AND socket_id=? AND address_kind=?", + -1, &statement, NULL) == SQLITE_OK); + sqlite3_bind_int64(statement, 1, (sqlite3_int64)owner->node_id); sqlite3_bind_int(statement, 2, socket->sock_id); + sqlite3_bind_int(statement, 3, TOPO_ADDR_NAT); + assert(sqlite3_step(statement) == SQLITE_ROW && !sqlite3_column_int(statement, 0)); + assert(sqlite3_column_int(statement, 1) == ADDR_TYPE_NAT); sqlite3_finalize(statement); + + /* Совпавший публичный IP означает отсутствие NAT даже при разных портах. */ + local = observed; ((struct sockaddr_in *)&local)->sin_port = htons(12345); + nat_detection_tcp_observed(owner->nat_det, link, &local, &observed); + assert(socket->address_profile.mapping == ADDRESS_MAP_UNCHANGED && socket->address_profile.inbound == ADDRESS_IN_UNKNOWN); + external->sin_addr.s_addr = htonl(UINT32_C(0xcb00712d)); + nat_detection_tcp_observed(owner->nat_det, link, &local, &observed); + assert(socket->address_profile.mapping == ADDRESS_MAP_UNKNOWN); + struct sockaddr_storage saved = socket->nat_addr; + external->sin_addr.s_addr = htonl(UINT32_C(0xc0a80101)); + nat_detection_tcp_observed(owner->nat_det, link, &local, &observed); + assert(route_ping_addr_equal(&socket->nat_addr, &saved, 1)); + external->sin_addr.s_addr = htonl(UINT32_C(0xcb00712c)); + link->remote_addr = remote; /* LAN-узел также не годится для наблюдения интернет-адреса. */ + nat_detection_tcp_observed(owner->nat_det, link, &local, &observed); + assert(route_ping_addr_equal(&socket->nat_addr, &saved, 1)); + assert(!nat_detection_get_status(owner->nat_det, socket, &status) && !status.rounds && !status.helper); + nat_fixture_cleanup(&fixture); + DEBUG_INFO(DEBUG_CATEGORY_NAT, "PASS TCP NAT presence: one peer, public/private IP, ignored ports, signed IP:0 and SQLite"); +} + /* Недостаточная UTUN-группа и достаточная CHAT-группа; helper доступен только через TCP next hop. */ static void test_all_groups_tcp(void) { struct nat_fixture fixture; nat_fixture_init(&fixture); @@ -177,13 +246,11 @@ int main(void) { assert(connection->links && connection->links->remote_socket_id == observer_socket->sock_id); assert(connection->links->local_as_seen_by_peer.ss_family == AF_INET); assert(route_ping_addr_equal(&connection->links->local_as_seen_by_peer, &socket->local_addr, 1)); - /* Подписанная запись A нужна независимому испытанию настоящего TCP listener. */ nat_fixture_connect(&fixture, observer, "udp", owner); owner->ntp.reachable = 0; /* ни измерение, ни публикация не требуют интернет-NTP */ nat_detection_request_check_all(owner->nat_det); wait_profile(&fixture, socket, ADDRESS_MAP_UNCHANGED, ADDRESS_IN_OPEN); wait_profile(&fixture, second_socket, ADDRESS_MAP_UNCHANGED, ADDRESS_IN_OPEN); - wait_profile(&fixture, tcp, ADDRESS_MAP_UNCHANGED, ADDRESS_IN_OPEN); struct nat_check_status status; assert(nat_detection_get_status(owner->nat_det, socket, &status) == 0); @@ -194,7 +261,7 @@ int main(void) { assert(status.round != second_status.round); check_snapshot(owner, socket); check_snapshot(owner, second_socket); - check_snapshot(owner, tcp); + assert(tcp->address_profile.mapping == ADDRESS_MAP_UNKNOWN && tcp->address_profile.inbound == ADDRESS_IN_UNKNOWN); /* Профиль B не заимствует профиль A, даже когда транспорт переиспользуется в обе стороны. */ assert(!route_ping_addr_equal(&socket->local_addr, &observer_socket->local_addr, 1)); @@ -232,5 +299,6 @@ int main(void) { nat_fixture_cleanup(&fixture); test_all_groups_tcp(); + test_tcp_presence(); return 0; }