utun - это сеть узлов. у каждого узла есть собственные локальные подсети.
глобальная задача: создать у каждого узла полную таблицу маршрутизации.
Узлы преимущественно создают связь напрямую друг с другом. Но если это не получается отправляют трафик транзитом через доступные узлы.
Иногда бывает что через транзитные узлы метрики лучше чем напрямую. Используем приоритетно узлы с лучшей метриков, при нехватке bandwidth используем разные каналы (агрегируем).
Динамически обновлем метрики каналов чтобы при отказе быстро переключаться на другие и не фризить обмен из-за отказов.
Ключевые изменения в понимании
paths
и узлы обмениваются таблицой маршрутов между собой так чтобы у каждого была актуальная таблица подсетей всех узлов.
маршрутами меняются клиенты, подключения которых которые взяты из конфига. и сервера принявшие подключения если клиент инициировал обмен маршрутами.
проверяет текущую версию nodeinfo, если совпадает - только bgp_update local nodelist
если версия новая или нет узла - spread:
bgp_update local nodelist
realloc: добавляет next hop: hoplist += prev_node_id, hop_count++
bgp_spread
инициируется подключение, клиент отправляет свою таблицу. когда сервер принимает таблицу - сервер помечает что по этому маршруту надо обмениваться маршрутами, далее;
- добавляет узел в список рассылки обновлений маршрутов
- отправляет свою таблицу
- добавляет в свою таблицу отсутствующие маршруты
- если что-то добавил:
- рассылает измененные маршруты по списку рассылки
- список рассылки - это linked-list очередей (также на базе ll_queue - каждый элемент = подписчик). один маршрут = одна отправленная кодограмма
если сервер получил кодограмму маршрута - он помечает флаг в etcp что с узлом надо обмениваться маршрутами (etcp_conn->routing_exchange_active=2) и добавляет в очередь рассылки маршрутов
=========================================
механизм инкрементальной синхронизации (реализация - потом, пока мысли)
1. вычисляем хеш каждой записи в роутинг таблице. используем ip+mask+node_uid
2. потом из этих хешей создаем хеш таблицу (старшие n бит номер ячейки). далее вычисляются хеши каждой ячейки.
на первом этапе n=16, на втором - n=16*16 на третьем n=16*16*16.
отправляем хеши удаленному узлу в формате: [n, 1 байт] ([индекс хеша 2 байта - используется n старших бит][хеш - 8 байт])
удаленный узел считает свои хеши и сравнивает. где не совпало смотрит сколько записей.
если записей не много - передает эти записи.
если записей много - добавляет 4 бита к хеш таблице и строит субтаблицу для
==========================================
Формат роутинга:
Таблица узлов состоит из записей:
- uid
- name
- links
- 3 транзитных узла с метриками (RTT)
- маршруты узла
- текущая загрузка линка (за последние 10 сек) можно частоту адаптировать под размер сети
- bandidth limit
- transit bandwidth limit
две группы узлов:
- узлы за nat. подключаются через транзитные узлы. измеряют пинги до транзитных и выбирают N (3 default) лучшие линки. 3 лучших используем для распространения маршрутов
- транзитные узлы. имеют линки с загрузкой.
добавить кодограмму - отменить распространение маршрутов по линку (+ сделать важным линком)
карта маршрутизации:
План:
- сделать передачу роутинга в
- сделать фоновый probe для узлов (условно 1 нода в секунду). выигравшие по качеству соатновятся основными
- сделать etcp дизконнект:
- отправить disconnect request + дождаться ack дальше master удаляет, slave удаляет по down.
2. withdraw:
bgp_withdraw(struct ROUTE_BGP bgp, uint64_t node_to_del, uint64_t wd_source) - wd_source это узел который захотел withdraw.
- находим у себя node to del. удаляем если в hoplist найден wd_node (или мы = wd_node)
если удалили - распространяем по всем линкам с этими же аргументами
// задача модуля: поиск по таблице маршрутизации нужной записи, вставка-удаление маршрутов
// Forward declarations
structETCP_CONNECTIONS;
structROUTE_TABLE;
structROUTE_ENTRY;
structNODEINFO_Q;
/**
*@briefФлагиузла
@ -18,42 +21,6 @@ typedef enum {
}route_flags_t;
structNODE_CONN_INFO{
uint32_tendpoint_ip;// IP для прямого подключения (к nexthop_node_id). 0 - нет IP (если мы - сервер)
uint16_tendpoint_port;// Порт для прямого подключения (к nexthop_node_id). 0 - нет PORT (если мы - сервер)
uint8_tpublic_key[64];// публичный ключ узла (для прямого подключения)
structETCP_CONN*conn_id;// Указатель на подключение к next_hop, может быть null.
// next_hop - это первый элемент в hop_list.
// hop list может быть разный для разных подключений. Это надо понимать и учитывать.
uint64_t*hop_list;// маршрут до узла (next hop -> ... -> destination_hop): список промежуточных узлов (NODE ID, кол-во - hop_count) включая конечный узел, не включая наш узел. null - локальный маршрут
uint8_thop_count;// Количество узлов до узла назначения. 0 - я (not used), 1 - direct connect (hoplist 1 запись nexthop_node_id = id узла назначения)
};
structNODE_CONNS_INFO{// Один NODE_CONNS_INFO на один node_id. несколько маршрутов с одинаковым node_id должны ссылаться на один экземпляр NODE_CONNS_INFO.
uint64_tnode_id;// ID узла назначения. Если = моему ID - локальный маршрут.
uint8_tflags;// флаги узла
uint8_tpreferred_conn;// выбранное соединение (его будем распространять далее по BGP). по умолчанию 0. при изменении conn_info надо за ним присмотреть.
uint8_tconninfo_count;// число подключений к узлу
uint8_tconninfo_memsize;// размер выделенной памяти (оптимизация realloc)
uint16_tref_count;// счетчик ссылок с разных route_entry для освобождения
structNODE_CONN_INFOconn_info[0];// сами подключения. управлять памятью своими силами. использовать u_malloc, u_reclloc, u_free (совместимо с stdlib). максимально просто - только увеличиваем при нехватке, не уменьшаем.
В таблице роутинга маршруты не пересекаются. Т.е. не может быть одновременно 192.168.1.1/24 и 192.168.1.100/30
Как работает роутинг:
1. типов маршрута бывает два: learned и local.
1. типов маршрута бывает два: learned (роутятся в ETCP instance) и local (роутятся в TUN).
local - локальные маршруты из конфига (опция конфига my_subnet=IP/Mask)
при инициализации они сразу добавляются в роутинг таблицу (и используются для отправки пакетов в tun интерфейс).
при инициализации local сразу добавляются в роутинг таблицу (ипользуя запись BGP_NODEINFO_PACKET* my_nodeinfo).
2. При установке подключения к новому узлу мы отправляем этому узлу полностью свою таблицу маршрутизации (local + learned узлы). Если несколько доступных линков - отправляем только один - preferred_conn
BGP_NODEINFO собирается на узле владельце node, далее распространяется по остальным узлам.
2. При установке подключения к новому узлу мы отправляем этому узлу все nodeinfo. Если несколько доступных линков - отправляем только один - preferred_conn
3. При изменении preferred_conn рассылаем reroute (например старый preferred_conn удален)
3. При удалении всех подключений рассылаем withdraw.
3. При получении маршрута мы смотрим есть ли такой маршрут уже в таблице. Если такой маршрут есть и ID узла другой - игнорируем с ошибкой. иначе добавляем/обновляем (для одного узла может быть несколько маршрутов). Игнорируем маршрут если наш ID есть в списке узлов (hop_list).
3. При получении BGP_NODEINFO_PACKET мы смотрим есть ли такой узел уже в таблице. Если узла нет - добавляем, если есть - обновляем информацию о узле включая его маршруты и список next_hop через которые доступен узел (nb_routes).
Добавляем так:
инкрементируем hop_count
устанавливая etcp линк с которого приняли как next_hop, добавляем его в hop_list.
рассылаем по всем активным линкам кроме линка с которого получили (обязательно)
Обновление: обновляем hop_list если поменялся
4. При отключении от узла мы
Удаляем все маршруты узла
4. При отключении от узла (если у node не осталось nb_routes) мы:
Удаляем node и вложенные структуры: все маршруты узла
Рассылаем withdraw для узла hop_id.
Логика рассылки withdraw:
Если получен withdraw - удаляем этот маршрут и распространяем withdraw или reroute по всем линкам кроме того с которого получили. В зависимости от изменений (остались ли резервные линки или изменился preferred_conn).
Метрик маршрута пока нет. используется первый доступный.
Как работаеут маршрутизация:
destination IP -> route table lookup -> route entry (ip/mask, node_info*) -> выбор лучшего next_hop -> отправка в next_hop
uint8_tver;// версия пакета (циклический счетчик чтобы быстро сравнивать с локальной копией - были ли обновления)
uint8_tpublic_key[SC_PUBKEY_SIZE];// node pubkey
uint8_tnode_name_len;// размер в байтах (без null терминации)
uint8_tlocal_v4_sockets;// NODEINFO_IPV4_SOCKET число локальных ipv4 сокетов узла (для direct incoming connections)
uint8_tlocal_v6_sockets;// NODEINFO_IPV6_SOCKET число локальных ipv6 сокетов узла (для direct incoming connections) (пока 0)
uint8_tlocal_v4_subnets;// NODEINFO_IPV4_SUBNET число локальных ipv4 подсетей узла
uint8_tlocal_v6_subnets;// NODEINFO_IPV6_SUBNET число локальных ipv6 подсетей узла (пока 0)
uint8_ttranzit_nodes;// NODEINFO_TRANZIT_NODE лучшие транзитные узлы для этой ноды (минимальный пинг / лучшее качество каналов. выбирается/обновляется узлом)
uint8_thop_count;// hop list: маршрут по которому распространялся этот NODEINFO_PACKET. для избежания зацикливаний при распространении по узлам. каждый узел при передаче инкрементирует и добавляет в конец свой node_id.
// далее идут динамическип поля по порядку следования полей в этой структуре: char node_name[node_name_len], сокеты, роуты, tranzit nodes, hop list (блоки описаны структурами ниже). hop list - это массив node_id[hop_count].
}__attribute__((packed));
structNODEINFO_IPV4_SOCKET{
uint8_taddr[4];// network byte order
uint16_tport;
}__attribute__((packed));
structNODEINFO_IPV6_SOCKET{
uint8_taddr[16];
uint16_tport;
}__attribute__((packed));
structNODEINFO_IPV4_SUBNET{
uint8_taddr[4];// network byte order
uint8_tprefix_length;
}__attribute__((packed));
structNODEINFO_IPV6_SUBNET{
uint8_taddr[16];
uint8_tprefix_length;
}__attribute__((packed));
structNODEINFO_TRANZIT_NODE{
uint64_tnode_id;// (big-endian)
uint16_trtt;// x0.1 ms (измеренный удаленным узлом RTT до транзитного узла)
uint16_tlink_q;// меньше - лучше (потери + 1/BW)
}__attribute__((packed));
structNODEINFO_PATH{
structll_entryll;
structETCP_CONN*conn;
uint8_thop_count;// hop list: маршрут этого path
}__attribute__((packed));
structNODEINFO_Q{
structll_entryll;
structll_queue*paths;
uint8_tdirty;
uint8_tlast_ver;
structNODEINFOnode;// Всегда в конце структуры - динамически расширяемый блок
if(route_insert(instance->rt,instance->bgp->local_node))DEBUG_INFO(DEBUG_CATEGORY_ROUTING,"Added local routes");elseDEBUG_WARN(DEBUG_CATEGORY_ROUTING,"Failed to add local routes");