tests: cover TCP NAT presence and zero-port address publication

This commit is contained in:
evgeny committed 2026-10-17 19:50:33 +02:00
1 parent c42d75c2f5
commit 62aee9f01c
1 file changed
+71 -3
+71 -3
View File
@@ -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;
}