Expose side-effect-free NCD transport compatibility using shared link matching
This commit is contained in:
1 parent
ee8baae160
commit
ca5a374042
2 files changed
+40
-17
No files matched your search
@@ -220,25 +220,27 @@ static int ncd_add_tcp_link(struct ETCP_CONN* conn, struct ETCP_SOCKET* sock,
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Проверка совместимости и создание используют один обход пар, включая TCP fallback и IPv6. */
|
||||||
static int ncd_create_links(struct ncd_entry* entry, struct TOPO_NODE* ni,
|
static int ncd_create_links(struct ncd_entry* entry, struct TOPO_NODE* ni,
|
||||||
struct ETCP_SOCKET* specific_sock) {
|
struct ETCP_SOCKET* specific_sock, struct UTUN_INSTANCE* check_inst) {
|
||||||
if (!ni || ni->timestamp < entry->node_timestamp) {
|
if (!ni) { DEBUG_WARN(DEBUG_CATEGORY_NCD, "[ncd] link matching without node snapshot"); return 0; }
|
||||||
|
if (!check_inst && ni->timestamp < entry->node_timestamp) {
|
||||||
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] ignore stale addresses node=%016llx current=%llu incoming=%llu",
|
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] ignore stale addresses node=%016llx current=%llu incoming=%llu",
|
||||||
(unsigned long long)entry->node_id, (unsigned long long)entry->node_timestamp,
|
(unsigned long long)entry->node_id, (unsigned long long)entry->node_timestamp,
|
||||||
(unsigned long long)(ni ? ni->timestamp : 0)); return 0;
|
(unsigned long long)(ni ? ni->timestamp : 0)); return 0;
|
||||||
}
|
}
|
||||||
struct ETCP_CONN* conn = entry->conn;
|
struct ETCP_CONN* conn = check_inst ? NULL : entry->conn;
|
||||||
struct UTUN_INSTANCE* inst = conn->instance;
|
struct UTUN_INSTANCE* inst = check_inst ? check_inst : conn->instance;
|
||||||
if (memcmp(ni->public_key, conn->crypto_ctx.peer_public_key, SC_PUBKEY_SIZE)) {
|
if (!check_inst && memcmp(ni->public_key, conn->crypto_ctx.peer_public_key, SC_PUBKEY_SIZE)) {
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_NCD, "[ncd] address update key mismatch node=%016llx", (unsigned long long)entry->node_id);
|
DEBUG_ERROR(DEBUG_CATEGORY_NCD, "[ncd] address update key mismatch node=%016llx", (unsigned long long)entry->node_id);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (!entry->node_ref && topo_node_registry_find(inst->topo_groups, entry->node_id) == ni) {
|
if (!check_inst && !entry->node_ref && topo_node_registry_find(inst->topo_groups, entry->node_id) == ni) {
|
||||||
topo_node_registry_ref(inst->topo_groups, entry->node_id); entry->node_ref = 1;
|
topo_node_registry_ref(inst->topo_groups, entry->node_id); entry->node_ref = 1;
|
||||||
}
|
}
|
||||||
entry->node_timestamp = ni->timestamp;
|
if (!check_inst) entry->node_timestamp = ni->timestamp;
|
||||||
int link_count = 0, v4_skip = 0, v6_skip = 0, v4_addrs = 0, v6_addrs = 0;
|
int link_count = 0, v4_skip = 0, v6_skip = 0, v4_addrs = 0, v6_addrs = 0;
|
||||||
uint64_t nid = entry->node_id;
|
uint64_t nid = check_inst ? ni->node_id : entry->node_id;
|
||||||
|
|
||||||
/* ---- IPv4 ---- */
|
/* ---- IPv4 ---- */
|
||||||
struct sock_view views[NCD_MAX_SOCKET_VIEWS];
|
struct sock_view views[NCD_MAX_SOCKET_VIEWS];
|
||||||
@@ -269,21 +271,25 @@ static int ncd_create_links(struct ncd_entry* entry, struct TOPO_NODE* ni,
|
|||||||
if (!v->is_tcp) continue;
|
if (!v->is_tcp) continue;
|
||||||
tcp_sockets++;
|
tcp_sockets++;
|
||||||
if (specific_sock ? v->sock != specific_sock : !sock_match(v, a->type, tcfg, tnat, tip)) continue;
|
if (specific_sock ? v->sock != specific_sock : !sock_match(v, a->type, tcfg, tnat, tip)) continue;
|
||||||
|
if (check_inst) return 1;
|
||||||
int added = ncd_add_tcp_link(conn, v->sock, &sa, topo_node_find_reality_sock(ni, a->socket_id));
|
int added = ncd_add_tcp_link(conn, v->sock, &sa, topo_node_find_reality_sock(ni, a->socket_id));
|
||||||
if (added > 0) link_count += added;
|
if (added > 0) link_count += added;
|
||||||
}
|
}
|
||||||
if (!specific_sock && !tcp_sockets) {
|
if (!specific_sock && !tcp_sockets) {
|
||||||
|
if (check_inst) return 1;
|
||||||
int added = ncd_add_tcp_link(conn, NULL, &sa, topo_node_find_reality_sock(ni, a->socket_id));
|
int added = ncd_add_tcp_link(conn, NULL, &sa, topo_node_find_reality_sock(ni, a->socket_id));
|
||||||
if (added > 0) link_count += added;
|
if (added > 0) link_count += added;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (a->protocol & TOPO_PROTO_UDP) {
|
if (a->protocol & TOPO_PROTO_UDP) {
|
||||||
if (specific_sock && !specific_sock->is_tcp && specific_sock->local_addr.ss_family == AF_INET) {
|
if (specific_sock && !specific_sock->is_tcp && specific_sock->local_addr.ss_family == AF_INET) {
|
||||||
|
if (check_inst) return 1;
|
||||||
link_count += ncd_add_udp_link(conn, specific_sock, &sa);
|
link_count += ncd_add_udp_link(conn, specific_sock, &sa);
|
||||||
} else if (!specific_sock) {
|
} else if (!specific_sock) {
|
||||||
for (int vi = 0; vi < nviews; vi++) {
|
for (int vi = 0; vi < nviews; vi++) {
|
||||||
const struct sock_view* v = &views[vi];
|
const struct sock_view* v = &views[vi];
|
||||||
if (v->is_tcp || !sock_match(v, a->type, tcfg, tnat, tip)) continue;
|
if (v->is_tcp || !sock_match(v, a->type, tcfg, tnat, tip)) continue;
|
||||||
|
if (check_inst) return 1;
|
||||||
link_count += ncd_add_udp_link(conn, v->sock, &sa);
|
link_count += ncd_add_udp_link(conn, v->sock, &sa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -313,23 +319,27 @@ static int ncd_create_links(struct ncd_entry* entry, struct TOPO_NODE* ni,
|
|||||||
if (!s->is_tcp || s->local_addr.ss_family != AF_INET6) continue;
|
if (!s->is_tcp || s->local_addr.ss_family != AF_INET6) continue;
|
||||||
tcp_sockets++;
|
tcp_sockets++;
|
||||||
if (specific_sock ? s != specific_sock : s->type == CFG_SERVER_TYPE_PRIVATE) continue;
|
if (specific_sock ? s != specific_sock : s->type == CFG_SERVER_TYPE_PRIVATE) continue;
|
||||||
|
if (check_inst) return 1;
|
||||||
if (is_ll) { sin6.sin6_scope_id = s->netif_index; memcpy(&sa, &sin6, sizeof(sin6)); }
|
if (is_ll) { sin6.sin6_scope_id = s->netif_index; memcpy(&sa, &sin6, sizeof(sin6)); }
|
||||||
int added = ncd_add_tcp_link(conn, s, &sa, topo_node_find_reality_sock(ni, a->socket_id));
|
int added = ncd_add_tcp_link(conn, s, &sa, topo_node_find_reality_sock(ni, a->socket_id));
|
||||||
if (added > 0) link_count += added;
|
if (added > 0) link_count += added;
|
||||||
}
|
}
|
||||||
if (!specific_sock && !tcp_sockets && !is_ll) {
|
if (!specific_sock && !tcp_sockets && !is_ll) {
|
||||||
|
if (check_inst) return 1;
|
||||||
int added = ncd_add_tcp_link(conn, NULL, &sa, topo_node_find_reality_sock(ni, a->socket_id));
|
int added = ncd_add_tcp_link(conn, NULL, &sa, topo_node_find_reality_sock(ni, a->socket_id));
|
||||||
if (added > 0) link_count += added;
|
if (added > 0) link_count += added;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (a->protocol & TOPO_PROTO_UDP) {
|
if (a->protocol & TOPO_PROTO_UDP) {
|
||||||
if (specific_sock && !specific_sock->is_tcp && specific_sock->local_addr.ss_family == AF_INET6) {
|
if (specific_sock && !specific_sock->is_tcp && specific_sock->local_addr.ss_family == AF_INET6) {
|
||||||
|
if (check_inst) return 1;
|
||||||
if (is_ll) sin6.sin6_scope_id = specific_sock->netif_index;
|
if (is_ll) sin6.sin6_scope_id = specific_sock->netif_index;
|
||||||
struct sockaddr_storage sa; memcpy(&sa, &sin6, sizeof(sin6));
|
struct sockaddr_storage sa; memcpy(&sa, &sin6, sizeof(sin6));
|
||||||
link_count += ncd_add_udp_link(conn, specific_sock, &sa);
|
link_count += ncd_add_udp_link(conn, specific_sock, &sa);
|
||||||
} else if (!specific_sock) {
|
} else if (!specific_sock) {
|
||||||
for (struct ETCP_SOCKET* s = inst->etcp_sockets; s; s = s->next) {
|
for (struct ETCP_SOCKET* s = inst->etcp_sockets; s; s = s->next) {
|
||||||
if (s->is_tcp || s->local_addr.ss_family != AF_INET6 || s->type == CFG_SERVER_TYPE_PRIVATE) continue;
|
if (s->is_tcp || s->local_addr.ss_family != AF_INET6 || s->type == CFG_SERVER_TYPE_PRIVATE) continue;
|
||||||
|
if (check_inst) return 1;
|
||||||
if (is_ll) sin6.sin6_scope_id = s->netif_index;
|
if (is_ll) sin6.sin6_scope_id = s->netif_index;
|
||||||
struct sockaddr_storage sa; memcpy(&sa, &sin6, sizeof(sin6));
|
struct sockaddr_storage sa; memcpy(&sa, &sin6, sizeof(sin6));
|
||||||
link_count += ncd_add_udp_link(conn, s, &sa);
|
link_count += ncd_add_udp_link(conn, s, &sa);
|
||||||
@@ -345,12 +355,21 @@ static int ncd_create_links(struct ncd_entry* entry, struct TOPO_NODE* ni,
|
|||||||
return link_count;
|
return link_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int node_conn_direct_compatible(struct UTUN_INSTANCE* inst, uint64_t node_id) {
|
||||||
|
if (!inst || !node_id) { DEBUG_ERROR(DEBUG_CATEGORY_NCD, "[ncd] compatibility invalid arguments"); return 0; }
|
||||||
|
struct ETCP_CONN* conn = instance_find_conn(inst, node_id);
|
||||||
|
if (conn && conn->state != 2 && !conn->close_requested && conn->links_up) return 1;
|
||||||
|
struct TOPO_NODE* node = topo_node_registry_find(inst->topo_groups, node_id);
|
||||||
|
if (!node) { DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] compatibility unknown snapshot node=%016llx", (unsigned long long)node_id); return 0; }
|
||||||
|
return ncd_create_links(NULL, node, NULL, inst) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
int node_conn_direct_update_node(struct UTUN_INSTANCE* inst, uint64_t node_id) {
|
int node_conn_direct_update_node(struct UTUN_INSTANCE* inst, uint64_t node_id) {
|
||||||
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_NCD, "[ncd] update without instance"); return -1; }
|
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_NCD, "[ncd] update without instance"); return -1; }
|
||||||
struct ncd_entry* entry = ncd_registry_find(inst, node_id);
|
struct ncd_entry* entry = ncd_registry_find(inst, node_id);
|
||||||
struct TOPO_NODE* ni = topo_node_registry_find(inst->topo_groups, node_id);
|
struct TOPO_NODE* ni = topo_node_registry_find(inst->topo_groups, node_id);
|
||||||
if (!entry || !ni) return 0;
|
if (!entry || !ni) return 0;
|
||||||
int added = ncd_create_links(entry, ni, NULL);
|
int added = ncd_create_links(entry, ni, NULL, NULL);
|
||||||
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] apply node=%016llx timestamp=%llu up=%d added=%d",
|
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] apply node=%016llx timestamp=%llu up=%d added=%d",
|
||||||
(unsigned long long)node_id, (unsigned long long)ni->timestamp, entry->up, added);
|
(unsigned long long)node_id, (unsigned long long)ni->timestamp, entry->up, added);
|
||||||
return added;
|
return added;
|
||||||
@@ -672,7 +691,7 @@ static void ncd_revive_entry(struct ncd_entry* entry, struct UTUN_INSTANCE* inst
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
entry->timed_out = 0;
|
entry->timed_out = 0;
|
||||||
ncd_create_links(entry, ni, specific_sock);
|
ncd_create_links(entry, ni, specific_sock, NULL);
|
||||||
entry->connect_timer = uasync_set_timeout(inst->ua, (int)inst->etcp_connect_timeout_tb,
|
entry->connect_timer = uasync_set_timeout(inst->ua, (int)inst->etcp_connect_timeout_tb,
|
||||||
entry, ncd_connect_timeout_cb, "ncd_connect");
|
entry, ncd_connect_timeout_cb, "ncd_connect");
|
||||||
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] revive: node=0x%016llx links recreated, connect timer restarted",
|
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] revive: node=0x%016llx links recreated, connect timer restarted",
|
||||||
@@ -763,7 +782,7 @@ int node_conn_direct_open(struct UTUN_INSTANCE* inst, uint64_t node_id,
|
|||||||
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] open REUSED new-entry (ready) node=0x%016llx conn=%p", (unsigned long long)node_id, (void*)conn);
|
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] open REUSED new-entry (ready) node=0x%016llx conn=%p", (unsigned long long)node_id, (void*)conn);
|
||||||
} else {
|
} else {
|
||||||
{ struct TOPO_NODE* ni = ncd_lookup_node(inst, node_id);
|
{ struct TOPO_NODE* ni = ncd_lookup_node(inst, node_id);
|
||||||
if (ni) { ncd_create_links(entry, ni, specific_sock); topo_node_registry_unref(inst->topo_groups, ni->node_id); }
|
if (ni) { ncd_create_links(entry, ni, specific_sock, NULL); topo_node_registry_unref(inst->topo_groups, ni->node_id); }
|
||||||
}
|
}
|
||||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "[ncd] connect timer SET: node=0x%016llx value_tb=%u now_tb=%llu path=REUSED_entry_pending",
|
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "[ncd] connect timer SET: node=0x%016llx value_tb=%u now_tb=%llu path=REUSED_entry_pending",
|
||||||
(unsigned long long)node_id, inst->etcp_connect_timeout_tb, (unsigned long long)get_time_tb());
|
(unsigned long long)node_id, inst->etcp_connect_timeout_tb, (unsigned long long)get_time_tb());
|
||||||
@@ -836,7 +855,7 @@ int node_conn_direct_open(struct UTUN_INSTANCE* inst, uint64_t node_id,
|
|||||||
etcp_conn_add_cbk(conn, ncd_up_cb, entry, ETCP_CBK_EVENT_UP);
|
etcp_conn_add_cbk(conn, ncd_up_cb, entry, ETCP_CBK_EVENT_UP);
|
||||||
etcp_conn_add_cbk(conn, ncd_down_cb, entry, ETCP_CBK_EVENT_DOWN);
|
etcp_conn_add_cbk(conn, ncd_down_cb, entry, ETCP_CBK_EVENT_DOWN);
|
||||||
|
|
||||||
int link_count = ncd_create_links(entry, ni, specific_sock);
|
int link_count = ncd_create_links(entry, ni, specific_sock, NULL);
|
||||||
|
|
||||||
if (link_count == 0)
|
if (link_count == 0)
|
||||||
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] no links created (UDP only, TCP added by conn_mgr) node=0x%016llx", (unsigned long long)node_id);
|
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] no links created (UDP only, TCP added by conn_mgr) node=0x%016llx", (unsigned long long)node_id);
|
||||||
@@ -880,14 +899,14 @@ static int ncd_open_node(struct UTUN_INSTANCE* inst, uint64_t node_id,
|
|||||||
entry->handle_count++;
|
entry->handle_count++;
|
||||||
*out_handle = h;
|
*out_handle = h;
|
||||||
if (entry->conn && entry->conn->state == 1 && entry->up) {
|
if (entry->conn && entry->conn->state == 1 && entry->up) {
|
||||||
ncd_create_links(entry, ni, specific_sock);
|
ncd_create_links(entry, ni, specific_sock, NULL);
|
||||||
ncd_schedule_deliver_up(inst->ua, h);
|
ncd_schedule_deliver_up(inst->ua, h);
|
||||||
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] open_node REUSED (ready) node=0x%016llx handles=%d", (unsigned long long)node_id, entry->handle_count);
|
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] open_node REUSED (ready) node=0x%016llx handles=%d", (unsigned long long)node_id, entry->handle_count);
|
||||||
} else if (entry->timed_out) {
|
} else if (entry->timed_out) {
|
||||||
ncd_revive_entry(entry, inst, ni, specific_sock);
|
ncd_revive_entry(entry, inst, ni, specific_sock);
|
||||||
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] open_node REUSED (revived) node=0x%016llx handles=%d", (unsigned long long)node_id, entry->handle_count);
|
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] open_node REUSED (revived) node=0x%016llx handles=%d", (unsigned long long)node_id, entry->handle_count);
|
||||||
} else {
|
} else {
|
||||||
ncd_create_links(entry, ni, specific_sock);
|
ncd_create_links(entry, ni, specific_sock, NULL);
|
||||||
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] open_node REUSED (pending) node=0x%016llx handles=%d", (unsigned long long)node_id, entry->handle_count);
|
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] open_node REUSED (pending) node=0x%016llx handles=%d", (unsigned long long)node_id, entry->handle_count);
|
||||||
}
|
}
|
||||||
return NCD_REUSED;
|
return NCD_REUSED;
|
||||||
@@ -919,11 +938,11 @@ static int ncd_open_node(struct UTUN_INSTANCE* inst, uint64_t node_id,
|
|||||||
etcp_conn_add_cbk(conn, ncd_down_cb, entry, ETCP_CBK_EVENT_DOWN);
|
etcp_conn_add_cbk(conn, ncd_down_cb, entry, ETCP_CBK_EVENT_DOWN);
|
||||||
|
|
||||||
if (conn->state == 1 && entry->up) {
|
if (conn->state == 1 && entry->up) {
|
||||||
ncd_create_links(entry, ni, specific_sock);
|
ncd_create_links(entry, ni, specific_sock, NULL);
|
||||||
ncd_schedule_deliver_up(inst->ua, h);
|
ncd_schedule_deliver_up(inst->ua, h);
|
||||||
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] open_node REUSED new-entry (ready) node=0x%016llx conn=%p", (unsigned long long)node_id, (void*)conn);
|
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] open_node REUSED new-entry (ready) node=0x%016llx conn=%p", (unsigned long long)node_id, (void*)conn);
|
||||||
} else {
|
} else {
|
||||||
ncd_create_links(entry, ni, specific_sock);
|
ncd_create_links(entry, ni, specific_sock, NULL);
|
||||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "[ncd] connect timer SET: node=0x%016llx value_tb=%u now_tb=%llu path=REUSED_conn_pending",
|
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "[ncd] connect timer SET: node=0x%016llx value_tb=%u now_tb=%llu path=REUSED_conn_pending",
|
||||||
(unsigned long long)node_id, inst->etcp_connect_timeout_tb, (unsigned long long)get_time_tb());
|
(unsigned long long)node_id, inst->etcp_connect_timeout_tb, (unsigned long long)get_time_tb());
|
||||||
entry->connect_timer = uasync_set_timeout(inst->ua, (int)inst->etcp_connect_timeout_tb, entry, ncd_connect_timeout_cb, "ncd_connect_node");
|
entry->connect_timer = uasync_set_timeout(inst->ua, (int)inst->etcp_connect_timeout_tb, entry, ncd_connect_timeout_cb, "ncd_connect_node");
|
||||||
@@ -984,7 +1003,7 @@ static int ncd_open_node(struct UTUN_INSTANCE* inst, uint64_t node_id,
|
|||||||
etcp_conn_add_cbk(conn, ncd_up_cb, entry, ETCP_CBK_EVENT_UP);
|
etcp_conn_add_cbk(conn, ncd_up_cb, entry, ETCP_CBK_EVENT_UP);
|
||||||
etcp_conn_add_cbk(conn, ncd_down_cb, entry, ETCP_CBK_EVENT_DOWN);
|
etcp_conn_add_cbk(conn, ncd_down_cb, entry, ETCP_CBK_EVENT_DOWN);
|
||||||
|
|
||||||
int link_count = ncd_create_links(entry, ni, specific_sock);
|
int link_count = ncd_create_links(entry, ni, specific_sock, NULL);
|
||||||
|
|
||||||
if (link_count == 0)
|
if (link_count == 0)
|
||||||
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] open_node: no new compatible links node=0x%016llx", (unsigned long long)node_id);
|
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] open_node: no new compatible links node=0x%016llx", (unsigned long long)node_id);
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ struct ETCP_SOCKET;
|
|||||||
/* ─── Opaque handle ─── */
|
/* ─── Opaque handle ─── */
|
||||||
struct NODE_CONN_DIRECT;
|
struct NODE_CONN_DIRECT;
|
||||||
|
|
||||||
|
/* Совместимая пара адрес/сокет либо рабочий ETCP; без линков, handles, таймеров и сетевых проб.
|
||||||
|
* Использует тот же обход, что open, включая IPv6 и TCP без слушающего сокета. Не доказывает достижимость. */
|
||||||
|
int node_conn_direct_compatible(struct UTUN_INSTANCE* inst, uint64_t node_id);
|
||||||
|
|
||||||
/* ─── Callback events (единый callback на handle) ─── */
|
/* ─── Callback events (единый callback на handle) ─── */
|
||||||
enum ncd_event {
|
enum ncd_event {
|
||||||
NCD_EVENT_UP = 0, /* соединение поднялось (линки работают) */
|
NCD_EVENT_UP = 0, /* соединение поднялось (линки работают) */
|
||||||
|
|||||||
Reference in new issue
Block a user