You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
448 lines
19 KiB
448 lines
19 KiB
/** |
|
* @file nat_detection.c |
|
* @brief NAT-детекция: STUN-like проверка через третьего пира. |
|
* |
|
* Самодостаточный модуль: |
|
* - Сам биндится на ETCP_ID_NAT_DETECTION (0x02) и принимает свои пакеты |
|
* - Сам подписывается на conn_up/conn_down новых ETCP-соединений |
|
* - Ищет третий узел прямо в instance->connections (без привязки к BGP-группе) |
|
*/ |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <stdio.h> |
|
#ifdef _WIN32 |
|
#include <winsock2.h> |
|
#include <ws2tcpip.h> |
|
#else |
|
#include <arpa/inet.h> |
|
#endif |
|
#include "../lib/platform_compat.h" |
|
#include "../lib/debug_config.h" |
|
#include "../lib/mem.h" |
|
#include "utun_instance.h" |
|
#include "etcp_api.h" |
|
#include "etcp.h" |
|
#include "etcp_connections.h" |
|
#include "config_parser.h" |
|
#include "topo_node.h" |
|
#include "topo_group.h" |
|
#include "route_ping.h" |
|
#include "nat_detection.h" |
|
#include "../lib/u_async.h" |
|
|
|
|
|
/* ====== Internal NAT check logic ====== */ |
|
|
|
static struct ETCP_CONN* nat_check_find_third_node(struct NAT_DETECTION* nd, struct ETCP_CONN* exclude) { |
|
if (!nd || !nd->inst || !nd->inst->connections) return NULL; |
|
struct ll_entry* e = nd->inst->connections->head; |
|
while (e) { |
|
struct conn_queue_entry* ce = (struct conn_queue_entry*)e->data; |
|
if (ce->conn && ce->conn != exclude && ce->conn->links) |
|
return ce->conn; |
|
e = e->next; |
|
} |
|
return NULL; |
|
} |
|
|
|
static void nat_link_check_cb(int success, uint16_t avg_rtt, uint8_t count_sent, uint8_t count_ok, void* arg) { |
|
(void)avg_rtt; (void)count_sent; (void)count_ok; |
|
struct nat_check_arg* na = (struct nat_check_arg*)arg; |
|
if (!na || !na->link) { u_free(na); return; } |
|
struct ETCP_LINK* link = na->link; |
|
if (link->nat_check_status != NAT_CHECK_IN_PROGRESS) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "nat check cb: link status=%d, skip (conn down?)", link->nat_check_status); |
|
u_free(na); return; |
|
} |
|
link->nat_type = success ? NAT_TYPE_EIM : NAT_TYPE_STRICT; |
|
link->nat_check_status = success ? NAT_CHECK_EIM : NAT_CHECK_STRICT; |
|
if (link->etcp) |
|
nat_detection_send_nat_info(link->etcp->instance->nat_det, |
|
link->etcp, link->remote_socket_id, |
|
na->nat_ip, na->nat_port, link->nat_type); |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT check result: type=%s for conn=%s nat=%s:%u", |
|
success ? "EIM" : "STRICT", link->etcp->log_name, |
|
ip_to_str(&na->nat_ip, AF_INET).str, na->nat_port); |
|
u_free(na); |
|
} |
|
|
|
static void nat_detection_start_link_check(struct NAT_DETECTION* nd, struct ETCP_LINK* link) { |
|
if (!nd || !link || !link->conn || !link->etcp) return; |
|
if (link->nat_type == NAT_TYPE_DIRECT) return; |
|
if (link->nat_check_status == NAT_CHECK_IN_PROGRESS) return; |
|
if (link->conn->type == CFG_SERVER_TYPE_PRIVATE || link->conn->type == CFG_SERVER_TYPE_LOCAL) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "skip nat check for private/local socket link"); return; |
|
} |
|
struct ETCP_CONN* third_conn = nat_check_find_third_node(nd, link->etcp); |
|
if (!third_conn) { DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "no third node found for link"); return; } |
|
|
|
uint32_t target_ip; uint16_t target_port; |
|
if (link->nat_ip != 0 && link->nat_port != 0) { |
|
target_ip = link->nat_ip; target_port = link->nat_port; |
|
} else { |
|
struct sockaddr_in* sin = (struct sockaddr_in*)&link->remote_addr; |
|
target_ip = sin->sin_addr.s_addr; target_port = ntohs(sin->sin_port); |
|
} |
|
if (!nd->allow_nat_check_local && is_local_subnet(target_ip)) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "local connection: %s, skip nat check", |
|
ip_to_str(&target_ip, AF_INET).str); return; |
|
} |
|
|
|
struct TOPO_GROUP* group = nd->inst->topo_groups |
|
? topo_groups_get_default(nd->inst->topo_groups) : NULL; |
|
struct nat_check_arg* arg = u_calloc(1, sizeof(struct nat_check_arg)); |
|
if (!arg) return; |
|
arg->link = link; arg->nat_ip = target_ip; arg->nat_port = target_port; |
|
const uint8_t* pubkey = link->etcp->crypto_ctx.peer_key_set ? link->etcp->crypto_ctx.peer_public_key : NULL; |
|
if (!pubkey) { u_free(arg); DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "no peer pubkey for link=%p", (void*)link); return; } |
|
int ret = route_ping_send_req_addr(nd, group, third_conn, target_ip, target_port, |
|
3, 500, 1000, 5000, nat_link_check_cb, arg, pubkey); |
|
if (ret == 0) { |
|
link->nat_check_status = NAT_CHECK_IN_PROGRESS; |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "ping remote request via=%s (%s:%d)", |
|
third_conn->log_name, ip_to_str(&target_ip, AF_INET).str, target_port); |
|
} else { |
|
u_free(arg); |
|
DEBUG_WARN(DEBUG_CATEGORY_BGP, "failed to start for link=%s (%s:%d)", |
|
third_conn->log_name, ip_to_str(&target_ip, AF_INET).str, target_port); |
|
} |
|
} |
|
|
|
|
|
/* ====== Handle incoming NAT messages (static, dispatched from receive_cbk) ====== */ |
|
|
|
static void nat_detection_handle_nat_info(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, |
|
struct ETCP_CONN* from_conn, |
|
const uint8_t* data, size_t len) { |
|
if (!nd || !from_conn || !data || len < sizeof(struct NATDET_MSG_NAT_INFO)) return; |
|
|
|
const struct NATDET_MSG_NAT_INFO* info = (const struct NATDET_MSG_NAT_INFO*)data; |
|
uint32_t nat_ip = (info->nat_ip[0] << 24) | (info->nat_ip[1] << 16) |
|
| (info->nat_ip[2] << 8) | info->nat_ip[3]; |
|
uint16_t nat_port = ntohs(info->nat_port); |
|
uint8_t socket_id = info->socket_id; |
|
|
|
uint8_t verified_type; |
|
if (info->nat_type == NAT_TYPE_EIM) verified_type = NAT_VERIFIED_EIM; |
|
else if (info->nat_type == NAT_TYPE_STRICT) verified_type = NAT_VERIFIED_STRICT; |
|
else if (info->nat_type == NAT_TYPE_DIRECT) verified_type = NAT_VERIFIED_DIRECT; |
|
else verified_type = NAT_VERIFIED_UNKNOWN; |
|
|
|
if (group && group->local_node) { |
|
struct TOPO_NODE* ni = topo_node_registry_find(group->instance->topo_groups, group->local_node->node_id); |
|
if (!ni) return; |
|
int data_changed = 0; |
|
struct TOPO_SOCKMETA4* sm = ni->v4_sock_meta; |
|
while (sm) { |
|
if (sm->id == socket_id) { |
|
if (sm->nat_type != verified_type) { sm->nat_type = verified_type; data_changed = 1; } |
|
break; |
|
} |
|
sm = sm->next; |
|
} |
|
|
|
struct ETCP_SOCKET* es = group->instance->etcp_sockets; |
|
while (es) { |
|
if (es->sock_id == socket_id) { |
|
if (verified_type == NAT_VERIFIED_STRICT) { |
|
struct sockaddr_in* nat_sin = (struct sockaddr_in*)&es->nat_addr; |
|
nat_sin->sin_family = AF_INET; |
|
nat_sin->sin_addr.s_addr = nat_ip; |
|
nat_sin->sin_port = htons(nat_port); |
|
es->nat_type = verified_type; |
|
} else { |
|
struct sockaddr_in* nat_sin = (struct sockaddr_in*)&es->nat_addr; |
|
uint32_t old_nat_ip = nat_sin->sin_addr.s_addr; |
|
uint16_t old_nat_port = ntohs(nat_sin->sin_port); |
|
nat_sin->sin_family = AF_INET; |
|
nat_sin->sin_addr.s_addr = nat_ip; |
|
nat_sin->sin_port = htons(nat_port); |
|
if (old_nat_ip != nat_ip || old_nat_port != nat_port) data_changed = 1; |
|
es->nat_type = verified_type; |
|
} |
|
break; |
|
} |
|
es = es->next; |
|
} |
|
|
|
if (data_changed) { |
|
int prev_v4a = topo_list_count((struct _topo_head*)ni->v4_addrs); |
|
topo_group_update_my_nodeinfo(group->instance, group); |
|
if (topo_list_count((struct _topo_head*)ni->v4_addrs) != prev_v4a) { |
|
group->local_node->dirty = 1; |
|
ni->ver = (ni->ver % 255) + 1; |
|
group->local_node->last_ver = ni->ver; |
|
} |
|
if (topo_list_count((struct _topo_head*)ni->v4_addrs) == prev_v4a) { |
|
struct TOPO_ADDR4* a = ni->v4_addrs; |
|
while (a) { |
|
if (a->type == TOPO_ADDR_INTERFACE && a->socket_id == socket_id) { |
|
uint32_t a_ip; memcpy(&a_ip, a->addr, 4); |
|
if (a_ip == nat_ip && a->port == nat_port) { |
|
a->type = TOPO_ADDR_NAT; a->socket_id = socket_id | 1; |
|
group->local_node->dirty = 1; |
|
ni->ver = (ni->ver % 255) + 1; |
|
group->local_node->last_ver = ni->ver; |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT_INFO matched interface addr, updated entry type to NAT sock=%d", socket_id); |
|
} |
|
break; |
|
} |
|
a = a->next; |
|
} |
|
} |
|
if (group->local_node->dirty && group->senders_list) { |
|
struct ll_entry* se = group->senders_list->head; |
|
while (se) { |
|
struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)se->data; |
|
if (item && item->conn) |
|
topo_group_send_nodeinfo(group, group->local_node, item->conn, 0); |
|
se = se->next; |
|
} |
|
} |
|
} |
|
} else { |
|
struct ETCP_SOCKET* es = nd->inst->etcp_sockets; |
|
while (es) { |
|
if (es->sock_id == socket_id) { |
|
struct sockaddr_in* nat_sin = (struct sockaddr_in*)&es->nat_addr; |
|
nat_sin->sin_family = AF_INET; |
|
nat_sin->sin_addr.s_addr = nat_ip; |
|
nat_sin->sin_port = htons(nat_port); |
|
es->nat_type = verified_type; |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT_INFO socket=%s id=%d type=%d addr=%s:%u (no topo_group)", |
|
es->name, socket_id, verified_type, |
|
ip_to_str(&nat_ip, AF_INET).str, nat_port); |
|
break; |
|
} |
|
es = es->next; |
|
} |
|
} |
|
|
|
struct ETCP_LINK* l = from_conn->links; |
|
while (l) { l->nat_type = info->nat_type; l = l->next; } |
|
} |
|
|
|
static void nat_detection_handle_nat_check_req(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, |
|
struct ETCP_CONN* from_conn, |
|
const uint8_t* data, size_t len) { |
|
if (!nd || !from_conn || !data || len < sizeof(struct NATDET_MSG_NAT_CHECK_REQ)) return; |
|
(void)group; |
|
const struct NATDET_MSG_NAT_CHECK_REQ* req = (const struct NATDET_MSG_NAT_CHECK_REQ*)data; |
|
uint8_t socket_id = req->socket_id; |
|
struct ETCP_LINK* target_link = NULL; |
|
struct ETCP_LINK* l = from_conn->links; |
|
while (l) { |
|
if (l->remote_socket_id == socket_id) { target_link = l; break; } |
|
l = l->next; |
|
} |
|
if (!target_link) return; |
|
if (target_link->nat_check_status == NAT_CHECK_IN_PROGRESS) return; |
|
nat_detection_start_link_check(nd, target_link); |
|
} |
|
|
|
|
|
/* ====== ETCP callbacks (self-registering) ====== */ |
|
|
|
static void nat_detection_trigger_checks(struct NAT_DETECTION* nd); |
|
|
|
static void nat_detection_on_conn_up(struct ETCP_CONN* conn, int event, void* arg) { (void)event; |
|
struct NAT_DETECTION* nd = (struct NAT_DETECTION*)arg; |
|
if (!nd) return; |
|
nat_detection_trigger_checks(nd); |
|
} |
|
|
|
static void nat_detection_on_conn_down(struct ETCP_CONN* conn, int event, void* arg) { (void)event; |
|
struct NAT_DETECTION* nd = (struct NAT_DETECTION*)arg; |
|
if (!nd || !conn) return; |
|
route_ping_cancel_for_conn(nd, conn); |
|
struct ETCP_LINK* lnk = conn->links; |
|
while (lnk) { |
|
if (lnk->nat_check_status == NAT_CHECK_IN_PROGRESS) lnk->nat_check_status = NAT_CHECK_NONE; |
|
lnk = lnk->next; |
|
} |
|
} |
|
|
|
static void nat_detection_new_conn_cbk(struct ETCP_CONN* conn, void* arg) { |
|
if (!conn || !conn->instance) return; |
|
struct NAT_DETECTION* nd = (struct NAT_DETECTION*)arg; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT detection set callbacks: %s", conn->log_name); |
|
etcp_conn_add_cbk(conn, nat_detection_on_conn_up, nd, ETCP_CBK_EVENT_UP); |
|
etcp_conn_add_cbk(conn, nat_detection_on_conn_down, nd, ETCP_CBK_EVENT_DOWN); |
|
} |
|
|
|
|
|
/* ====== Receive callback (self-binded on ETCP_ID_NAT_DETECTION) ====== */ |
|
|
|
static const char* nat_subcmd_name(uint8_t subcmd) { |
|
switch (subcmd) { |
|
case NATDET_SUBCMD_PING_REQ: return "PING_REQ"; |
|
case NATDET_SUBCMD_PING_RESP: return "PING_RESP"; |
|
case NATDET_SUBCMD_NAT_INFO: return "NAT_INFO"; |
|
case NATDET_SUBCMD_NAT_CHECK_REQ: return "NAT_CHECK_REQ"; |
|
default: return "?"; |
|
} |
|
} |
|
|
|
static void nat_detection_receive_cbk(struct ETCP_CONN* from_conn, struct ll_entry* entry) { |
|
if (!from_conn || !entry || entry->len < 2) { |
|
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); } return; |
|
} |
|
struct UTUN_INSTANCE* instance = from_conn->instance; |
|
if (!instance || !instance->nat_det) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid instance/nat_det"); |
|
queue_dgram_free(entry); queue_entry_free(entry); return; |
|
} |
|
struct TOPO_GROUP* group = instance->topo_groups |
|
? topo_groups_get_default(instance->topo_groups) : NULL; |
|
struct NAT_DETECTION* nd = instance->nat_det; |
|
uint8_t* data = entry->dgram; uint8_t subcmd = data[1]; |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT recv %s from %s len=%zu group=%s", |
|
nat_subcmd_name(subcmd), from_conn->log_name, entry->len, group ? "yes" : "no"); |
|
|
|
if (subcmd == NATDET_SUBCMD_PING_REQ) |
|
route_ping_handle_req(nd, group, from_conn, data, entry->len); |
|
else if (subcmd == NATDET_SUBCMD_PING_RESP) |
|
route_ping_handle_resp(nd, from_conn, data, entry->len); |
|
else if (subcmd == NATDET_SUBCMD_NAT_INFO) |
|
nat_detection_handle_nat_info(nd, group, from_conn, data, entry->len); |
|
else if (subcmd == NATDET_SUBCMD_NAT_CHECK_REQ) |
|
nat_detection_handle_nat_check_req(nd, group, from_conn, data, entry->len); |
|
|
|
queue_dgram_free(entry); queue_entry_free(entry); |
|
} |
|
|
|
|
|
/* ====== NAT check trigger ====== */ |
|
|
|
static void nat_detection_trigger_checks(struct NAT_DETECTION* nd) { |
|
if (!nd || !nd->inst) return; |
|
struct ll_entry* entry = nd->inst->connections->head; |
|
while (entry) { |
|
struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data; |
|
struct ETCP_LINK* l = ce->conn->links; |
|
while (l) { |
|
if (l->initialized && l->conn && l->nat_check_status < NAT_CHECK_IN_PROGRESS) |
|
nat_detection_start_link_check(nd, l); |
|
l = l->next; |
|
} |
|
entry = entry->next; |
|
} |
|
} |
|
|
|
|
|
/* ====== Lifecycle ====== */ |
|
|
|
struct NAT_DETECTION* nat_detection_create(struct UTUN_INSTANCE* inst) { |
|
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "nat_detection_create: inst is NULL"); return NULL; } |
|
struct NAT_DETECTION* nd = u_calloc(1, sizeof(struct NAT_DETECTION)); |
|
if (!nd) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "nat_detection_create: alloc failed"); return NULL; } |
|
nd->inst = inst; |
|
nd->next_ping_req_id = 1; |
|
nd->allow_nat_check_local = 0; |
|
etcp_bind(inst, ETCP_ID_NAT_DETECTION, nat_detection_receive_cbk); |
|
etcp_add_new_conn_cbk(inst, nat_detection_new_conn_cbk, nd); |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT detection initialized"); |
|
return nd; |
|
} |
|
|
|
void nat_detection_destroy(struct NAT_DETECTION* nd) { |
|
if (!nd) return; |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT detection destroy"); |
|
etcp_remove_new_conn_cbk(nd->inst, nat_detection_new_conn_cbk, nd); |
|
etcp_unbind(nd->inst, ETCP_ID_NAT_DETECTION); |
|
route_ping_destroy_pending(nd); |
|
u_free(nd); |
|
} |
|
|
|
|
|
/* ====== Link lifecycle ====== */ |
|
|
|
void nat_detection_link_ready(struct NAT_DETECTION* nd, struct ETCP_LINK* link) { |
|
if (!nd || !link) return; |
|
if (link->nat_check_status < NAT_CHECK_IN_PROGRESS) |
|
nat_detection_start_link_check(nd, link); |
|
} |
|
|
|
void nat_detection_cancel_for_conn(struct NAT_DETECTION* nd, struct ETCP_CONN* conn) { |
|
if (!nd || !conn) return; |
|
route_ping_cancel_for_conn(nd, conn); |
|
} |
|
|
|
|
|
/* ====== Send NAT messages ====== */ |
|
|
|
void nat_detection_send_nat_info(struct NAT_DETECTION* nd, struct ETCP_CONN* to_conn, |
|
uint8_t socket_id, uint32_t nat_ip, uint16_t nat_port, |
|
uint8_t nat_type) { |
|
(void)nd; |
|
if (!to_conn) return; |
|
struct NATDET_MSG_NAT_INFO* pkt = u_calloc(1, sizeof(struct NATDET_MSG_NAT_INFO)); |
|
if (!pkt) return; |
|
pkt->cmd = ETCP_ID_NAT_DETECTION; pkt->subcmd = NATDET_SUBCMD_NAT_INFO; |
|
pkt->socket_id = socket_id; |
|
pkt->nat_ip[0] = (nat_ip >> 24) & 0xFF; pkt->nat_ip[1] = (nat_ip >> 16) & 0xFF; |
|
pkt->nat_ip[2] = (nat_ip >> 8) & 0xFF; pkt->nat_ip[3] = nat_ip & 0xFF; |
|
pkt->nat_port = htons(nat_port); pkt->nat_type = nat_type; |
|
struct ll_entry* e = queue_entry_new(0); |
|
if (!e) { u_free(pkt); return; } |
|
e->dgram = (uint8_t*)pkt; e->len = sizeof(struct NATDET_MSG_NAT_INFO); |
|
if (etcp_send(to_conn, e) != 0) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "etcp_send NAT_INFO failed"); |
|
u_free(pkt); queue_entry_free(e); |
|
} |
|
} |
|
|
|
static void nat_detection_send_nat_check_req(struct NAT_DETECTION* nd, struct ETCP_CONN* to_conn, |
|
uint8_t socket_id) { |
|
(void)nd; |
|
if (!to_conn) return; |
|
struct NATDET_MSG_NAT_CHECK_REQ* pkt = u_calloc(1, sizeof(struct NATDET_MSG_NAT_CHECK_REQ)); |
|
if (!pkt) return; |
|
pkt->cmd = ETCP_ID_NAT_DETECTION; pkt->subcmd = NATDET_SUBCMD_NAT_CHECK_REQ; |
|
pkt->socket_id = socket_id; |
|
struct ETCP_LINK* l = to_conn->links; |
|
while (l) { |
|
if (l->remote_socket_id == socket_id) { |
|
struct sockaddr_in* sin = (struct sockaddr_in*)&l->conn->interface_addr; |
|
if (sin->sin_family == AF_INET) { |
|
pkt->interface_ip = sin->sin_addr.s_addr; |
|
pkt->interface_port = sin->sin_port; |
|
} break; |
|
} |
|
l = l->next; |
|
} |
|
struct ll_entry* e = queue_entry_new(0); |
|
if (!e) { u_free(pkt); return; } |
|
e->dgram = (uint8_t*)pkt; e->len = sizeof(struct NATDET_MSG_NAT_CHECK_REQ); |
|
if (etcp_send(to_conn, e) != 0) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "etcp_send NAT_CHECK_REQ failed"); |
|
u_free(pkt); queue_entry_free(e); |
|
} |
|
} |
|
|
|
|
|
/* ====== Control API ====== */ |
|
|
|
void nat_detection_request_check_all(struct NAT_DETECTION* nd) { |
|
if (!nd || !nd->inst) return; |
|
struct ll_entry* entry = nd->inst->connections->head; |
|
while (entry) { |
|
struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data; |
|
struct ETCP_LINK* link = ce->conn->links; |
|
while (link) { |
|
if (link->nat_check_status < NAT_CHECK_IN_PROGRESS) |
|
nat_detection_start_link_check(nd, link); |
|
link = link->next; |
|
} |
|
entry = entry->next; |
|
} |
|
} |
|
|
|
|
|
/* ====== Test helper ====== */ |
|
|
|
void nat_detection_set_allow_local(struct NAT_DETECTION* nd, int allow) { |
|
if (!nd) return; |
|
nd->allow_nat_check_local = allow ? 1 : 0; |
|
}
|
|
|