/** * @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 "etcp_connections.h" #include "utun_instance.h" #include "config_parser.h" #include "../lib/debug_config.h" #include "../lib/mem.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_send_reroute(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn, uint64_t old_node_id, const struct ROUTE_ENTRY* route); static void route_bgp_broadcast_route(struct ROUTE_BGP* bgp, const struct ROUTE_ENTRY* route, struct ETCP_CONN* exclude); static void route_bgp_broadcast_withdraw(struct ROUTE_BGP* bgp, uint64_t node_id, struct ETCP_CONN* exclude); static void route_bgp_send_withdraw(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn, uint64_t node_id); static void route_bgp_send_table_to_conn_internal(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn); static void route_bgp_on_route_change(struct ROUTE_TABLE* table, struct ROUTE_ENTRY* entry, int action, void* arg); static void route_bgp_send_withdraw_for_conn(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn); static const char* route_type_to_str(route_type_t type) { switch (type) { case ROUTE_TYPE_LOCAL: return "LOCAL"; case ROUTE_TYPE_LEARNED: return "LEARNED"; default: return "UNKNOWN"; } } static void format_ip(uint32_t ip, char* buffer) { if (!buffer) return; uint8_t* b = (uint8_t*)&ip; snprintf(buffer, 16, "%u.%u.%u.%u", b[3], b[2], b[1], b[0]); } typedef struct { uint8_t cmd; uint8_t subcmd; uint64_t node_id; } BGP_WITHDRAW_PACKET __attribute__((packed)); typedef struct { uint8_t cmd; uint8_t subcmd; uint64_t old_node_id; struct ROUTE_PEER_INFO peer_info; } BGP_REROUTE_PACKET __attribute__((packed)); /** * @brief Рассылает маршрут всем соединениям (кроме exclude) */ static void route_bgp_broadcast_route(struct ROUTE_BGP* bgp, const struct ROUTE_ENTRY* route, struct ETCP_CONN* exclude) { if (!bgp || !route) return; struct ll_entry* entry = bgp->senders_list->head; int sent_count = 0; while (entry) { struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)entry->data; if (item->conn != exclude) { route_bgp_send_route(bgp, item->conn, route); sent_count++; } entry = entry->next; } char net_buf[16]; format_ip(route->network, net_buf); DEBUG_INFO(DEBUG_CATEGORY_BGP, "Broadcasted route %s/%d to %d connections", net_buf, route->prefix_length, sent_count); } /** * @brief Рассылает withdraw всем соединениям (кроме exclude) */ static void route_bgp_broadcast_withdraw(struct ROUTE_BGP* bgp, uint64_t node_id, struct ETCP_CONN* exclude) { if (!bgp) return; struct ll_entry* entry = bgp->senders_list->head; int sent_count = 0; while (entry) { struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)entry->data; if (item->conn != exclude) { route_bgp_send_withdraw(bgp, item->conn, node_id); sent_count++; } entry = entry->next; } DEBUG_INFO(DEBUG_CATEGORY_BGP, "Broadcasted withdraw for node %016llx to %d connections", (unsigned long long)node_id, sent_count); } /** * @brief Отправляет маршрут конкретному соединению */ static void route_bgp_send_route(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn, const struct ROUTE_ENTRY* route) { if (!bgp || !conn || !route) return; struct BGP_ENTRY_PACKET *pkt = u_calloc(1, sizeof(struct BGP_ENTRY_PACKET)); if (!pkt) return; pkt->cmd = ETCP_ID_ROUTE_ENTRY; pkt->subcmd = ROUTE_SUBCMD_ENTRY; pkt->peer_info.node_id = htobe64(route->node_id); pkt->peer_info.network = htonl(route->network); pkt->peer_info.prefix_length = route->prefix_length; pkt->peer_info.hop_count = route->hop_count; pkt->peer_info.latency = htons(route->latency); // Add other fields if present, e.g., bandwidth struct ll_entry *send_entry = queue_entry_new(0); if (!send_entry) { u_free(pkt); return; } send_entry->dgram = (uint8_t*)pkt; send_entry->len = sizeof(struct BGP_ENTRY_PACKET); if (etcp_send(conn, send_entry) != 0) { queue_entry_free(send_entry); u_free(pkt); } } /** * @brief Отправляет reroute конкретному соединению */ static void route_bgp_send_reroute(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn, uint64_t old_node_id, const struct ROUTE_ENTRY* route) { if (!bgp || !conn || !route) return; struct BGP_REROUTE_PACKET *pkt = u_calloc(1, sizeof(struct BGP_REROUTE_PACKET)); if (!pkt) return; pkt->cmd = ETCP_ID_ROUTE_ENTRY; pkt->subcmd = ROUTE_SUBCMD_ENTRY_REROUTE; pkt->old_node_id = htobe64(old_node_id); pkt->peer_info.node_id = htobe64(route->node_id); pkt->peer_info.network = htonl(route->network); pkt->peer_info.prefix_length = route->prefix_length; pkt->peer_info.hop_count = route->hop_count; pkt->peer_info.latency = htons(route->latency); struct ll_entry *send_entry = queue_entry_new(0); if (!send_entry) { u_free(pkt); return; } send_entry->dgram = (uint8_t*)pkt; send_entry->len = sizeof(struct BGP_REROUTE_PACKET); if (etcp_send(conn, send_entry) != 0) { queue_entry_free(send_entry); u_free(pkt); } } /** * @brief Отправляет withdraw конкретному соединению */ static void route_bgp_send_withdraw(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn, uint64_t node_id) { if (!bgp || !conn) 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 = htobe64(node_id); struct ll_entry *send_entry = queue_entry_new(0); if (!send_entry) { u_free(pkt); return; } send_entry->dgram = (uint8_t*)pkt; send_entry->len = sizeof(struct BGP_WITHDRAW_PACKET); if (etcp_send(conn, send_entry) != 0) { queue_entry_free(send_entry); u_free(pkt); } } /** * @brief Отправляет withdraw для всех affected node_id при удалении conn */ static void route_bgp_send_withdraw_for_conn(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn) { if (!bgp || !conn || !conn->instance || !conn->instance->rt) return; struct ROUTE_TABLE* table = conn->instance->rt; // Collect unique node_ids where next_hop == conn uint64_t unique_nodes[1024]; // assume max size_t unique_count = 0; bool found; for (size_t i = 0; i < table->count; i++) { if (table->entries[i].next_hop == conn) { uint64_t nid = table->entries[i].node_id; found = false; for (size_t j = 0; j < unique_count; j++) { if (unique_nodes[j] == nid) { found = true; break; } } if (!found && unique_count < 1024) { unique_nodes[unique_count++] = nid; } } } // Broadcast withdraw for each unique node_id for (size_t j = 0; j < unique_count; j++) { route_bgp_broadcast_withdraw(bgp, unique_nodes[j], conn); // exclude conn, but since removing, anyway } } /** * @brief Отправляет полную таблицу конкретному соединению */ static void route_bgp_send_table_to_conn_internal(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn) { if (!bgp || !conn || !bgp->instance || !bgp->instance->rt) return; struct ROUTE_TABLE* table = bgp->instance->rt; for (size_t i = 0; i < table->count; i++) { route_bgp_send_route(bgp, conn, &table->entries[i]); } DEBUG_INFO(DEBUG_CATEGORY_BGP, "Sent full routing table (%zu entries) to connection %p", table->count, (void*)conn); } /** * @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 < 2) { 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 BGP_ENTRY_PACKET* pkt = (struct BGP_ENTRY_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; } char ip_buf[16]; uint32_t network; uint8_t prefix_length; uint64_t node_id; switch (pkt->subcmd) { case ROUTE_SUBCMD_ENTRY: case ROUTE_SUBCMD_ENTRY_REROUTE: { uint64_t old_node_id = 0; struct ROUTE_PEER_INFO *peer_info; if (pkt->subcmd == ROUTE_SUBCMD_ENTRY_REROUTE) { if (entry->len < sizeof(BGP_REROUTE_PACKET)) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Invalid REROUTE packet size"); queue_dgram_free(entry); queue_entry_free(entry); return; } BGP_REROUTE_PACKET *reroute_pkt = (BGP_REROUTE_PACKET*)pkt; old_node_id = be64toh(reroute_pkt->old_node_id); peer_info = &reroute_pkt->peer_info; } else { if (entry->len < sizeof(BGP_ENTRY_PACKET)) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Invalid ENTRY packet size"); queue_dgram_free(entry); queue_entry_free(entry); return; } peer_info = &pkt->peer_info; } node_id = be64toh(peer_info->node_id); network = ntohl(peer_info->network); prefix_length = peer_info->prefix_length; uint8_t hop_count = peer_info->hop_count; uint16_t latency = ntohs(peer_info->latency); format_ip(network, ip_buf); DEBUG_INFO(DEBUG_CATEGORY_BGP, "Received BGP %s: network=%s/%d, node_id=%016llx, hop=%d, lat=%u", (pkt->subcmd == ROUTE_SUBCMD_ENTRY) ? "ENTRY" : "REROUTE", ip_buf, prefix_length, (unsigned long long)node_id, hop_count, latency); struct ROUTE_ENTRY new_route; memset(&new_route, 0, sizeof(new_route)); new_route.node_id = node_id; new_route.network = network; new_route.prefix_length = prefix_length; new_route.next_hop = from_conn; new_route.type = ROUTE_TYPE_LEARNED; new_route.flags = ROUTE_FLAG_ACTIVE | ROUTE_FLAG_LEARNED; new_route.hop_count = hop_count + 1; new_route.latency = latency + (from_conn->rtt_last * 10); new_route.metrics.latency_ms = new_route.latency / 10; new_route.metrics.hop_count = new_route.hop_count; // Set defaults for other metrics new_route.metrics.bandwidth_kbps = 100000; // example default new_route.metrics.packet_loss_rate = 0; route_update_quality(&new_route); struct ROUTE_TABLE* table = instance->rt; bool exists = false; bool updated = false; for (size_t i = 0; i < table->count; i++) { struct ROUTE_ENTRY* existing = &table->entries[i]; if (existing->network == network && existing->prefix_length == prefix_length) { exists = true; if (new_route.hop_count < existing->hop_count) { // Update to better route existing->next_hop = new_route.next_hop; existing->hop_count = new_route.hop_count; existing->latency = new_route.latency; existing->metrics = new_route.metrics; existing->last_update = get_time_tb(); if (table->change_callback) { table->change_callback(table, existing, 1, table->change_callback_arg); } updated = true; } break; } } if (!exists) { new_route.created_time = get_time_tb(); new_route.last_update = new_route.created_time; new_route.metrics.last_updated = new_route.created_time; if (route_table_insert(table, &new_route)) { updated = true; // treat insert as update for broadcast } } if (updated) { route_bgp_broadcast_route(bgp, &new_route, from_conn); } break; } case ROUTE_SUBCMD_WITHDRAW: { if (entry->len < sizeof(BGP_WITHDRAW_PACKET)) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Invalid WITHDRAW packet size"); queue_dgram_free(entry); queue_entry_free(entry); return; } BGP_WITHDRAW_PACKET *withdraw_pkt = (BGP_WITHDRAW_PACKET*)pkt; uint64_t withdrawn_node_id = be64toh(withdraw_pkt->node_id); DEBUG_INFO(DEBUG_CATEGORY_BGP, "Received WITHDRAW for node %016llx", (unsigned long long)withdrawn_node_id); struct ROUTE_TABLE* table = instance->rt; bool has_alternative = false; size_t i = 0; while (i < table->count) { struct ROUTE_ENTRY* rte = &table->entries[i]; if (rte->node_id == withdrawn_node_id) { if (rte->next_hop == from_conn) { route_table_delete_entry(table, rte->network, rte->prefix_length, from_conn); // Do not increment i, array shifted } else { has_alternative = true; i++; } } else { i++; } } if (has_alternative) { // Send reroute for each remaining route to from_conn for (size_t j = 0; j < table->count; j++) { if (table->entries[j].node_id == withdrawn_node_id) { route_bgp_send_reroute(bgp, from_conn, withdrawn_node_id, &table->entries[j]); } } } else { // Propagate withdraw route_bgp_broadcast_withdraw(bgp, withdrawn_node_id, from_conn); } break; } case ROUTE_SUBCMD_NODEINFO: { if (entry->len < sizeof(struct BGP_NODEINFO_PACKET)) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Invalid NODEINFO packet size"); queue_dgram_free(entry); queue_entry_free(entry); return; } struct BGP_NODEINFO_PACKET *info_pkt = (struct BGP_NODEINFO_PACKET*)pkt; struct NODE_CONNS_INFO *node_info = &info_pkt->node_info; if (node_info->connlist_count == 0) break; uint64_t target_node_id = be64toh(node_info->NODE_CONN_INFO[0].node_id); // assume first DEBUG_INFO(DEBUG_CATEGORY_BGP, "Received NODEINFO for node %016llx, count=%d", (unsigned long long)target_node_id, node_info->connlist_count); struct ROUTE_TABLE* table = instance->rt; for (size_t i = 0; i < table->count; i++) { if (table->entries[i].node_id == target_node_id) { size_t size = sizeof(struct NODE_CONNS_INFO) + node_info->connlist_count * sizeof(struct NODE_CONN_INFO); struct NODE_CONNS_INFO *new_list = u_malloc(size); if (new_list) { memcpy(new_list, node_info, size); new_list->ref_count = 1; if (table->entries[i].conn_list) { if (--table->entries[i].conn_list->ref_count == 0) { u_free(table->entries[i].conn_list); } } table->entries[i].conn_list = new_list; if (table->change_callback) { table->change_callback(table, &table->entries[i], 1, table->change_callback_arg); } } break; } } break; } default: DEBUG_WARN(DEBUG_CATEGORY_BGP, "Unknown BGP subcommand: %d", pkt->subcmd); break; } queue_dgram_free(entry); queue_entry_free(entry); } /** * @brief Callback на изменение маршрута (broadcast при insert/update) */ 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; if (action == 0 || action == 1) { // insert or update route_bgp_broadcast_route(bgp, entry, NULL); } // for delete, handled in remove_conn/withdraw } struct ROUTE_BGP* route_bgp_init(struct UTUN_INSTANCE* instance) { if (!instance) { return NULL; } struct ROUTE_BGP* bgp = (struct ROUTE_BGP*)u_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, "BGP sender_list"); if (!bgp->senders_list) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to create senders queue"); u_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); u_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); } u_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); }