From 3735b7832883e44a947743ad81d37f58a786b71e Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 12 Oct 2026 01:20:56 +0200 Subject: [PATCH] Verify STUN cache expiry and preserve partial ETCP measurement priority --- src/transport_layer/stun_nat.c | 13 ++++++++++--- tests/test_stun_nat.c | 30 ++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/transport_layer/stun_nat.c b/src/transport_layer/stun_nat.c index e9a3dd94..b91a5af3 100644 --- a/src/transport_layer/stun_nat.c +++ b/src/transport_layer/stun_nat.c @@ -727,7 +727,7 @@ static void scan_sources(struct STUN_NAT* mod) { while (p && !key_equal(&p->source.key, &source.key)) p = p->next; if (p) { if (!p->seen) { - p->changed = !policy_equal(&p->source, &source); + p->changed = !policy_equal(&p->source, &source) || (!p->generation && get_time_tb() >= p->due); p->source = source; } else if (!policy_equal(&p->source, &source)) p->scan_conflict = 1; p->seen = 1; p->members++; @@ -756,7 +756,10 @@ static void scan_sources(struct STUN_NAT* mod) { if (p->changed || p->conflict != p->scan_conflict) { close_probe_io(p); memset(&p->cached, 0, sizeof(p->cached)); p->conflict = p->scan_conflict; - if (random_id(&p->generation)) { p->generation = 0; p->due = get_time_tb() + STUN_RETRY_TB; } + if (random_id(&p->generation)) { + p->generation = 0; memset(&p->result, 0, sizeof(p->result)); p->result.key = p->source.key; + p->result.status = STUN_NAT_FAILED; p->result.mapping_reason = STUN_REASON_IO; finish_probe(p); + } else restart_probe(p); DEBUG_INFO(DEBUG_CATEGORY_NAT, "STUN group source=%s IP=%s netif=%u members=%u generation=%016llx conflict=%d", p->source.name, ip_to_str(&p->source.addr.sin_addr, AF_INET).str, p->source.netif_index, @@ -812,8 +815,12 @@ void stun_nat_recheck(struct UTUN_INSTANCE* inst) { DEBUG_INFO(DEBUG_CATEGORY_NAT, "STUN rechecking interfaces using fresh test ports"); scan_sources(mod); for (struct stun_probe* p = mod->probes; p; p = p->next) { + close_probe_io(p); memset(&p->cached, 0, sizeof(p->cached)); - if (random_id(&p->generation)) continue; + if (random_id(&p->generation)) { + p->generation = 0; memset(&p->result, 0, sizeof(p->result)); p->result.key = p->source.key; + p->result.status = STUN_NAT_FAILED; p->result.mapping_reason = STUN_REASON_IO; finish_probe(p); continue; + } restart_probe(p); } } diff --git a/tests/test_stun_nat.c b/tests/test_stun_nat.c index 23c32299..d5d307c6 100644 --- a/tests/test_stun_nat.c +++ b/tests/test_stun_nat.c @@ -325,11 +325,35 @@ static void test_groups(void) { 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); duplicate = extra_socket(&f, "another-ip", "127.0.0.5"); + test_clock_run_for(f.ua, 30001); 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(!stun_nat_get_socket_result(&f.inst, &f.source, &first)); + assert(second.key.address[3] == 5 && first.key.address[3] == 1 && second.evidence_id != first.evidence_id); 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"); } +/* Штатный refresh сохраняет прежний результат до TTL, не продлевая его возраст. */ +static void test_cache(void) { + struct fixture f = {.rfc = 1, .mapping = STUN_MAP_EIM, .filtering = STUN_FILTER_EIF, + .multiple_clients = 1}; fixture_init(&f); fixture_start(&f, 0, 0, 0); + struct stun_nat_result first = wait_result(&f), current; + assert(first.status == STUN_NAT_COMPLETE); + f.drop_primary = 1; + test_clock_run_for(f.ua, 1830000); + assert(!stun_nat_get_socket_result(&f.inst, &f.source, ¤t)); + assert(current.evidence_id == first.evidence_id && current.measured_at_us == first.measured_at_us && current.refreshing); + test_clock_run_for(f.ua, 1300000); + assert(!stun_nat_get_socket_result(&f.inst, &f.source, ¤t)); + assert(current.evidence_id != first.evidence_id && current.mapping == STUN_MAP_UNKNOWN && current.filtering == STUN_FILTER_UNKNOWN); + fixture_destroy(&f); + DEBUG_INFO(DEBUG_CATEGORY_NAT, "PASS STUN cache: automatic refresh, preserved age, failed refresh and expiry"); +} + 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) { @@ -382,6 +406,11 @@ static void test_fallback(void) { 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); + nat_detection_request_check_all(owner->nat_det); end = get_time_tb() + 300000; + do { test_clock_poll(nf.ua, 10000); } + while (get_time_tb() < end && socket->address_profile.evidence_id == evidence); + assert(socket->address_profile.source == ADDRESS_SOURCE_ETCP && socket->address_profile.evidence_id != evidence); + assert(socket->address_profile.mapping == ADDRESS_MAP_UNCHANGED && socket->address_profile.inbound == ADDRESS_IN_UNKNOWN); 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); } @@ -397,6 +426,7 @@ int main(void) { test_clock_init(10000); test_config(); test_groups(); + test_cache(); 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 };