/** * @file route_bgp.c * @brief Модуль обмена роутинг-таблицами между узлами (BGP-like) * * Функции: * - Прием и обработка роутинг-пакетов от других узлов * - Рассылка локальной таблицы при подключении нового узла * - Обновление таблицы при получении изменений */ #include #include #include "../lib/platform_compat.h" #include "route_bgp.h" #include "route_lib.h" #include "etcp_api.h" #include "etcp.h" #include "utun_instance.h" #include "../lib/debug_config.h" // Размер пакета (без заголовка ETCP) #define BGP_PACKET_SIZE (sizeof(struct ROUTE_BGP_PACKET)) // Forward declarations static void route_bgp_send_route(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn, const struct ROUTE_ENTRY* route); static void route_bgp_on_route_change(struct ROUTE_TABLE* table, struct ROUTE_ENTRY* entry, int action, void* arg); /** * @brief Колбэк для приема роутинг-пакетов от ETCP * * Вызывается когда приходит пакет с ETCP_ID_ROUTE_ENTRY. * Парсит пакет и добавляет маршрут в таблицу. */ static void route_bgp_receive_cbk(struct ETCP_CONN* from_conn, struct ll_entry* entry) { if (!from_conn || !entry || !entry->dgram || entry->len < sizeof(struct ROUTE_BGP_PACKET)) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Invalid BGP packet received"); if (entry) { queue_dgram_free(entry); queue_entry_free(entry); } return; } struct UTUN_INSTANCE* instance = from_conn->instance; if (!instance || !instance->rt) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "No instance or routing table"); queue_dgram_free(entry); queue_entry_free(entry); return; } struct ROUTE_BGP_PACKET* pkt = (struct ROUTE_BGP_PACKET*)entry->dgram; // Проверяем команду if (pkt->cmd != ETCP_ID_ROUTE_ENTRY) { DEBUG_WARN(DEBUG_CATEGORY_BGP, "Unknown BGP command: %d", pkt->cmd); queue_dgram_free(entry); queue_entry_free(entry); return; } DEBUG_INFO(DEBUG_CATEGORY_BGP, "Received BGP packet: subcmd=%d, network=%08x/%d, node_id=%016llx", pkt->subcmd, ntohl(pkt->network), pkt->prefix_length & 0x3F, (unsigned long long)pkt->node_id); switch (pkt->subcmd) { case ROUTE_SUBCMD_ENTRY: { // Создаем запись маршрута struct ROUTE_ENTRY route; memset(&route, 0, sizeof(route)); route.network = ntohl(pkt->network); route.prefix_length = pkt->prefix_length & 0x3F; // Младшие 6 бит - длина префикса route.next_hop = from_conn; route.type = ROUTE_TYPE_LEARNED; route.flags = ROUTE_FLAG_ACTIVE | ROUTE_FLAG_LEARNED; // Метрики route.metrics.hop_count = pkt->hop_count + 1; // Инкрементируем route.metrics.latency_ms = pkt->latency / 10; // x0.1 ms -> ms route.metrics.bandwidth_kbps = ntohl(pkt->bandwidth_kbps); route.metrics.last_updated = 0; // Будет установлено при вставке // BGP-специфичные поля route.endpoint_ip = ntohl(pkt->endpoint_ip); route.endpoint_port = ntohs(pkt->endpoint_port); route.destination_node_id = pkt->node_id; // Проверяем флаги if (pkt->prefix_length & ROUTE_PREFIX_F_LOCAL) { // Локальный маршрут - не пересылаем route.flags |= ROUTE_FLAG_ADVERTISED; } // Вставляем в таблицу if (route_table_insert(instance->rt, &route)) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "Added learned route: %08x/%d via node %016llx (hops=%d)", route.network, route.prefix_length, (unsigned long long)route.destination_node_id, route.metrics.hop_count); } else { DEBUG_WARN(DEBUG_CATEGORY_BGP, "Failed to insert route: %08x/%d", route.network, route.prefix_length); } break; } case ROUTE_SUBCMD_WITHDRAW: { // Удаление маршрута (withdrawal) uint32_t network = ntohl(pkt->network); uint8_t prefix_length = pkt->prefix_length & 0x3F; DEBUG_INFO(DEBUG_CATEGORY_BGP, "Processing route withdrawal: %08x/%d from conn %p", network, prefix_length, (void*)from_conn); if (route_table_delete_entry(instance->rt, network, prefix_length, from_conn)) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "Route withdrawn successfully: %08x/%d", network, prefix_length); } else { DEBUG_WARN(DEBUG_CATEGORY_BGP, "Route to withdraw not found: %08x/%d", network, prefix_length); } break; } case ROUTE_SUBCMD_REQUEST: { // Запрос полной таблицы - отправляем всё DEBUG_INFO(DEBUG_CATEGORY_BGP, "Full table request received from connection"); // Получаем bgp из instance (нужно добавить поле в instance) // Пока просто игнорируем - таблица отправляется при new_conn break; } default: DEBUG_WARN(DEBUG_CATEGORY_BGP, "Unknown BGP subcommand: %d", pkt->subcmd); break; } queue_dgram_free(entry); queue_entry_free(entry); } /** * @brief Отправить withdraw (отзыв маршрута) через ETCP */ static void route_bgp_send_withdraw(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn, const struct ROUTE_ENTRY* route) { if (!bgp || !conn || !route) { return; } // Создаем ETCP пакет struct ll_entry* entry = ll_alloc_lldgram(sizeof(struct ROUTE_BGP_PACKET)); if (!entry) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to allocate withdraw packet"); return; } struct ROUTE_BGP_PACKET* pkt = (struct ROUTE_BGP_PACKET*)entry->dgram; pkt->cmd = ETCP_ID_ROUTE_ENTRY; pkt->subcmd = ROUTE_SUBCMD_WITHDRAW; pkt->hop_count = 0; pkt->prefix_length = route->prefix_length & 0x3F; pkt->latency = 0; pkt->endpoint_port = 0; pkt->endpoint_ip = 0; pkt->network = htonl(route->network); pkt->bandwidth_kbps = 0; pkt->node_id = route->destination_node_id; entry->len = sizeof(struct ROUTE_BGP_PACKET); DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Sending withdraw: %08x/%d to conn %p", route->network, route->prefix_length, (void*)conn); // Отправляем через ETCP if (etcp_send(conn, entry) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to send withdraw via ETCP"); queue_dgram_free(entry); queue_entry_free(entry); } } /** * @brief Отправить withdraw для всех маршрутов через указанное соединение */ static void route_bgp_send_withdraw_for_conn(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn) { if (!bgp || !conn || !bgp->instance->rt) { return; } struct ROUTE_TABLE* rt = bgp->instance->rt; DEBUG_INFO(DEBUG_CATEGORY_BGP, "Sending withdraw for routes via conn %p to all peers", (void*)conn); // Находим все маршруты через это соединение и отправляем withdraw for (size_t i = 0; i < rt->count; i++) { struct ROUTE_ENTRY* entry = &rt->entries[i]; if (entry->next_hop == conn && entry->type == ROUTE_TYPE_LEARNED) { // Отправляем withdraw всем соединениям в senders_list struct ll_entry* item_entry = bgp->senders_list->head; while (item_entry) { struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)item_entry->data; if (item->conn != conn) { // Не отправляем тому, кто прислал route_bgp_send_withdraw(bgp, item->conn, entry); } item_entry = item_entry->next; } } } } /** * @brief Колбэк при изменении таблицы маршрутизации * * Рассылает обновления всем соединениям в senders_list */ static void route_bgp_on_route_change(struct ROUTE_TABLE* table, struct ROUTE_ENTRY* entry, int action, void* arg) { struct ROUTE_BGP* bgp = (struct ROUTE_BGP*)arg; if (!bgp || !entry) { return; } // Не рассылаем локальные маршруты помеченные ROUTE_PREFIX_F_LOCAL if (entry->type == ROUTE_TYPE_LOCAL && (entry->prefix_length & ROUTE_PREFIX_F_LOCAL)) { return; } // Не рассылаем маршруты через которые мы их получили (чтобы избежать петель) // Это делается в цикле ниже DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Route change: action=%d, network=%08x/%d, type=%d", action, entry->network, entry->prefix_length, entry->type); // Перебираем все соединения в senders_list struct ll_entry* item_entry = bgp->senders_list->head; while (item_entry) { struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)item_entry->data; // Не отправляем тому, от кого пришел маршрут if (entry->type == ROUTE_TYPE_LEARNED && entry->next_hop == item->conn) { item_entry = item_entry->next; continue; } if (action == 2) { // delete - отправить withdraw route_bgp_send_withdraw(bgp, item->conn, entry); } else { // insert (0) или update (1) - отправить маршрут route_bgp_send_route(bgp, item->conn, entry); } item_entry = item_entry->next; } } /** * @brief Отправить один маршрут через ETCP */ static void route_bgp_send_route(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn, const struct ROUTE_ENTRY* route) { if (!bgp || !conn || !route) { return; } // Пропускаем неактивные маршруты if (!(route->flags & ROUTE_FLAG_ACTIVE)) { return; } // Пропускаем локальные маршруты если они не для анонса if ((route->flags & ROUTE_FLAG_ADVERTISED) && (route->prefix_length & ROUTE_PREFIX_F_LOCAL)) { return; } // Создаем ETCP пакет struct ll_entry* entry = ll_alloc_lldgram(sizeof(struct ROUTE_BGP_PACKET)); if (!entry) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to allocate packet"); return; } struct ROUTE_BGP_PACKET* pkt = (struct ROUTE_BGP_PACKET*)entry->dgram; pkt->cmd = ETCP_ID_ROUTE_ENTRY; pkt->subcmd = ROUTE_SUBCMD_ENTRY; pkt->hop_count = route->metrics.hop_count; pkt->prefix_length = route->prefix_length; // Устанавливаем флаги if (route->endpoint_ip && route->endpoint_port) { pkt->prefix_length |= ROUTE_PREFIX_F_DIRECT; } // Если это локальный маршрут - отмечаем (не будет пересылаться дальше) if (route->type == ROUTE_TYPE_LOCAL) { pkt->prefix_length |= ROUTE_PREFIX_F_LOCAL; } pkt->latency = htons(route->metrics.latency_ms * 10); // ms -> x0.1 ms pkt->endpoint_port = htons(route->endpoint_port); pkt->endpoint_ip = htonl(route->endpoint_ip); pkt->network = htonl(route->network); pkt->bandwidth_kbps = htonl(route->metrics.bandwidth_kbps); pkt->node_id = bgp->instance->node_id; // Наш node_id entry->len = sizeof(struct ROUTE_BGP_PACKET); DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Sending route: %08x/%d to conn %p", route->network, route->prefix_length & 0x3F, (void*)conn); // Отправляем через ETCP if (etcp_send(conn, entry) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to send route via ETCP"); queue_dgram_free(entry); queue_entry_free(entry); } } /** * @brief Отправить полную таблицу указанному соединению */ static void route_bgp_send_table_to_conn_internal(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn) { if (!bgp || !conn || !bgp->instance->rt) { return; } struct ROUTE_TABLE* rt = bgp->instance->rt; DEBUG_INFO(DEBUG_CATEGORY_BGP, "Sending full routing table (%zu routes) to connection", rt->count); // Отправляем все маршруты из таблицы for (size_t i = 0; i < rt->count; i++) { route_bgp_send_route(bgp, conn, &rt->entries[i]); } } struct ROUTE_BGP* route_bgp_init(struct UTUN_INSTANCE* instance) { if (!instance) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Cannot init BGP: no instance"); return NULL; } struct ROUTE_BGP* bgp = (struct ROUTE_BGP*)calloc(1, sizeof(struct ROUTE_BGP)); if (!bgp) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to allocate BGP structure"); return NULL; } bgp->instance = instance; // Создаем очередь для рассылки bgp->senders_list = queue_new(instance->ua, 0); if (!bgp->senders_list) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to create senders queue"); free(bgp); return NULL; } // Регистрируем колбэк для приема роутинг-пакетов if (etcp_bind(instance, ETCP_ID_ROUTE_ENTRY, route_bgp_receive_cbk) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to bind ETCP callback"); queue_free(bgp->senders_list); free(bgp); return NULL; } // Устанавливаем callback на изменение таблицы маршрутизации if (instance->rt) { instance->rt->change_callback = route_bgp_on_route_change; instance->rt->change_callback_arg = bgp; DEBUG_INFO(DEBUG_CATEGORY_BGP, "Route change callback registered"); } DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP module initialized"); return bgp; } void route_bgp_destroy(struct UTUN_INSTANCE* instance) { if (!instance) { return; } // Отвязываем колбэк etcp_unbind(instance, ETCP_ID_ROUTE_ENTRY); // Очищаем и освобождаем структуру BGP if (instance->bgp) { // Убираем callback из таблицы маршрутизации if (instance->rt && instance->rt->change_callback == route_bgp_on_route_change) { instance->rt->change_callback = NULL; instance->rt->change_callback_arg = NULL; } if (instance->bgp->senders_list) { // Очищаем очередь - освобождаем элементы conn_item struct ll_entry* entry; while ((entry = queue_data_get(instance->bgp->senders_list)) != NULL) { queue_entry_free(entry); } queue_free(instance->bgp->senders_list); } free(instance->bgp); instance->bgp = NULL; } DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP module destroyed"); } void route_bgp_new_conn(struct ETCP_CONN* conn) { if (!conn || !conn->instance) { return; } struct UTUN_INSTANCE* instance = conn->instance; struct ROUTE_BGP* bgp = instance->bgp; if (!bgp) { DEBUG_WARN(DEBUG_CATEGORY_BGP, "BGP not initialized"); return; } // Создаем элемент для добавления в senders_list struct ll_entry* entry = queue_entry_new(sizeof(struct ROUTE_BGP_CONN_ITEM)); if (!entry) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to allocate connection item"); return; } struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)entry->data; item->conn = conn; // Добавляем в очередь (id = 0, не используем хеш) if (queue_data_put(bgp->senders_list, entry, 0) != 0) { queue_entry_free(entry); DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to add connection to senders_list"); return; } DEBUG_INFO(DEBUG_CATEGORY_BGP, "Added connection %p to senders_list", (void*)conn); // Отправляем полную таблицу новому соединению route_bgp_send_table_to_conn_internal(bgp, conn); } void route_bgp_remove_conn(struct ETCP_CONN* conn) { if (!conn || !conn->instance) { return; } struct UTUN_INSTANCE* instance = conn->instance; struct ROUTE_BGP* bgp = instance->bgp; if (!bgp) { return; } DEBUG_INFO(DEBUG_CATEGORY_BGP, "Removing connection %p from senders_list", (void*)conn); // Отправляем withdraw всем остальным соединениям для маршрутов через это соединение route_bgp_send_withdraw_for_conn(bgp, conn); // Находим и удаляем соединение из senders_list struct ll_entry* entry = bgp->senders_list->head; while (entry) { struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)entry->data; if (item->conn == conn) { // Удаляем из очереди queue_remove_data(bgp->senders_list, entry); queue_entry_free(entry); DEBUG_INFO(DEBUG_CATEGORY_BGP, "Connection %p removed from senders_list", (void*)conn); break; } entry = entry->next; } // Удаляем все маршруты, связанные с этим соединением if (instance->rt) { route_table_delete(instance->rt, conn); } } void route_bgp_close_conn(struct ETCP_CONN* conn) { // Устаревшая функция, используйте route_bgp_remove_conn route_bgp_remove_conn(conn); }