|
|
|
|
@ -4,6 +4,7 @@
|
|
|
|
|
#include "../src/transport_layer/crc32.h" |
|
|
|
|
#include "../lib/debug_config.h" |
|
|
|
|
#include "test_clock.h" |
|
|
|
|
#include "nat_fixture.h" |
|
|
|
|
#include <assert.h> |
|
|
|
|
#include <stdio.h> |
|
|
|
|
#include <stdlib.h> |
|
|
|
|
@ -26,7 +27,7 @@ struct fixture {
|
|
|
|
|
struct endpoint endpoints[6]; |
|
|
|
|
enum stun_nat_mapping mapping; |
|
|
|
|
enum stun_nat_filtering filtering; |
|
|
|
|
int rfc, ignored_change, error_change, drop_alternates, drop_primary, drop_first, tamper, unstable, direct; |
|
|
|
|
int rfc, ignored_change, error_change, drop_alternates, drop_primary, drop_first, tamper, unstable, direct, multiple_clients; |
|
|
|
|
uint8_t first_id[12], late[80]; |
|
|
|
|
size_t late_len; |
|
|
|
|
struct sockaddr_in test_client; |
|
|
|
|
@ -74,10 +75,12 @@ static void server_read(socket_t fd, void* arg) {
|
|
|
|
|
assert(get32(request + len - 4) == (crc32_calc(request, (size_t)len - 8) ^ 0x5354554eu)); |
|
|
|
|
unsigned flags = len == 36 ? get32(request + 24) : 0; |
|
|
|
|
e->requests++; |
|
|
|
|
assert(client.sin_addr.s_addr == ((struct sockaddr_in*)&f->source.local_addr)->sin_addr.s_addr); |
|
|
|
|
assert(client.sin_port != ((struct sockaddr_in*)&f->source.local_addr)->sin_port); |
|
|
|
|
if (!f->test_client.sin_family) f->test_client = client; |
|
|
|
|
assert(client.sin_port == f->test_client.sin_port); |
|
|
|
|
if (!f->multiple_clients) { |
|
|
|
|
assert(client.sin_addr.s_addr == ((struct sockaddr_in*)&f->source.local_addr)->sin_addr.s_addr); |
|
|
|
|
assert(client.sin_port != ((struct sockaddr_in*)&f->source.local_addr)->sin_port); |
|
|
|
|
if (!f->test_client.sin_family) f->test_client = client; |
|
|
|
|
assert(client.sin_port == f->test_client.sin_port); |
|
|
|
|
} |
|
|
|
|
if (!e->index && !flags) f->primary_bindings++; |
|
|
|
|
if (f->drop_primary && !e->index) return; |
|
|
|
|
if (f->drop_alternates && e->index >= 1 && e->index <= 3) return; |
|
|
|
|
@ -144,7 +147,7 @@ static void server_read(socket_t fd, void* arg) {
|
|
|
|
|
|
|
|
|
|
/* Создаёт четыре RFC5780 endpoint и два независимых обычных сервера в том же event loop. */ |
|
|
|
|
static void fixture_init(struct fixture* f) { |
|
|
|
|
f->ua = uasync_create(); |
|
|
|
|
if (!f->ua) f->ua = uasync_create(); |
|
|
|
|
assert(f->ua); |
|
|
|
|
f->inst.ua = f->ua; |
|
|
|
|
f->source.fd = socket_create_udp(AF_INET); |
|
|
|
|
@ -159,6 +162,7 @@ static void fixture_init(struct fixture* f) {
|
|
|
|
|
f->source.instance = &f->inst; |
|
|
|
|
f->source.address_profile.mapping = ADDRESS_MAP_DEPENDENT; |
|
|
|
|
f->source.address_profile.inbound = ADDRESS_IN_CONTACT; |
|
|
|
|
f->source.address_profile.source = ADDRESS_SOURCE_ETCP; |
|
|
|
|
f->source.address_profile.evidence_id = 1; |
|
|
|
|
f->source.address_profile.checked_at = 1; |
|
|
|
|
f->source.nat_addr = f->source.local_addr; |
|
|
|
|
@ -277,6 +281,114 @@ static void run_case(const char* name, struct fixture* f, enum stun_nat_mapping
|
|
|
|
|
fixture_destroy(f); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Настоящий дополнительный рабочий порт; ссылка source остаётся собственностью теста. */ |
|
|
|
|
static struct ETCP_SOCKET extra_socket(struct fixture* f, const char* name, const char* ip) { |
|
|
|
|
struct ETCP_SOCKET socket = f->source; |
|
|
|
|
socket.next = NULL; socket.generation++; |
|
|
|
|
snprintf(socket.name, sizeof(socket.name), "%s", name); |
|
|
|
|
socket.fd = socket_create_udp(AF_INET); assert(socket.fd != SOCKET_INVALID); |
|
|
|
|
struct sockaddr_in local = {0}; local.sin_family = AF_INET; |
|
|
|
|
assert(inet_pton(AF_INET, ip, &local.sin_addr) == 1); |
|
|
|
|
assert(!bind(socket.fd, (struct sockaddr*)&local, sizeof(local))); |
|
|
|
|
socklen_t length = sizeof(socket.local_addr); |
|
|
|
|
assert(!getsockname(socket.fd, (struct sockaddr*)&socket.local_addr, &length)); |
|
|
|
|
socket.interface_addr = socket.local_addr; |
|
|
|
|
return socket; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Дедупликация по IP/netif, смена представителя, независимые пути и неоднозначная политика. */ |
|
|
|
|
static void test_groups(void) { |
|
|
|
|
struct fixture f = {.mapping = STUN_MAP_EIM}; fixture_init(&f); |
|
|
|
|
struct ETCP_SOCKET duplicate = extra_socket(&f, "duplicate", "127.0.0.1"); |
|
|
|
|
f.source.next = &duplicate; f.original = f.source; |
|
|
|
|
fixture_start(&f, 0, 0, 0); |
|
|
|
|
struct stun_nat_result first = wait_result(&f), second; |
|
|
|
|
assert(!stun_nat_get_socket_result(&f.inst, &duplicate, &second)); |
|
|
|
|
assert(first.members == 2 && first.evidence_id == second.evidence_id && first.generation == second.generation); |
|
|
|
|
assert(first.measured_at_us == second.measured_at_us && first.local.ss_family == AF_INET); |
|
|
|
|
unsigned requests = f.endpoints[0].requests; |
|
|
|
|
f.inst.etcp_sockets = &duplicate; test_clock_run_for(f.ua, 30001); |
|
|
|
|
assert(!stun_nat_get_socket_result(&f.inst, &duplicate, &second)); |
|
|
|
|
assert(second.members == 1 && second.evidence_id == first.evidence_id && f.endpoints[0].requests == requests); |
|
|
|
|
f.inst.etcp_sockets = &f.source; f.multiple_clients = 1; |
|
|
|
|
duplicate.netif_index = 1; test_clock_run_for(f.ua, 30001); |
|
|
|
|
uint64_t end = get_time_tb() + 3000000; |
|
|
|
|
do { assert(!stun_nat_get_socket_result(&f.inst, &duplicate, &second)); |
|
|
|
|
if (second.status != STUN_NAT_PENDING) break; test_clock_poll(f.ua, 10000); |
|
|
|
|
} while (get_time_tb() < end); |
|
|
|
|
assert(second.key.netif_index == 1 && second.evidence_id != first.evidence_id); |
|
|
|
|
duplicate.netif_index = 0; |
|
|
|
|
if (!socket_bind_to_device(duplicate.fd, "lo")) { |
|
|
|
|
test_clock_run_for(f.ua, 30001); |
|
|
|
|
assert(!stun_nat_get_socket_result(&f.inst, &f.source, &second)); |
|
|
|
|
assert(second.status == STUN_NAT_SKIPPED && second.mapping_reason == STUN_REASON_ROUTE); |
|
|
|
|
assert(!stun_nat_get_socket_result(&f.inst, &duplicate, &second)); |
|
|
|
|
assert(second.mapping_reason == STUN_REASON_ROUTE); |
|
|
|
|
} else DEBUG_WARN(DEBUG_CATEGORY_NAT, "STUN routing conflict subtest unavailable: bind-to-device denied"); |
|
|
|
|
socket_close_wrapper(duplicate.fd); f.source.next = NULL; f.original = f.source; |
|
|
|
|
fixture_destroy(&f); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_NAT, "PASS STUN groups: duplicate ports, representative removal, separate netif, policy conflict"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void wait_source(struct nat_fixture* nf, struct ETCP_SOCKET* socket, uint8_t source) { |
|
|
|
|
uint64_t end = get_time_tb() + 3000000; |
|
|
|
|
while (get_time_tb() < end) { |
|
|
|
|
if (socket->address_profile.source == source) return; |
|
|
|
|
test_clock_poll(nf->ua, 10000); |
|
|
|
|
} |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT source wait failed socket=%s source=%s expected=%s", socket->name, |
|
|
|
|
address_profile_source_name(socket->address_profile.source), address_profile_source_name(source)); |
|
|
|
|
abort(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Настоящее ядро: нет узлов -> один наблюдатель -> независимый третий узел.
|
|
|
|
|
* Сервер сообщает translated EIM, реальный рабочий loopback endpoint — UNCHANGED. */ |
|
|
|
|
static void test_fallback(void) { |
|
|
|
|
struct nat_fixture nf; nat_fixture_init(&nf); |
|
|
|
|
struct fixture server = {.ua = nf.ua, .rfc = 1, .mapping = STUN_MAP_EIM, |
|
|
|
|
.filtering = STUN_FILTER_EIF, .multiple_clients = 1}; fixture_init(&server); |
|
|
|
|
char config[512]; |
|
|
|
|
snprintf(config, sizeof(config), "[stun]\nenabled=yes\nserver=127.0.0.1:%u\nserver=127.0.0.3:%u\n" |
|
|
|
|
"[server:duplicate]\naddr=127.0.0.1:0\ntype=public\n", ntohs(server.endpoints[0].addr.sin_port), |
|
|
|
|
ntohs(server.endpoints[4].addr.sin_port)); |
|
|
|
|
struct UTUN_INSTANCE* owner = nat_fixture_node(&nf, "127.0.0.1", NULL, config); |
|
|
|
|
struct ETCP_SOCKET* socket = test_etcp_bound_socket(owner, "udp"); |
|
|
|
|
struct ETCP_SOCKET* duplicate = test_etcp_bound_socket(owner, "duplicate"); |
|
|
|
|
wait_source(&nf, socket, ADDRESS_SOURCE_STUN); wait_source(&nf, duplicate, ADDRESS_SOURCE_STUN); |
|
|
|
|
assert(socket->address_profile.mapping == ADDRESS_MAP_EIM && socket->address_profile.inbound == ADDRESS_IN_OPEN); |
|
|
|
|
assert(socket->address_profile.evidence_id == duplicate->address_profile.evidence_id); |
|
|
|
|
assert(!socket->nat_addr.ss_family && !duplicate->nat_addr.ss_family); |
|
|
|
|
struct nat_check_status status; assert(!nat_detection_get_status(owner->nat_det, socket, &status)); |
|
|
|
|
assert(status.reason == NAT_REASON_NO_OBSERVER && status.fallback_allowed); |
|
|
|
|
struct TOPO_NODE* snapshot = topo_node_registry_find(owner->topo_groups, owner->node_id); |
|
|
|
|
assert(snapshot && !topo_node_verify(snapshot)); |
|
|
|
|
for (const struct TOPO_ADDR4* address = snapshot->v4_addrs; address; address = address->next) |
|
|
|
|
assert(address->type != TOPO_ADDR_NAT); /* тестовый внешний порт не опубликован */ |
|
|
|
|
struct UTUN_INSTANCE* observer = nat_fixture_node(&nf, "127.0.0.5", NULL, NULL); |
|
|
|
|
nat_fixture_connect(&nf, owner, "udp", observer); nat_detection_request_check_all(owner->nat_det); |
|
|
|
|
uint64_t end = get_time_tb() + 300000; |
|
|
|
|
do { test_clock_poll(nf.ua, 10000); nat_detection_get_status(owner->nat_det, socket, &status); } |
|
|
|
|
while (get_time_tb() < end && status.reason != NAT_REASON_NO_HELPER); |
|
|
|
|
assert(status.reason == NAT_REASON_NO_HELPER && socket->address_profile.source == ADDRESS_SOURCE_STUN); |
|
|
|
|
struct UTUN_INSTANCE* helper = nat_fixture_node(&nf, "127.0.0.6", NULL, NULL); |
|
|
|
|
nat_fixture_connect(&nf, observer, "udp", helper); nat_detection_request_check_all(owner->nat_det); |
|
|
|
|
wait_source(&nf, socket, ADDRESS_SOURCE_ETCP); wait_source(&nf, duplicate, ADDRESS_SOURCE_ETCP); |
|
|
|
|
assert(socket->address_profile.mapping == ADDRESS_MAP_UNCHANGED && socket->address_profile.inbound == ADDRESS_IN_OPEN); |
|
|
|
|
uint64_t evidence = socket->address_profile.evidence_id; |
|
|
|
|
struct stun_nat_result prior, next; |
|
|
|
|
assert(!stun_nat_get_socket_result(owner, socket, &prior)); stun_nat_recheck(owner); |
|
|
|
|
end = get_time_tb() + 3000000; |
|
|
|
|
do { test_clock_poll(nf.ua, 10000); assert(!stun_nat_get_socket_result(owner, socket, &next)); } |
|
|
|
|
while (get_time_tb() < end && next.status == STUN_NAT_PENDING); |
|
|
|
|
assert(next.mapping == STUN_MAP_EIM && next.evidence_id != prior.evidence_id); |
|
|
|
|
assert(socket->address_profile.source == ADDRESS_SOURCE_ETCP && socket->address_profile.evidence_id == evidence); |
|
|
|
|
for (unsigned i = 0; i < 6; i++) { |
|
|
|
|
assert(!uasync_remove_socket_t(nf.ua, server.endpoints[i].fd)); socket_close_wrapper(server.endpoints[i].fd); |
|
|
|
|
} |
|
|
|
|
socket_close_wrapper(server.source.fd); nat_fixture_cleanup(&nf); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_NAT, "PASS STUN fallback: no peers, no independent helper, ETCP priority, unchanged real endpoint"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
int main(void) { |
|
|
|
|
debug_config_init(); |
|
|
|
|
debug_set_level(DEBUG_LEVEL_WARN); |
|
|
|
|
@ -284,6 +396,7 @@ int main(void) {
|
|
|
|
|
if (getenv("UTUN_TEST_DEBUG")) debug_set_category_level(DEBUG_CATEGORY_NAT, DEBUG_LEVEL_DEBUG); |
|
|
|
|
test_clock_init(10000); |
|
|
|
|
test_config(); |
|
|
|
|
test_groups(); |
|
|
|
|
struct fixture ordinary = { .mapping = STUN_MAP_EIM }; |
|
|
|
|
run_case("ordinary equal", &ordinary, STUN_MAP_MATCH, STUN_FILTER_UNKNOWN); |
|
|
|
|
struct fixture dependent = { .mapping = STUN_MAP_ADM }; |
|
|
|
|
@ -351,6 +464,7 @@ int main(void) {
|
|
|
|
|
result = wait_result(&changed); |
|
|
|
|
assert(result.mapping == STUN_MAP_MATCH); |
|
|
|
|
fixture_destroy(&changed); |
|
|
|
|
test_fallback(); |
|
|
|
|
test_clock_finish(); |
|
|
|
|
puts("PASS STUN independent sockets, RFC5780, fallback, lifecycle and ETCP isolation"); |
|
|
|
|
return 0; |
|
|
|
|
|