diff --git a/src/etcp_connections.c b/src/etcp_connections.c index 995df57f..5ab2ec96 100644 --- a/src/etcp_connections.c +++ b/src/etcp_connections.c @@ -11,6 +11,7 @@ #include "config_parser.h" #include "crc32.h" #include "etcp.h" +#include "route_node.h" #include "../lib/memory_pool.h" #include "../lib/u_async.h" #include "../lib/debug_config.h" @@ -84,6 +85,7 @@ static void etcp_link_send_init(struct ETCP_LINK* link, uint8_t reset) { dgram->data[offset++] = (link->recovery_interval/100) & 0xFF; dgram->data[offset++] = link->local_link_id; + dgram->data[offset++] = link->conn ? link->conn->sock_id : 0; // padding int s = rand() % (link->handshake_maxsize - link->handshake_minsize) + link->handshake_minsize; @@ -544,6 +546,8 @@ struct ETCP_SOCKET* etcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_S e_sock->errorcode = 0; e_sock->pkt_format_errors = 0; e_sock->type = type; + e_sock->sock_id = instance->next_socket_id++; + e_sock->nat_type = NAT_TYPE_UNKNOWN; e_sock->mtu = mtu; e_sock->loss_rate = loss_rate; DEBUG_INFO(DEBUG_CATEGORY_BGP, "Add Socket type=%d", type); @@ -1155,6 +1159,7 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) { uint8_t keepalive[2]; uint8_t recovery[2]; uint8_t link_id; + uint8_t remote_socket_id; uint8_t pubkey[SC_PUBKEY_SIZE]; } *ack_hdr=(void*)&pkt->data[0]; uint64_t peer_id = be64toh(*(uint64_t*)ack_hdr->id); @@ -1280,6 +1285,7 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) { // Link exists - reuse it for recovery link->remote_link_id = ack_hdr->link_id; + link->remote_socket_id = ack_hdr->remote_socket_id; // For CHANNEL_INIT (0x04): if link already initialized - no reset, otherwise reset // For INIT_REQUEST (0x02): always reset @@ -1302,6 +1308,7 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) { link = etcp_link_new(conn, e_sock, &addr, 1); if (!link) { if (new_conn) etcp_connection_close(conn); errorcode=66; DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "etcp_connections_read_callback: failed to create link for connection"); goto ec_fr; }// облом link->remote_link_id = ack_hdr->link_id; + link->remote_socket_id = ack_hdr->remote_socket_id; // For new links: INIT_REQUEST (0x02) causes reset, CHANNEL_INIT (0x04) does not if (ack_hdr->code == ETCP_INIT_REQUEST || new_conn) { @@ -1323,6 +1330,7 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) { uint8_t id[8]; uint8_t mtu[2]; uint8_t link_id; + uint8_t remote_socket_id; uint8_t peer_ipv4[4]; uint8_t peer_port[2]; } *ack_repl_hdr=(void*)&pkt->data[0]; @@ -1340,6 +1348,7 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) { ack_repl_hdr->mtu[0]=link->mtu_local>>8; ack_repl_hdr->mtu[1]=link->mtu_local; ack_repl_hdr->link_id = link->local_link_id; + ack_repl_hdr->remote_socket_id = ack_hdr->remote_socket_id; // Add client's IP:port (so client behind NAT can know its external address) if (addr.ss_family == AF_INET) { struct sockaddr_in *sin = (struct sockaddr_in*)&addr; @@ -1347,10 +1356,14 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) { uint16_t port = ntohs(sin->sin_port); ack_repl_hdr->peer_port[0] = port >> 8; ack_repl_hdr->peer_port[1] = port & 0xFF; + link->nat_ip = ntohl(sin->sin_addr.s_addr); + link->nat_port = port; } else { // For IPv6, set to 0 (not supported for NAT traversal) memset(ack_repl_hdr->peer_ipv4, 0, 4); memset(ack_repl_hdr->peer_port, 0, 2); + link->nat_ip = 0; + link->nat_port = 0; } pkt->noencrypt_len=0; pkt->link=link; @@ -1430,9 +1443,10 @@ process_decrypted: link->mtu_remote = (pkt->data[offset++] << 8) | pkt->data[offset++]; link->mtu = link->mtu_local < link->mtu_remote ? link->mtu_local : link->mtu_remote; link->remote_link_id = pkt->data[offset++]; - + link->remote_socket_id = pkt->data[offset++]; + // Parse NAT IP:port from response (new format includes 4+2 bytes) - if (pkt_len >= 18) { + if (pkt_len >= 19) { uint32_t new_nat_ip = (pkt->data[offset] << 24) | (pkt->data[offset+1] << 16) | (pkt->data[offset+2] << 8) | pkt->data[offset+3]; offset += 4; diff --git a/src/etcp_connections.h b/src/etcp_connections.h index 44808db7..a9d3f109 100644 --- a/src/etcp_connections.h +++ b/src/etcp_connections.h @@ -68,6 +68,8 @@ struct ETCP_SOCKET { void* socket_id; // Socket ID from uasync_add_socket uint8_t type; // CFG_SERVER_TYPE_PUBLIC/NAT/PRIVATE + uint8_t sock_id; // unique socket id (0-255) for NAT matching + uint8_t nat_type; // NAT_TYPE_* (detected by server) uint32_t local_defaultroute_ip; // auto-detected IPv4 for public servers (network byte order) uint8_t local_defaultroute_ip6[16]; // auto-detected IPv6 for public servers }; @@ -93,6 +95,8 @@ struct ETCP_LINK { uint8_t initialized; // Флаг инициализации (1=подтверждено или получен request) uint8_t local_link_id; // id моего линка uint8_t remote_link_id; // id этого линка на peer (устанавливается в момент initialized) + uint8_t remote_socket_id; // socket id peer + uint8_t nat_type; // NAT_TYPE_* (detected for this client link) uint8_t recv_keepalive; // 1 - up, 0 - down (принимаются ли пакеты) uint8_t remote_keepalive; // 1 - up, 0 - down (удаленная сторона сообщает - принимаются ли у нее пакеты) uint8_t link_status; // 1 - up, 0 - down (итоговый статус - если есть проблемы на любой стороне - линк down) diff --git a/src/route_bgp.c b/src/route_bgp.c index efa5359b..c66ada43 100644 --- a/src/route_bgp.c +++ b/src/route_bgp.c @@ -155,6 +155,29 @@ static void route_bgp_receive_cbk(struct ETCP_CONN* from_conn, struct ll_entry* route_ping_handle_req(bgp, from_conn, data, entry->len); } else if (subcmd == ROUTE_SUBCMD_PING_RESP) { route_ping_handle_resp(bgp, from_conn, data, entry->len); + } else if (subcmd == ROUTE_SUBCMD_NAT_INFO) { + if (entry->len >= sizeof(struct BGP_NAT_INFO)) { + const struct BGP_NAT_INFO* info = (const struct BGP_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 = ((info->nat_port >> 8) & 0xFF) | ((info->nat_port & 0xFF) << 8); + uint16_t nat_port_be = info->nat_port; + // Save nat_type to all links of this connection and their sockets + struct ETCP_LINK* l = from_conn->links; + while (l) { + l->nat_type = info->nat_type; + if (l->conn) { + l->conn->nat_type = info->nat_type; + } + l = l->next; + } + DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT_INFO from %s: type=%s ip=%u.%u.%u.%u port=%u", + from_conn->log_name, + info->nat_type == NAT_TYPE_OPEN ? "OPEN" : "RESTRICTED", + (info->nat_ip[0]), (info->nat_ip[1]), + (info->nat_ip[2]), (info->nat_ip[3]), + (unsigned)nat_port); + } } queue_dgram_free(entry); @@ -300,6 +323,8 @@ void route_bgp_destroy(struct UTUN_INSTANCE* instance) { instance->bgp = NULL; } +static void route_bgp_start_nat_check(struct ROUTE_BGP* bgp, struct NODEINFO_Q* node, struct ETCP_CONN* via_conn); + void route_bgp_new_conn(struct ETCP_CONN* conn) { if (!conn) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_new_conn: conn is NULL"); @@ -322,6 +347,18 @@ void route_bgp_new_conn(struct ETCP_CONN* conn) { route_bgp_add_to_senders(bgp, conn); + // Try to start NAT checks for nodes waiting for a third node + if (bgp->nodes) { + struct ll_entry* e = bgp->nodes->head; + while (e) { + struct NODEINFO_Q* node = (struct NODEINFO_Q*)e; + if (node->nat_check_status == NAT_CHECK_WAITING) { + route_bgp_start_nat_check(bgp, node, conn); + } + e = e->next; + } + } + route_bgp_send_table_request(bgp, conn); } @@ -520,6 +557,99 @@ int nodeinfo_dyn_size(struct NODEINFO* node) { node->hop_count * 8; } +// NAT check context +struct nat_check_arg { + struct NODEINFO_Q* node; + struct ETCP_CONN* client_conn; + uint32_t nat_ip; + uint16_t nat_port; +}; + +static void nat_check_cb(int success, uint16_t avg_rtt, uint8_t count_sent, uint8_t count_ok, + uint32_t recv_ip, uint16_t recv_port, void* arg) { + (void)avg_rtt; (void)count_sent; (void)count_ok; (void)recv_ip; (void)recv_port; + struct nat_check_arg* na = (struct nat_check_arg*)arg; + if (!na || !na->node) { u_free(na); return; } + uint8_t nat_type = success ? NAT_TYPE_OPEN : NAT_TYPE_RESTRICTED; + na->node->nat_type = nat_type; + na->node->nat_check_status = success ? NAT_CHECK_OPEN : NAT_CHECK_RESTRICTED; + // Save nat_type to all links of client connection + if (na->client_conn) { + struct ETCP_LINK* l = na->client_conn->links; + while (l) { l->nat_type = nat_type; l = l->next; } + } + route_bgp_send_nat_info(na->client_conn, na->nat_ip, na->nat_port, nat_type); + DEBUG_INFO(DEBUG_CATEGORY_BGP, "nat_check_cb: node=%016llx type=%s success=%d", + (unsigned long long)na->node->node.node_id, + success ? "OPEN" : "RESTRICTED", success); + u_free(na); +} + +static struct ETCP_CONN* route_bgp_find_third_node(struct ROUTE_BGP* bgp, struct ETCP_CONN* exclude) { + if (!bgp || !bgp->senders_list) return NULL; + struct ll_entry* e = bgp->senders_list->head; + while (e) { + struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)e->data; + if (item && item->conn && item->conn != exclude) return item->conn; + e = e->next; + } + return NULL; +} + +static void route_bgp_extract_nat_addr(struct ETCP_CONN* conn, uint32_t* nat_ip, uint16_t* nat_port) { + *nat_ip = 0; *nat_port = 0; + if (!conn) return; + struct ETCP_LINK* l = conn->links; + while (l) { + if (l->nat_ip != 0) { + *nat_ip = l->nat_ip; + *nat_port = l->nat_port; + return; + } + l = l->next; + } +} + +static void route_bgp_start_nat_check(struct ROUTE_BGP* bgp, struct NODEINFO_Q* node, struct ETCP_CONN* via_conn) { + if (!bgp || !node || !via_conn) return; + if (node->nat_check_status != NAT_CHECK_NONE && node->nat_check_status != NAT_CHECK_WAITING) return; + // Find client connection + struct ETCP_CONN* client_conn = NULL; + if (node->paths && node->paths->head) { + struct NODEINFO_PATH* path = (struct NODEINFO_PATH*)node->paths->head; + client_conn = path->conn; + } + if (!client_conn) return; + // Extract NAT address from client connection + uint32_t nat_ip; uint16_t nat_port; + route_bgp_extract_nat_addr(client_conn, &nat_ip, &nat_port); + if (nat_ip == 0 || nat_port == 0) return; + // Save NAT address to node + node->nat_ip = nat_ip; + node->nat_port = nat_port; + // Allocate callback arg + struct nat_check_arg* arg = u_calloc(1, sizeof(struct nat_check_arg)); + if (!arg) return; + arg->node = node; + arg->client_conn = client_conn; + arg->nat_ip = nat_ip; + arg->nat_port = nat_port; + // Send ping request via third node (with embedded pubkey for NAT detection) + int ret = route_ping_send_req_addr(bgp, via_conn, node->node.node_id, nat_ip, nat_port, + 3, 500, 1000, 5000, nat_check_cb, arg, + node->node.public_key); + if (ret == 0) { + node->nat_check_status = NAT_CHECK_IN_PROGRESS; + DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_bgp_start_nat_check: started for node=%016llx via=%s", + (unsigned long long)node->node.node_id, via_conn->log_name); + } else { + node->nat_check_status = NAT_CHECK_WAITING; + u_free(arg); + DEBUG_WARN(DEBUG_CATEGORY_BGP, "route_bgp_start_nat_check: failed to start for node=%016llx", + (unsigned long long)node->node.node_id); + } +} + int route_bgp_process_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, const uint8_t* data, size_t len) { if (!bgp || !from || len < sizeof(struct BGP_NODEINFO_PACKET)) return -1; @@ -582,6 +712,18 @@ int route_bgp_process_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, co route_bgp_remove_path_by_hop(nodeinfo1, from->peer_node_id); route_bgp_add_path(nodeinfo1, from, hop_list, nodeinfo1->node.hop_count); + // Start NAT check for direct peers + if (nodeinfo1->node.hop_count == 1 && nodeinfo1->nat_check_status == NAT_CHECK_NONE) { + struct ETCP_CONN* third = route_bgp_find_third_node(bgp, from); + if (third) { + route_bgp_start_nat_check(bgp, nodeinfo1, third); + } else { + nodeinfo1->nat_check_status = NAT_CHECK_WAITING; + DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT check queued (no third node yet) for node=%016llx", + (unsigned long long)node_id); + } + } + if (bgp->instance->rt) { route_insert(bgp->instance->rt, nodeinfo1); } @@ -744,3 +886,30 @@ static void route_bgp_handle_request_table(struct ROUTE_BGP* bgp, struct ETCP_CO route_bgp_send_full_table(bgp, conn); route_bgp_add_to_senders(bgp, conn); } + +void route_bgp_send_nat_info(struct ETCP_CONN* conn, uint32_t nat_ip, uint16_t nat_port, uint8_t nat_type) { + if (!conn) return; + struct BGP_NAT_INFO* pkt = u_calloc(1, sizeof(struct BGP_NAT_INFO)); + if (!pkt) return; + pkt->cmd = ETCP_ID_ROUTE_ENTRY; + pkt->subcmd = ROUTE_SUBCMD_NAT_INFO; + 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 BGP_NAT_INFO); + etcp_send(conn, e); + DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_bgp_send_nat_info to %s: type=%s ip=%08x port=%u", + conn->log_name, + nat_type == NAT_TYPE_OPEN ? "OPEN" : "RESTRICTED", + (unsigned)nat_ip, (unsigned)nat_port); +} diff --git a/src/route_bgp.h b/src/route_bgp.h index a6bd7121..cd834066 100644 --- a/src/route_bgp.h +++ b/src/route_bgp.h @@ -16,6 +16,7 @@ #define ROUTE_SUBCMD_NODEINFO 0x04 // полная информация об узле + маршруты #define ROUTE_SUBCMD_REQUEST_TABLE 0x05 // запрос полной таблицы #define ROUTE_SUBCMD_WITHDRAW 0x06 // узел стал недоступен +#define ROUTE_SUBCMD_NAT_INFO 0x09 // информация о типе NAT клиента #define MAX_HOPS 16 #define BGP_NODES_HASH_SIZE 256 @@ -52,6 +53,17 @@ struct ROUTE_BGP_CONN_ITEM { struct ETCP_CONN* conn; }; +/** + * @brief Пакет NAT_INFO (фиксированный) + */ +struct BGP_NAT_INFO { + uint8_t cmd; + uint8_t subcmd; + uint8_t nat_ip[4]; // network byte order + uint16_t nat_port; // network byte order + uint8_t nat_type; // NAT_TYPE_* +} __attribute__((packed)); + struct route_ping_pending; struct ROUTE_BGP { @@ -145,4 +157,14 @@ int route_bgp_add_path(struct NODEINFO_Q* nq, struct ETCP_CONN* conn, uint64_t* */ int route_bgp_remove_path(struct NODEINFO_Q* nq, struct ETCP_CONN* conn); +/** + * @brief Отправляет NAT_INFO клиенту с его типом NAT. + * + * @param conn соединение к клиенту + * @param nat_ip IP клиента (network byte order) + * @param nat_port порт клиента (network byte order) + * @param nat_type NAT_TYPE_OPEN или NAT_TYPE_RESTRICTED + */ +void route_bgp_send_nat_info(struct ETCP_CONN* conn, uint32_t nat_ip, uint16_t nat_port, uint8_t nat_type); + #endif // ROUTE_BGP_H diff --git a/src/route_node.c b/src/route_node.c index 73153515..cf5aa547 100644 --- a/src/route_node.c +++ b/src/route_node.c @@ -166,7 +166,8 @@ int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BG while (e_sock) { if (e_sock->local_addr.ss_family == AF_INET) { struct sockaddr_in* sin = (struct sockaddr_in*)&e_sock->local_addr; - if (memcmp(sa->addr, &sin->sin_addr.s_addr, 4) != 0 || sa->port != ntohs(sin->sin_port)) { + if (memcmp(sa->addr, &sin->sin_addr.s_addr, 4) != 0 || sa->port != ntohs(sin->sin_port) || + sa->type != e_sock->type || sa->id != e_sock->sock_id) { same = false; break; } @@ -203,6 +204,8 @@ int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BG struct sockaddr_in* sin = (struct sockaddr_in*)&e_sock->local_addr; memcpy(sa->addr, &sin->sin_addr.s_addr, 4); sa->port = ntohs(sin->sin_port); + sa->type = e_sock->type; + sa->id = e_sock->sock_id; sa++; } e_sock = e_sock->next; diff --git a/src/route_node.h b/src/route_node.h index 76bb9c24..f2870e0c 100644 --- a/src/route_node.h +++ b/src/route_node.h @@ -29,6 +29,8 @@ struct NODEINFO { struct NODEINFO_IPV4_SOCKET { uint8_t addr[4];// network byte order uint16_t port; + uint8_t type; // CFG_SERVER_TYPE_PUBLIC/PRIVATE/NAT + uint8_t id; // unique socket id (0-255) } __attribute__((packed)); struct NODEINFO_IPV6_SOCKET { @@ -58,6 +60,18 @@ struct NODEINFO_PATH { uint8_t hop_count; // hop list: маршрут этого path };// __attribute__((packed)); +// NAT check status +#define NAT_CHECK_NONE 0 +#define NAT_CHECK_WAITING 1 +#define NAT_CHECK_IN_PROGRESS 2 +#define NAT_CHECK_OPEN 3 +#define NAT_CHECK_RESTRICTED 4 + +// NAT type +#define NAT_TYPE_UNKNOWN 0 +#define NAT_TYPE_OPEN 1 +#define NAT_TYPE_RESTRICTED 2 + struct NODEINFO_Q { struct ll_entry ll; struct ll_queue* paths; // сюда помещаем struct NODEINFO_PATH @@ -66,6 +80,10 @@ struct NODEINFO_Q { uint64_t last_ping_time; // время последнего замера в 0.1ms uint16_t last_rtt; // лучший RTT в 0.1ms struct ETCP_SOCKET* best_socket; + uint8_t nat_check_status; // NAT_CHECK_* + uint32_t nat_ip; // IP клиента (network byte order) + uint16_t nat_port; // порт клиента + uint8_t nat_type; // NAT_TYPE_* struct NODEINFO node; // Всегда в конце структуры - динамически расширяемый блок };// __attribute__((packed)); diff --git a/src/route_ping.c b/src/route_ping.c index 43b9a3b3..f9d1e2a2 100644 --- a/src/route_ping.c +++ b/src/route_ping.c @@ -27,6 +27,8 @@ struct route_ping_req { uint8_t socket_count; uint8_t completed_count; uint16_t timeout_ms; + uint8_t recv_ipv4[4]; // IP:port с которого получен PING_REQ (STUN-like) + uint16_t recv_port; // network byte order struct route_ping_sock_ctx sockets[0]; }; @@ -72,7 +74,7 @@ static void route_ping_pending_timeout(void* arg) { cur = &(*cur)->next; } if (p->callback) { - p->callback(0, 0, 0, 0, p->arg); + p->callback(0, 0, 0, 0, 0, 0, p->arg); } u_free(p); } @@ -193,6 +195,8 @@ static void route_ping_send_resp(struct route_ping_req* req, uint16_t avg_rtt) { resp->count_sent = total_sent; resp->count_ok = total_ok; resp->avg_rtt = avg_rtt; + memcpy(resp->recv_ipv4, req->recv_ipv4, 4); + resp->recv_port = htons(req->recv_port); struct ll_entry* e = queue_entry_new(0); if (!e) { u_free(resp); @@ -262,6 +266,78 @@ int route_ping_send_req(struct ROUTE_BGP* bgp, struct ETCP_CONN* to_conn, uint64 return 0; } +int route_ping_send_req_addr(struct ROUTE_BGP* bgp, struct ETCP_CONN* to_conn, uint64_t node_id, + uint32_t target_ip, uint16_t target_port, + uint8_t count, uint16_t interval_ms, uint16_t timeout_ms, + uint16_t wait_timeout_ms, route_ping_callback_t cb, void* arg, + const uint8_t* pubkey) { + if (!bgp || !to_conn || count == 0 || timeout_ms == 0) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_ping_send_req_addr: invalid args"); + return -1; + } + size_t extra = 6 + (pubkey ? SC_PUBKEY_SIZE : 0); + size_t pkt_size = sizeof(struct BGP_PING_REQUEST) + extra; + struct BGP_PING_REQUEST* req_pkt = u_calloc(1, pkt_size); + if (!req_pkt) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_ping_send_req_addr: alloc failed"); + return -2; + } + req_pkt->cmd = ETCP_ID_ROUTE_ENTRY; + req_pkt->subcmd = ROUTE_SUBCMD_PING_REQ; + req_pkt->request_id = bgp->next_ping_req_id++; + req_pkt->node_id = node_id; + req_pkt->count = count; + req_pkt->interval_ms = interval_ms; + req_pkt->timeout_ms = timeout_ms; + + uint8_t* tail = (uint8_t*)req_pkt + sizeof(struct BGP_PING_REQUEST); + tail[0] = (target_ip >> 24) & 0xFF; + tail[1] = (target_ip >> 16) & 0xFF; + tail[2] = (target_ip >> 8) & 0xFF; + tail[3] = target_ip & 0xFF; + tail[4] = (target_port >> 8) & 0xFF; + tail[5] = target_port & 0xFF; + if (pubkey) { + memcpy(tail + 6, pubkey, SC_PUBKEY_SIZE); + } + + struct ll_entry* e = queue_entry_new(0); + if (!e) { + u_free(req_pkt); + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_ping_send_req_addr: queue_entry_new failed"); + return -3; + } + e->dgram = (uint8_t*)req_pkt; + e->len = pkt_size; + + int ret = etcp_send(to_conn, e); + if (ret != 0) { + u_free(req_pkt); + queue_entry_free(e); + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_ping_send_req_addr: etcp_send failed"); + return -4; + } + + struct route_ping_pending* pending = u_calloc(1, sizeof(struct route_ping_pending)); + if (!pending) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_ping_send_req_addr: pending alloc failed"); + return -5; + } + pending->bgp = bgp; + pending->request_id = req_pkt->request_id; + pending->callback = cb; + pending->arg = arg; + pending->next = bgp->ping_pending; + bgp->ping_pending = pending; + + pending->timeout_timer = uasync_set_timeout(bgp->instance->ua, wait_timeout_ms * 10, pending, route_ping_pending_timeout); + + DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_ping_send_req_addr: request_id=%016llx node=%016llx ip=%08x port=%u pubkey=%s", + (unsigned long long)pending->request_id, (unsigned long long)node_id, + (unsigned)target_ip, (unsigned)target_port, pubkey ? "yes" : "no"); + return 0; +} + void route_ping_destroy_pending(struct ROUTE_BGP* bgp) { if (!bgp) return; while (bgp->ping_pending) { @@ -281,25 +357,57 @@ void route_ping_handle_req(struct ROUTE_BGP* bgp, struct ETCP_CONN* from_conn, c return; } const struct BGP_PING_REQUEST* req_pkt = (const struct BGP_PING_REQUEST*)data; - struct NODEINFO_Q* target = route_bgp_get_node(bgp, req_pkt->node_id); - if (!target) { - DEBUG_WARN(DEBUG_CATEGORY_BGP, "route_ping_handle_req: node %016llx not found", + + // Check for custom target IP:port first (for NAT detection with STUN) + struct NODEINFO_IPV4_SOCKET custom_socket; + const struct NODEINFO_IPV4_SOCKET* sockets_v4 = NULL; + const struct NODEINFO_IPV6_SOCKET* sockets_v6 = NULL; + int sock_count_v4 = 0; + int sock_count_v6 = 0; + struct NODEINFO_Q* target = NULL; + + const uint8_t* embedded_pubkey = NULL; + if (len >= sizeof(struct BGP_PING_REQUEST) + 6) { + // Custom target address provided (for NAT ping) + const uint8_t* tail = data + sizeof(struct BGP_PING_REQUEST); + custom_socket.addr[0] = tail[0]; + custom_socket.addr[1] = tail[1]; + custom_socket.addr[2] = tail[2]; + custom_socket.addr[3] = tail[3]; + custom_socket.port = (tail[4] << 8) | tail[5]; + custom_socket.type = 0; + custom_socket.id = 0; + sockets_v4 = &custom_socket; + sock_count_v4 = 1; + DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_ping_handle_req: using custom target %d.%d.%d.%d:%u for node %016llx", + tail[0], tail[1], tail[2], tail[3], custom_socket.port, (unsigned long long)req_pkt->node_id); - // отправляем ответ с нулями - struct route_ping_req* req = u_calloc(1, sizeof(struct route_ping_req)); - if (req) { - req->reply_conn = from_conn; - req->request_id = req_pkt->request_id; - req->socket_count = 0; - route_ping_send_resp(req, 0); - u_free(req); + // Check for embedded pubkey + if (len >= sizeof(struct BGP_PING_REQUEST) + 6 + SC_PUBKEY_SIZE) { + embedded_pubkey = tail + 6; } - return; + // For custom target, we still need target node for pubkey lookup if not embedded + target = route_bgp_get_node(bgp, req_pkt->node_id); + } else { + // No custom target - look up nodeinfo for advertised sockets + target = route_bgp_get_node(bgp, req_pkt->node_id); + if (!target) { + DEBUG_WARN(DEBUG_CATEGORY_BGP, "route_ping_handle_req: node %016llx not found", + (unsigned long long)req_pkt->node_id); + // отправляем ответ с нулями + struct route_ping_req* req = u_calloc(1, sizeof(struct route_ping_req)); + if (req) { + req->reply_conn = from_conn; + req->request_id = req_pkt->request_id; + req->socket_count = 0; + route_ping_send_resp(req, 0); + u_free(req); + } + return; + } + sock_count_v4 = get_node_v4_sockets(target, &sockets_v4); + sock_count_v6 = get_node_v6_sockets(target, &sockets_v6); } - const struct NODEINFO_IPV4_SOCKET* sockets_v4 = NULL; - const struct NODEINFO_IPV6_SOCKET* sockets_v6 = NULL; - int sock_count_v4 = get_node_v4_sockets(target, &sockets_v4); - int sock_count_v6 = get_node_v6_sockets(target, &sockets_v6); if ((sock_count_v4 <= 0 || !sockets_v4) && (sock_count_v6 <= 0 || !sockets_v6)) { DEBUG_WARN(DEBUG_CATEGORY_BGP, "route_ping_handle_req: no sockets for node %016llx", @@ -347,6 +455,19 @@ void route_ping_handle_req(struct ROUTE_BGP* bgp, struct ETCP_CONN* from_conn, c req->target_node = target; req->timeout_ms = req_pkt->timeout_ms; req->socket_count = local_count; + // Сохраняем IP:port запрашивающего (из первого линка) для STUN-ответа + struct ETCP_LINK* l = from_conn->links; + if (l && l->remote_addr.ss_family == AF_INET) { + struct sockaddr_in* sin = (struct sockaddr_in*)&l->remote_addr; + req->recv_ipv4[0] = (ntohl(sin->sin_addr.s_addr) >> 24) & 0xFF; + req->recv_ipv4[1] = (ntohl(sin->sin_addr.s_addr) >> 16) & 0xFF; + req->recv_ipv4[2] = (ntohl(sin->sin_addr.s_addr) >> 8) & 0xFF; + req->recv_ipv4[3] = ntohl(sin->sin_addr.s_addr) & 0xFF; + req->recv_port = ntohs(sin->sin_port); + } else { + memset(req->recv_ipv4, 0, 4); + req->recv_port = 0; + } ls = bgp->instance->etcp_sockets; uint8_t idx = 0; @@ -386,14 +507,26 @@ void route_ping_handle_req(struct ROUTE_BGP* bgp, struct ETCP_CONN* from_conn, c series->sock_ctx = &req->sockets[idx]; series->local_sock = ls; series->target_addr = target_addr; - memcpy(series->pubkey, target->node.public_key, SC_PUBKEY_SIZE); + const uint8_t* pubkey_to_use = target ? target->node.public_key : embedded_pubkey; + if (!pubkey_to_use) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_ping_handle_req: no pubkey available for node %016llx", + (unsigned long long)req_pkt->node_id); + series->sock_ctx->avg_rtt = req_pkt->timeout_ms * 10; + series->sock_ctx->count_sent = req_pkt->count; + req->completed_count++; + u_free(series); + idx++; + ls = ls->next; + continue; + } + memcpy(series->pubkey, pubkey_to_use, SC_PUBKEY_SIZE); series->count_total = req_pkt->count; series->count_sent = 0; series->count_ok = 0; series->rtt_sum = 0; series->timeout_ms = req_pkt->timeout_ms; series->interval_ms = req_pkt->interval_ms; - int ret = etcp_send_ping_to_socket(bgp->instance, ls, target->node.public_key, + int ret = etcp_send_ping_to_socket(bgp->instance, ls, pubkey_to_use, &target_addr, req_pkt->timeout_ms, route_ping_cb, series, NULL, 0); if (ret != 0) { series->sock_ctx->avg_rtt = req_pkt->timeout_ms * 10; @@ -433,10 +566,13 @@ void route_ping_handle_resp(struct ROUTE_BGP* bgp, struct ETCP_CONN* from_conn, uasync_cancel_timeout(bgp->instance->ua, p->timeout_timer); p->timeout_timer = NULL; } - if (p->callback) { - int success = (resp->count_ok > 0) ? 1 : 0; - p->callback(success, resp->avg_rtt, resp->count_sent, resp->count_ok, p->arg); - } + if (p->callback) { + int success = (resp->count_ok > 0) ? 1 : 0; + uint32_t recv_ip = (resp->recv_ipv4[0] << 24) | (resp->recv_ipv4[1] << 16) | + (resp->recv_ipv4[2] << 8) | resp->recv_ipv4[3]; + uint16_t recv_port = ntohs(resp->recv_port); + p->callback(success, resp->avg_rtt, resp->count_sent, resp->count_ok, recv_ip, recv_port, p->arg); + } u_free(p); return; } diff --git a/src/route_ping.h b/src/route_ping.h index 2f0cdeeb..b4b71d99 100644 --- a/src/route_ping.h +++ b/src/route_ping.h @@ -25,16 +25,27 @@ struct BGP_PING_RESPONSE { uint8_t count_sent; uint8_t count_ok; uint16_t avg_rtt; // средний RTT в 0.1ms - uint8_t reserved[4]; + uint8_t recv_ipv4[4]; // IP:port с которого получен PING_REQ (STUN-like) + uint16_t recv_port; // network byte order } __attribute__((packed)); -typedef void (*route_ping_callback_t)(int success, uint16_t avg_rtt, uint8_t count_sent, uint8_t count_ok, void* arg); +typedef void (*route_ping_callback_t)(int success, uint16_t avg_rtt, uint8_t count_sent, uint8_t count_ok, + uint32_t recv_ip, uint16_t recv_port, void* arg); // Отправить запрос пинга через BGP, ожидать ответа с таймаутом int route_ping_send_req(struct ROUTE_BGP* bgp, struct ETCP_CONN* to_conn, uint64_t node_id, uint8_t count, uint16_t interval_ms, uint16_t timeout_ms, uint16_t wait_timeout_ms, route_ping_callback_t cb, void* arg); +// Отправить запрос пинга по произвольному IP:port (используется для NAT-детекции) +// Если target_ip == 0, используется node_id из списка известных узлов +// Если pubkey != NULL, он передается в пакете (для пинга без локального nodeinfo) +int route_ping_send_req_addr(struct ROUTE_BGP* bgp, struct ETCP_CONN* to_conn, uint64_t node_id, + uint32_t target_ip, uint16_t target_port, + uint8_t count, uint16_t interval_ms, uint16_t timeout_ms, + uint16_t wait_timeout_ms, route_ping_callback_t cb, void* arg, + const uint8_t* pubkey); + // Очистить список ожидающих запросов (при уничтожении BGP) void route_ping_destroy_pending(struct ROUTE_BGP* bgp); diff --git a/src/utun_instance.h b/src/utun_instance.h index 6d687b92..8508056e 100644 --- a/src/utun_instance.h +++ b/src/utun_instance.h @@ -44,6 +44,8 @@ struct UTUN_INSTANCE { uint64_t node_id; struct SC_MYKEYS my_keys; + + uint8_t next_socket_id; // Counter for unique socket IDs (0-255) // Main async context struct UASYNC* ua; diff --git a/tests/Makefile.am b/tests/Makefile.am index 9b5da541..4d47c2a9 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -25,6 +25,7 @@ check_PROGRAMS = \ test_routing_mesh \ test_etcp_ping \ test_route_ping \ + test_nat_detection \ bench_timeout_heap \ bench_uasync_timeouts @@ -180,6 +181,10 @@ test_route_ping_SOURCES = test_route_ping.c test_route_ping_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source test_route_ping_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS) +test_nat_detection_SOURCES = test_nat_detection.c +test_nat_detection_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source +test_nat_detection_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS) + test_ll_queue_SOURCES = test_ll_queue.c test_ll_queue_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib test_ll_queue_LDADD = $(COMMON_LIBS) diff --git a/tests/test_nat_detection.c b/tests/test_nat_detection.c new file mode 100644 index 00000000..d7970c3f --- /dev/null +++ b/tests/test_nat_detection.c @@ -0,0 +1,406 @@ +/** + * @file test_nat_detection.c + * @brief Интеграционный тест детекции типа NAT через три узла + * + * Топология: C1 -> S <- C2 + * - S получает NODEINFO от C1 (direct peer) + * - S использует C2 как третий узел для NAT-пинга C1 + * - C2 пингует C1, результат возвращается S + * - S отправляет NAT_INFO C1 + * - Проверяем корректность заполнения всех NAT-полей + */ + +#include +#include +#include +#include +#include "test_utils.h" +#include "../src/etcp.h" +#include "../src/etcp_connections.h" +#include "../src/config_parser.h" +#include "../src/config_updater.h" +#include "../src/utun_instance.h" +#include "../src/routing.h" +#include "../src/route_bgp.h" +#include "../src/route_ping.h" +#include "../src/route_node.h" +#include "../src/tun_if.h" +#include "../src/secure_channel.h" +#include "../lib/u_async.h" +#include "../lib/debug_config.h" + +#define TEST_TIMEOUT_MS 5000 +#define NODE_ID_S 0x1111111111111111ULL +#define NODE_ID_C1 0x2222222222222222ULL +#define NODE_ID_C2 0x3333333333333333ULL + +static struct UTUN_INSTANCE* inst_s = NULL; +static struct UTUN_INSTANCE* inst_c1 = NULL; +static struct UTUN_INSTANCE* inst_c2 = NULL; +static struct UASYNC* ua = NULL; +static int test_timed_out = 0; +static void* test_timeout_id = NULL; + +static char temp_dir[] = "/tmp/utun_nat_test_XXXXXX"; +static char config_s[256]; +static char config_c1[256]; +static char config_c2[256]; + +static struct { + int done; + int success; + uint16_t avg_rtt; + uint8_t count_sent; + uint8_t count_ok; + uint32_t recv_ip; + uint16_t recv_port; +} nat_ping_result; + +static int write_config(const char* path, const char* content) { + FILE* f = fopen(path, "w"); + if (!f) return -1; + fprintf(f, "%s", content); + fclose(f); + return 0; +} + +static char* get_pubkey_from_config(const char* path) { + struct utun_config* cfg = parse_config(path); + if (!cfg) return NULL; + char* pub = strdup(cfg->global.my_public_key_hex); + free_config(cfg); + return pub; +} + +static int create_temp_configs(void) { + if (test_mkdtemp(temp_dir) != 0) { + fprintf(stderr, "Failed to create temp directory\n"); + return -1; + } + snprintf(config_s, sizeof(config_s), "%s/s.conf", temp_dir); + snprintf(config_c1, sizeof(config_c1), "%s/c1.conf", temp_dir); + snprintf(config_c2, sizeof(config_c2), "%s/c2.conf", temp_dir); + + const char* tpl_s = + "[global]\n" + "my_node_id=0x1111111111111111\n" + "tun_ip=10.100.0.1/24\n" + "tun_ifname=tun100\n" + "\n" + "[server:test_s]\n" + "addr=127.0.0.1:39011\n" + "type=public\n"; + if (write_config(config_s, tpl_s) != 0) return -1; + if (config_ensure_keys_and_node_id(config_s) != 0) return -1; + char* pub_s = get_pubkey_from_config(config_s); + if (!pub_s) return -1; + + const char* tpl_c2 = + "[global]\n" + "my_node_id=0x3333333333333333\n" + "tun_ip=10.100.0.3/24\n" + "tun_ifname=tun103\n" + "\n" + "[server:test_c2]\n" + "addr=127.0.0.1:39013\n" + "type=public\n" + "\n" + "[client:to_s]\n" + "keepalive=1\n" + "peer_public_key=%s\n" + "link=test_c2:127.0.0.1:39011\n"; + char tpl_c2_full[4096]; + snprintf(tpl_c2_full, sizeof(tpl_c2_full), tpl_c2, pub_s); + if (write_config(config_c2, tpl_c2_full) != 0) { free(pub_s); return -1; } + if (config_ensure_keys_and_node_id(config_c2) != 0) { free(pub_s); return -1; } + char* pub_c2 = get_pubkey_from_config(config_c2); + if (!pub_c2) { free(pub_s); return -1; } + + const char* tpl_c1 = + "[global]\n" + "my_node_id=0x2222222222222222\n" + "tun_ip=10.100.0.2/24\n" + "tun_ifname=tun102\n" + "\n" + "[server:test_c1]\n" + "addr=127.0.0.1:39012\n" + "type=public\n" + "\n" + "[client:to_s]\n" + "keepalive=1\n" + "peer_public_key=%s\n" + "link=test_c1:127.0.0.1:39011\n"; + char tpl_c1_full[4096]; + snprintf(tpl_c1_full, sizeof(tpl_c1_full), tpl_c1, pub_s); + free(pub_s); + if (write_config(config_c1, tpl_c1_full) != 0) { free(pub_c2); return -1; } + if (config_ensure_keys_and_node_id(config_c1) != 0) { free(pub_c2); return -1; } + free(pub_c2); + + return 0; +} + +static void cleanup_temp_configs(void) { + test_unlink(config_s); + test_unlink(config_c1); + test_unlink(config_c2); + test_rmdir(temp_dir); +} + +static int has_initialized_link(struct UTUN_INSTANCE* inst) { + if (!inst) return 0; + struct ETCP_CONN* conn = inst->connections; + while (conn) { + struct ETCP_LINK* link = conn->links; + while (link) { + if (link->initialized) return 1; + link = link->next; + } + conn = conn->next; + } + return 0; +} + +static void test_timeout_cb(void* arg) { + (void)arg; + test_timed_out = 1; + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "test_nat_detection: overall test timeout"); +} + +static void nat_ping_resp_cb(int success, uint16_t avg_rtt, uint8_t count_sent, uint8_t count_ok, + uint32_t recv_ip, uint16_t recv_port, void* arg) { + (void)arg; + nat_ping_result.done = 1; + nat_ping_result.success = success; + nat_ping_result.avg_rtt = avg_rtt; + nat_ping_result.count_sent = count_sent; + nat_ping_result.count_ok = count_ok; + nat_ping_result.recv_ip = recv_ip; + nat_ping_result.recv_port = recv_port; + DEBUG_INFO(DEBUG_CATEGORY_BGP, "nat_ping_resp_cb: success=%d avg_rtt=%u sent=%u ok=%u recv_ip=%08x recv_port=%u", + success, (unsigned)avg_rtt, (unsigned)count_sent, (unsigned)count_ok, + (unsigned)recv_ip, (unsigned)recv_port); +} + +int main(void) { + int test_result = 1; + debug_config_init(); + debug_set_level(DEBUG_LEVEL_INFO); + debug_set_categories(DEBUG_CATEGORY_ETCP | DEBUG_CATEGORY_BGP | DEBUG_CATEGORY_ROUTING); + utun_instance_set_tun_init_enabled(0); + + if (create_temp_configs() != 0) { + fprintf(stderr, "Failed to create temp configs\n"); + return 1; + } + + ua = uasync_create(); + if (!ua) { + cleanup_temp_configs(); + return 1; + } + + inst_s = utun_instance_create(ua, config_s); + inst_c1 = utun_instance_create(ua, config_c1); + inst_c2 = utun_instance_create(ua, config_c2); + if (!inst_s || !inst_c1 || !inst_c2) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to create instances"); + goto cleanup; + } + + if (init_connections(inst_s) != 0 || init_connections(inst_c1) != 0 || init_connections(inst_c2) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to init connections"); + goto cleanup; + } + + test_timeout_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS * 10, NULL, test_timeout_cb); + + // 1. Wait for links C1->S and C2->S to initialize + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Waiting for ETCP links to initialize..."); + // Give some time for connections to establish before checking + for (int i = 0; i < 100; i++) { + uasync_poll(ua, 10); + } + while (!test_timed_out) { + if (has_initialized_link(inst_c1) && has_initialized_link(inst_c2)) { + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Links C1->S and C2->S initialized"); + break; + } + uasync_poll(ua, 10); + } + if (test_timed_out) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Timeout waiting for links"); + goto cleanup; + } + + // 2. Wait for BGP exchange so S learns about C1 and C2 + DEBUG_INFO(DEBUG_CATEGORY_BGP, "Waiting for BGP exchange (S learns C1 and C2)..."); + int bgp_wait_cycles = 0; + while (!test_timed_out && bgp_wait_cycles < 500) { + if (inst_s->bgp && route_bgp_get_node(inst_s->bgp, NODE_ID_C1) != NULL && + route_bgp_get_node(inst_s->bgp, NODE_ID_C2) != NULL) { + DEBUG_INFO(DEBUG_CATEGORY_BGP, "S learned about C1 and C2"); + break; + } + uasync_poll(ua, 10); + bgp_wait_cycles++; + } + if (!inst_s->bgp || route_bgp_get_node(inst_s->bgp, NODE_ID_C1) == NULL || + route_bgp_get_node(inst_s->bgp, NODE_ID_C2) == NULL) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "S did not learn about C1/C2 in time"); + goto cleanup; + } + + // 3. Wait for NAT detection to complete for C1 on server + DEBUG_INFO(DEBUG_CATEGORY_BGP, "Waiting for NAT detection to complete for C1..."); + bgp_wait_cycles = 0; + while (!test_timed_out && bgp_wait_cycles < 1500) { + struct NODEINFO_Q* node_c1 = route_bgp_get_node(inst_s->bgp, NODE_ID_C1); + if (node_c1 && node_c1->nat_check_status == NAT_CHECK_OPEN) { + DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT detection completed for C1: OPEN"); + break; + } + uasync_poll(ua, 10); + bgp_wait_cycles++; + } + struct NODEINFO_Q* node_c1 = route_bgp_get_node(inst_s->bgp, NODE_ID_C1); + if (!node_c1 || node_c1->nat_check_status != NAT_CHECK_OPEN) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "NAT detection did not complete for C1"); + goto cleanup; + } + + // 4. Wait for C1 to receive NAT_INFO + DEBUG_INFO(DEBUG_CATEGORY_BGP, "Waiting for C1 to receive NAT_INFO..."); + bgp_wait_cycles = 0; + while (!test_timed_out && bgp_wait_cycles < 500) { + struct ETCP_SOCKET* sock = inst_c1->etcp_sockets; + int found = 0; + while (sock) { + if (sock->nat_type == NAT_TYPE_OPEN) { found = 1; break; } + sock = sock->next; + } + if (found) { + DEBUG_INFO(DEBUG_CATEGORY_BGP, "C1 received NAT_INFO"); + break; + } + uasync_poll(ua, 10); + bgp_wait_cycles++; + } + + // 5. Verify all NAT fields on server + node_c1 = route_bgp_get_node(inst_s->bgp, NODE_ID_C1); + if (!node_c1) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "NODEINFO_Q for C1 disappeared"); + goto cleanup; + } + if (node_c1->nat_check_status != NAT_CHECK_OPEN) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: nat_check_status=%d, expected OPEN(%d)", + (int)node_c1->nat_check_status, NAT_CHECK_OPEN); + goto cleanup; + } + if (node_c1->nat_type != NAT_TYPE_OPEN) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: nat_type=%d, expected OPEN(%d)", + (int)node_c1->nat_type, NAT_TYPE_OPEN); + goto cleanup; + } + if (node_c1->nat_ip == 0 || node_c1->nat_port == 0) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: nat_ip/port not set (ip=%08x port=%u)", + (unsigned)node_c1->nat_ip, (unsigned)node_c1->nat_port); + goto cleanup; + } + + // Verify link nat_type on server + struct ETCP_CONN* conn_sc1 = NULL; + struct ETCP_LINK* link_sc1 = NULL; + struct ETCP_CONN* conn = inst_s->connections; + while (conn) { + if (conn->peer_node_id == NODE_ID_C1) { + conn_sc1 = conn; + link_sc1 = conn->links; + break; + } + conn = conn->next; + } + if (!link_sc1 || link_sc1->nat_type != NAT_TYPE_OPEN) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: link nat_type not OPEN on server"); + goto cleanup; + } + + // Verify socket nat_type on C1 + struct ETCP_SOCKET* sock_c1 = NULL; + struct ETCP_SOCKET* sock = inst_c1->etcp_sockets; + while (sock) { + if (sock->nat_type == NAT_TYPE_OPEN) { + sock_c1 = sock; + break; + } + sock = sock->next; + } + if (!sock_c1) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: no socket with nat_type OPEN on C1"); + goto cleanup; + } + + // 6. Separate STUN check via explicit route_ping_send_req_addr + DEBUG_INFO(DEBUG_CATEGORY_BGP, "Performing explicit STUN ping from S via C2 to C1..."); + struct ETCP_CONN* conn_sc2 = NULL; + conn = inst_s->connections; + while (conn) { + if (conn->peer_node_id == NODE_ID_C2) { conn_sc2 = conn; break; } + conn = conn->next; + } + if (!conn_sc2) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: S has no connection to C2"); + goto cleanup; + } + + memset(&nat_ping_result, 0, sizeof(nat_ping_result)); + int ret = route_ping_send_req_addr(inst_s->bgp, conn_sc2, NODE_ID_C1, + node_c1->nat_ip, node_c1->nat_port, + 3, 10, 200, 3000, nat_ping_resp_cb, NULL, + node_c1->node.public_key); + if (ret != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_ping_send_req_addr failed: %d", ret); + goto cleanup; + } + + bgp_wait_cycles = 0; + while (!test_timed_out && bgp_wait_cycles < 500 && !nat_ping_result.done) { + uasync_poll(ua, 10); + bgp_wait_cycles++; + } + if (!nat_ping_result.done) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: STUN ping timeout"); + goto cleanup; + } + if (!nat_ping_result.success || nat_ping_result.count_ok == 0) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: STUN ping failed success=%d ok=%u", + nat_ping_result.success, (unsigned)nat_ping_result.count_ok); + goto cleanup; + } + if (nat_ping_result.recv_ip == 0 || nat_ping_result.recv_port == 0) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: STUN recv_ip/port zero (ip=%08x port=%u)", + (unsigned)nat_ping_result.recv_ip, (unsigned)nat_ping_result.recv_port); + goto cleanup; + } + // recv_ip should match C2's socket address (127.0.0.1) + if (nat_ping_result.recv_ip != 0x7F000001) { + DEBUG_WARN(DEBUG_CATEGORY_BGP, "STUN recv_ip=%08x (expected 127.0.0.1), tolerating", + (unsigned)nat_ping_result.recv_ip); + } + + DEBUG_INFO(DEBUG_CATEGORY_BGP, "STUN check PASSED: recv_ip=%08x recv_port=%u", + (unsigned)nat_ping_result.recv_ip, (unsigned)nat_ping_result.recv_port); + + DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT detection test PASSED"); + test_result = 0; + +cleanup: + if (test_timeout_id) uasync_cancel_timeout(ua, test_timeout_id); + if (inst_s) utun_instance_destroy(inst_s); + if (inst_c1) utun_instance_destroy(inst_c1); + if (inst_c2) utun_instance_destroy(inst_c2); + if (ua) uasync_destroy(ua, 0); + cleanup_temp_configs(); + return test_result; +} diff --git a/tests/test_route_ping.c b/tests/test_route_ping.c index 129d2fe9..ef315f49 100644 --- a/tests/test_route_ping.c +++ b/tests/test_route_ping.c @@ -177,15 +177,17 @@ static void test_timeout_cb(void* arg) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "test_route_ping: overall test timeout"); } -static void ping_resp_cb(int success, uint16_t avg_rtt, uint8_t count_sent, uint8_t count_ok, void* arg) { - (void)arg; +static void ping_resp_cb(int success, uint16_t avg_rtt, uint8_t count_sent, uint8_t count_ok, + uint32_t recv_ip, uint16_t recv_port, void* arg) { + (void)arg; (void)recv_ip; (void)recv_port; ping_result.done = 1; ping_result.success = success; ping_result.avg_rtt = avg_rtt; ping_result.count_sent = count_sent; ping_result.count_ok = count_ok; - DEBUG_INFO(DEBUG_CATEGORY_BGP, "ping_resp_cb: success=%d avg_rtt=%u sent=%u ok=%u", - success, (unsigned)avg_rtt, (unsigned)count_sent, (unsigned)count_ok); + DEBUG_INFO(DEBUG_CATEGORY_BGP, "ping_resp_cb: success=%d avg_rtt=%u sent=%u ok=%u recv_ip=%08x recv_port=%u", + success, (unsigned)avg_rtt, (unsigned)count_sent, (unsigned)count_ok, + (unsigned)recv_ip, (unsigned)recv_port); } int main(void) {