|
|
|
|
@ -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; |
|
|
|
|
} |
|
|
|
|
|