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.
 
 
 
 
 
 

1099 lines
43 KiB

/**
* @file route_bgp.c
* @brief BGP-like обмен маршрутами — исправленная версия под новую route_lib
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#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 "etcp_debug.h"
#include "config_parser.h"
#include "route_node.h"
#include "route_lib.h"
#include "route_bgp.h"
#include "route_ping.h"
// ============================================================================
// Вспомогательные функции
// ============================================================================
static void route_bgp_send_table_request(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn) {
if (!bgp || !conn) return;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Sending table request to %s", conn->log_name);
struct BGP_ROUTE_REQUEST* req = u_calloc(1, sizeof(struct BGP_ROUTE_REQUEST));
if (!req) return;
req->cmd = ETCP_ID_ROUTE_ENTRY;
req->subcmd = ROUTE_SUBCMD_REQUEST_TABLE;
struct ll_entry* e = queue_entry_new(0);
if (!e) {
u_free(req);
return;
}
e->dgram = (uint8_t*)req;
e->len = sizeof(struct BGP_ROUTE_REQUEST);
etcp_send(conn, e);
}
static void route_bgp_add_to_senders(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn);
static bool route_bgp_should_send_to(const struct NODEINFO_Q* nq, uint64_t target_id);
static void route_bgp_send_full_table(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn);
static void route_bgp_handle_request_table(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn);
static char* nodeinfo_format(const uint8_t* data, size_t len) {
if (!data || len < sizeof(struct BGP_NODEINFO_PACKET)) return NULL;
struct BGP_NODEINFO_PACKET* pkt = (struct BGP_NODEINFO_PACKET*)data;
struct NODEINFO* ni = &pkt->node;
uint64_t node_id = ni->node_id;
const uint8_t* dyn = data + sizeof(struct BGP_NODEINFO_PACKET);
size_t off = 0;
char name_buf[64] = {0};
size_t nl = ni->node_name_len;
if (nl > 0 && nl < 63 && off + nl < len - sizeof(struct BGP_NODEINFO_PACKET)) {
memcpy(name_buf, dyn + off, nl); off += nl;
}
off += ni->local_v4_sockets * sizeof(struct NODEINFO_IPV4_SOCKET);
off += ni->local_v6_sockets * sizeof(struct NODEINFO_IPV6_SOCKET);
char subs_buf[512] = {0};
if (ni->local_v4_subnets > 0) {
const struct NODEINFO_IPV4_SUBNET* subs = (const struct NODEINFO_IPV4_SUBNET*)(dyn + off);
size_t sl = 0;
for (uint8_t i = 0; i < ni->local_v4_subnets && sl < 400; i++) {
uint32_t addr = (subs[i].addr[0]<<24)|(subs[i].addr[1]<<16)|(subs[i].addr[2]<<8)|subs[i].addr[3];
char tmp[64]; snprintf(tmp,sizeof(tmp),"%d.%d.%d.%d/%u ",(addr>>24)&255,(addr>>16)&255,(addr>>8)&255,addr&255,subs[i].prefix_length);
strcat(subs_buf,tmp); sl += strlen(tmp);
}
}
int need = snprintf(NULL,0,"NODEINFO nid=%016llx ver=%u name=\"%s\" v4subs=\"%s\" v4s=%u v6s=%u v4subcnt=%u hop=%u", (unsigned long long)node_id,(unsigned)ni->ver,name_buf,subs_buf,(unsigned)ni->local_v4_sockets,(unsigned)ni->local_v6_sockets,(unsigned)ni->local_v4_subnets,(unsigned)ni->hop_count);
char* buf = u_malloc(need+1);
if (!buf) return NULL;
snprintf(buf,need+1,"NODEINFO nid=%016llx ver=%u name=\"%s\" v4subs=\"%s\" v4s=%u v6s=%u v4subcnt=%u hop=%u", (unsigned long long)node_id,(unsigned)ni->ver,name_buf,subs_buf,(unsigned)ni->local_v4_sockets,(unsigned)ni->local_v6_sockets,(unsigned)ni->local_v4_subnets,(unsigned)ni->hop_count);
return buf;
}
/* Old full table sending removed - now using NODEINFO broadcast now */
// ============================================================================
// Broadcast / Withdraw: node_id - удаляемый узел, wd_source - от кого получена команда удалить
// ============================================================================
static void route_bgp_broadcast_withdraw(struct ROUTE_BGP* bgp, uint64_t node_id, uint64_t wd_source, struct ETCP_CONN* exclude) {
if (!bgp) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "bgp is NULL"); return; }
struct BGP_WITHDRAW_PACKET* pkt = u_calloc(1, sizeof(struct BGP_WITHDRAW_PACKET));
if (!pkt) return;
pkt->cmd = ETCP_ID_ROUTE_ENTRY;
pkt->subcmd = ROUTE_SUBCMD_WITHDRAW;
pkt->node_id = node_id;
pkt->wd_source = wd_source;
struct ll_entry* e = bgp->senders_list ? bgp->senders_list->head : NULL;
while (e) {
struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)e->data;
if (item && item->conn && item->conn != exclude) {
struct ll_entry* copy = queue_entry_new(0);
if (copy) {
copy->dgram = u_malloc(sizeof(*pkt));
if (copy->dgram) memcpy(copy->dgram, pkt, sizeof(*pkt));
copy->len = sizeof(*pkt);
etcp_send(item->conn, copy);
}
}
e = e->next;
}
u_free(pkt);
}
// ============================================================================
// Приём пакетов
// ============================================================================
static void route_bgp_start_link_nat_check(struct ROUTE_BGP* bgp, struct ETCP_LINK* link);
static void route_bgp_handle_nat_info(struct ROUTE_BGP* bgp, struct ETCP_CONN* from_conn, const uint8_t* data, size_t len);
static void route_bgp_handle_nat_check_req(struct ROUTE_BGP* bgp, struct ETCP_CONN* from_conn, const uint8_t* data, size_t len);
static void route_bgp_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->bgp) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_receive_cbk: invalid instance/bgp");
queue_dgram_free(entry);
queue_entry_free(entry);
return;
}
struct ROUTE_BGP* bgp = instance->bgp;
uint8_t* data = entry->dgram;
uint8_t cmd = data[0];
uint8_t subcmd = data[1];
if (cmd != ETCP_ID_ROUTE_ENTRY) {
queue_dgram_free(entry);
queue_entry_free(entry);
return;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Received BGP packet from %s, subcmd=%d, len=%zu",
from_conn->log_name, subcmd, entry->len);
if (subcmd == ROUTE_SUBCMD_NODEINFO) {
char* s = nodeinfo_format(data, entry->len); if (s) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s from %s", s, from_conn->log_name); u_free(s); }
route_bgp_process_nodeinfo(bgp, from_conn, data, entry->len);
} else if (subcmd == ROUTE_SUBCMD_WITHDRAW) {
route_bgp_process_withdraw(bgp, from_conn, data, entry->len);
} else if (subcmd == ROUTE_SUBCMD_REQUEST_TABLE) {
route_bgp_handle_request_table(bgp, from_conn);
} else if (subcmd == ROUTE_SUBCMD_PING_REQ) {
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) {
route_bgp_handle_nat_info(bgp, from_conn, data, entry->len);
} else if (subcmd == ROUTE_SUBCMD_NAT_CHECK_REQ) {
route_bgp_handle_nat_check_req(bgp, from_conn, data, entry->len);
}
queue_dgram_free(entry);
queue_entry_free(entry);
}
// ============================================================================
// Init / Destroy / New / Remove conn
// ============================================================================
// Callback для готовности ETCP соединения - отправляет маршруты
static void route_bgp_on_conn_up(struct ETCP_CONN* conn, void* arg) {
(void)arg;
if (conn && conn->instance && conn->instance->bgp) {
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "BGP ON UP %s", conn->log_name);
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "Connection %s on up ", conn->log_name);
route_bgp_new_conn(conn);
}
}
static void route_bgp_on_conn_down(struct ETCP_CONN* conn, void* arg) {
(void)arg;
if (conn && conn->instance && conn->instance->bgp) {
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "BGP ON DOWN %s", conn->log_name);
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "Connection %s on down ", conn->log_name);
route_bgp_remove_conn(conn);
}
}
// Callback для новых ETCP соединений - устанавливает ready callback
static void route_bgp_etcp_conn_cbk(struct ETCP_CONN* conn, void* arg) {
(void)arg;
if (conn && conn->instance && conn->instance->bgp) {
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "BGP SET CBKS %s", conn->log_name);
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "Set ETCP ready callback for connection %s", conn->log_name);
etcp_conn_set_up_cbk(conn, route_bgp_on_conn_up, conn->instance->bgp);
etcp_conn_set_down_cbk(conn, route_bgp_on_conn_down, conn->instance->bgp);
}
}
struct ROUTE_BGP* route_bgp_init(struct UTUN_INSTANCE* instance) {
if (!instance) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_init: instance is NULL");
return NULL;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_bgp_init: node_id=%016llx",
(unsigned long long)instance->node_id);
struct ROUTE_BGP* bgp = u_calloc(1, sizeof(struct ROUTE_BGP));
if (!bgp) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_init: alloc failed");
return NULL;
}
bgp->instance = instance;
bgp->ping_pending = NULL;
bgp->next_ping_req_id = 1;
// Create nodes queue with hash support for fast lookup by node_id
bgp->nodes = queue_new(instance->ua, BGP_NODES_HASH_SIZE, "bgp_nodes");
if (!bgp->nodes) {
u_free(bgp);
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_init: nodes queue creation failed");
return NULL;
}
bgp->senders_list = queue_new(instance->ua, 0, "BGP_senders");
if (!bgp->senders_list) {
queue_free(bgp->nodes);
u_free(bgp);
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_init: senders_list queue creation failed");
return NULL;
}
int vc = 0;
struct CFG_ROUTE_ENTRY* s = instance->config->my_subnets;
while (s) {
if (s->ip.family == AF_INET) vc++;
s = s->next;
}
size_t d = vc * sizeof(struct NODEINFO_IPV4_SUBNET);
bgp->local_node = u_calloc(1, sizeof(struct NODEINFO_Q) + d);
if (!bgp->local_node) {
queue_free(bgp->nodes);
queue_free(bgp->senders_list);
u_free(bgp);
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_init: local_node alloc failed");
return NULL;
}
bgp->local_node->node.node_id = instance->node_id;
bgp->local_node->node.hop_count = 0;
bgp->local_node->node.ver = 1;
bgp->local_node->dirty = 0;
route_bgp_update_my_nodeinfo(instance, bgp);
etcp_bind(instance, ETCP_ID_ROUTE_ENTRY, route_bgp_receive_cbk);
// Устанавливаем callback для новых ETCP соединений
etcp_set_new_conn_cbk(instance, route_bgp_etcp_conn_cbk, NULL);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP module initialized (NODEINFO based routing)");
return bgp;
}
void route_bgp_destroy(struct UTUN_INSTANCE* instance) {
if (!instance) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_destroy: instance is NULL");
return;
}
if (!instance->bgp) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_destroy: bgp is NULL");
return;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_bgp_destroy: node_id=%016llx",
(unsigned long long)instance->node_id);
etcp_unbind(instance, ETCP_ID_ROUTE_ENTRY);
// route_ping_destroy_pending(instance->bgp);
struct ll_entry* e;
while ((e = queue_data_get(instance->bgp->senders_list)) != NULL) {
queue_entry_free(e);
}
queue_free(instance->bgp->senders_list);
if (instance->bgp->local_node) {
if (instance->bgp->local_node->paths) {
queue_free(instance->bgp->local_node->paths);
}
u_free(instance->bgp->local_node);
}
if (instance->bgp->nodes) {
queue_free(instance->bgp->nodes);
}
u_free(instance->bgp);
instance->bgp = NULL;
}
void route_bgp_set_nat_check_local(struct ROUTE_BGP* bgp, int allow) {
if (!bgp) return;
bgp->allow_nat_check_local = allow ? 1 : 0;
}
void route_bgp_new_conn(struct ETCP_CONN* conn) {
if (!conn) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_new_conn: conn is NULL");
return;
}
if (!conn->instance) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_new_conn: conn->instance is NULL");
return;
}
if (!conn->instance->bgp) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_new_conn: conn->instance->bgp is NULL");
return;
}
if (!conn->instance->rt) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_new_conn: conn->instance->rt is NULL");
return;
}
struct ROUTE_BGP* bgp = conn->instance->bgp;
route_bgp_add_to_senders(bgp, conn);
// Scan ALL connections and ALL links in instance - start NAT check if not started
struct ETCP_CONN* c = conn->instance->connections;
while (c) {
struct ETCP_LINK* l = c->links;
while (l) {
if (l->initialized && l->conn && l->nat_check_status < NAT_CHECK_IN_PROGRESS) {
route_bgp_start_link_nat_check(bgp, l);
}
l = l->next;
}
c = c->next;
}
route_bgp_send_table_request(bgp, conn);
}
void route_bgp_remove_conn(struct ETCP_CONN* conn) {
if (!conn) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_remove_conn: conn is NULL");
return;
}
if (!conn->instance || !conn->instance->bgp) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_remove_conn: invalid instance/bgp");
return;
}
struct ROUTE_BGP* bgp = conn->instance->bgp;
// SAFETY: проверяем что conn ещё есть в senders_list
bool found_in_list = false;
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->conn == conn) {
found_in_list = true;
break;
}
e = e->next;
}
if (!found_in_list) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "route_bgp_remove_conn: conn already removed or not in list, skipping");
return;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_bgp_remove_conn: peer=%016llx",
(unsigned long long)conn->peer_node_id);
struct ROUTE_TABLE* rt = conn->instance->rt;
// Remove this connection from all nodes' path lists
// and send WITHDRAW if a node becomes unreachable
bool need_withdraw = false;
struct ll_entry* node_entry = bgp->nodes ? bgp->nodes->head : NULL;
while (node_entry) {
struct ll_entry* next = node_entry->next;
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)node_entry;
if (route_bgp_remove_path(nq, conn) == 1) {
need_withdraw = true;
if (rt) {
route_delete(rt, nq);
}
nq->dirty = 1;
if (nq->paths) {
queue_free(nq->paths);
nq->paths = NULL;
}
uint64_t key = nq->node.node_id;
struct ll_entry* entry = node_entry;
if (entry) {
queue_remove_data(bgp->nodes, entry);
queue_entry_free(entry);
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Removed node %016llx after link down", (unsigned long long)key);
}
node_entry = next;
}
// Удаляем из списка рассылки
e = bgp->senders_list->head;
while (e) {
struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)e->data;
if (item->conn == conn) {
queue_remove_data(bgp->senders_list, e);
queue_entry_free(e);
break;
}
e = e->next;
}
if (need_withdraw) {
route_bgp_broadcast_withdraw(bgp, conn->peer_node_id, conn->instance->node_id, NULL);
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Connection removed, paths updated");
}
/* ================================================
* NEW NODEINFO BASED IMPLEMENTATION
* ================================================ */
struct NODEINFO_Q* route_bgp_get_node(struct ROUTE_BGP* bgp, uint64_t node_id) {
if (!bgp || !bgp->nodes) {
return NULL;
}
uint64_t key = node_id;
struct ll_entry* e = queue_find_data_by_index(bgp->nodes, &key, 8);
return e ? (struct NODEINFO_Q*)e : NULL;
}
int route_bgp_add_path(struct NODEINFO_Q* nq, struct ETCP_CONN* conn, uint64_t* hop_list, uint8_t hop_count) {
if (!nq || !conn || hop_count > MAX_HOPS || !hop_list) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "add_path: invalid args");
return -1;
}
if (!nq->paths) {
nq->paths = queue_new(conn->instance->ua, 0, "node_paths");
if (!nq->paths) return -1;
}
size_t hop_size = hop_count * 8;
size_t path_size = sizeof(struct NODEINFO_PATH) - sizeof(struct ll_entry) + hop_size;
struct ll_entry* pe = queue_entry_new(path_size);
if (!pe) return -1;
struct NODEINFO_PATH* path = (struct NODEINFO_PATH*)pe;
path->conn = conn;
path->hop_count = hop_count;
uint64_t* stored = (uint64_t*)((uint8_t*)path + sizeof(struct NODEINFO_PATH));
memcpy(stored, hop_list, hop_size);
queue_data_put(nq->paths, pe);
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "Added path for node %016llx via conn %s with %d hops", (unsigned long long)nq->node.node_id, conn->log_name, hop_count);
return 0;
}
int route_bgp_remove_path_by_hop(struct NODEINFO_Q* nq, uint64_t wd_source) {
if (!nq || !nq->paths) return 0;
int removed = 0;
struct ll_entry* e = nq->paths->head;
while (e) {
struct NODEINFO_PATH* path = (struct NODEINFO_PATH*)e;
// parse hoplist in dyn after hop_count (after fixed PATH)
uint64_t* hop = (uint64_t*)((uint8_t*)path + sizeof(struct NODEINFO_PATH));
bool has_wd = false;
for (uint8_t i = 0; i < path->hop_count; i++) {
if (hop[i] == wd_source) {
has_wd = true;
break;
}
}
if (has_wd) {
struct ll_entry* next = e->next;
queue_remove_data(nq->paths, e);
queue_entry_free(e);
removed++;
e = next;
continue;
}
e = e->next;
}
if (removed > 0 && nq->paths && queue_entry_count(nq->paths) == 0) {
queue_free(nq->paths);
nq->paths = NULL;
return 1; // unreachable
}
return removed;
}
int route_bgp_remove_path(struct NODEINFO_Q* nq, struct ETCP_CONN* conn)
{
if (!nq || !conn || !nq->paths) {
return -1;
}
// Remove conn from paths queue
struct ll_entry* e = nq->paths->head;
while (e) {
struct NODEINFO_PATH* path = (struct NODEINFO_PATH*)e;
if (path->conn == conn) {
queue_remove_data(nq->paths, e);
queue_entry_free(e);
break;
}
e = e->next;
}
int remaining = nq->paths ? queue_entry_count(nq->paths) : 0;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Removed path for node %016llx via %s, remaining paths: %d",
(unsigned long long)nq->node.node_id, conn->log_name, remaining);
// If no paths left - node is unreachable
if (remaining == 0) {
if (nq->paths) {
queue_free(nq->paths);
nq->paths = NULL;
}
return 1; // signal that node became unreachable
}
return 0;
}
int nodeinfo_dyn_size(struct NODEINFO* node) {
return node->node_name_len +
node->local_v4_sockets * sizeof(struct NODEINFO_IPV4_SOCKET) +
node->local_v6_sockets * sizeof(struct NODEINFO_IPV6_SOCKET) +
node->local_v4_subnets * sizeof(struct NODEINFO_IPV4_SUBNET) +
node->local_v6_subnets * sizeof(struct NODEINFO_IPV6_SUBNET) +
node->tranzit_nodes * 8 +
node->hop_count * 8;
}
// NAT check context
struct nat_check_arg {
struct ETCP_LINK* link;
uint32_t nat_ip;
uint16_t nat_port;
};
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;
uint8_t nat_type = success ? NAT_TYPE_OPEN : NAT_TYPE_RESTRICTED;
link->nat_type = nat_type;
link->nat_check_status = success ? NAT_CHECK_OPEN : NAT_CHECK_RESTRICTED;
// Send NAT_INFO to peer (via their connection)
if (link->etcp) {
uint8_t socket_id = link->remote_socket_id;
route_bgp_send_nat_info(link->etcp, socket_id, na->nat_ip, na->nat_port, nat_type);
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "nat check result: type=%s for node %s", success ? "OPEN" : "RESTRICTED", link->etcp->log_name);
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 && item->conn->links && item->conn->links->remote_only_local==0) 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_link_nat_check(struct ROUTE_BGP* bgp, struct ETCP_LINK* link) {// проверяем тип нат для линка link
if (!bgp || !link || !link->conn || !link->etcp) return;
if (link->nat_check_status == NAT_CHECK_IN_PROGRESS) return;
// Find third node to send ping through
struct ETCP_CONN* third_conn = route_bgp_find_third_node(bgp, link->etcp);
if (!third_conn) {
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "no third node found for link");
return;
}
// Get target address - use nat_ip/nat_port if set, otherwise use remote_addr
/* link->nat_ip и sin_addr.s_addr в network byte order,
конвертируем в host order для API route_ping_send_req_addr */
uint32_t target_ip;
uint16_t target_port;
if (link->nat_ip != 0 && link->nat_port != 0) {
target_ip = ntohl(link->nat_ip); /* network → host order */
target_port = link->nat_port; /* уже в host order */
} else {
struct sockaddr_in* sin = (struct sockaddr_in*)&link->remote_addr;
target_ip = ntohl(sin->sin_addr.s_addr); /* network → host order */
target_port = ntohs(sin->sin_port); /* network → host order */
}
if (!bgp->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;
}
// Allocate callback arg
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;
// Use peer public key from the connection's crypto context
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;
}
// Send ping via third node - embed peer pubkey for NAT detection
int ret = route_ping_send_req_addr(bgp, 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);
}
}
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;
struct BGP_NODEINFO_PACKET* pkt = (struct BGP_NODEINFO_PACKET*)data;
struct NODEINFO* ni = &pkt->node;
int dyn_size=nodeinfo_dyn_size(ni);
if (len!=dyn_size + sizeof(struct BGP_NODEINFO_PACKET)) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "Incorrect packet size (%s)",from->log_name);
return -1;
}
uint64_t node_id = ni->node_id;
if (ni->hop_count >= MAX_HOPS) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "NODEINFO from %s dropped: too many hops (%d)",
from->log_name, ni->hop_count);
return -1;
}
struct NODEINFO_Q* nodeinfo1 = route_bgp_get_node(bgp, node_id);
uint8_t new_ver = ni->ver;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "NODEINFO from %s node=%016llx new_ver=%d local_ver=%d local_node=%p nodeinfo1=%p",
from->log_name, (unsigned long long)node_id, new_ver,
nodeinfo1 ? nodeinfo1->last_ver : 0,
(void*)bgp->local_node, (void*)nodeinfo1);
if (nodeinfo1 && (int8_t)(nodeinfo1->last_ver-new_ver)>=0) {
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "NODEINFO from %s ignored (old ver %d <= %d)",
from->log_name, new_ver, nodeinfo1->last_ver);
return 0;
}
int new_data_size=sizeof(struct NODEINFO_Q) - sizeof(struct ll_entry) + dyn_size + 8;
struct ll_queue* paths=NULL;
int need_alloc=0;
if (nodeinfo1) {// remove old node
paths=nodeinfo1->paths;
if (nodeinfo1->ll.size < new_data_size) {
need_alloc=1;
}
} else need_alloc=1;
if (need_alloc) {
if (nodeinfo1) {
queue_remove_data(bgp->nodes, &nodeinfo1->ll);
queue_entry_free(&nodeinfo1->ll);
}
nodeinfo1 = (struct NODEINFO_Q*)queue_entry_new(new_data_size);
paths = queue_new(bgp->instance->ua, 0, "node_paths");
memcpy(&nodeinfo1->node, ni, sizeof(struct NODEINFO) + dyn_size);
queue_data_put_with_index(bgp->nodes, &nodeinfo1->ll, offsetof(struct NODEINFO_Q, node.node_id)-sizeof(struct ll_entry), 8);
}
else memcpy(&nodeinfo1->node, ni, sizeof(struct NODEINFO) + dyn_size);
/* Если это наш local_node (по node_id), обновляем его данные */
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Checking local_node update: node_id=%016llx my_id=%016llx local_node=%p",
(unsigned long long)node_id, (unsigned long long)bgp->instance->node_id, (void*)bgp->local_node);
if (node_id == bgp->instance->node_id && bgp->local_node) {
/* Копируем динамическую часть (сокеты, подсети) из nodeinfo1 в local_node */
uint8_t* dyn_src = (uint8_t*)&nodeinfo1->node + sizeof(struct NODEINFO);
uint8_t* dyn_dst = (uint8_t*)&bgp->local_node->node + sizeof(struct NODEINFO);
int dyn_sz = nodeinfo1->node.node_name_len +
nodeinfo1->node.local_v4_sockets * sizeof(struct NODEINFO_IPV4_SOCKET) +
nodeinfo1->node.local_v6_sockets * sizeof(struct NODEINFO_IPV6_SOCKET) +
nodeinfo1->node.local_v4_subnets * sizeof(struct NODEINFO_IPV4_SUBNET) +
nodeinfo1->node.local_v6_subnets * sizeof(struct NODEINFO_IPV6_SUBNET);
memcpy(dyn_dst, dyn_src, dyn_sz);
bgp->local_node->node.ver = nodeinfo1->node.ver;
bgp->local_node->last_ver = new_ver;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Updated local_node from NODEINFO for node %016llx", (unsigned long long)node_id);
}
nodeinfo1->paths = paths;
nodeinfo1->last_ver = new_ver;
// add last hop
uint64_t* hop_list = (uint64_t*)((uint8_t*)&nodeinfo1->node + sizeof(struct NODEINFO) + dyn_size);
uint64_t sender = from->peer_node_id;
memcpy(hop_list, &sender, 8);
nodeinfo1->node.hop_count++;
// update node path
route_bgp_remove_path_by_hop(nodeinfo1, from->peer_node_id);
route_bgp_add_path(nodeinfo1, from, hop_list, nodeinfo1->node.hop_count);
if (bgp->instance->rt) {
route_insert(bgp->instance->rt, nodeinfo1);
}
int hop_count=nodeinfo1->node.hop_count;
// рассылаем по узлам которых нет в hop_list этой ноды (loop prevention)
struct ll_entry* e = bgp->senders_list ? bgp->senders_list->head : NULL;
while (e) {
struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)e->data;
if (item->conn) {
uint64_t id=item->conn->peer_node_id;
int found=0;
for (int i=0; i<hop_count; i++) if (hop_list[i]==id) found=1;
if (found==0) {
route_bgp_send_nodeinfo(nodeinfo1, item->conn);
}
else DEBUG_INFO(DEBUG_CATEGORY_BGP, "Skip send NODEINFO to node %016llx",id);
}
e = e->next;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Processed NODEINFO from %s (node=%016llx,ver=%d,paths=%d)",
from->log_name, (unsigned long long)node_id, new_ver,
nodeinfo1->paths ? queue_entry_count(nodeinfo1->paths) : 0);
return 0;
}
int route_bgp_process_withdraw(struct ROUTE_BGP* bgp, struct ETCP_CONN* sender, const uint8_t* data, size_t len) {
if (!bgp || len < sizeof(struct BGP_WITHDRAW_PACKET)) return -1;
struct BGP_WITHDRAW_PACKET* wp = (struct BGP_WITHDRAW_PACKET*)data;
uint64_t node_id = wp->node_id;
uint64_t wd_source = wp->wd_source;
struct NODEINFO_Q* nq = route_bgp_get_node(bgp, node_id);
if (!nq) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "node not found");
return 0;
}
int ret=route_bgp_remove_path_by_hop(nq, wd_source);
if (ret>0 || !nq->paths || (nq->paths && queue_entry_count(nq->paths) == 0)) {
if (bgp->instance && bgp->instance->rt) {
route_delete(bgp->instance->rt, nq);
}
nq->dirty = 1;
if (nq->paths) {
queue_free(nq->paths);
nq->paths = NULL;
}
uint64_t key = node_id;
struct ll_entry* entry = queue_find_data_by_index(bgp->nodes, &key, 8);
if (entry) {
queue_remove_data(bgp->nodes, entry);
queue_entry_free(entry);
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Removed node %016llx after WITHDRAW", (unsigned long long)node_id);
route_bgp_broadcast_withdraw(bgp, node_id, wd_source, sender);
}
return 0;
}
void route_bgp_send_nodeinfo(struct NODEINFO_Q* node, struct ETCP_CONN* conn) {
if (!node || !conn) {
return;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Sending NODEINFO to %s", conn->log_name);
int dyn = nodeinfo_dyn_size(&node->node);
size_t ps = sizeof(struct BGP_NODEINFO_PACKET) + dyn;
uint8_t* p = u_malloc(ps);
if (!p) {
return;
}
p[0] = ETCP_ID_ROUTE_ENTRY;
p[1] = ROUTE_SUBCMD_NODEINFO;
memcpy(p + 2, &node->node, sizeof(struct NODEINFO));
uint8_t* ds = (uint8_t*)&node->node + sizeof(struct NODEINFO);
memcpy(p + sizeof(struct BGP_NODEINFO_PACKET), ds, dyn);
struct ll_entry* e = queue_entry_new(0);
if (!e) {
u_free(p);
return;
}
e->dgram = p;
e->len = ps;
int ret = etcp_send(conn, e);
if (ret != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "etcp_send NODEINFO failed: %d", ret);
}
}
static void route_bgp_add_to_senders(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn) {
if (!bgp || !conn || !bgp->senders_list) return;
bool already = false;
struct ll_entry* e = bgp->senders_list->head;
while (e) {
if (((struct ROUTE_BGP_CONN_ITEM*)e->data)->conn == conn) { already = true; break; }
e = e->next;
}
if (!already) {
struct ll_entry* item_entry = queue_entry_new(sizeof(struct ROUTE_BGP_CONN_ITEM));
if (item_entry) {
((struct ROUTE_BGP_CONN_ITEM*)item_entry->data)->conn = conn;
queue_data_put(bgp->senders_list, item_entry);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Added to senders_list");
}
}
}
static bool route_bgp_should_send_to(const struct NODEINFO_Q* nq, uint64_t target_id) {
if (!nq || !nq->paths) return false;
struct ll_entry* e = nq->paths->head;
while (e) {
struct NODEINFO_PATH* path = (struct NODEINFO_PATH*)e;
uint64_t* hop = (uint64_t*)((uint8_t*)path + sizeof(struct NODEINFO_PATH));
bool has_id = false;
for (uint8_t i = 0; i < path->hop_count; i++) {
if (hop[i] == target_id) { has_id = true; break; }
}
if (!has_id) return true;
e = e->next;
}
return false;
}
static void route_bgp_send_full_table(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn) {
if (!bgp || !conn) return;
uint64_t target = conn->peer_node_id;
struct ll_entry* e = bgp->nodes ? bgp->nodes->head : NULL;
while (e) {
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)e;
if (nq->node.hop_count == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local node in learned list");
e = e->next; continue;
}
if (!nq->paths) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node has no paths");
e = e->next; continue;
}
if (route_bgp_should_send_to(nq, target)) {
route_bgp_send_nodeinfo(nq, conn);
}
e = e->next;
}
}
static void route_bgp_handle_request_table(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn) {
if (!bgp || !conn) return;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Received table request from %s", conn->log_name);
route_bgp_send_nodeinfo(bgp->local_node, conn);
route_bgp_send_full_table(bgp, conn);
route_bgp_add_to_senders(bgp, conn);
}
static void route_bgp_handle_nat_info(struct ROUTE_BGP* bgp, struct ETCP_CONN* from_conn, const uint8_t* data, size_t len) {
if (!bgp || !from_conn || !data || len < sizeof(struct BGP_NAT_INFO) || !bgp->local_node) return;
const struct BGP_NAT_INFO* info = (const struct BGP_NAT_INFO*)data;
/* IP в пакете в network byte order (big-endian bytes), конвертируем в host order */
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); /* порт из network byte order */
uint8_t socket_id = info->socket_id;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT_INFO recv: ip_bytes=0x%02x%02x%02x%02x ip_host=0x%08x port_net=%u port_host=%u",
info->nat_ip[0], info->nat_ip[1], info->nat_ip[2], info->nat_ip[3],
nat_ip, info->nat_port, nat_port);
uint8_t* dynamic = (uint8_t*)&bgp->local_node->node + sizeof(struct NODEINFO);
dynamic += bgp->local_node->node.node_name_len;
struct NODEINFO_IPV4_SOCKET* sockets = (struct NODEINFO_IPV4_SOCKET*)dynamic;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT_INFO updating local_node: sockets_count=%d looking for socket_id=%u",
bgp->local_node->node.local_v4_sockets, socket_id);
for (int i = 0; i < bgp->local_node->node.local_v4_sockets; i++) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, " checking socket %d: id=%u", i, sockets[i].id);
if (sockets[i].id == socket_id) {
uint8_t old_type = sockets[i].type;
/* addr[] хранится в network byte order (big-endian bytes) */
uint32_t old_ip = (sockets[i].addr[0] << 24) | (sockets[i].addr[1] << 16) |
(sockets[i].addr[2] << 8) | sockets[i].addr[3];
uint16_t old_port = sockets[i].port;
memcpy(sockets[i].addr, info->nat_ip, 4);
sockets[i].port = nat_port;
uint8_t verified_type;
if (info->nat_type == NAT_TYPE_OPEN) {
verified_type = NAT_VERIFIED_OPEN;
} else if (info->nat_type == NAT_TYPE_RESTRICTED) {
verified_type = NAT_VERIFIED_RESTRICTED;
} else {
verified_type = NAT_VERIFIED_UNKNOWN;
}
sockets[i].type = verified_type;
struct ETCP_SOCKET* es = bgp->instance->etcp_sockets;
while (es) {
if (es->sock_id == socket_id) {
es->nat_type = verified_type;
break;
}
es = es->next;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT_INFO update: socket_id=%u old_ip=0x%08x nat_ip=0x%08x old_port=%u nat_port=%u old_type=%u verified_type=%u changed=%d",
socket_id, old_ip, nat_ip, old_port, nat_port, old_type, verified_type,
(old_ip != nat_ip || old_port != nat_port || old_type != verified_type) ? 1 : 0);
if (old_ip != nat_ip || old_port != nat_port || old_type != verified_type) {
bgp->local_node->dirty = 1;
bgp->local_node->node.ver = (bgp->local_node->node.ver + 1) % 255 + 1;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT_INFO triggered NODEINFO send, ver=%d senders=%d",
bgp->local_node->node.ver,
bgp->senders_list ? queue_entry_count(bgp->senders_list) : 0);
if (bgp->senders_list) {
struct ll_entry* se = bgp->senders_list->head;
while (se) {
struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)se->data;
if (item && item->conn) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT_INFO sending NODEINFO to %s", item->conn->log_name);
route_bgp_send_nodeinfo(bgp->local_node, item->conn);
}
se = se->next;
}
}
}
break;
}
}
struct ETCP_LINK* l = from_conn->links;
while (l) {
l->nat_type = info->nat_type;
l = l->next;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT_INFO from %s: socket_id=%u type=%s ip=%u.%u.%u.%u %s port=%u",
from_conn->log_name, socket_id,
info->nat_type == NAT_TYPE_OPEN ? "OPEN" : "RESTRICTED",
(info->nat_ip[0]), (info->nat_ip[1]),
(info->nat_ip[2]), (info->nat_ip[3]), ip_to_str(&info->nat_ip, AF_INET).str,
(unsigned)nat_port);
}
static void route_bgp_handle_nat_check_req(struct ROUTE_BGP* bgp, struct ETCP_CONN* from_conn, const uint8_t* data, size_t len) {// поиск нужного линка и запуск nat_check
if (!bgp || !from_conn || !data || len < sizeof(struct BGP_NAT_CHECK_REQ)) return;
const struct BGP_NAT_CHECK_REQ* req = (const struct BGP_NAT_CHECK_REQ*)data;
uint8_t socket_id = req->socket_id;
struct sockaddr_storage dummy;
memset(&dummy, 0, sizeof(dummy));
if (req->interface_ip != 0) {
struct sockaddr_in* sin = (struct sockaddr_in*)&dummy;
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = req->interface_ip;
sin->sin_port = req->interface_port;
}
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) {
struct sockaddr_in* sin = (struct sockaddr_in*)&dummy;
DEBUG_WARN(DEBUG_CATEGORY_BGP, "NAT_CHECK_REQ from %s: no link with remote_socket_id=%u interface=%s:%u",
from_conn->log_name, socket_id,
sin->sin_addr.s_addr ? ip_to_str(&sin->sin_addr, AF_INET).str : "none",
(unsigned)ntohs(sin->sin_port));
} else if (target_link->nat_check_status == NAT_CHECK_IN_PROGRESS) {
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "NAT_CHECK_REQ from %s: check already in progress for socket_id=%u",
from_conn->log_name, socket_id);
} else {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT_CHECK_REQ from %s: restarting NAT check for socket_id=%u",
from_conn->log_name, socket_id);
route_bgp_start_link_nat_check(bgp, target_link);
}
}
void route_bgp_send_nat_info(struct ETCP_CONN* conn, uint8_t socket_id, 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->socket_id = socket_id;
/* IP в пакете хранится в network byte order (big-endian bytes) */
uint32_t nat_ip_be = htonl(nat_ip);
pkt->nat_ip[0] = (nat_ip_be >> 24) & 0xFF;
pkt->nat_ip[1] = (nat_ip_be >> 16) & 0xFF;
pkt->nat_ip[2] = (nat_ip_be >> 8) & 0xFF;
pkt->nat_ip[3] = nat_ip_be & 0xFF;
pkt->nat_port = htons(nat_port);
pkt->nat_type = nat_type;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT_INFO send: ip_host=0x%08x ip_bytes=0x%02x%02x%02x%02x port_host=%u port_net=%u",
nat_ip, pkt->nat_ip[0], pkt->nat_ip[1], pkt->nat_ip[2], pkt->nat_ip[3],
nat_port, pkt->nat_port);
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: socket_id=%u type=%s ip=%s port=%u",
conn->log_name, socket_id,
nat_type == NAT_TYPE_OPEN ? "OPEN" : "RESTRICTED",
ip_to_str(pkt->nat_ip, AF_INET).str, (unsigned)nat_port);
}
void route_bgp_send_nat_check_req(struct ETCP_CONN* conn, uint8_t socket_id) {
if (!conn) return;
struct BGP_NAT_CHECK_REQ* pkt = u_calloc(1, sizeof(struct BGP_NAT_CHECK_REQ));
if (!pkt) return;
pkt->cmd = ETCP_ID_ROUTE_ENTRY;
pkt->subcmd = ROUTE_SUBCMD_NAT_CHECK_REQ;
pkt->socket_id = socket_id;
struct ETCP_LINK* l = 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 BGP_NAT_CHECK_REQ);
etcp_send(conn, e);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_bgp_send_nat_check_req to %s: socket_id=%u interface=%s:%u",
conn->log_name, socket_id,
ip_to_str(&pkt->interface_ip, AF_INET).str,
(unsigned)ntohs(pkt->interface_port));
}