diff --git a/src/routing_layer/topo_group.c b/src/routing_layer/topo_group.c index 5696381d..54db4b39 100644 --- a/src/routing_layer/topo_group.c +++ b/src/routing_layer/topo_group.c @@ -157,7 +157,8 @@ static void topo_group_peer_event(struct NODE_CONN_DIRECT* handle, enum ncd_even int topo_group_peer_open(struct TOPO_GROUP* group, uint64_t node_id, struct TOPO_PEER_REQUEST** out) { if (out) *out = NULL; - if (!group || !out || !node_id || !group->senders_list || group->stopping || !topo_group_peer_allowed(group, node_id)) { + if (!group || !group->instance || !out || !node_id || node_id == group->instance->node_id || + !group->senders_list || group->stopping || !topo_group_peer_allowed(group, node_id)) { DEBUG_WARN(DEBUG_CATEGORY_BGP, "cannot request group peer=%016llx: invalid arguments, stopped group or unknown member", (unsigned long long)node_id); return -1; } @@ -352,38 +353,6 @@ static void nodeinfo_dump_log(const uint8_t* data, size_t len) { ni->local_v4_sockets, ni->local_v4_addrs, ni->hop_count); } -/* Форматирует NODEINFO в читаемую строку для лога (имя, подсети, адреса). */ -static char* nodeinfo_format(const uint8_t* data, size_t len) { - if (!data || len < sizeof(struct TOPOMSG_NODEINFO_PKT)) return NULL; - struct TOPOMSG_NODEINFO_PKT* pkt = (struct TOPOMSG_NODEINFO_PKT*)data; - struct TOPOMSG_NODE* ni = &pkt->node; - uint64_t node_id = ni->node_id; - const uint8_t* dyn = data + sizeof(struct TOPOMSG_NODEINFO_PKT); - 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 TOPOMSG_NODEINFO_PKT)) { - memcpy(name_buf, dyn + off, nl); off += nl; - } - off += ni->local_v4_sockets * sizeof(struct TOPOMSG_SOCKMETA4); - off += ni->local_v4_addrs * sizeof(struct TOPOMSG_ADDR4); - off += ni->local_v6_sockets * sizeof(struct TOPOMSG_SOCKMETA6); - off += ni->local_v6_addrs * sizeof(struct TOPOMSG_ADDR6); - char subs_buf[512] = {0}; - if (ni->local_v4_subnets > 0) { - const struct TOPOMSG_SUBNET4* subs = (const struct TOPOMSG_SUBNET4*)(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 v4a=%u hop=%u", (unsigned long long)node_id,(unsigned)ni->ver,name_buf,subs_buf,(unsigned)ni->local_v4_sockets,(unsigned)ni->local_v4_addrs,(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 v4a=%u hop=%u", (unsigned long long)node_id,(unsigned)ni->ver,name_buf,subs_buf,(unsigned)ni->local_v4_sockets,(unsigned)ni->local_v4_addrs,(unsigned)ni->hop_count); - return buf; -} /* Читаемое имя sub-команды BGP для логов. */ static const char* group_subcmd_name(uint8_t subcmd) { @@ -982,7 +951,10 @@ void topo_groups_set_node_updated_cb(struct TOPO_GROUPS* groups, topo_node_updat static int topo_group_begin_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn, int retain) { if (!group || !conn) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args"); return -1; } if (group->stopping) { DEBUG_WARN(DEBUG_CATEGORY_BGP, "join on stopped group"); return -1; } - if (!conn->instance) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "conn->instance is NULL"); return -1; } + if (conn->instance != group->instance || !conn->links_up || conn->close_requested) { + DEBUG_WARN(DEBUG_CATEGORY_BGP, "group join needs a live transport of the same instance peer=%016llx group=%016llx", + (unsigned long long)conn->peer_node_id, (unsigned long long)group->group_id); return -1; + } if (!conn->instance->rt) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "conn->instance->rt is NULL"); return -1; } if (!topo_group_peer_allowed(group, conn->peer_node_id)) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "group connect deferred: member unknown peer=%016llx group=%016llx", @@ -1269,7 +1241,10 @@ static void topo_group_send_err_group_mismatch(struct TOPO_GROUP* group, struct /* Обрабатывает NODEINFO: проверка подписи/версии, обновление узла, пути, роутинг, форвард. */ int topo_group_process_nodeinfo(struct TOPO_GROUP* group, struct ETCP_CONN* from, const uint8_t* data, size_t len) { - if (!group || !from || len < sizeof(struct TOPOMSG_NODEINFO_PKT)) return -1; + if (!group || !group->instance || !from || from->instance != group->instance || !data || + len < sizeof(struct TOPOMSG_NODEINFO_PKT)) { + DEBUG_WARN(DEBUG_CATEGORY_BGP, "invalid NODEINFO context or length=%zu", len); return -1; + } struct TOPOMSG_NODEINFO_PKT* pkt = (struct TOPOMSG_NODEINFO_PKT*)data; struct TOPOMSG_NODE* ni = &pkt->node; @@ -1483,7 +1458,7 @@ int topo_group_process_nodeinfo(struct TOPO_GROUP* group, struct ETCP_CONN* from /* Обрабатывает WITHDRAW: удаляет узел/пути, чистит роутинг, форвардит withdraw. */ int topo_group_process_withdraw(struct TOPO_GROUP* group, struct ETCP_CONN* sender, const uint8_t* data, size_t len) { - if (!group || !sender || len != sizeof(struct TOPOMSG_WITHDRAW_PKT)) { + if (!group || !group->instance || !sender || sender->instance != group->instance || !data || len != sizeof(struct TOPOMSG_WITHDRAW_PKT)) { DEBUG_WARN(DEBUG_CATEGORY_BGP, "invalid WITHDRAW len=%zu", len); return -1; } struct TOPOMSG_WITHDRAW_PKT* wp = (struct TOPOMSG_WITHDRAW_PKT*)data; @@ -1954,9 +1929,9 @@ void topo_group_remove_peer_ready_cbk(struct TOPO_GROUP* group, topo_peer_ready_ } void topo_group_add_node_cbk(struct TOPO_GROUP* group, topo_node_event_fn fn, void* arg) { - if (!group || !fn) return; + if (!group || !fn) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node subscription: invalid arguments"); return; } struct topo_node_cbk_entry* e = u_malloc(sizeof(*e)); - if (!e) return; + if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node subscription allocation failed"); return; } e->fn = fn; e->arg = arg; e->next = group->node_cbks; group->node_cbks = e; @@ -1980,9 +1955,9 @@ void topo_group_remove_node_cbk(struct TOPO_GROUP* group, topo_node_event_fn fn, /* Регистрирует глобальный колбэк изменения nodeinfo (уровень инстанса). */ void utun_add_nodeinfo_cbk(struct UTUN_INSTANCE* instance, nodeinfo_cbk_fn fn, void* arg) { - if (!instance || !fn) return; + if (!instance || !fn) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "nodeinfo subscription: invalid arguments"); return; } struct nodeinfo_cbk_entry* e = u_malloc(sizeof(*e)); - if (!e) return; + if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "nodeinfo subscription allocation failed"); return; } e->fn = fn; e->arg = arg; e->next = instance->nodeinfo_cbks; instance->nodeinfo_cbks = e; diff --git a/src/routing_layer/topo_group.h b/src/routing_layer/topo_group.h index 123ace93..49faaa35 100644 --- a/src/routing_layer/topo_group.h +++ b/src/routing_layer/topo_group.h @@ -76,6 +76,11 @@ typedef void (*topo_node_updated_fn)(struct UTUN_INSTANCE* inst, uint64_t node_i typedef void (*topo_node_event_fn)(struct TOPO_GROUP* group, uint64_t node_id, int event, void* arg); +/* Подписки принадлежат группе/instance; fn и arg заимствованы до remove/destroy. + * Вызов синхронный, в uasync. Callback не удаляет группу/узел и не меняет список + * подписчиков во время обхода; такие действия нужно отложить через uasync_call_soon. + * Повторная регистрация создаёт ещё одну подписку; remove снимает первое совпадение. */ + struct topo_node_cbk_entry { topo_node_event_fn fn; void* arg; @@ -300,12 +305,15 @@ struct TOPO_GROUP* topo_groups_get_default(struct TOPO_GROUPS* g); struct TOPO_GROUP* topo_groups_find(struct TOPO_GROUPS* g, uint64_t group_id); /** - * @brief Создаёт новую группу с указанным типом. + * @brief Создаёт и регистрирует новую группу; существующий group_id — ошибка. + * После регистрации присоединяет уже поднятые транспорты: CHAT — локальных мемберов, + * UTUN — пиров, которыми владеет конфигурация. Для CHAT по group_autoconnect + * (по умолчанию 1) включает подбор пиров. Ошибки этих попыток логируются отдельно. * * @param g контейнер групп * @param group_id уникальный идентификатор * @param group_type TOPO_GROUP_TYPE_UTUN или TOPO_GROUP_TYPE_CHAT - * @param channel_id идентификатор канала для групп типа CHAT (может быть NULL) + * @param channel_id локальный ключ таблиц CHAT; копируется до 63 байт, NULL оставляет пустой ключ * @return TOPO_GROUP с local_node, conn_mgr, broadcast и radio или NULL с полным откатом создания */ struct TOPO_GROUP* topo_groups_create_group(struct TOPO_GROUPS* g, uint64_t group_id, uint8_t group_type, const char* channel_id); @@ -320,7 +328,7 @@ struct TOPO_GROUP* topo_groups_create_group(struct TOPO_GROUPS* g, uint64_t grou void topo_groups_remove_group(struct TOPO_GROUPS* g, uint64_t group_id); /** - * @brief Присоединяет существующий транспорт к группе и начинает JOIN/ACCEPT. + * @brief Присоединяет UP-транспорт того же instance к группе и начинает JOIN/ACCEPT. * Группа приобретает собственный NCD handle; повтор не сбрасывает сессию. 0/-1. * Для обычного инициатора используйте отменяемый topo_group_peer_open(). */ @@ -345,7 +353,7 @@ enum topo_peer_phase { TOPO_PEER_FAILED, TOPO_PEER_CONNECTING, TOPO_PEER_SYNCING * Запрос остаётся валиден после удаления группы (phase=FAILED), caller обязан * закрыть его. Все операции выполняются в потоке единственного uasync. * progress — timebase последнего принятого NODEINFO/шага обмена, не polling time. - * open: 0 = запрос создан в *out, -1 = ошибка. */ + * open: node_id ненулевой и не self; 0 = запрос создан в *out, -1 = ошибка (*out=NULL). */ int topo_group_peer_open(struct TOPO_GROUP* group, uint64_t node_id, struct TOPO_PEER_REQUEST** out); /* Освободить свой запрос; после вызова request недействителен. NULL допустим. */ void topo_group_peer_close(struct TOPO_PEER_REQUEST* request); @@ -373,10 +381,18 @@ void topo_group_remove_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn, en /** * @brief Обрабатывает пакет NODEINFO. * - * Проверка подписи и timestamp, обновление TOPO_GROUP_NODE, добавление пути, - * вставка в роутинг, broadcast если не max hops. + * data/len — полный TOPOMSG_NODEINFO_PKT с динамическими секциями; память заимствована. + * Диспетчер заранее проверяет поколения/членство сессии. Здесь проверяются длина, + * group_id тела, тип, подпись, конфликт timestamp и цепочка; добавляется ID from. + * Старый timestamp обновляет только путь. Для новой записи путь готовится до commit: + * отказ памяти сохраняет прежние запись/подсети/пути. Входная цепочка < MAX_HOPS, + * сохранённая после добавления соседа — 1..MAX_HOPS. + * Изменения распространяются из актуального состояния с backpressure. + * Для UTUN прежние IPv4-маршруты узла удаляются перед установкой нового набора. + * Пересечение/OOM маршрутов оставляет новый набор неустановленным, возвращает -1 + * и запрещает READY; подписанная запись и групповые пути уже могут быть приняты. * - * @return 0 при успехе + * @return 0 при применении/игнорировании собственного объявления, -1 при ошибке */ int topo_group_process_nodeinfo(struct TOPO_GROUP* group, struct ETCP_CONN* from, const uint8_t* data, size_t len); /* Присоединить UP-транспорт; переданный handle остаётся у вызывающего. 0/-1. */ @@ -387,15 +403,20 @@ int topo_group_leave_peer(struct TOPO_GROUP* group, struct NODE_CONN_DIRECT* han /** * @brief Обрабатывает WITHDRAW. * - * Удаляет затронутые пути; узел удаляется только без оставшихся путей. + * data/len — заимствованный полный TOPOMSG_WITHDRAW_PKT; поколения/членство + * проверяет диспетчер. Удаляет только путь sender; узел удаляется без оставшихся путей. + * Неизвестный узел/отсутствующий путь sender — успешный no-op. * - * @return 0 при успехе + * @return 0 при успехе, -1 при неверных аргументах/длине */ int topo_group_process_withdraw(struct TOPO_GROUP* group, struct ETCP_CONN* sender, const uint8_t* data, size_t len); /** * @brief Планирует NODEINFO узла одному пиру с backpressure. + * group/node/conn заимствованы на время вызова; очередь хранит только node_id. * Запись и RTT читаются при отправке; повторные изменения узла объединяются. + * 0 — запланировано либо сессия ещё не ACCEPT, -1 — ошибка. Ошибка самой отложенной + * отправки сбрасывает READY и перезапускает обмен по watchdog. */ int topo_group_send_nodeinfo(struct TOPO_GROUP* group, struct TOPO_GROUP_NODE* node, struct ETCP_CONN* conn); @@ -414,7 +435,9 @@ struct ETCP_CONN* topo_group_find_conn_for_node(struct TOPO_GROUP* group, uint64 /** * @brief Атомарно заменяет путь через conn; остальные соседи не затрагиваются. * - * @return 0 при успехе + * nq/conn/hop_list заимствованы; цепочка 1..MAX_HOPS копируется. Семантику и петли + * проверяет вызывающий; функция проверяет размер и атомарно заменяет путь conn. + * @return 0 при успехе, -1 при неверных аргументах/OOM */ int topo_group_add_path(struct TOPO_GROUP_NODE* nq, struct ETCP_CONN* conn, uint64_t* hop_list, uint8_t hop_count, uint16_t cumulative_rtt);