Browse Source

Group STUN probes by interface path and refresh independent-port results

master
evgeny 13 hours ago
parent
commit
6beae21ec4
  1. 158
      src/transport_layer/stun_nat.c
  2. 14
      src/transport_layer/stun_nat.h

158
src/transport_layer/stun_nat.c

@ -5,6 +5,7 @@
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/platform_compat.h"
#include "../ntp_time.h"
#include "crc32.h"
#include <stdlib.h>
#include <string.h>
@ -24,12 +25,16 @@
#define STUN_OTHER 0x802c
#define STUN_REQUESTS 7
#define STUN_SCAN_TB 30000
#define STUN_TTL_TB UINT64_C(3000000)
#define STUN_REFRESH_TB UINT64_C(1800000)
#define STUN_RETRY_TB UINT64_C(300000)
enum stun_stage { STUN_BASE, STUN_FILTER_BOTH, STUN_CONTROL_BOTH, STUN_FILTER_PORT,
STUN_CONTROL_PORT, STUN_CHECK_OTHER, STUN_CHECK_PORT, STUN_MAP_IP, STUN_MAP_PORT, STUN_VERIFY };
/* Снимок исходного сокета используется только для выбора того же сетевого пути. */
struct stun_source {
struct stun_nat_key key;
char name[MAX_CONN_NAME_LEN];
struct sockaddr_in addr;
uint32_t netif_index;
@ -37,6 +42,9 @@ struct stun_source {
char device[64];
};
/* Порты не переиспользуются и после удаления/пересоздания группы в этом ядре. */
struct stun_ports { struct stun_ports* next; struct stun_nat_key key; uint8_t used[2048]; };
struct stun_history {
struct stun_history* next;
struct sockaddr_in addr;
@ -47,13 +55,17 @@ struct stun_probe {
struct STUN_NAT* owner;
struct stun_source source;
struct stun_nat_result result;
struct stun_nat_result cached;
struct stun_ports* ports;
uint64_t generation, due;
unsigned members;
int changed, conflict, scan_conflict;
socket_t fd;
void* socket_handle;
void* timer;
struct adns_query* dns;
const struct CFG_STUN_SERVER* cursor;
uint16_t dns_port;
uint16_t last_port;
struct stun_history* history;
struct sockaddr_in primary, other, reference, base_mapping, ip_mapping, target, expected;
enum stun_stage stage;
@ -67,6 +79,7 @@ struct stun_probe {
struct STUN_NAT {
struct UTUN_INSTANCE* inst;
struct stun_probe* probes;
struct stun_ports* ports;
void* scan_timer;
uint64_t next_transaction_tb;
};
@ -82,6 +95,21 @@ static void begin_mapping(struct stun_probe* p);
static void finish_server(struct stun_probe* p);
static void scan_sources(struct STUN_NAT* mod);
static int key_equal(const struct stun_nat_key* a, const struct stun_nat_key* b) {
return a->family == b->family && a->netif_index == b->netif_index && !memcmp(a->address, b->address, 16);
}
static int policy_equal(const struct stun_source* a, const struct stun_source* b) {
return a->mark == b->mark && a->fib == b->fib && a->skipped == b->skipped && !strcmp(a->device, b->device);
}
static int random_id(uint64_t* value) {
do { if (random_bytes((uint8_t*)value, sizeof(*value))) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "STUN group/evidence random generation failed"); return -1;
} } while (!*value);
return 0;
}
static uint16_t read16(const uint8_t* p) { return ((uint16_t)p[0] << 8) | p[1]; }
static uint32_t read32(const uint8_t* p) { return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | p[3]; }
static void write16(uint8_t* p, uint16_t v) { p[0] = v >> 8; p[1] = v; }
@ -102,7 +130,8 @@ const char* stun_nat_filtering_name(enum stun_nat_filtering v) {
const char* stun_nat_reason_name(enum stun_nat_reason v) {
static const char* names[] = { "ok", "RFC5780 unsupported", "prior STUN traffic", "timeout", "server error",
"unstable mapping", "no independent server pair", "DNS failure", "socket I/O failure", "unsupported socket", "out of memory" };
"unstable mapping", "no independent server pair", "DNS failure", "socket I/O failure", "unsupported socket", "out of memory",
"incompatible routing policy", "no fresh test port" };
return (unsigned)v < sizeof(names) / sizeof(names[0]) ? names[v] : "invalid reason";
}
@ -126,11 +155,17 @@ static void close_probe_io(struct stun_probe* p) {
static void finish_probe(struct stun_probe* p) {
struct stun_nat_result* r = &p->result;
r->measured_at_tb = get_time_tb();
int64_t now_us = ntp_time_get_us(p->owner->inst);
if (now_us > 0) r->measured_at_us = (uint64_t)now_us;
else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "STUN measurement wall time unavailable source=%s", p->source.name);
if (r->status == STUN_NAT_PENDING) {
if (!r->server_count) r->status = STUN_NAT_FAILED;
else if (r->server_count < 2 || r->mapping == STUN_MAP_UNKNOWN || r->filtering == STUN_FILTER_UNKNOWN) r->status = STUN_NAT_PARTIAL;
else r->status = STUN_NAT_COMPLETE;
}
if (r->status == STUN_NAT_COMPLETE || r->status == STUN_NAT_PARTIAL) p->cached = *r;
p->due = r->status == STUN_NAT_COMPLETE ? r->measured_at_tb + STUN_REFRESH_TB : r->measured_at_tb + STUN_RETRY_TB;
if (r->status == STUN_NAT_SKIPPED) p->due = UINT64_MAX;
DEBUG_INFO(DEBUG_CATEGORY_NAT,
"STUN result source=%s local=%s servers=%u mapping=%s (%s) filtering=%s (%s) translated=%d RFC5780=%d status=%d",
p->source.name, sockaddr_storage_to_str(&r->local).str, r->server_count,
@ -553,9 +588,17 @@ static int source_snapshot(const struct ETCP_SOCKET* sock, struct stun_source* s
memset(source, 0, sizeof(*source));
snprintf(source->name, sizeof(source->name), "%s", sock->name);
source->netif_index = sock->netif_index;
source->skipped = sock->is_tcp || sock->socks_udp || sock->only_local;
const struct sockaddr_storage* addr = &sock->interface_addr;
source->key.netif_index = sock->netif_index;
source->skipped = sock->is_tcp || sock->probe_only || sock->socks_udp || sock->only_local;
const struct sockaddr_storage* addr = &sock->local_addr;
if (addr->ss_family != AF_INET || !((const struct sockaddr_in*)addr)->sin_addr.s_addr) addr = &sock->local_addr;
if (addr->ss_family == AF_INET6) {
source->key.family = 6;
memcpy(source->key.address, &((const struct sockaddr_in6*)addr)->sin6_addr, 16);
} else if (addr->ss_family == AF_INET) {
source->key.family = 4;
memcpy(source->key.address, &((const struct sockaddr_in*)addr)->sin_addr, 4);
}
if (addr->ss_family != AF_INET || !((const struct sockaddr_in*)addr)->sin_addr.s_addr) source->skipped = 1;
else { source->addr = *(const struct sockaddr_in*)addr; source->addr.sin_port = 0; }
if (source->skipped) return 0;
@ -589,15 +632,25 @@ static int open_probe_socket(struct stun_probe* p) {
#endif
struct sockaddr_in local = p->source.addr;
int bound = 0;
for (int i = 0; i < 16; i++) {
for (int i = 0; i < 32; i++) {
uint16_t random_port;
if (random_bytes((uint8_t*)&random_port, sizeof(random_port)) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "STUN source=%s random port generation failed", p->source.name);
return -1;
}
local.sin_port = htons(49152 + (random_port & 16383));
if (local.sin_port == p->last_port) continue;
if (bind(p->fd, (const struct sockaddr*)&local, sizeof(local)) == 0) { bound = 1; break; }
unsigned port = random_port & 16383;
local.sin_port = htons(49152 + port);
if (p->ports->used[port / 8] & (1u << (port % 8))) continue;
int working = 0;
for (const struct ETCP_SOCKET* socket = p->owner->inst->etcp_sockets; socket; socket = socket->next) {
if (socket->local_addr.ss_family != AF_INET) continue;
const struct sockaddr_in* address = (const struct sockaddr_in*)&socket->local_addr;
if (address->sin_addr.s_addr == local.sin_addr.s_addr && address->sin_port == local.sin_port) working = 1;
}
if (working) continue;
if (bind(p->fd, (const struct sockaddr*)&local, sizeof(local)) == 0) {
p->ports->used[port / 8] |= (uint8_t)(1u << (port % 8)); bound = 1; break;
}
#ifdef _WIN32
if (socket_get_error() != WSAEADDRINUSE) goto error;
#else
@ -605,10 +658,10 @@ static int open_probe_socket(struct stun_probe* p) {
#endif
}
if (!bound) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "STUN source=%s no free random test port after 16 attempts", p->source.name);
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "STUN source=%s no fresh free test port after 32 attempts", p->source.name);
p->result.mapping_reason = p->result.filtering_reason = STUN_REASON_PORTS;
return -1;
}
p->last_port = local.sin_port;
memcpy(&p->result.local, &local, sizeof(local));
p->socket_handle = uasync_add_socket_t(p->owner->inst->ua, p->fd, read_response, NULL, NULL, "stun_probe", p);
if (!p->socket_handle) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "STUN source=%s cannot register test socket", p->source.name); return -1; }
@ -631,10 +684,11 @@ static void destroy_probe(struct stun_probe* p) {
u_free(p);
}
/* Начинает измерение на новом порту, исключая непосредственно предыдущий тестовый порт. */
/* Начинает новый раунд; действующий кэш сохраняется до TTL при штатном refresh. */
static void restart_probe(struct stun_probe* p) {
close_probe_io(p);
memset(&p->result, 0, sizeof(p->result));
p->result.key = p->source.key; p->result.generation = p->generation; p->result.members = p->members;
p->result.translated = -1;
p->result.mapping_reason = STUN_REASON_NO_PAIR;
p->result.filtering_reason = STUN_REASON_UNSUPPORTED;
@ -642,44 +696,69 @@ static void restart_probe(struct stun_probe* p) {
p->capability = 0;
p->pending_filter = STUN_FILTER_UNKNOWN;
p->sent = 0;
if (p->source.skipped) {
if (random_id(&p->result.evidence_id)) {
p->result.status = STUN_NAT_FAILED; p->result.mapping_reason = STUN_REASON_IO; finish_probe(p); return;
}
if (p->source.skipped || p->conflict) {
p->result.status = STUN_NAT_SKIPPED;
p->result.mapping_reason = p->result.filtering_reason = STUN_REASON_SOCKET;
p->result.mapping_reason = p->result.filtering_reason = p->conflict ? STUN_REASON_ROUTE : STUN_REASON_SOCKET;
finish_probe(p);
} else if (open_probe_socket(p) < 0) {
p->result.mapping_reason = STUN_REASON_IO;
if (p->result.mapping_reason != STUN_REASON_PORTS) p->result.mapping_reason = STUN_REASON_IO;
finish_probe(p);
} else next_server(p);
}
/* Сверяет снимки: новый/изменённый интерфейс получает свежую пробу, исчезнувший удаляется. */
static void scan_sources(struct STUN_NAT* mod) {
for (struct stun_probe* p = mod->probes; p; p = p->next) p->seen = 0;
for (struct stun_probe* p = mod->probes; p; p = p->next) {
p->seen = 0; p->members = 0; p->scan_conflict = 0; p->changed = 0;
}
for (struct ETCP_SOCKET* sock = mod->inst->etcp_sockets; sock; sock = sock->next) {
if (sock->is_tcp || sock->probe_only) continue;
if (sock->is_tcp || sock->probe_only || sock->socks_udp || sock->only_local) continue;
struct stun_source source;
if (source_snapshot(sock, &source) < 0) continue;
struct stun_probe** slot = &mod->probes;
while (*slot && strcmp((*slot)->source.name, source.name)) slot = &(*slot)->next;
if (*slot && !memcmp(&(*slot)->source, &source, sizeof(source))) { (*slot)->seen = 1; continue; }
if (*slot) {
struct stun_probe* old = *slot;
*slot = old->next;
destroy_probe(old);
struct stun_probe* p = mod->probes;
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->source = source;
} else if (!policy_equal(&p->source, &source)) p->scan_conflict = 1;
p->seen = 1; p->members++;
continue;
}
struct stun_probe* p = u_calloc(1, sizeof(*p));
p = u_calloc(1, sizeof(*p));
if (!p) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "STUN source=%s probe allocation failed", source.name); continue; }
p->owner = mod;
p->source = source;
p->seen = 1;
p->seen = 1; p->members = 1; p->changed = 1;
p->ports = mod->ports;
while (p->ports && !key_equal(&p->ports->key, &source.key)) p->ports = p->ports->next;
if (!p->ports) {
p->ports = u_calloc(1, sizeof(*p->ports));
if (!p->ports) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "STUN port history allocation failed"); u_free(p); continue; }
p->ports->key = source.key; p->ports->next = mod->ports; mod->ports = p->ports;
}
p->fd = SOCKET_INVALID;
p->next = mod->probes;
mod->probes = p;
restart_probe(p);
}
struct stun_probe** slot = &mod->probes;
while (*slot) {
if ((*slot)->seen) { slot = &(*slot)->next; continue; }
if ((*slot)->seen) {
struct stun_probe* p = *slot;
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; }
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,
p->members, (unsigned long long)p->generation, p->conflict);
} else if (p->result.status != STUN_NAT_PENDING && get_time_tb() >= p->due) restart_probe(p);
slot = &p->next; continue;
}
struct stun_probe* p = *slot;
*slot = p->next;
destroy_probe(p);
@ -716,6 +795,7 @@ void stun_nat_destroy(struct UTUN_INSTANCE* inst) {
inst->stun_nat = NULL;
if (mod->scan_timer) uasync_cancel_timeout(inst->ua, mod->scan_timer);
while (mod->probes) { struct stun_probe* next = mod->probes->next; destroy_probe(mod->probes); mod->probes = next; }
while (mod->ports) { struct stun_ports* next = mod->ports->next; u_free(mod->ports); mod->ports = next; }
DEBUG_INFO(DEBUG_CATEGORY_NAT, "STUN destroyed; test sockets/DNS/timers released");
u_free(mod);
}
@ -725,14 +805,34 @@ void stun_nat_recheck(struct UTUN_INSTANCE* inst) {
struct STUN_NAT* mod = inst->stun_nat;
mod->next_transaction_tb = get_time_tb();
DEBUG_INFO(DEBUG_CATEGORY_NAT, "STUN rechecking interfaces using fresh test ports");
for (struct stun_probe* p = mod->probes; p; p = p->next) restart_probe(p);
scan_sources(mod);
for (struct stun_probe* p = mod->probes; p; p = p->next) {
memset(&p->cached, 0, sizeof(p->cached));
if (random_id(&p->generation)) continue;
restart_probe(p);
}
}
int stun_nat_get_result(const struct UTUN_INSTANCE* inst, const char* socket_name, struct stun_nat_result* result) {
if (!inst || !inst->stun_nat || !socket_name || !result) return -1;
for (const struct ETCP_SOCKET* socket = inst->etcp_sockets; socket; socket = socket->next)
if (!strcmp(socket->name, socket_name)) return stun_nat_get_socket_result(inst, socket, result);
return -1;
}
int stun_nat_get_socket_result(const struct UTUN_INSTANCE* inst, const struct ETCP_SOCKET* socket, struct stun_nat_result* result) {
if (!inst || !inst->stun_nat || !socket || !result || socket->instance != inst ||
socket->is_tcp || socket->probe_only || socket->socks_udp || socket->only_local) return -1;
struct stun_source source;
if (source_snapshot(socket, &source)) return -1;
for (const struct stun_probe* p = inst->stun_nat->probes; p; p = p->next) {
if (!strcmp(p->source.name, socket_name)) { *result = p->result; return 0; }
if (!key_equal(&p->source.key, &source.key) || !policy_equal(&p->source, &source)) continue;
uint64_t now = get_time_tb();
int fresh = p->cached.measured_at_tb && now >= p->cached.measured_at_tb && now - p->cached.measured_at_tb < STUN_TTL_TB;
*result = fresh ? p->cached : p->result;
result->refreshing = fresh && p->result.status == STUN_NAT_PENDING;
result->members = p->members;
return 0;
}
return -1;
}

14
src/transport_layer/stun_nat.h

@ -20,16 +20,25 @@ extern "C" {
struct UTUN_INSTANCE;
struct STUN_NAT;
struct ETCP_SOCKET;
/* Ключ пути не содержит порт/имя рабочего сокета. family — 4 или 6. */
struct stun_nat_key { uint8_t family, address[16]; uint32_t netif_index; };
enum stun_nat_status { STUN_NAT_PENDING, STUN_NAT_COMPLETE, STUN_NAT_PARTIAL, STUN_NAT_FAILED, STUN_NAT_SKIPPED };
enum stun_nat_mapping { STUN_MAP_UNKNOWN, STUN_MAP_MATCH, STUN_MAP_DEPENDENT, STUN_MAP_EIM, STUN_MAP_ADM, STUN_MAP_APDM };
enum stun_nat_filtering { STUN_FILTER_UNKNOWN, STUN_FILTER_EIF, STUN_FILTER_ADF, STUN_FILTER_APDF };
enum stun_nat_reason {
STUN_REASON_NONE, STUN_REASON_UNSUPPORTED, STUN_REASON_TRAFFIC, STUN_REASON_TIMEOUT, STUN_REASON_SERVER_ERROR,
STUN_REASON_UNSTABLE, STUN_REASON_NO_PAIR, STUN_REASON_DNS, STUN_REASON_IO, STUN_REASON_SOCKET, STUN_REASON_NOMEM
STUN_REASON_UNSTABLE, STUN_REASON_NO_PAIR, STUN_REASON_DNS, STUN_REASON_IO, STUN_REASON_SOCKET, STUN_REASON_NOMEM,
STUN_REASON_ROUTE, STUN_REASON_PORTS
};
struct stun_nat_result {
struct stun_nat_key key;
uint64_t generation, evidence_id, measured_at_us;
unsigned members;
int refreshing;
enum stun_nat_status status;
enum stun_nat_mapping mapping;
enum stun_nat_filtering filtering;
@ -50,6 +59,9 @@ void stun_nat_destroy(struct UTUN_INSTANCE* inst);
void stun_nat_recheck(struct UTUN_INSTANCE* inst);
/* Копирует результат для имени исходного сокета; -1 если модуль выключен/имя отсутствует. */
int stun_nat_get_result(const struct UTUN_INSTANCE* inst, const char* socket_name, struct stun_nat_result* result);
/* Проверяет принадлежность текущего рабочего сокета группе и её routing options.
* Во время refresh возвращает ещё действующий кэш; время кэша не омолаживается. */
int stun_nat_get_socket_result(const struct UTUN_INSTANCE* inst, const struct ETCP_SOCKET* socket, struct stun_nat_result* result);
const char* stun_nat_mapping_name(enum stun_nat_mapping value);
const char* stun_nat_filtering_name(enum stun_nat_filtering value);
const char* stun_nat_reason_name(enum stun_nat_reason value);

Loading…
Cancel
Save