test(nat): cover helper rotation, preserved OPEN and budget resume
This commit is contained in:
1 parent
a72e514dc5
commit
edc78cbf4a
1 file changed
+81
-5
@@ -6,7 +6,8 @@
|
||||
|
||||
enum fault_mode { FAULT_NONE, FAULT_NO_PONG, FAULT_FILTERED, FAULT_LATE_PRE, FAULT_CONTACT,
|
||||
FAULT_DEPENDENT, FAULT_UNSTABLE, FAULT_NO_REPORT, FAULT_INGRESS_BLOCKED, FAULT_NO_ANCHOR,
|
||||
FAULT_LOCAL_INGRESS, FAULT_LOCAL_OBSERVER, FAULT_LOCAL_HELPER_MAPPING, FAULT_CONTROL_STALL, FAULT_EIM_UNVERIFIED };
|
||||
FAULT_LOCAL_INGRESS, FAULT_LOCAL_OBSERVER, FAULT_LOCAL_HELPER_MAPPING, FAULT_CONTROL_STALL, FAULT_EIM_UNVERIFIED,
|
||||
FAULT_KNOWN_ALIAS, FAULT_BUDGET };
|
||||
|
||||
static struct {
|
||||
struct nat_fixture *fixture;
|
||||
@@ -32,7 +33,8 @@ int __real_etcp_route_send(struct UTUN_INSTANCE *, uint64_t, uint64_t, struct ll
|
||||
/* Помощник отправляет пробы, но его управляющие результаты до наблюдателя не доходят. */
|
||||
int __wrap_etcp_route_send(struct UTUN_INSTANCE *instance, uint64_t group, uint64_t destination,
|
||||
struct ll_entry *entry, int force, int mode) {
|
||||
if (fault.fixture && fault.mode == FAULT_CONTROL_STALL && instance == fault.helper &&
|
||||
if (fault.fixture && ((fault.mode == FAULT_CONTROL_STALL && instance == fault.helper) ||
|
||||
(fault.mode == FAULT_BUDGET && instance != fault.owner && instance != fault.observer)) &&
|
||||
destination == fault.observer->node_id && entry->len && entry->dgram[0] == ETCP_RT_ID_NAT_DETECTION) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NAT, "fault fixture: helper control result dropped before observer delivery");
|
||||
fault.dropped++; queue_dgram_free(entry); queue_entry_free(entry); return 0;
|
||||
@@ -102,12 +104,16 @@ ssize_t __wrap_socket_sendto(socket_t fd, const void *packet, size_t length, con
|
||||
&source->local_addr, 500, ignored_ping, NULL, NULL, 0, 0));
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NAT, "fault fixture: real outgoing PING contaminates PRE source IP");
|
||||
}
|
||||
if (fault.mode == FAULT_INGRESS_BLOCKED || fault.mode == FAULT_CONTROL_STALL ||
|
||||
if (fault.mode == FAULT_INGRESS_BLOCKED || fault.mode == FAULT_CONTROL_STALL || fault.mode == FAULT_BUDGET ||
|
||||
((fault.mode == FAULT_FILTERED || fault.mode == FAULT_LATE_PRE || fault.mode == FAULT_NO_ANCHOR) && !fault.contacted)) {
|
||||
if (fault.mode == FAULT_LATE_PRE && !fault.delayed_length && !fault.injected) {
|
||||
memcpy(fault.delayed, packet, length); fault.delayed_length = length; fault.delayed_fd = fd;
|
||||
fault.delayed_target = destination; fault.delayed_target_length = target_length;
|
||||
}
|
||||
if (fault.mode == FAULT_BUDGET) {
|
||||
memcpy(fault.delayed, packet, length); fault.delayed_length = length; fault.delayed_fd = fd;
|
||||
fault.delayed_target = destination; fault.delayed_target_length = target_length;
|
||||
}
|
||||
fault.dropped++; return length;
|
||||
}
|
||||
fault.direct_received++;
|
||||
@@ -203,6 +209,7 @@ static void run_case(enum fault_mode mode, uint8_t mapping, uint8_t inbound) {
|
||||
assert(fault.socket->address_profile.mapping == mapping && fault.socket->address_profile.inbound == inbound);
|
||||
if (mode == FAULT_NO_PONG || mode == FAULT_FILTERED || mode == FAULT_LATE_PRE || mode == FAULT_NO_REPORT) assert(fault.dropped);
|
||||
if (mode == FAULT_LATE_PRE || mode == FAULT_CONTACT) assert(fault.injected);
|
||||
if (mode == FAULT_CONTACT) assert(status.rounds == rounds + 1 && status.helper_reselections == 1);
|
||||
if (mode == FAULT_DEPENDENT || mode == FAULT_UNSTABLE) assert(fault.altered);
|
||||
if (mode == FAULT_LOCAL_OBSERVER) assert(fault.injected && status.helper_reselections && !fault.observations);
|
||||
if (mode == FAULT_CONTROL_STALL) assert(fault.dropped && fault.observations >= 2 && status.rounds == rounds + 1);
|
||||
@@ -240,10 +247,18 @@ static void run_alias(const char *alias_ip, enum fault_mode mode) {
|
||||
memset(&fault, 0, sizeof(fault)); fault.fixture = &fixture; fault.owner = owner; fault.socket = socket;
|
||||
fault.observer = observer; fault.helper = helper; fault.alias = alias; fault.alias_source = test_etcp_bound_socket(alias, "alias");
|
||||
fault.mode = mode;
|
||||
if (mode == FAULT_KNOWN_ALIAS)
|
||||
assert(!etcp_send_ping_to_socket(owner, socket, alias->my_keys.public_key,
|
||||
&fault.alias_source->local_addr, 500, ignored_ping, NULL, NULL, 0, 0));
|
||||
struct nat_check_status status = {0}; uint64_t end = get_time_tb() + 400000;
|
||||
nat_detection_get_status(owner->nat_det, socket, &status); uint32_t rounds = status.rounds;
|
||||
assert(!nat_detection_request_check_socket(owner->nat_det, socket, observer->node_id));
|
||||
do { test_clock_poll(fixture.ua, 1000); assert(!nat_detection_get_status(owner->nat_det, socket, &status)); }
|
||||
int partial_seen = 0;
|
||||
do {
|
||||
test_clock_poll(fixture.ua, 1000); assert(!nat_detection_get_status(owner->nat_det, socket, &status));
|
||||
if (status.cycling && socket->address_profile.mapping == ADDRESS_MAP_UNCHANGED &&
|
||||
socket->address_profile.inbound == ADDRESS_IN_UNKNOWN) partial_seen = 1;
|
||||
}
|
||||
while (get_time_tb() < end && (status.rounds == rounds || status.phase || status.cycling));
|
||||
assert(socket->address_profile.mapping == ADDRESS_MAP_UNCHANGED && socket->address_profile.inbound == ADDRESS_IN_OPEN);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NAT, "alias fixture result source=%s helper=%016llx expected=%016llx reselections=%u remapped=%u",
|
||||
@@ -253,6 +268,25 @@ static void run_alias(const char *alias_ip, enum fault_mode mode) {
|
||||
if (mode == FAULT_LOCAL_HELPER_MAPPING) assert(fault.injected && fault.dropped);
|
||||
else assert(fault.altered);
|
||||
if (mode == FAULT_LOCAL_INGRESS) assert(fault.injected);
|
||||
if (mode == FAULT_KNOWN_ALIAS) {
|
||||
assert(partial_seen);
|
||||
/* Сохранённый OPEN не завершает повторную проверку на знакомом фактическом IP alias. */
|
||||
struct SC_MYKEYS next;
|
||||
do { assert(sc_generate_keypair(&next) == SC_OK); }
|
||||
while (sc_derive_node_id_from_pubkey(next.public_key) <= alias->node_id);
|
||||
struct UTUN_INSTANCE *fresh = nat_fixture_node(&fixture, "127.0.0.6", &next, NULL);
|
||||
nat_detection_set_automatic(fresh->nat_det, 0); nat_fixture_connect(&fixture, observer, "udp", fresh);
|
||||
fault.helper = fresh; rounds = status.rounds;
|
||||
assert(!nat_detection_request_check_socket(owner->nat_det, socket, observer->node_id));
|
||||
int preserved_seen = 0; end = get_time_tb() + 400000;
|
||||
do {
|
||||
test_clock_poll(fixture.ua, 1000); assert(!nat_detection_get_status(owner->nat_det, socket, &status));
|
||||
if (status.helper == alias->node_id && status.reason == NAT_REASON_CONTACT_HISTORY && status.cycling &&
|
||||
socket->address_profile.inbound == ADDRESS_IN_OPEN) preserved_seen = 1;
|
||||
} while (get_time_tb() < end && (status.rounds == rounds || status.phase || status.cycling));
|
||||
assert(preserved_seen && !status.phase && !status.cycling && status.helper == fresh->node_id);
|
||||
assert(socket->address_profile.mapping == ADDRESS_MAP_UNCHANGED && socket->address_profile.inbound == ADDRESS_IN_OPEN);
|
||||
}
|
||||
fault.fixture = NULL; nat_fixture_cleanup(&fixture);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NAT, "PASS NAT receiver rejects helper source=%s", alias_ip);
|
||||
}
|
||||
@@ -354,6 +388,47 @@ static void run_endpoint_retry(void) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NAT, "PASS NAT retries another observer IP after timeout");
|
||||
}
|
||||
|
||||
/* Несколько медленных помощников исчерпывают общий бюджет посередине следующей попытки. */
|
||||
static void run_budget_resume(void) {
|
||||
struct nat_fixture fixture; nat_fixture_init(&fixture);
|
||||
struct UTUN_INSTANCE *owner = nat_fixture_node(&fixture, "127.0.0.2", NULL, NULL);
|
||||
struct UTUN_INSTANCE *observer = nat_fixture_node(&fixture, "127.0.0.3", NULL, NULL);
|
||||
nat_detection_set_automatic(owner->nat_det, 0); nat_detection_set_automatic(observer->nat_det, 0);
|
||||
for (unsigned i = 0; i < 4; i++) {
|
||||
char ip[32]; snprintf(ip, sizeof(ip), "127.0.0.%u", i + 4);
|
||||
struct UTUN_INSTANCE *helper = nat_fixture_node(&fixture, ip, NULL, NULL);
|
||||
nat_detection_set_automatic(helper->nat_det, 0); nat_fixture_connect(&fixture, observer, "udp", helper);
|
||||
}
|
||||
nat_fixture_connect(&fixture, owner, "udp", observer);
|
||||
memset(&fault, 0, sizeof(fault)); fault.fixture = &fixture; fault.owner = owner; fault.observer = observer;
|
||||
fault.socket = test_etcp_bound_socket(owner, "udp"); fault.mode = FAULT_BUDGET;
|
||||
assert(!nat_detection_request_check_socket(owner->nat_det, fault.socket, observer->node_id));
|
||||
struct nat_check_status status = {0}; uint64_t end = get_time_tb() + 500000;
|
||||
do { test_clock_poll(fixture.ua, 1000); assert(!nat_detection_get_status(owner->nat_det, fault.socket, &status)); }
|
||||
while (get_time_tb() < end && (status.phase || !status.cycling || status.reason != NAT_REASON_BUDGET));
|
||||
assert(!status.phase && status.cycling && status.reason == NAT_REASON_BUDGET);
|
||||
assert(status.helper_reselections >= 2 && fault.dropped && fault.delayed_length);
|
||||
assert(fault.socket->address_profile.mapping == ADDRESS_MAP_UNCHANGED &&
|
||||
fault.socket->address_profile.inbound == ADDRESS_IN_UNKNOWN);
|
||||
uint64_t paused_round = status.round, paused_helper = status.helper;
|
||||
uint32_t completed_pairs = status.helper_reselections;
|
||||
/* Поздняя подлинная проба не завершает уже отменённую попытку. */
|
||||
assert(__real_socket_sendto(fault.delayed_fd, fault.delayed, fault.delayed_length,
|
||||
(const struct sockaddr *)&fault.delayed_target, fault.delayed_target_length) == (ssize_t)fault.delayed_length);
|
||||
test_clock_run_for(fixture.ua, 2000);
|
||||
assert(!nat_detection_get_status(owner->nat_det, fault.socket, &status));
|
||||
assert(!status.phase && status.cycling && status.round == paused_round && status.helper_reselections == completed_pairs);
|
||||
assert(fault.socket->address_profile.inbound == ADDRESS_IN_UNKNOWN);
|
||||
fault.mode = FAULT_NONE; fault.delayed_length = 0; end = get_time_tb() + 700000;
|
||||
do { test_clock_poll(fixture.ua, 1000); assert(!nat_detection_get_status(owner->nat_det, fault.socket, &status)); }
|
||||
while (get_time_tb() < end && status.cycling);
|
||||
assert(!status.phase && !status.cycling && status.round != paused_round && status.helper == paused_helper);
|
||||
assert(status.helper_reselections == completed_pairs && fault.socket->address_profile.mapping == ADDRESS_MAP_UNCHANGED &&
|
||||
fault.socket->address_profile.inbound == ADDRESS_IN_OPEN);
|
||||
fault.fixture = NULL; nat_fixture_cleanup(&fixture);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NAT, "PASS NAT budget pause retains mapping, rejects late ingress and retries the unfinished pair");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); debug_set_category_level(DEBUG_CATEGORY_NAT, DEBUG_LEVEL_DEBUG);
|
||||
test_clock_init(1000);
|
||||
@@ -371,6 +446,7 @@ int main(void) {
|
||||
run_case(FAULT_EIM_UNVERIFIED, ADDRESS_MAP_EIM, ADDRESS_IN_UNKNOWN);
|
||||
run_alias("127.0.0.3", FAULT_NONE); run_alias("127.0.0.2", FAULT_NONE);
|
||||
run_alias("127.0.0.9", FAULT_LOCAL_INGRESS); run_alias("127.0.0.9", FAULT_LOCAL_HELPER_MAPPING);
|
||||
run_lan_observation(); run_unknown_address(); run_endpoint_retry();
|
||||
run_alias("127.0.0.9", FAULT_KNOWN_ALIAS);
|
||||
run_lan_observation(); run_unknown_address(); run_endpoint_retry(); run_budget_resume();
|
||||
test_clock_finish(); return 0;
|
||||
}
|
||||
Reference in new issue
Block a user