Browse Source

nat: reject local probe sources and preserve internet profiles on LAN

stream-multiview
evgeny 1 week ago
parent
commit
83efa229ed
  1. 53
      src/routing_layer/nat_detection.c
  2. 3
      tests/Makefile.am
  3. 74
      tests/test_nat_detection_faults.c

53
src/routing_layer/nat_detection.c

@ -517,6 +517,21 @@ void nat_detection_record_observation(struct NAT_DETECTION *detector, struct ETC
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT observation has invalid group/identity group=%016llx owner=%016llx",
(unsigned long long)group_id, (unsigned long long)owner); return;
}
int eligible = public_address(detector, source);
if (!eligible) DEBUG_WARN(DEBUG_CATEGORY_NAT,
"NAT observation rejected by receiver group=%016llx owner=%016llx round=%016llx source=%s reason=non_public_source",
(unsigned long long)group_id, (unsigned long long)owner, (unsigned long long)round, sockaddr_storage_to_str(source).str);
if (length == NAT_QUERY_TAG_SIZE) {
struct nat_wire report = {.command = NAT_OBSERVATION, .owner_socket = tag[8], .step = tag[ROUTE_OBSERVE_TAG_SIZE],
.reason = eligible ? NAT_REASON_NONE : NAT_REASON_INVALID_OBSERVATION};
write64(report.owner, owner); write64(report.generation, generation); write64(report.round, round);
write64(report.token, read64(tag + ROUTE_OBSERVE_TAG_SIZE + 1));
write64(report.observer, detector->instance->node_id); write64(report.helper, detector->instance->node_id);
if (route_ping_addr_encode(report.target, source) || send_control(detector, group_id, owner, &report))
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT routed observation delivery failed group=%016llx owner=%016llx round=%016llx",
(unsigned long long)group_id, (unsigned long long)owner, (unsigned long long)round);
}
if (!eligible) return;
struct nat_binding *binding = binding_find(detector, group_id, owner, generation, round);
if (!binding) {
unsigned count = 0;
@ -532,15 +547,6 @@ void nat_detection_record_observation(struct NAT_DETECTION *detector, struct ETC
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT observation group=%016llx owner=%016llx socket=%u generation=%016llx round=%016llx observed=%s via=%s",
(unsigned long long)group_id, (unsigned long long)owner, tag[8], (unsigned long long)generation, (unsigned long long)round,
sockaddr_storage_to_str(source).str, socket->name);
if (length == NAT_QUERY_TAG_SIZE) {
struct nat_wire report = {.command = NAT_OBSERVATION, .owner_socket = tag[8], .step = tag[ROUTE_OBSERVE_TAG_SIZE]};
write64(report.owner, owner); write64(report.generation, generation); write64(report.round, round);
write64(report.token, read64(tag + ROUTE_OBSERVE_TAG_SIZE + 1));
write64(report.observer, detector->instance->node_id); write64(report.helper, detector->instance->node_id);
if (route_ping_addr_encode(report.target, source) || send_control(detector, group_id, owner, &report))
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT routed observation delivery failed group=%016llx owner=%016llx round=%016llx",
(unsigned long long)group_id, (unsigned long long)owner, (unsigned long long)round);
}
}
static void clear_profile(struct nat_socket *state, uint8_t reason) {
@ -669,7 +675,8 @@ static void owner_heartbeat_cb(int success, uint16_t rtt, void *arg, uint64_t no
struct nat_socket *state = arg; state->heartbeat_active = 0;
if (!state->phase) return;
struct sockaddr_storage observed;
if (success && !route_ping_observation(data, length, state->socket, state->group_id, state->round, &observed)) {
if (success && !route_ping_observation(data, length, state->socket, state->group_id, state->round, &observed) &&
public_address(state->detector, &observed)) {
if (state->baseline.ss_family && !route_ping_addr_equal(&state->baseline, &observed, 1)) {
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT anchor mapping changed socket=%s round=%016llx previous=%s current=%s",
state->socket->name, (unsigned long long)state->round,
@ -697,6 +704,11 @@ static void owner_ping_cb(int success, uint16_t rtt, void *arg, uint64_t nonce,
if (++state->retries < NAT_PROBE_COUNT) { state->due = get_time_tb() + 1000; return; }
owner_finish(state, success ? NAT_REASON_INVALID_OBSERVATION : NAT_REASON_TIMEOUT); return;
}
if (!public_address(state->detector, &observed)) {
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT observation unusable socket=%s phase=%u round=%016llx observed=%s reason=non_public_source",
state->socket->name, state->phase, (unsigned long long)state->round, sockaddr_storage_to_str(&observed).str);
owner_finish(state, NAT_REASON_INVALID_OBSERVATION); return;
}
state->retries = 0;
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT own observation socket=%s phase=%u round=%016llx observed=%s",
state->socket->name, state->phase, (unsigned long long)state->round, sockaddr_storage_to_str(&observed).str);
@ -757,11 +769,20 @@ void nat_detection_receive_ingress(struct NAT_DETECTION *detector, struct ETCP_S
if (!step || memcmp(tag, expected, sizeof(expected)) || tag[ROUTE_OBSERVE_TAG_SIZE] != step ||
read64(tag + ROUTE_OBSERVE_TAG_SIZE + 1) != state->token ||
sc_derive_node_id_from_pubkey(peer_key) != state->helper || memcmp(peer_key, state->helper_key, 32) ||
source->ss_family != state->local.ss_family || !public_address(detector, source) ||
((step == NAT_STEP_POST || state->pre_received) && !route_ping_addr_equal(source, &state->helper_address, 1))) {
source->ss_family != state->local.ss_family) {
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT ingress correlation/source mismatch socket=%s phase=%u round=%016llx source=%s",
socket->name, state->phase, (unsigned long long)state->round, sockaddr_storage_to_str(source).str); return;
}
if (!public_address(detector, source)) {
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT helper rejected by receiver socket=%s round=%016llx helper=%016llx source=%s reason=non_public_source",
socket->name, (unsigned long long)state->round, (unsigned long long)state->helper, sockaddr_storage_to_str(source).str);
owner_finish(state, NAT_REASON_NO_HELPER); return;
}
if ((step == NAT_STEP_POST || state->pre_received) && !route_ping_addr_equal(source, &state->helper_address, 1)) {
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT ingress endpoint mismatch socket=%s phase=%u round=%016llx source=%s expected=%s",
socket->name, state->phase, (unsigned long long)state->round,
sockaddr_storage_to_str(source).str, sockaddr_storage_to_str(&state->helper_address).str); return;
}
if (route_ping_addr_equal(source, &state->observer_address, 0) || route_ping_addr_equal(source, &state->baseline, 0)) {
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT helper rejected by receiver socket=%s round=%016llx helper=%016llx source=%s reason=%s",
socket->name, (unsigned long long)state->round, (unsigned long long)state->helper,
@ -859,7 +880,7 @@ static void helper_ping_cb(int success, uint16_t rtt, void *arg, uint64_t nonce,
struct ETCP_SOCKET *socket = find_socket(probe->detector, probe->io_socket, probe->io_generation);
struct sockaddr_storage mapped;
if (success && socket && !route_ping_observation(data, length, socket, probe->group_id,
read64(probe->message.round), &mapped)) {
read64(probe->message.round), &mapped) && public_address(probe->detector, &mapped)) {
if (route_ping_addr_encode(probe->message.primary, &mapped)) { helper_finish(probe, NAT_REASON_INVALID_OBSERVATION); return; }
probe->message.received++;
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT helper endpoint measured group=%016llx owner=%016llx step=%u socket=%s mapped=%s fallback=%u",
@ -1089,6 +1110,7 @@ static void receive_observation(struct NAT_DETECTION *detector, uint64_t group,
probe->io_generation != socket->generation || read64(probe->message.round) != read64(message->round) ||
read64(probe->message.token) != read64(message->token) || read64(probe->message.observer) != sender) continue;
etcp_cancel_pings(detector->instance, helper_ping_cb, probe);
if (message->reason) { helper_finish(probe, message->reason); return; }
helper_ping_cb(1, 0, probe, 0, data, sizeof(data)); return;
}
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT stale helper observation report ignored"); return;
@ -1101,6 +1123,7 @@ static void receive_observation(struct NAT_DETECTION *detector, uint64_t group,
(unsigned long long)group, (unsigned long long)sender, message->step); return;
}
etcp_cancel_pings(detector->instance, owner_ping_cb, state);
if (message->reason) { owner_finish(state, message->reason); return; }
owner_ping_cb(1, 0, state, 0, data, sizeof(data));
}
@ -1514,6 +1537,10 @@ void nat_detection_link_ready(struct NAT_DETECTION *detector, struct ETCP_LINK *
void nat_detection_observed_by_peer(struct NAT_DETECTION *detector, struct ETCP_LINK *link,
const struct sockaddr_storage *observed) {
if (!detector || !link || !link->conn || link->is_tcp || link->conn->socks_udp || !observed->ss_family) return;
if (!public_address(detector, observed)) {
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT ignores LAN observation socket=%s peer=%016llx observed=%s",
link->conn->name, (unsigned long long)link->etcp->peer_node_id, sockaddr_storage_to_str(observed).str); return;
}
struct nat_socket *state = socket_state(detector, link->conn, 1);
if (!state) return;
if ((state->measured_profile.mapping == ADDRESS_MAP_UNCHANGED &&

3
tests/Makefile.am

@ -450,7 +450,8 @@ check_PROGRAMS += test_nat_detection_faults
test_nat_detection_faults_SOURCES = test_nat_detection_faults.c $(test_clock_sources)
test_nat_detection_faults_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_nat_detection_faults_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_nat_detection_faults_LDFLAGS = $(test_clock_ldflags) -Wl,--wrap=socket_sendto -Wl,--wrap=nat_detection_record_observation
test_nat_detection_faults_LDFLAGS = $(test_clock_ldflags) -Wl,--wrap=socket_sendto \
-Wl,--wrap=nat_detection_record_observation -Wl,--wrap=nat_detection_receive_ingress
endif
test_stun_nat_SOURCES = test_stun_nat.c $(test_clock_sources)

74
tests/test_nat_detection_faults.c

@ -4,7 +4,8 @@
#include "test_clock.h"
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_DEPENDENT, FAULT_UNSTABLE, FAULT_NO_REPORT, FAULT_INGRESS_BLOCKED, FAULT_NO_ANCHOR,
FAULT_LOCAL_INGRESS, FAULT_LOCAL_OBSERVER, FAULT_LOCAL_HELPER_MAPPING };
static struct {
struct nat_fixture *fixture;
@ -23,6 +24,8 @@ static struct {
ssize_t __real_socket_sendto(socket_t, const void *, size_t, const struct sockaddr *, socklen_t);
void __real_nat_detection_record_observation(struct NAT_DETECTION *, struct ETCP_SOCKET *, const uint8_t *,
const struct sockaddr_storage *, const uint8_t *, size_t);
void __real_nat_detection_receive_ingress(struct NAT_DETECTION *, struct ETCP_SOCKET *, const uint8_t *,
const struct sockaddr_storage *, const uint8_t *, size_t);
static void ignored_ping(int success, uint16_t rtt, void *arg, uint64_t nonce, const uint8_t *data, size_t length) {
(void)success; (void)rtt; (void)arg; (void)nonce; (void)data; (void)length;
@ -59,6 +62,7 @@ ssize_t __wrap_socket_sendto(socket_t fd, const void *packet, size_t length, con
int to_owner = route_ping_addr_equal(&destination, &fault.socket->local_addr, 1);
if (to_owner && source->instance == fault.alias && plain[3] == ETCP_PING &&
(plain[4] & ETCP_PING_FLAG_ADDRESS_CHECK)) {
if (fault.mode == FAULT_LOCAL_HELPER_MAPPING) { fault.dropped++; return length; }
fault.altered++;
return __real_socket_sendto(fault.alias_source->fd, packet, length, target, target_length);
}
@ -99,6 +103,15 @@ ssize_t __wrap_socket_sendto(socket_t fd, const void *packet, size_t length, con
void __wrap_nat_detection_record_observation(struct NAT_DETECTION *detector, struct ETCP_SOCKET *socket,
const uint8_t *key, const struct sockaddr_storage *source, const uint8_t *tag, size_t length) {
if (fault.fixture && socket->instance == fault.observer && length == ROUTE_OBSERVE_TAG_SIZE + 9 &&
((fault.mode == FAULT_LOCAL_OBSERVER && !memcmp(key, fault.owner->my_keys.public_key, 32)) ||
(fault.mode == FAULT_LOCAL_HELPER_MAPPING && !memcmp(key, fault.alias->my_keys.public_key, 32)))) {
/* Настоящий source — loopback; в этом callback включаем промышленную проверку публичности. */
fault.injected++;
nat_detection_set_allow_local(detector, 0);
__real_nat_detection_record_observation(detector, socket, key, source, tag, length);
nat_detection_set_allow_local(detector, 1); return;
}
if (!fault.fixture || socket->instance != fault.helper || memcmp(key, fault.owner->my_keys.public_key, 32) ||
length != ROUTE_OBSERVE_TAG_SIZE + 9) {
__real_nat_detection_record_observation(detector, socket, key, source, tag, length); return;
@ -116,6 +129,31 @@ void __wrap_nat_detection_record_observation(struct NAT_DETECTION *detector, str
__real_nat_detection_record_observation(detector, socket, key, &observed, tag, length);
}
/* Подлинная проба приходит с локального IP, несмотря на пригодный предварительный адрес помощника. */
void __wrap_nat_detection_receive_ingress(struct NAT_DETECTION *detector, struct ETCP_SOCKET *socket,
const uint8_t *key, const struct sockaddr_storage *source, const uint8_t *tag, size_t length) {
if (fault.fixture && fault.mode == FAULT_LOCAL_INGRESS && detector == fault.owner->nat_det &&
!memcmp(key, fault.alias->my_keys.public_key, 32)) {
struct nat_check_status before, after;
assert(!nat_detection_get_status(detector, socket, &before));
nat_detection_set_allow_local(detector, 0);
/* Неверный token не должен позволять постороннему пакету сменить кандидата. */
uint8_t stale[ROUTE_OBSERVE_TAG_SIZE + 9]; assert(length == sizeof(stale));
memcpy(stale, tag, length); stale[length - 1] ^= 1;
__real_nat_detection_receive_ingress(detector, socket, key, source, stale, length);
assert(!nat_detection_get_status(detector, socket, &after));
assert(after.phase == before.phase && after.helper_reselections == before.helper_reselections);
__real_nat_detection_receive_ingress(detector, socket, key, source, tag, length);
assert(!nat_detection_get_status(detector, socket, &after));
if (before.phase) {
assert(!after.phase && after.helper_reselections == before.helper_reselections + 1);
assert(after.reason == NAT_REASON_NO_HELPER); fault.injected++;
}
nat_detection_set_allow_local(detector, 1); return;
}
__real_nat_detection_receive_ingress(detector, socket, key, source, tag, length);
}
static void run_case(enum fault_mode mode, uint8_t mapping, uint8_t inbound) {
struct nat_fixture fixture; nat_fixture_init(&fixture);
struct UTUN_INSTANCE *owner = nat_fixture_node(&fixture, "127.0.0.2", NULL, NULL);
@ -141,12 +179,13 @@ static void run_case(enum fault_mode mode, uint8_t mapping, uint8_t 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_DEPENDENT || mode == FAULT_UNSTABLE) assert(fault.altered);
if (mode == FAULT_LOCAL_OBSERVER) assert(fault.injected && status.helper_reselections && !fault.observations);
fault.fixture = NULL; nat_fixture_cleanup(&fixture);
DEBUG_INFO(DEBUG_CATEGORY_NAT, "PASS NAT fault mode=%d", mode);
}
/* Разные ключи не означают разные IP: неподходящего помощника отклоняет получатель. */
static void run_alias(const char *alias_ip) {
static void run_alias(const char *alias_ip, enum fault_mode mode) {
struct nat_fixture fixture; nat_fixture_init(&fixture);
struct SC_MYKEYS first, second; assert(sc_generate_keypair(&first) == SC_OK && sc_generate_keypair(&second) == SC_OK);
if (sc_derive_node_id_from_pubkey(first.public_key) > sc_derive_node_id_from_pubkey(second.public_key)) {
@ -164,6 +203,7 @@ static void run_alias(const char *alias_ip) {
struct ETCP_SOCKET *socket = test_etcp_bound_socket(owner, "udp");
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;
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));
@ -173,11 +213,34 @@ static void run_alias(const char *alias_ip) {
DEBUG_INFO(DEBUG_CATEGORY_NAT, "alias fixture result source=%s helper=%016llx expected=%016llx reselections=%u remapped=%u",
alias_ip, (unsigned long long)status.helper, (unsigned long long)helper->node_id,
status.helper_reselections, fault.altered);
assert(status.helper == helper->node_id && status.helper_reselections && fault.altered);
assert(status.helper == helper->node_id && status.helper_reselections);
if (mode == FAULT_LOCAL_HELPER_MAPPING) assert(fault.injected && fault.dropped);
else assert(fault.altered);
if (mode == FAULT_LOCAL_INGRESS) assert(fault.injected);
fault.fixture = NULL; nat_fixture_cleanup(&fixture);
DEBUG_INFO(DEBUG_CATEGORY_NAT, "PASS NAT receiver rejects helper source=%s", alias_ip);
}
/* LAN INIT_RESPONSE описывает свой путь и не инвалидирует уже известный интернет-профиль. */
static void run_lan_observation(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 *peer = nat_fixture_node(&fixture, "127.0.0.3", NULL, NULL);
nat_detection_set_automatic(owner->nat_det, 0); nat_detection_set_automatic(peer->nat_det, 0);
struct ETCP_CONN *connection = nat_fixture_connect(&fixture, owner, "udp", peer);
struct ETCP_SOCKET *socket = test_etcp_bound_socket(owner, "udp");
struct address_profile profile = {ADDRESS_MAP_EIM, ADDRESS_IN_OPEN, ADDRESS_SOURCE_ETCP};
struct sockaddr_storage external = socket->local_addr;
assert(inet_pton(AF_INET, "8.8.8.8", &((struct sockaddr_in *)&external)->sin_addr) == 1);
socket->address_profile = profile; socket->nat_addr = external;
nat_detection_set_allow_local(owner->nat_det, 0);
nat_detection_observed_by_peer(owner->nat_det, connection->links, &socket->local_addr);
assert(!memcmp(&socket->address_profile, &profile, sizeof(profile)));
assert(route_ping_addr_equal(&socket->nat_addr, &external, 1));
nat_fixture_cleanup(&fixture);
DEBUG_INFO(DEBUG_CATEGORY_NAT, "PASS LAN observation preserves internet mapping and inbound profile");
}
/* У наблюдателя первый UDP listener недоступен, но управление по TCP и второй IP работают. */
static void run_endpoint_retry(void) {
struct nat_fixture fixture; nat_fixture_init(&fixture);
@ -219,6 +282,9 @@ int main(void) {
run_case(FAULT_NO_REPORT, ADDRESS_MAP_UNKNOWN, ADDRESS_IN_UNKNOWN);
run_case(FAULT_INGRESS_BLOCKED, ADDRESS_MAP_UNCHANGED, ADDRESS_IN_UNKNOWN);
run_case(FAULT_NO_ANCHOR, ADDRESS_MAP_UNCHANGED, ADDRESS_IN_UNKNOWN);
run_alias("127.0.0.3"); run_alias("127.0.0.2"); run_endpoint_retry();
run_case(FAULT_LOCAL_OBSERVER, ADDRESS_MAP_UNKNOWN, 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_endpoint_retry();
test_clock_finish(); return 0;
}

Loading…
Cancel
Save