Merge origin/master with encrypted attachment ownership fixes
Validation: ./build.sh --no-gui -j4 and make check -j4 passed; 186 tests, no failures.
This commit is contained in:
commit
bafda5ae3a
38 files changed
+2854
-984
No files matched your search
@@ -22,11 +22,12 @@ enum address_inbound {
|
||||
enum address_profile_source { ADDRESS_SOURCE_NONE, ADDRESS_SOURCE_ETCP, ADDRESS_SOURCE_STUN };
|
||||
|
||||
struct address_profile {
|
||||
uint8_t mapping;
|
||||
uint8_t inbound;
|
||||
uint8_t mapping; /* enum address_mapping: UNKNOWN, без трансляции, EIM или зависимый mapping. */
|
||||
uint8_t inbound; /* enum address_inbound: UNKNOWN, OPEN либо нужен исходящий контакт. */
|
||||
uint8_t source; /* ETCP — измерение рабочего endpoint; STUN — предварительный EIM? с неизвестным входом. */
|
||||
};
|
||||
|
||||
/* Постоянные строки для диагностики; неизвестное значение возвращает INVALID. */
|
||||
static inline const char *address_mapping_name(uint8_t value) {
|
||||
static const char *const names[] = {"UNKNOWN", "UNCHANGED", "EIM", "DEPENDENT"};
|
||||
return value <= ADDRESS_MAP_DEPENDENT ? names[value] : "INVALID";
|
||||
@@ -42,6 +43,8 @@ static inline const char *address_profile_source_name(uint8_t value) {
|
||||
return value <= ADDRESS_SOURCE_STUN ? names[value] : "INVALID";
|
||||
}
|
||||
|
||||
/* 1 — поля входят в enum и при source=NONE оба свойства UNKNOWN; 0 — иначе/NULL.
|
||||
* Проверяет представление, а не достоверность измерения или сочетания свойств. */
|
||||
static inline int address_profile_valid(const struct address_profile *profile) {
|
||||
return profile && profile->mapping <= ADDRESS_MAP_DEPENDENT && profile->inbound <= ADDRESS_IN_CONTACT &&
|
||||
profile->source <= ADDRESS_SOURCE_STUN &&
|
||||
|
||||
@@ -39,10 +39,17 @@ struct ROUTE6_TABLE {
|
||||
struct ll_queue *queue; // indexed by node_id for delete
|
||||
};
|
||||
|
||||
/* Создать таблицу в ua (его время жизни длиннее таблицы); NULL при ошибке. */
|
||||
struct ROUTE6_TABLE *route6_table_create(struct UASYNC *ua);
|
||||
/* Освободить таблицу и записи, сохраняя заимствованные узлы; NULL допустим. */
|
||||
void route6_table_destroy(struct ROUTE6_TABLE *table);
|
||||
/* Вставить IPv6-подсети node. Вложенные префиксы допустимы; дубликаты radix пропускает.
|
||||
* true означает завершённый обход, даже если ничего не добавлено. false — неверный вход/нет подсетей/OOM;
|
||||
* при OOM ранее вставленные подсети остаются. Адреса и /0..128 должен проверить вызывающий. */
|
||||
bool route6_insert(struct ROUTE6_TABLE *table, struct TOPO_GROUP_NODE *node);
|
||||
/* Удалить все записи с node->node_id (не по равенству указателей); NULL table/node допустимы. */
|
||||
void route6_delete(struct ROUTE6_TABLE *table, struct TOPO_GROUP_NODE *node);
|
||||
/* Найти наиболее длинный подходящий префикс; addr — ненулевой указатель на 16 сетевых байт. NULL — нет маршрута. */
|
||||
const struct ROUTE6_DATA *route6_lookup(struct ROUTE6_TABLE *table, const uint8_t addr[16]);
|
||||
|
||||
|
||||
|
||||
@@ -16,18 +16,23 @@ extern "C" {
|
||||
struct UTUN_INSTANCE;
|
||||
struct TOPO_GROUP;
|
||||
|
||||
// Запускает зондирование связности ко всем адресам удалённого узла
|
||||
/* Запустить асинхронные пробы известных адресов nq из registry; group — его группа.
|
||||
* UDP: до 8 сокетов, серия CONN_PROBE_COUNT с каждого до первого успешного сокета;
|
||||
* TCP: один STCP ping с REALITY-параметрами при наличии. RTT — 0.1 мс, UINT16_MAX неизвестен.
|
||||
* До 16 уникальных IP:port на семейство. Собственный/спящий узел и текущие пробы пропускаются;
|
||||
* NAT/standby могут отложить старт. Результат — в nq->connectivity, завершение уведомляет topo callback. */
|
||||
void route_connectivity_probe_node(struct UTUN_INSTANCE* instance, struct TOPO_GROUP* group,
|
||||
struct TOPO_GROUP_NODE* nq);
|
||||
|
||||
// Отменяет все pending пробы для узла (при удалении / withdraw)
|
||||
/* Отменить UDP/TCP-пробы и отложенный старт, освободить контексты; NULL допустим.
|
||||
* Сбрасывает probe_status/pending_count, сохранённые RTT/status остаются. */
|
||||
void route_connectivity_cancel_node(struct TOPO_GROUP_NODE* nq);
|
||||
|
||||
// Отменяет все pending пробы для всех узлов (при destroy)
|
||||
// Отменяет pending пробы всех узлов во всех группах instance (при destroy)
|
||||
void route_connectivity_cancel_all(struct UTUN_INSTANCE* instance);
|
||||
|
||||
// Устанавливает таймаут одного probe-пинга (по умолчанию CONN_PROBE_TIMEOUT_MS).
|
||||
// Используется тестами для ускорения probe-циклов.
|
||||
// Используется тестами для ускорения probe-циклов; настройка общая для процесса, 0 игнорируется.
|
||||
void route_connectivity_set_probe_timeout_ms(uint16_t ms);
|
||||
|
||||
|
||||
|
||||
@@ -1,64 +1,42 @@
|
||||
# route_connectivity — зондирование прямой связности с удалённым узлом
|
||||
# route_connectivity — прямые пробы адресов узла
|
||||
|
||||
## 1. Назначение
|
||||
Проверяет прямую IP-достижимость всех адресов удалённого узла через `etcp_send_ping_to_socket()`.
|
||||
Для каждого адреса перебирает локальные сокеты-кандидаты (до 8), с каждого делает серию из 3 пингов
|
||||
(CONN_PROBE_COUNT=3, CONN_PROBE_TIMEOUT_MS=1000). Результаты (reachable/unreachable, min_rtt)
|
||||
сохраняются в `TOPO_CONNECTIVITY` узла и используются `conn_mgr` для выбора оптимального пути.
|
||||
`route_connectivity_probe_node(instance, group, nq)` берёт общую запись узла из
|
||||
registry и запускает асинхронные пробы. Собственный узел, пир в SLEEP-фазе и уже
|
||||
идущие/отложенные пробы пропускаются. NAT-окно сохраняет незнакомые IP для проверки
|
||||
входящих; Android standby откладывает старт до активного интервала.
|
||||
|
||||
В отличие от `route_ping` (пинг через BGP-посредника), здесь пинги идут напрямую с локальных сокетов
|
||||
на адреса удалённого узла — без промежуточных узлов.
|
||||
Для каждого семейства перебираются до 16 уникальных IP:port. Нулевые адреса/порты
|
||||
пропускаются. Дедупликация выполняется по IP:port, без учёта типа адреса/протокола.
|
||||
|
||||
## 2. Как пользоваться
|
||||
```c
|
||||
// Запустить зондирование узла (вызывается conn_mgr при обнаружении нового узла):
|
||||
route_connectivity_probe_node(instance, nodeq);
|
||||
UDP проверяется с обычных локальных сокетов того же семейства (до 8).
|
||||
IPv4-кандидаты фильтруются через `sock_match` в порядке списка сокетов; предпочтений
|
||||
по /24 или рейтинга DIRECT/EIM здесь нет. Для IPv6 сначала идут сокеты той же /64,
|
||||
затем остальные IPv6. TCP-сокеты и `probe_only` для UDP исключены.
|
||||
С каждого UDP-сокета идёт последовательная серия из `CONN_PROBE_COUNT=3` ping;
|
||||
достаточно одного ответа в серии, чтобы завершить этот адрес успешно. Следующий
|
||||
сокет пробуется, если текущая серия не дала успеха. RTT — минимум успешной серии,
|
||||
а не обязательный минимум по всем доступным сокетам.
|
||||
|
||||
// Отменить зондирование (при удалении узла / withdraw):
|
||||
route_connectivity_cancel_node(instance, nodeq);
|
||||
TCP-адрес проверяется одним `etcp_send_tcp_ping` через STCP-клиент, независимо
|
||||
от набора UDP-сокетов. Используются REALITY-параметры из записи узла; адрес с
|
||||
REALITY-флагом без параметров пропускается. Проба не создаёт READY-сессию группы.
|
||||
Таймаут по умолчанию 1000 мс; `route_connectivity_set_probe_timeout_ms(ms)` меняет
|
||||
его для новых контекстов всего процесса, 0 игнорируется.
|
||||
|
||||
// Отменить все зондирования (при destroy):
|
||||
route_connectivity_cancel_all(instance);
|
||||
```
|
||||
Результаты находятся в `nq->connectivity`: `probe_status`, `pending_count`,
|
||||
`interface/nat/real_status`, соответствующие `*_min_rtt` и `*_probe_time`.
|
||||
RTT и время — timebase (0.1 мс), UINT16_MAX означает неизвестный RTT.
|
||||
INTERFACE обновляет также исторические поля REAL; отдельного третьего адреса
|
||||
для этого не требуется. Успех одного адреса не затирается неудачей другого.
|
||||
Завершение последней pending-пробы устанавливает DONE, обновляет БД и вызывает
|
||||
`topo_fire_nodeinfo_cbk`. Если пригодных адресов/сокетов нет, DONE означает только
|
||||
конец обхода: UNKNOWN не превращается автоматически в UNREACHABLE.
|
||||
|
||||
Результаты доступны через `nodeq->connectivity`:
|
||||
- `probe_status` — `PROBE_STATUS_NONE` / `PROBE_STATUS_IN_PROGRESS` / `PROBE_STATUS_DONE`
|
||||
- `interface_status/nat_status/real_status` — `PROBE_RESULT_UNKNOWN` / `REACHABLE` / `UNREACHABLE`
|
||||
- `interface_min_rtt/nat_min_rtt/real_min_rtt` — минимальный RTT по всем сокетам (0.1ms)
|
||||
- `pending_count` — счётчик незавершённых серий (когда 0 → PROBE_STATUS_DONE)
|
||||
`route_connectivity_cancel_node(nq)` отменяет все UDP/TCP-пробы и отложенный старт,
|
||||
освобождает контексты и сбрасывает probe_status/pending_count. Последние результаты
|
||||
RTT/status сохраняются. Отмена нужна до удаления узла, группы или рабочих сокетов.
|
||||
`route_connectivity_cancel_all(instance)` обходит все группы, не только UTUN.
|
||||
Все вызовы выполняются в потоке ядра; group/nq заимствованы до завершения или отмены.
|
||||
|
||||
Ключевой нюанс: зондирование пропускает собственный узел (`nq->node->node_id == instance->node_id`).
|
||||
Если зондирование уже в процессе, повторный вызов игнорируется.
|
||||
|
||||
## 3. API
|
||||
|
||||
### Внутренние структуры
|
||||
| Структура | Назначение |
|
||||
|-----------|------------|
|
||||
| `conn_probe_ctx` | Контекст одной серии проб: `addr_type` (INTERFACE/NAT/REAL), `target_addr`, `candidate_sockets[8]`, `best_across_sockets` (min RTT по всем опробованным сокетам), `min_rtt` (в текущей серии), счётчики |
|
||||
|
||||
### Подбор сокетов-кандидатов (`conn_match_candidate_sockets`)
|
||||
Для каждого адреса узла подбирает локальные сокеты в порядке предпочтения:
|
||||
1. **INTERFACE-адрес**: PRIVATE/LOCAL сокеты в той же /24 подсети
|
||||
2. **NAT/REAL-адрес**: DIRECT > EIM > PUBLIC/UNKNOWN сокеты
|
||||
3. Fallback: остальные подходящие сокеты, затем все IPv4
|
||||
|
||||
Для IPv6: предпочитает сокеты в той же /64 подсети.
|
||||
|
||||
### Функции
|
||||
| Функция | Назначение |
|
||||
|---------|------------|
|
||||
| `route_connectivity_probe_node(instance, nq)` | Перебрать все v4/v6 адреса узла, для каждого подобрать сокеты-кандидаты, запустить серии пингов по 3. Заполняет `nq->connectivity` |
|
||||
| `route_connectivity_cancel_node(instance, nq)` | Сбросить `probe_status=NONE`, `pending_count=0`, занулить `nq` в ctx (чтобы callback не обновлял статистику) |
|
||||
| `route_connectivity_cancel_all(instance)` | Сбросить состояние всех узлов в default-группе |
|
||||
|
||||
### Логика завершения
|
||||
- Серия из 3 пингов с одного сокета: если все 3 потеряны → переход к следующему сокету
|
||||
- Если хоть 1 пинг успешен → серия считается успешной для данного адреса, обновляется `*_status=REACHABLE` и `*_min_rtt`
|
||||
- Когда `pending_count` доходит до 0 → `probe_status = PROBE_STATUS_DONE`
|
||||
|
||||
### Зависимости
|
||||
- `src/etcp_connections.h` — `etcp_send_ping_to_socket()` (прямой пинг)
|
||||
- `src/topo_node.h` — `TOPO_GROUP_NODE`, `TOPO_CONNECTIVITY`, `TOPO_ADDR4`, `TOPO_ADDR6`
|
||||
- `src/topo_group.h` — `topo_groups_get_default()` (для cancel_all)
|
||||
- `lib/u_async.h` — get_time_tb() для отметок времени
|
||||
`route_ping` также выполняет прямые UDP-пробы, но для измерения наблюдаемого endpoint;
|
||||
выбор наблюдателей/помощников и вычисление NAT-профиля находятся в `nat_detection`.
|
||||
@@ -9,6 +9,9 @@
|
||||
// Порядок кодирования: encrypt-then-sign (подпись заголовка и payload, включая шифртекст).
|
||||
// Список пройденных узлов добавляет/снимает etcp_router снаружи этого формата.
|
||||
// При ошибке декодирования (плохая подпись / не удалось расшифровать) пакет дропается.
|
||||
// Все вызовы — в потоке ядра; inst и его registry/ключи должны быть готовы.
|
||||
// Вход заимствован и не изменяется; при успехе *out принадлежит вызывающему (u_free).
|
||||
// При ошибке out/out_len не устанавливаются. Буфер выделяется и при mode=0/отсутствии crypto-флагов.
|
||||
#ifndef ROUTE_CRYPTO_H
|
||||
#define ROUTE_CRYPTO_H
|
||||
|
||||
@@ -28,6 +31,7 @@ struct UTUN_INSTANCE;
|
||||
|
||||
// Кодировать пакет: base = [SVC_ROUTE_HDR][plaintext payload].
|
||||
// mode — битовая маска ROUTE_CRYPTO_SIGN / ROUTE_CRYPTO_ENCRYPT.
|
||||
// peer_node_id — получатель ECDH; hdr.src_node_id должен быть локальным, исходные crypto-флаги очищены.
|
||||
// Возвращает новый буфер (u_malloc) с финальным пакетом. 0 = успех, <0 = ошибка.
|
||||
int route_crypto_encode(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
|
||||
const uint8_t* base, size_t base_len, int mode,
|
||||
@@ -36,6 +40,7 @@ int route_crypto_encode(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
|
||||
// Декодировать пакет: wire = [SVC_ROUTE_HDR][payload][sig?].
|
||||
// Проверяет подпись и/или расшифровывает. Через *rx_flags сообщает, что было на wire
|
||||
// (ROUTE_CRYPTO_SIGN / ROUTE_CRYPTO_ENCRYPT). Возвращает [SVC_ROUTE_HDR][plaintext] с очищенными флагами.
|
||||
// Ключ отправителя берётся по hdr.src_node_id из registry. rx_flags обязателен; может быть заполнен и при ошибке.
|
||||
// 0 = успех, <0 = ошибка (пакет дропается).
|
||||
int route_crypto_decode(struct UTUN_INSTANCE* inst,
|
||||
const uint8_t* wire, size_t wire_len,
|
||||
|
||||
@@ -21,22 +21,12 @@ static uint32_t prefix_to_mask(uint8_t prefix) {
|
||||
}
|
||||
|
||||
static bool routes_overlap(uint32_t net1, uint8_t pre1, uint32_t net2, uint8_t pre2) {
|
||||
if (pre1 == pre2 && net1 == net2) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool check_route_overlap_in_table(uint32_t network, uint8_t prefix,
|
||||
const struct ROUTE_ENTRY *entries, size_t count) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (routes_overlap(network, prefix, entries[i].network, entries[i].prefix_length)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
uint32_t mask = prefix_to_mask(pre1 < pre2 ? pre1 : pre2);
|
||||
return (net1 & mask) == (net2 & mask);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Поиск (LPM)
|
||||
// Поиск по непересекающимся диапазонам
|
||||
// ============================================================================
|
||||
|
||||
static int binary_search_insert_pos(struct ROUTE_ENTRY *entries, size_t count,
|
||||
@@ -56,51 +46,19 @@ static int binary_search_insert_pos(struct ROUTE_ENTRY *entries, size_t count,
|
||||
return left;
|
||||
}
|
||||
|
||||
static struct ROUTE_ENTRY* binary_search_lpm(struct ROUTE_TABLE *table, uint32_t dest_ip) {
|
||||
if (table->count == 0) return NULL;
|
||||
|
||||
/* В отсортированной таблице без пересечений подходящий диапазон может быть только один. */
|
||||
static struct ROUTE_ENTRY* binary_search_route(struct ROUTE_TABLE *table, uint32_t dest_ip) {
|
||||
int left = 0;
|
||||
int right = (int)table->count - 1;
|
||||
struct ROUTE_ENTRY *best = NULL;
|
||||
uint8_t best_prefix = 0;
|
||||
|
||||
while (left <= right) {
|
||||
int mid = left + (right - left) / 2;
|
||||
struct ROUTE_ENTRY *entry = &table->entries[mid];
|
||||
uint32_t mask = prefix_to_mask(entry->prefix_length);
|
||||
|
||||
if ((dest_ip & mask) == (entry->network & mask)) {
|
||||
if (entry->prefix_length > best_prefix) {
|
||||
best_prefix = entry->prefix_length;
|
||||
best = entry;
|
||||
}
|
||||
if (mid + 1 < (int)table->count) {
|
||||
struct ROUTE_ENTRY *next = &table->entries[mid + 1];
|
||||
uint32_t next_mask = prefix_to_mask(next->prefix_length);
|
||||
if ((dest_ip & next_mask) == (next->network & next_mask)) {
|
||||
left = mid + 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else if (dest_ip > entry->network) {
|
||||
left = mid + 1;
|
||||
} else {
|
||||
right = mid - 1;
|
||||
}
|
||||
if ((dest_ip & mask) == entry->network) return entry;
|
||||
if (dest_ip > entry->network) left = mid + 1;
|
||||
else right = mid - 1;
|
||||
}
|
||||
|
||||
// fallback проверка предыдущей записи
|
||||
if (!best && left > 0) {
|
||||
struct ROUTE_ENTRY *entry = &table->entries[left - 1];
|
||||
uint32_t mask = prefix_to_mask(entry->prefix_length);
|
||||
if ((dest_ip & mask) == (entry->network & mask)) {
|
||||
best = entry;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -160,8 +118,25 @@ bool route_insert(struct ROUTE_TABLE *table, struct TOPO_GROUP_NODE *node) {
|
||||
|
||||
for (const struct TOPO_SUBNET4* s = sub; s; s = s->next) {
|
||||
uint32_t network; memcpy(&network, s->addr, 4); network = ntohl(network);
|
||||
if (check_route_overlap_in_table(network, s->prefix_length, table->entries, table->count)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "overlap detected for %s/%d", ip_to_string(network).a, s->prefix_length);
|
||||
if (s->prefix_length > 32) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "reject node=%016llx subnet=%s/%u: invalid IPv4 prefix",
|
||||
(unsigned long long)node->node_id, ip_to_string(network).a, s->prefix_length);
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < table->count; i++) {
|
||||
const struct ROUTE_ENTRY *e = &table->entries[i];
|
||||
if (!routes_overlap(network, s->prefix_length, e->network, e->prefix_length)) continue;
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "reject node=%016llx subnet=%s/%u: overlaps node=%016llx subnet=%s/%u",
|
||||
(unsigned long long)node->node_id, ip_to_string(network).a, s->prefix_length,
|
||||
(unsigned long long)e->v_node_info->node_id, ip_to_string(e->network).a, e->prefix_length);
|
||||
return false;
|
||||
}
|
||||
for (const struct TOPO_SUBNET4 *previous = sub; previous != s; previous = previous->next) {
|
||||
uint32_t other; memcpy(&other, previous->addr, 4); other = ntohl(other);
|
||||
if (!routes_overlap(network, s->prefix_length, other, previous->prefix_length)) continue;
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "reject node=%016llx subnet=%s/%u: overlaps own subnet=%s/%u",
|
||||
(unsigned long long)node->node_id, ip_to_string(network).a, s->prefix_length,
|
||||
ip_to_string(other).a, previous->prefix_length);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -176,6 +151,7 @@ bool route_insert(struct ROUTE_TABLE *table, struct TOPO_GROUP_NODE *node) {
|
||||
|
||||
for (const struct TOPO_SUBNET4* s = sub; s; s = s->next) {
|
||||
uint32_t network; memcpy(&network, s->addr, 4); network = ntohl(network);
|
||||
network &= prefix_to_mask(s->prefix_length);
|
||||
int pos = binary_search_insert_pos(table->entries, table->count, network, s->prefix_length);
|
||||
if (pos < (int)table->count) memmove(&table->entries[pos + 1], &table->entries[pos], (table->count - pos) * sizeof(struct ROUTE_ENTRY));
|
||||
struct ROUTE_ENTRY* e = &table->entries[pos];
|
||||
@@ -210,7 +186,7 @@ struct ROUTE_ENTRY* route_lookup(struct ROUTE_TABLE *table, uint32_t dest_ip) {
|
||||
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "dest_ip=%s", ip_to_string(dest_ip).a);
|
||||
|
||||
struct ROUTE_ENTRY *result = binary_search_lpm(table, dest_ip);
|
||||
struct ROUTE_ENTRY *result = binary_search_route(table, dest_ip);
|
||||
|
||||
if (result) {
|
||||
table->stats.routes_lookup_hits++;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/* route_lib — локальная IPv4-таблица подсетей UTUN, отсортированный массив без пересечения маршрутов.
|
||||
* create → insert/add_local_subnet → lookup → delete/destroy; все операции в одном uasync-потоке.
|
||||
* create → insert → lookup → delete/destroy; все операции в одном uasync-потоке.
|
||||
* Пересечения и дубликаты запрещены, в том числе внутри одного набора подсетей; /0 занимает всю таблицу.
|
||||
* Вставка нормализует адреса по маске; при отказе таблица и её статистика не меняются.
|
||||
* network/dest_ip/parse_subnet — host order; только is_local_subnet принимает network order.
|
||||
* TOPO_GROUP_NODE заимствован: удалить его маршруты до освобождения узла.
|
||||
* lookup возвращает элемент массива, действительный до следующего изменения таблицы. */
|
||||
@@ -39,24 +41,24 @@ typedef enum {
|
||||
struct ROUTE_ENTRY {
|
||||
uint32_t network; // Сетевой адрес в host order
|
||||
uint8_t prefix_length; // Длина префикса подсети
|
||||
struct TOPO_GROUP_NODE* v_node_info; // узел владелец этих маршрутов. null если - локальный маршрут.
|
||||
struct TOPO_GROUP_NODE* v_node_info; // заимствованный владелец; локальный маршрут тоже содержит узел
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Таблица маршрутизации
|
||||
*
|
||||
* Структура представляет собой таблицу маршрутизации, содержащую записи маршрутов, подсети и статистику.
|
||||
* маршруты не должны пересекаться. т.е. адрес 192.168.1.1/30 нельзя добавить если есть 192.168.1.2/32 или 192.168.1.1/24
|
||||
* Подсети не пересекаются: например, 192.168.1.0/24 исключает 192.168.1.0/30 и 192.168.1.2/32.
|
||||
*/
|
||||
struct ROUTE_TABLE {
|
||||
struct ROUTE_ENTRY *entries; /**< Массив записей маршрутов */
|
||||
size_t count; /**< Текущее количество записей */
|
||||
size_t capacity; /**< Максимальная емкость таблицы */
|
||||
uint64_t my_node_id; /**< ID локального узла */
|
||||
uint32_t *dynamic_subnets; /**< Динамические подсети (массив пар: сеть, префикс) */
|
||||
size_t dynamic_subnet_count; /**< Количество динамических подсетей */
|
||||
uint32_t *local_subnets; /**< Локальные подсети (массив пар: сеть, префикс) */
|
||||
size_t local_subnet_count; /**< Количество локальных подсетей */
|
||||
uint32_t *dynamic_subnets; /**< Не используется текущей реализацией; destroy освобождает память */
|
||||
size_t dynamic_subnet_count; /**< Не используется текущей реализацией */
|
||||
uint32_t *local_subnets; /**< Не используется текущей реализацией; destroy освобождает память */
|
||||
size_t local_subnet_count; /**< Не используется текущей реализацией */
|
||||
struct {
|
||||
uint64_t local_routes; /**< Количество локальных маршрутов */
|
||||
uint64_t learned_routes; /**< Количество изученных маршрутов */
|
||||
@@ -80,10 +82,12 @@ struct ROUTE_TABLE *route_table_create(uint64_t my_node_id);
|
||||
void route_table_destroy(struct ROUTE_TABLE *table);
|
||||
|
||||
/**
|
||||
* @brief Вставляет в таблицу маршрутизации все подсети для указанного узла
|
||||
* @brief Атомарно вставляет все IPv4-подсети узла; /0..32, адреса нормализуются по маске
|
||||
* @param table Таблица без пересечений; перед заменой маршрутов узла вызвать route_delete
|
||||
* @param node узел, все маршруты которого нужно добавить
|
||||
*
|
||||
* @return true если подсети добавлены; false при отсутствии подсетей, пересечении или ошибке выделения памяти
|
||||
* @return true если весь набор добавлен; false при неверных аргументах/префиксе, отсутствии подсетей,
|
||||
* пересечении с таблицей/внутри набора или нехватке памяти. При false таблица не меняется.
|
||||
*/
|
||||
bool route_insert(struct ROUTE_TABLE *table, struct TOPO_GROUP_NODE *node);
|
||||
|
||||
@@ -112,7 +116,7 @@ struct ROUTE_ENTRY* route_lookup(struct ROUTE_TABLE *table, uint32_t dest_ip);
|
||||
void route_table_print(const struct ROUTE_TABLE *table);
|
||||
|
||||
/**
|
||||
* @brief Парсит строку подсети в сетевой адрес и длину префикса
|
||||
* @brief Разбирает IPv4/префикс; host-биты адреса сохраняются, префикс читается через atoi
|
||||
*
|
||||
* @param subnet_str Строка подсети (например, "192.168.1.0/24")
|
||||
* @param network Указатель для сохранения адреса в host order
|
||||
@@ -121,8 +125,6 @@ void route_table_print(const struct ROUTE_TABLE *table);
|
||||
*/
|
||||
int parse_subnet(const char *subnet_str, uint32_t *network, uint8_t *prefix_length);
|
||||
|
||||
bool route_add_local_subnet(struct ROUTE_TABLE *table, uint32_t network, uint8_t prefix_length);
|
||||
|
||||
/**
|
||||
* @brief Проверяет что IP адрес НЕ является публичным (роутится в интернете)
|
||||
*
|
||||
|
||||
@@ -1,31 +1,34 @@
|
||||
В таблице роутинга маршруты не пересекаются. Т.е. не может быть одновременно 192.168.1.1/24 и 192.168.1.100/30
|
||||
|
||||
Как работает роутинг:
|
||||
1. типов маршрута бывает два: learned (роутятся в ETCP instance) и local (роутятся в TUN).
|
||||
local - локальные маршруты из конфига (опция конфига my_subnet=IP/Mask)
|
||||
при инициализации local сразу добавляются в роутинг таблицу (ипользуя запись BGP_NODEINFO_PACKET* my_nodeinfo).
|
||||
|
||||
BGP_NODEINFO собирается на узле владельце node, далее распространяется по остальным узлам.
|
||||
|
||||
2. При установке подключения к новому узлу мы отправляем этому узлу все nodeinfo. Если несколько доступных линков - отправляем только один - preferred_conn
|
||||
3. При изменении preferred_conn рассылаем reroute (например старый preferred_conn удален)
|
||||
3. При удалении всех подключений рассылаем withdraw.
|
||||
|
||||
3. При получении BGP_NODEINFO_PACKET мы смотрим есть ли такой узел уже в таблице. Если узла нет - добавляем, если есть - обновляем информацию о узле включая его маршруты и список next_hop через которые доступен узел (nb_routes).
|
||||
Добавляем так:
|
||||
инкрементируем hop_count
|
||||
устанавливая etcp линк с которого приняли как next_hop, добавляем его в hop_list.
|
||||
рассылаем по всем активным линкам кроме линка с которого получили (обязательно)
|
||||
Обновление: обновляем hop_list если поменялся
|
||||
|
||||
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
|
||||
route_lib — локальная таблица IPv4-подсетей UTUN
|
||||
================================================
|
||||
|
||||
Подсети в таблице не пересекаются. Запрещены одинаковые и вложенные диапазоны,
|
||||
как между узлами, так и внутри одного переданного набора. Например,
|
||||
10.0.0.0/8 исключает 10.1.0.0/24; соседние 10.1.0.0/24 и 10.1.1.0/24 допустимы.
|
||||
Маршрут /0 занимает всё адресное пространство и может быть только единственным.
|
||||
|
||||
route_insert(table, node) добавляет все node->subnets->v4_subnets атомарно.
|
||||
Префиксы должны быть в диапазоне 0..32; адреса нормализуются маской перед хранением.
|
||||
При неверных аргументах, отсутствии подсетей, пересечении или OOM возвращает false,
|
||||
не меняя таблицу и её статистику. Перед заменой маршрутов узла вызывают route_delete.
|
||||
|
||||
Таблица — отсортированный массив. route_lookup получает IPv4 в host byte order
|
||||
и бинарным поиском возвращает единственный подходящий диапазон либо NULL.
|
||||
Это не LPM-таблица: вложенные префиксы исключены при вставке.
|
||||
Указатель на ROUTE_ENTRY действителен только до следующего изменения таблицы.
|
||||
Адреса TOPO_SUBNET4 хранятся в сетевых байтах, parse_subnet возвращает host order,
|
||||
is_local_subnet принимает network order. parse_subnet сохраняет host-биты и читает
|
||||
префикс через atoi; это не строгий валидатор всей строки CIDR.
|
||||
|
||||
TOPO_GROUP_NODE заимствован, в том числе для локальных маршрутов.
|
||||
Маршруты удаляют до освобождения узла. destroy освобождает массив, но не узлы.
|
||||
Все вызовы выполняются в одном потоке ядра.
|
||||
|
||||
Наполнение таблицы и поиск следующего узла — разные задачи:
|
||||
IPv4 destination -> route_lookup -> TOPO_GROUP_NODE -> etcp_route_send
|
||||
-> выбор живого next hop через CM/BGP -> ETCP.
|
||||
Для локального владельца routing доставляет пакет в TUN после firewall.
|
||||
Групповые пути, WITHDRAW, выбор по числу переходов и READY описаны в topo_group.h;
|
||||
общая подписанная запись узла — в topo_node.h и doc/node_snapshot.md.
|
||||
|
||||
IPv6-таблица route6_lib использует radix и разрешает вложенные префиксы.
|
||||
Основной routing.c пока доставляет только IPv4.
|
||||
@@ -1,5 +1,6 @@
|
||||
/* Прямые UDP-наблюдения с указанного рабочего сокета. Управление помощниками —
|
||||
* nat_detection; этот модуль не вычисляет NAT-тип и не выбирает чужой сокет. */
|
||||
* nat_detection; этот модуль не вычисляет NAT-тип и не выбирает чужой сокет.
|
||||
* Вызовы с ETCP_SOCKET выполняются в потоке его ядра; буферы адресов/тегов предоставляет вызывающий. */
|
||||
#ifndef ROUTE_PING_H
|
||||
#define ROUTE_PING_H
|
||||
|
||||
@@ -14,16 +15,25 @@ extern "C" {
|
||||
#define ROUTE_ADDR_WIRE_SIZE 19
|
||||
#define ROUTE_OBSERVE_TAG_SIZE 33
|
||||
|
||||
/* family(1:4/6), IP(16: IPv4 + 12 нулей либо IPv6), port(2 BE), без IPv6 scope_id.
|
||||
* encode/decode: 0/-1; decode также отвергает нулевой порт и ненулевой IPv4-padding.
|
||||
* При ошибке выход может быть частично заполнен. encode сам по себе нулевой порт не запрещает. */
|
||||
int route_ping_addr_encode(uint8_t out[ROUTE_ADDR_WIRE_SIZE], const struct sockaddr_storage *address);
|
||||
int route_ping_addr_decode(const uint8_t in[ROUTE_ADDR_WIRE_SIZE], struct sockaddr_storage *address);
|
||||
/* 1 — совпадают семейство/IP и (при include_port!=0) порт; 0 — иначе. IPv6 scope_id не сравнивается. */
|
||||
int route_ping_addr_equal(const struct sockaddr_storage *a, const struct sockaddr_storage *b, int include_port);
|
||||
|
||||
/* Ответ: address(19) || tag(33); tag связывает socket/generation/round/group. Числа BE, IP — сетевые байты. */
|
||||
/* Отправить OBSERVE PING: group/round и generation сокета ненулевые, timeout_ms — мс.
|
||||
* peer_key — 32 байта ключа наблюдателя, peer_address — его UDP endpoint.
|
||||
* Возвращает 0 при запуске, отрицательный код ошибки ETCP при отказе; callback/arg — контракт ETCP.
|
||||
* Ответ: address(19) || tag(33); профиль сокета здесь не изменяется. */
|
||||
int route_ping_observe(struct ETCP_SOCKET *socket, const uint8_t peer_key[SC_PUBKEY_SIZE],
|
||||
const struct sockaddr_storage *peer_address, uint64_t group, uint64_t round, int timeout_ms,
|
||||
etcp_ping_callback_t callback, void *arg);
|
||||
/* Проверить точные размер/тег и декодировать observed; 0/-1. Подлинность ответа проверяет ETCP. */
|
||||
int route_ping_observation(const uint8_t *data, size_t length, const struct ETCP_SOCKET *socket,
|
||||
uint64_t group, uint64_t round, struct sockaddr_storage *observed);
|
||||
/* NAT4UDP!(8) || socket_id(1) || generation(8 BE) || round(8 BE) || group(8 BE); out/socket обязательны. */
|
||||
void route_ping_make_tag(uint8_t out[ROUTE_OBSERVE_TAG_SIZE], const struct ETCP_SOCKET *socket, uint64_t group, uint64_t round);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -4,15 +4,16 @@
|
||||
`ETCP_SOCKET`. Выбор B/C, пересылка управляющих сообщений и классификация
|
||||
профиля принадлежат `nat_detection`; алгоритм — в `doc/nat_detection.txt`.
|
||||
|
||||
`route_ping_observe(socket, peer_key, peer_address, round, timeout_ms, callback, arg)`
|
||||
`route_ping_observe(socket, peer_key, peer_address, group, round, timeout_ms, callback, arg)`
|
||||
передаёт PING с `ETCP_PING_FLAG_OBSERVE`. PONG содержит фактически наблюдаемый
|
||||
source IP:port и исходный тег. Профиль не меняется от единственного ответа.
|
||||
`route_ping_observation` проверяет размер, ID/поколение сокета и round.
|
||||
`route_ping_observation` проверяет размер, ID/поколение сокета, round и group.
|
||||
|
||||
Адрес занимает 19 байт: family(1: 4/6), IP(16: IPv4 + 12 нулей либо IPv6),
|
||||
port(2, BE). Тег занимает 25 байт: `NAT2UDP!`(8), socket_id(1),
|
||||
generation(8, BE), round(8, BE). Неправильные family, скрытые IPv4-байты
|
||||
и нулевой порт отвергаются. Новый wire не использует native packed integers.
|
||||
port(2, BE). Тег занимает 33 байта: `NAT4UDP!`(8), socket_id(1),
|
||||
generation(8, BE), round(8, BE), group(8, BE). Декодирование отвергает неправильные
|
||||
family, скрытые IPv4-байты и нулевой порт. IPv6 scope_id в wire не передаётся.
|
||||
Кодирование нулевой порт не запрещает. Числа тега передаются в BE.
|
||||
|
||||
В транспорте nonce генерируется CSPRNG; PONG должен соответствовать ожидаемому
|
||||
ключу, source IP:port и принимающему socket generation. Pending снимается
|
||||
@@ -20,8 +21,8 @@ generation(8, BE), round(8, BE). Неправильные family, скрытые
|
||||
совпадающие запросы без callback; arg остаётся собственностью вызывающего.
|
||||
|
||||
`ETCP_PING_FLAG_ADDRESS_CHECK` обозначает прямое входящее испытание. Его
|
||||
receipt фиксируется на рабочем сокете владельца до отправки PONG. Потеря
|
||||
обратного PONG поэтому не превращает полученный ingress в отрицательный результат.
|
||||
приём передаётся `nat_detection_receive_ingress` без UDP-ответа: ответ мог бы
|
||||
испортить историю контактов. Результат передаётся по защищённому управляющему каналу.
|
||||
UDP-результаты не используются для TCP listener и SOCKS5 endpoint.
|
||||
|
||||
Проверки: `tests/test_route_ping.c`, `tests/test_nat_detection.c`,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* Учёт первой доставки payload логического канала. Один uasync-поток.
|
||||
* Десять завершённых интервалов по 50мс и текущий интервал; reset начинает новую историю.
|
||||
* Повторы учитывает вызывающий: add вызывается только при снятии подтверждённого inflight. */
|
||||
* Повторы учитывает вызывающий: ack вызывается только при снятии подтверждённого inflight.
|
||||
* Время — монотонные микросекунды; память всех структур принадлежит вызывающему. */
|
||||
#ifndef ROUTE_TX_STATS_H
|
||||
#define ROUTE_TX_STATS_H
|
||||
|
||||
@@ -14,16 +15,21 @@ extern "C" {
|
||||
#define ROUTE_TX_WINDOW_US 500000u
|
||||
#define ROUTE_TX_BUCKETS 11
|
||||
|
||||
/* tick_plus_one=0 означает пустую ячейку; иначе это номер 50-мс интервала + 1. */
|
||||
struct route_tx_bucket { uint64_t tick_plus_one, bytes, packets; };
|
||||
struct route_tx_meter {
|
||||
struct route_tx_bucket buckets[ROUTE_TX_BUCKETS];
|
||||
uint64_t started_us, last_us, bytes, packets;
|
||||
uint64_t started_us, last_us, bytes, packets; /* reset/последний ACK и накопленные итоги эпохи */
|
||||
};
|
||||
/* end_us — правая граница окна; valid=1 после накопления полных 500 мс с момента reset. */
|
||||
struct route_tx_window { uint64_t end_us, bytes, packets; int valid; };
|
||||
|
||||
/* Обнулить историю и начать эпоху в now_us; meter обязателен. */
|
||||
void route_tx_meter_reset(struct route_tx_meter *meter, uint64_t now_us);
|
||||
/* Учесть один впервые подтверждённый payload (bytes>0); 0/-1, при ошибке история не меняется. */
|
||||
int route_tx_meter_ack(struct route_tx_meter *meter, uint64_t now_us, uint64_t bytes);
|
||||
/* Неполное окно после reset возвращает valid=0; текущая незавершённая ячейка исключается. */
|
||||
/* Заполнить out суммой завершённых ячеек; 0/-1 (при ошибке out не меняется).
|
||||
* Неполное окно после reset возвращает valid=0; текущая незавершённая ячейка исключается. */
|
||||
int route_tx_meter_window(const struct route_tx_meter *meter, uint64_t now_us, struct route_tx_window *out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -33,7 +33,7 @@ int routing_create(struct UTUN_INSTANCE* instance);
|
||||
int routing_bind(struct UTUN_INSTANCE* instance);
|
||||
|
||||
/**
|
||||
* @brief Destroy routing module for instance
|
||||
* @brief Снять DATA binding и освободить IPv4-таблицу; callback TUN снимается при остановке самого TUN
|
||||
* @param instance UTUN instance
|
||||
*/
|
||||
void routing_destroy(struct UTUN_INSTANCE* instance);
|
||||
@@ -58,7 +58,9 @@ void routing_del_conn(struct ETCP_CONN* etcp);
|
||||
void routing_set_tun(struct UTUN_INSTANCE* instance);
|
||||
|
||||
/**
|
||||
* @brief Route a single packet (exposed for NAT module interception)
|
||||
* @brief Маршрутизировать [cmd:1][IPv4] в TUN либо через TOPO_GROUP_UTUN; требует готовых instance->rt/tun
|
||||
* @param entry Передаваемые entry и dgram: модуль забирает владение при любом исходе
|
||||
* @param src_node_id Исходный узел для диагностики, для выхода из TUN — instance->node_id
|
||||
*/
|
||||
void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry, uint64_t src_node_id);
|
||||
|
||||
|
||||
@@ -20,8 +20,13 @@ IPv6-пакеты на данный момент дропаются на эта
|
||||
**Ключевые нюансы:**
|
||||
- `routing_add_conn` / `routing_del_conn` — **deprecated** (no-op), маршруты теперь управляются через BGP и таблицу `route_lib`
|
||||
- Все не-IPv4 пакеты (IPv6, unknown) дропаются в `extract_dst_ip`
|
||||
- Multicast (224.0.0.0/4) и limited broadcast (255.255.255.255) дропаются без логирования
|
||||
- Multicast (224.0.0.0/4) и limited broadcast (255.255.255.255) отбрасываются с DEBUG-логом routing
|
||||
- Пакеты без маршрута дропаются с инкрементом `instance->dropped_packets`
|
||||
- `route_pkt` забирает entry и dgram при любом исходе; ожидает `[cmd:1][IPv4]` и готовые `rt`/`tun`.
|
||||
- Удалённая доставка идёт через `TOPO_GROUP_UTUN`; локальная — в `tun->input_queue`.
|
||||
Firewall проверяется перед маршрутизацией выхода из TUN и перед локальной доставкой в TUN.
|
||||
- Все вызовы выполняются в потоке ядра. `routing_destroy` снимает DATA binding и освобождает таблицу;
|
||||
callback очереди TUN снимается при остановке самого TUN/UTUN-сервиса.
|
||||
|
||||
## 3. API
|
||||
|
||||
|
||||
@@ -21,17 +21,22 @@ struct ETCP_SOCKET;
|
||||
#define SOCK_TC_PROBE 4
|
||||
|
||||
struct sock_view {
|
||||
struct ETCP_SOCKET *sock;
|
||||
uint8_t role, has_inet, has_priv, family, is_tcp;
|
||||
uint32_t local_ip4, public_ip4;
|
||||
struct address_profile address_profile;
|
||||
struct ETCP_SOCKET *sock; /* заимствован; не переживает удаление сокета */
|
||||
uint8_t role, has_inet, has_priv, family, is_tcp; /* SOCK_ROLE_*, доступ к Internet/LAN, AF_INET, TCP-флаг */
|
||||
uint32_t local_ip4, public_ip4; /* network order; public_ip4=0, если публичный адрес неизвестен */
|
||||
struct address_profile address_profile; /* копия текущего профиля */
|
||||
};
|
||||
|
||||
/* public_address — классификация IP в соответствующем семействе, не доказательство входа. */
|
||||
/* Возвращает SOCK_TC_*; address_type=TOPO_ADDR_*, config_type=CFG_SERVER_TYPE_*.
|
||||
* public_address — классификация IP в соответствующем семействе, не доказательство входа. */
|
||||
uint8_t sock_target_class(uint8_t address_type, uint8_t config_type, const struct address_profile *profile,
|
||||
int public_address);
|
||||
/* 1 — разрешена попытка по target_ip4 (network order), 0 — нет; проверяет has_priv/has_inet.
|
||||
* Семейство/протокол и выбор маршрута проверяет вызывающий; profile может быть NULL. */
|
||||
int sock_match(const struct sock_view *view, uint8_t address_type, uint8_t config_type,
|
||||
const struct address_profile *profile, uint32_t target_ip4);
|
||||
/* В потоке ядра заполнить до max views обычных IPv4-сокетов, включая TCP; probe_only исключены.
|
||||
* Возвращает число заполненных (0 также при неверных аргументах); специальной сортировки нет. */
|
||||
int sock_collect_views(struct UTUN_INSTANCE *instance, struct sock_view *out, int max);
|
||||
|
||||
#endif
|
||||
@@ -1265,7 +1265,12 @@ int topo_group_process_nodeinfo(struct TOPO_GROUP* group, struct ETCP_CONN* from
|
||||
}
|
||||
|
||||
int dyn = topo_node_dyn_size(ni);
|
||||
if ((size_t)dyn + sizeof(struct TOPOMSG_NODEINFO_PKT) != len) { DEBUG_WARN(DEBUG_CATEGORY_BGP, "Incorrect packet size (%s)", from->log_name); return -1; }
|
||||
if (dyn < 0) return -1; // Причина уже записана в topo_node_dyn_size.
|
||||
if ((size_t)dyn != len - sizeof(struct TOPOMSG_NODEINFO_PKT)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "NODEINFO size mismatch from=%s node=%016llx length=%zu expected=%zu",
|
||||
from->log_name, (unsigned long long)ni->node_id, len, sizeof(struct TOPOMSG_NODEINFO_PKT) + (size_t)dyn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint64_t node_id = ni->node_id;
|
||||
if (ni->hop_count >= MAX_HOPS) { DEBUG_WARN(DEBUG_CATEGORY_BGP, "NODEINFO from %s dropped: too many hops (%d)", from->log_name, ni->hop_count); return -1; }
|
||||
|
||||
+322
-214
@@ -1,6 +1,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/debug_config.h"
|
||||
@@ -475,15 +476,20 @@ int topo_node_verify(const struct TOPO_NODE* ni) {
|
||||
|
||||
/* Вычисляет суммарный размер динамической части NODEINFO по счётчикам заголовка. */
|
||||
int topo_node_dyn_size(const struct TOPOMSG_NODE* msg) {
|
||||
if (!msg) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "NODEINFO size: missing header"); return -1; }
|
||||
if (!(msg->flags & TOPO_FLAG_SEND_SUBNETS) && (msg->local_v4_subnets || msg->local_v6_subnets)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "NODEINFO size: subnets without flag node=%016llx flags=%02x v4=%u v6=%u",
|
||||
(unsigned long long)msg->node_id, msg->flags, msg->local_v4_subnets, msg->local_v6_subnets);
|
||||
return -1;
|
||||
}
|
||||
return msg->node_name_len
|
||||
+ msg->local_v4_sockets * sizeof(struct TOPOMSG_SOCKMETA4)
|
||||
+ msg->local_v4_addrs * sizeof(struct TOPOMSG_ADDR4)
|
||||
+ msg->local_v6_sockets * sizeof(struct TOPOMSG_SOCKMETA6)
|
||||
+ msg->local_v6_addrs * sizeof(struct TOPOMSG_ADDR6)
|
||||
+ msg->local_reality_socks * sizeof(struct TOPOMSG_REALITY_SOCK)
|
||||
+ ((msg->flags & TOPO_FLAG_SEND_SUBNETS)
|
||||
? (msg->local_v4_subnets * sizeof(struct TOPOMSG_SUBNET4) + msg->local_v6_subnets * sizeof(struct TOPOMSG_SUBNET6))
|
||||
: 0)
|
||||
+ msg->local_v4_subnets * sizeof(struct TOPOMSG_SUBNET4)
|
||||
+ msg->local_v6_subnets * sizeof(struct TOPOMSG_SUBNET6)
|
||||
+ msg->hop_count * 8;
|
||||
}
|
||||
|
||||
@@ -491,7 +497,7 @@ int topo_node_dyn_size(const struct TOPOMSG_NODE* msg) {
|
||||
int topo_node_serialize(struct TOPO_NODE* ni, struct TOPO_GROUP_NODE* nq,
|
||||
uint64_t group_id, uint8_t flags,
|
||||
uint8_t* out, size_t out_max, uint16_t cumulative_rtt) {
|
||||
if (!ni || !nq || !out) return -1;
|
||||
if (!ni || !nq || !out) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "NODEINFO encode: missing node/group/output"); return -1; }
|
||||
|
||||
struct TOPOMSG_NODE msg;
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
@@ -510,7 +516,8 @@ int topo_node_serialize(struct TOPO_NODE* ni, struct TOPO_GROUP_NODE* nq,
|
||||
topo_list_count((struct _topo_head*)ni->v6_sock_meta) > UINT8_MAX ||
|
||||
topo_list_count((struct _topo_head*)ni->v6_addrs) > UINT8_MAX ||
|
||||
topo_list_count((struct _topo_head*)ni->reality_socks) > UINT8_MAX ||
|
||||
(nq->subnets && (topo_list_count((struct _topo_head*)nq->subnets->v4_subnets) > UINT8_MAX ||
|
||||
((flags & TOPO_FLAG_SEND_SUBNETS) && nq->subnets &&
|
||||
(topo_list_count((struct _topo_head*)nq->subnets->v4_subnets) > UINT8_MAX ||
|
||||
topo_list_count((struct _topo_head*)nq->subnets->v6_subnets) > UINT8_MAX))) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node exceeds wire format limits"); return -1;
|
||||
}
|
||||
@@ -519,18 +526,25 @@ int topo_node_serialize(struct TOPO_NODE* ni, struct TOPO_GROUP_NODE* nq,
|
||||
msg.local_v6_sockets = topo_list_count((struct _topo_head*)ni->v6_sock_meta);
|
||||
msg.local_v6_addrs = topo_list_count((struct _topo_head*)ni->v6_addrs);
|
||||
msg.local_reality_socks = topo_list_count((struct _topo_head*)ni->reality_socks);
|
||||
msg.local_v4_subnets = nq->subnets ? topo_list_count((struct _topo_head*)nq->subnets->v4_subnets) : 0;
|
||||
msg.local_v6_subnets = nq->subnets ? topo_list_count((struct _topo_head*)nq->subnets->v6_subnets) : 0;
|
||||
{ uint8_t hc; uint64_t* hl = topo_node_best_hop_list(nq, &hc, NULL);
|
||||
msg.hop_count = hc; }
|
||||
if ((flags & TOPO_FLAG_SEND_SUBNETS) && nq->subnets) {
|
||||
msg.local_v4_subnets = topo_list_count((struct _topo_head*)nq->subnets->v4_subnets);
|
||||
msg.local_v6_subnets = topo_list_count((struct _topo_head*)nq->subnets->v6_subnets);
|
||||
}
|
||||
uint64_t *hop_list = topo_node_best_hop_list(nq, &msg.hop_count, NULL);
|
||||
msg.client_type = ni->client_type;
|
||||
msg.client_activity = ni->client_activity;
|
||||
msg.cumulative_rtt = cumulative_rtt;
|
||||
if (nq->radio) msg.flags |= TOPO_FLAG_RADIO;
|
||||
msg.flags |= nq->phone_status;
|
||||
|
||||
size_t total = TOPOMSG_NODE_HDR_SIZE + (size_t)topo_node_dyn_size(&msg);
|
||||
if (total > out_max) return -1;
|
||||
int dyn = topo_node_dyn_size(&msg);
|
||||
if (dyn < 0) return -1; // Причина уже записана в topo_node_dyn_size.
|
||||
size_t total = TOPOMSG_NODE_HDR_SIZE + (size_t)dyn;
|
||||
if (total > out_max) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "NODEINFO encode: output too small node=%016llx need=%zu capacity=%zu",
|
||||
(unsigned long long)ni->node_id, total, out_max);
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(out, &msg, TOPOMSG_NODE_HDR_SIZE);
|
||||
uint8_t* dp = out + TOPOMSG_NODE_HDR_SIZE;
|
||||
@@ -574,8 +588,9 @@ int topo_node_serialize(struct TOPO_NODE* ni, struct TOPO_GROUP_NODE* nq,
|
||||
}}
|
||||
}
|
||||
}
|
||||
{ uint8_t hc; uint64_t* hl = topo_node_best_hop_list(nq, &hc, NULL);
|
||||
if (hc && hl) { size_t sz = hc * 8; memcpy(dp, hl, sz); dp += sz; } }
|
||||
if (msg.hop_count) { size_t size = msg.hop_count * sizeof(*hop_list); memcpy(dp, hop_list, size); dp += size; }
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "NODEINFO encoded: node=%016llx bytes=%zu flags=%02x subnets=%u/%u hops=%u",
|
||||
(unsigned long long)ni->node_id, total, msg.flags, msg.local_v4_subnets, msg.local_v6_subnets, msg.hop_count);
|
||||
return (int)(dp - out);
|
||||
}
|
||||
|
||||
@@ -585,129 +600,190 @@ int topo_node_serialize(struct TOPO_NODE* ni, struct TOPO_GROUP_NODE* nq,
|
||||
int topo_node_deserialize(struct TOPO_GROUP* group, const uint8_t* data, size_t len, struct TOPO_NODE** out_ni, struct TOPO_NODESUBNETS** out_subnets,
|
||||
uint64_t** out_hop_list, uint8_t* out_hop_count,
|
||||
uint16_t* out_cumulative_rtt) {
|
||||
if (!group || !data || len < TOPOMSG_NODE_HDR_SIZE || !out_ni) return -1;
|
||||
|
||||
if (out_ni) *out_ni = NULL;
|
||||
if (out_subnets) *out_subnets = NULL;
|
||||
if (out_hop_list) *out_hop_list = NULL;
|
||||
if (out_hop_count) *out_hop_count = 0;
|
||||
if (out_cumulative_rtt) *out_cumulative_rtt = 0;
|
||||
if (!group || !group->instance || !group->instance->topo_groups || !data || !out_ni) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "NODEINFO decode: missing group/instance/registry/data/output");
|
||||
return -1;
|
||||
}
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "NODEINFO decode: length=%zu", len);
|
||||
if (len < TOPOMSG_NODE_HDR_SIZE) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "NODEINFO decode: short header length=%zu need=%zu", len, TOPOMSG_NODE_HDR_SIZE);
|
||||
return -1;
|
||||
}
|
||||
const struct TOPOMSG_NODE* msg = (const struct TOPOMSG_NODE*)data;
|
||||
int dyn = topo_node_dyn_size(msg);
|
||||
if ((size_t)dyn + TOPOMSG_NODE_HDR_SIZE != len) return -1;
|
||||
if (dyn < 0) return -1; // Причина уже записана в topo_node_dyn_size.
|
||||
if ((size_t)dyn != len - TOPOMSG_NODE_HDR_SIZE) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "NODEINFO decode: size mismatch node=%016llx length=%zu expected=%zu flags=%02x subnets=%u/%u hops=%u",
|
||||
(unsigned long long)msg->node_id, len, TOPOMSG_NODE_HDR_SIZE + (size_t)dyn,
|
||||
msg->flags, msg->local_v4_subnets, msg->local_v6_subnets, msg->hop_count);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct TOPO_NODE* ni = u_calloc(1, sizeof(struct TOPO_NODE));
|
||||
if (!ni) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "alloc TOPO_NODE failed"); return -1; }
|
||||
ni->group_ref_count = 0;
|
||||
ni->node_id = msg->node_id;
|
||||
ni->ver = msg->ver;
|
||||
ni->timestamp = msg->timestamp;
|
||||
struct TOPO_GROUPS* groups = group->instance->topo_groups;
|
||||
struct TOPO_NODE* ni = u_calloc(1, sizeof(*ni));
|
||||
struct TOPO_NODESUBNETS* subnets = NULL;
|
||||
uint64_t* hops = NULL;
|
||||
const uint8_t* dp = data + TOPOMSG_NODE_HDR_SIZE;
|
||||
const char* section = "node";
|
||||
unsigned index = 0;
|
||||
if (!ni) goto no_memory;
|
||||
ni->node_id = msg->node_id; ni->ver = msg->ver; ni->timestamp = msg->timestamp;
|
||||
memcpy(ni->public_key, msg->public_key, SC_PUBKEY_SIZE);
|
||||
memcpy(ni->ed25519_public_key, msg->ed25519_public_key, SC_PUBKEY_SIZE);
|
||||
memcpy(ni->x25519_self_sig, msg->x25519_self_sig, 64);
|
||||
ni->client_type = msg->client_type;
|
||||
ni->client_activity = msg->client_activity;
|
||||
|
||||
const uint8_t* dp = data + TOPOMSG_NODE_HDR_SIZE;
|
||||
ni->client_type = msg->client_type; ni->client_activity = msg->client_activity;
|
||||
|
||||
section = "name";
|
||||
if (msg->node_name_len) {
|
||||
ni->node_name = u_malloc(msg->node_name_len + 1);
|
||||
if (ni->node_name) { memcpy(ni->node_name, dp, msg->node_name_len); ni->node_name[msg->node_name_len] = 0; }
|
||||
dp += msg->node_name_len;
|
||||
if (!ni->node_name) goto no_memory;
|
||||
memcpy(ni->node_name, dp, msg->node_name_len); ni->node_name[msg->node_name_len] = 0;
|
||||
}
|
||||
dp += msg->node_name_len;
|
||||
|
||||
{ struct TOPO_SOCKMETA4* head = NULL; struct TOPO_SOCKMETA4* tail = NULL;
|
||||
for (int i = 0; i < msg->local_v4_sockets; i++) {
|
||||
/* Списки сразу принадлежат ni: общий выход ошибки видит и незавершённую секцию. */
|
||||
section = "v4 socket metadata";
|
||||
{ struct TOPO_SOCKMETA4** tail = &ni->v4_sock_meta;
|
||||
for (index = 0; index < msg->local_v4_sockets; index++) {
|
||||
const struct TOPOMSG_SOCKMETA4* w = (const struct TOPOMSG_SOCKMETA4*)dp; dp += sizeof(*w);
|
||||
struct TOPO_SOCKMETA4* e = memory_pool_alloc(group->instance->topo_groups->v4_sock_meta_pool);
|
||||
if (!e) { topo_node_free_raw(group->instance->topo_groups, ni); return -1; }
|
||||
e->id = w->id; e->config_type = w->config_type; e->next = NULL;
|
||||
if (sockmeta_wire_read((const uint8_t *)w, &e->address_profile)) {
|
||||
memory_pool_free(group->instance->topo_groups->v4_sock_meta_pool, e);
|
||||
ni->v4_sock_meta = head; topo_node_free_raw(group->instance->topo_groups, ni); return -1;
|
||||
}
|
||||
if (!head) head = tail = e; else { tail->next = e; tail = e; }
|
||||
struct TOPO_SOCKMETA4* e = memory_pool_alloc(groups->v4_sock_meta_pool);
|
||||
if (!e) goto no_memory;
|
||||
*tail = e; tail = &e->next;
|
||||
e->id = w->id; e->config_type = w->config_type;
|
||||
if (sockmeta_wire_read((const uint8_t*)w, &e->address_profile)) goto invalid_profile;
|
||||
}
|
||||
ni->v4_sock_meta = head; }
|
||||
{ struct TOPO_ADDR4* head = NULL; struct TOPO_ADDR4* tail = NULL;
|
||||
for (int i = 0; i < msg->local_v4_addrs; i++) {
|
||||
}
|
||||
section = "v4 addresses";
|
||||
{ struct TOPO_ADDR4** tail = &ni->v4_addrs;
|
||||
for (index = 0; index < msg->local_v4_addrs; index++) {
|
||||
const struct TOPOMSG_ADDR4* w = (const struct TOPOMSG_ADDR4*)dp; dp += sizeof(*w);
|
||||
struct TOPO_ADDR4* e = memory_pool_alloc(group->instance->topo_groups->v4_addr_pool);
|
||||
if (!e) { topo_node_free_raw(group->instance->topo_groups, ni); return -1; }
|
||||
memcpy(e->addr, w->addr, 4); e->port = w->port; e->type = w->type; e->socket_id = w->socket_id; e->protocol = w->protocol; e->next = NULL;
|
||||
if (!head) head = tail = e; else { tail->next = e; tail = e; }
|
||||
struct TOPO_ADDR4* e = memory_pool_alloc(groups->v4_addr_pool);
|
||||
if (!e) goto no_memory;
|
||||
*tail = e; tail = &e->next;
|
||||
memcpy(e->addr, w->addr, 4); e->port = w->port; e->type = w->type;
|
||||
e->socket_id = w->socket_id; e->protocol = w->protocol;
|
||||
}
|
||||
ni->v4_addrs = head; }
|
||||
{ struct TOPO_SOCKMETA6* head = NULL; struct TOPO_SOCKMETA6* tail = NULL;
|
||||
for (int i = 0; i < msg->local_v6_sockets; i++) {
|
||||
}
|
||||
section = "v6 socket metadata";
|
||||
{ struct TOPO_SOCKMETA6** tail = &ni->v6_sock_meta;
|
||||
for (index = 0; index < msg->local_v6_sockets; index++) {
|
||||
const struct TOPOMSG_SOCKMETA6* w = (const struct TOPOMSG_SOCKMETA6*)dp; dp += sizeof(*w);
|
||||
struct TOPO_SOCKMETA6* e = memory_pool_alloc(group->instance->topo_groups->v6_sock_meta_pool);
|
||||
if (!e) { topo_node_free_raw(group->instance->topo_groups, ni); return -1; }
|
||||
e->id = w->id; e->config_type = w->config_type; e->next = NULL;
|
||||
if (sockmeta_wire_read((const uint8_t *)w, &e->address_profile)) {
|
||||
memory_pool_free(group->instance->topo_groups->v6_sock_meta_pool, e);
|
||||
ni->v6_sock_meta = head; topo_node_free_raw(group->instance->topo_groups, ni); return -1;
|
||||
}
|
||||
if (!head) head = tail = e; else { tail->next = e; tail = e; }
|
||||
struct TOPO_SOCKMETA6* e = memory_pool_alloc(groups->v6_sock_meta_pool);
|
||||
if (!e) goto no_memory;
|
||||
*tail = e; tail = &e->next;
|
||||
e->id = w->id; e->config_type = w->config_type;
|
||||
if (sockmeta_wire_read((const uint8_t*)w, &e->address_profile)) goto invalid_profile;
|
||||
}
|
||||
ni->v6_sock_meta = head; }
|
||||
{ struct TOPO_ADDR6* head = NULL; struct TOPO_ADDR6* tail = NULL;
|
||||
for (int i = 0; i < msg->local_v6_addrs; i++) {
|
||||
}
|
||||
section = "v6 addresses";
|
||||
{ struct TOPO_ADDR6** tail = &ni->v6_addrs;
|
||||
for (index = 0; index < msg->local_v6_addrs; index++) {
|
||||
const struct TOPOMSG_ADDR6* w = (const struct TOPOMSG_ADDR6*)dp; dp += sizeof(*w);
|
||||
struct TOPO_ADDR6* e = memory_pool_alloc(group->instance->topo_groups->v6_addr_pool);
|
||||
if (!e) { topo_node_free_raw(group->instance->topo_groups, ni); return -1; }
|
||||
memcpy(e->addr, w->addr, 16); e->port = w->port; e->type = w->type; e->socket_id = w->socket_id; e->protocol = w->protocol; e->next = NULL;
|
||||
if (!head) head = tail = e; else { tail->next = e; tail = e; }
|
||||
struct TOPO_ADDR6* e = memory_pool_alloc(groups->v6_addr_pool);
|
||||
if (!e) goto no_memory;
|
||||
*tail = e; tail = &e->next;
|
||||
memcpy(e->addr, w->addr, 16); e->port = w->port; e->type = w->type;
|
||||
e->socket_id = w->socket_id; e->protocol = w->protocol;
|
||||
}
|
||||
ni->v6_addrs = head; }
|
||||
{ struct TOPO_REALITY_SOCK* head = NULL; struct TOPO_REALITY_SOCK* tail = NULL;
|
||||
for (int i = 0; i < msg->local_reality_socks; i++) {
|
||||
}
|
||||
section = "REALITY sockets";
|
||||
{ struct TOPO_REALITY_SOCK** tail = &ni->reality_socks;
|
||||
for (index = 0; index < msg->local_reality_socks; index++) {
|
||||
const struct TOPOMSG_REALITY_SOCK* w = (const struct TOPOMSG_REALITY_SOCK*)dp; dp += sizeof(*w);
|
||||
struct TOPO_REALITY_SOCK* e = u_calloc(1, sizeof(struct TOPO_REALITY_SOCK));
|
||||
if (!e) { topo_node_free_raw(group->instance->topo_groups, ni); return -1; }
|
||||
struct TOPO_REALITY_SOCK* e = u_calloc(1, sizeof(*e));
|
||||
if (!e) goto no_memory;
|
||||
*tail = e; tail = &e->next;
|
||||
e->socket_id = w->socket_id;
|
||||
memcpy(e->short_id, w->short_id, 8);
|
||||
memcpy(e->server_pubkey, w->server_pubkey, 32);
|
||||
memcpy(e->version, w->version, 3);
|
||||
memcpy(e->server_name, w->server_name, TOPO_REALITY_SNAME_MAX);
|
||||
memcpy(e->short_id, w->short_id, 8); memcpy(e->server_pubkey, w->server_pubkey, 32);
|
||||
memcpy(e->version, w->version, 3); memcpy(e->server_name, w->server_name, TOPO_REALITY_SNAME_MAX);
|
||||
e->server_name[TOPO_REALITY_SNAME_MAX - 1] = 0;
|
||||
e->next = NULL;
|
||||
if (!head) head = tail = e; else { tail->next = e; tail = e; }
|
||||
}
|
||||
ni->reality_socks = head; }
|
||||
}
|
||||
|
||||
if (out_subnets && (msg->flags & TOPO_FLAG_SEND_SUBNETS) && (msg->local_v4_subnets || msg->local_v6_subnets)) {
|
||||
struct TOPO_NODESUBNETS* r = u_calloc(1, sizeof(struct TOPO_NODESUBNETS));
|
||||
if (!r) { topo_node_free_raw(group->instance->topo_groups, ni); return -1; }
|
||||
for (int i = 0; i < msg->local_v4_subnets; i++) {
|
||||
section = "subnets"; index = 0;
|
||||
if (out_subnets && (msg->local_v4_subnets || msg->local_v6_subnets)) {
|
||||
subnets = u_calloc(1, sizeof(*subnets));
|
||||
if (!subnets) goto no_memory;
|
||||
}
|
||||
/* Курсор проходит все присутствующие секции; out_subnets управляет только выделением памяти. */
|
||||
section = "v4 subnets";
|
||||
{ struct TOPO_SUBNET4** tail = subnets ? &subnets->v4_subnets : NULL;
|
||||
for (index = 0; index < msg->local_v4_subnets; index++) {
|
||||
const struct TOPOMSG_SUBNET4* w = (const struct TOPOMSG_SUBNET4*)dp; dp += sizeof(*w);
|
||||
struct TOPO_SUBNET4* e = memory_pool_alloc(group->instance->topo_groups->v4_subnet_pool);
|
||||
if (!e) { u_free(r); topo_node_free_raw(group->instance->topo_groups, ni); return -1; }
|
||||
if (w->prefix_length > 32) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "NODEINFO decode: invalid IPv4 prefix node=%016llx index=%u prefix=%u",
|
||||
(unsigned long long)msg->node_id, index, w->prefix_length);
|
||||
goto fail;
|
||||
}
|
||||
if (!subnets) continue;
|
||||
struct TOPO_SUBNET4* e = memory_pool_alloc(groups->v4_subnet_pool);
|
||||
if (!e) goto no_memory;
|
||||
*tail = e; tail = &e->next;
|
||||
memcpy(e->addr, w->addr, 4); e->prefix_length = w->prefix_length;
|
||||
e->next = r->v4_subnets; r->v4_subnets = e;
|
||||
}
|
||||
for (int i = 0; i < msg->local_v6_subnets; i++) {
|
||||
}
|
||||
section = "v6 subnets";
|
||||
{ struct TOPO_SUBNET6** tail = subnets ? &subnets->v6_subnets : NULL;
|
||||
for (index = 0; index < msg->local_v6_subnets; index++) {
|
||||
const struct TOPOMSG_SUBNET6* w = (const struct TOPOMSG_SUBNET6*)dp; dp += sizeof(*w);
|
||||
struct TOPO_SUBNET6* e = memory_pool_alloc(group->instance->topo_groups->v6_subnet_pool);
|
||||
if (!e) { u_free(r); topo_node_free_raw(group->instance->topo_groups, ni); return -1; }
|
||||
if (w->prefix_length > 128) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "NODEINFO decode: invalid IPv6 prefix node=%016llx index=%u prefix=%u",
|
||||
(unsigned long long)msg->node_id, index, w->prefix_length);
|
||||
goto fail;
|
||||
}
|
||||
if (!subnets) continue;
|
||||
struct TOPO_SUBNET6* e = memory_pool_alloc(groups->v6_subnet_pool);
|
||||
if (!e) goto no_memory;
|
||||
*tail = e; tail = &e->next;
|
||||
memcpy(e->addr, w->addr, 16); e->prefix_length = w->prefix_length;
|
||||
e->next = r->v6_subnets; r->v6_subnets = e;
|
||||
}
|
||||
*out_subnets = r;
|
||||
} else if (!(msg->flags & TOPO_FLAG_SEND_SUBNETS)) {
|
||||
dp += msg->local_v4_subnets * sizeof(struct TOPOMSG_SUBNET4)
|
||||
+ msg->local_v6_subnets * sizeof(struct TOPOMSG_SUBNET6);
|
||||
if (out_subnets) *out_subnets = NULL;
|
||||
} else {
|
||||
if (out_subnets) *out_subnets = NULL;
|
||||
}
|
||||
|
||||
if (out_hop_list && msg->hop_count > 0) {
|
||||
size_t hsz = msg->hop_count * 8;
|
||||
*out_hop_list = u_malloc(hsz);
|
||||
if (!*out_hop_list) { topo_node_free_raw(group->instance->topo_groups, ni); return -1; }
|
||||
memcpy(*out_hop_list, dp, hsz); dp += hsz;
|
||||
*out_hop_count = msg->hop_count;
|
||||
section = "hops"; index = 0;
|
||||
size_t hop_bytes = msg->hop_count * sizeof(*hops);
|
||||
if (out_hop_list && hop_bytes) {
|
||||
hops = u_malloc(hop_bytes);
|
||||
if (!hops) goto no_memory;
|
||||
memcpy(hops, dp, hop_bytes);
|
||||
}
|
||||
dp += hop_bytes;
|
||||
if ((size_t)(dp - data) != len) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "NODEINFO decode: cursor mismatch node=%016llx consumed=%zu length=%zu",
|
||||
(unsigned long long)msg->node_id, (size_t)(dp - data), len);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (out_cumulative_rtt) *out_cumulative_rtt = msg->cumulative_rtt;
|
||||
*out_ni = ni;
|
||||
if (out_subnets) *out_subnets = subnets;
|
||||
if (out_hop_list) *out_hop_list = hops;
|
||||
if (out_hop_count) *out_hop_count = msg->hop_count;
|
||||
if (out_cumulative_rtt) *out_cumulative_rtt = msg->cumulative_rtt;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "NODEINFO decoded: node=%016llx bytes=%zu flags=%02x subnets=%u/%u hops=%u keep_subnets=%d keep_hops=%d",
|
||||
(unsigned long long)msg->node_id, len, msg->flags, msg->local_v4_subnets, msg->local_v6_subnets,
|
||||
msg->hop_count, out_subnets != NULL, out_hop_list != NULL);
|
||||
return 0;
|
||||
|
||||
invalid_profile:
|
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "NODEINFO decode: invalid profile node=%016llx section=%s index=%u",
|
||||
(unsigned long long)msg->node_id, section, index);
|
||||
goto fail;
|
||||
no_memory:
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "NODEINFO decode: allocation failed node=%016llx section=%s index=%u offset=%zu",
|
||||
(unsigned long long)msg->node_id, section, index, (size_t)(dp - data));
|
||||
fail:
|
||||
topo_node_free_raw(groups, ni);
|
||||
if (subnets) {
|
||||
free_v4_sub_list(groups->v4_subnet_pool, subnets->v4_subnets);
|
||||
free_v6_sub_list(groups->v6_subnet_pool, subnets->v6_subnets);
|
||||
u_free(subnets);
|
||||
}
|
||||
u_free(hops);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Ищет per-group узел TOPO_GROUP_NODE в группе по node_id (NULL, если нет). */
|
||||
@@ -896,23 +972,36 @@ void topo_node_dump_all(struct TOPO_GROUP* group) {
|
||||
|
||||
/* То же, что dump_all, но в строковый буфер (для control-сервера). */
|
||||
int topo_node_format_all(struct TOPO_GROUP* group, char* buf, size_t buf_size) {
|
||||
if (!group || !group->nodes || !buf || buf_size == 0) return 0;
|
||||
int pos = 0;
|
||||
if (buf && buf_size) buf[0] = '\0';
|
||||
if (!group || !group->nodes || !buf || buf_size == 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node dump: invalid arguments group=%p buf=%p capacity=%zu", (void*)group, (void*)buf, buf_size);
|
||||
return 0;
|
||||
}
|
||||
if (buf_size > INT_MAX) buf_size = INT_MAX;
|
||||
size_t pos = 0;
|
||||
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0,
|
||||
"=== BGP Nodes dump: %d nodes ===\n", queue_entry_count(group->nodes));
|
||||
#define FMT_ADD(...) do { \
|
||||
int added = snprintf(buf + pos, buf_size - pos, __VA_ARGS__); \
|
||||
if (added < 0) { \
|
||||
buf[pos] = '\0'; \
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node dump: formatting failed after %zu bytes", pos); \
|
||||
return -1; \
|
||||
} \
|
||||
if ((size_t)added >= buf_size - pos) { pos = buf_size - 1; goto truncated; } \
|
||||
pos += (size_t)added; \
|
||||
} while (0)
|
||||
|
||||
FMT_ADD("=== BGP Nodes dump: %d nodes ===\n", queue_entry_count(group->nodes));
|
||||
|
||||
int idx = 0;
|
||||
struct ll_entry* e = group->nodes->head;
|
||||
while (e && (size_t)pos < buf_size) {
|
||||
while (e) {
|
||||
struct TOPO_GROUP_NODE* nq = (struct TOPO_GROUP_NODE*)e;
|
||||
struct TOPO_NODE* ni = topo_node_registry_find(group->instance->topo_groups, nq->node_id);
|
||||
if (!ni) { e = e->next; continue; }
|
||||
int is_local = (group->local_node == nq);
|
||||
const char* name_s = ni->node_name ? ni->node_name : "";
|
||||
|
||||
#define FMT_ADD(fmt, ...) pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, fmt, ##__VA_ARGS__)
|
||||
|
||||
FMT_ADD("--- Node #%d: id=%016llx name=\"%s\" ver=%u grp=%llu%s ---\n",
|
||||
idx, (unsigned long long)ni->node_id, name_s, ni->ver,
|
||||
(unsigned long long)group->group_id, is_local ? " [LOCAL]" : "");
|
||||
@@ -977,15 +1066,24 @@ int topo_node_format_all(struct TOPO_GROUP* group, char* buf, size_t buf_size) {
|
||||
FMT_ADD("]\n");
|
||||
}
|
||||
|
||||
#undef FMT_ADD
|
||||
e = e->next; idx++;
|
||||
}
|
||||
#undef FMT_ADD
|
||||
|
||||
if ((size_t)pos >= buf_size) {
|
||||
pos = (int)buf_size - 14; if (pos < 0) pos = 0;
|
||||
pos += snprintf(buf + pos, buf_size - pos, "\n[TRUNCATED]\n");
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "node dump: group=%016llx nodes=%d written=%zu capacity=%zu",
|
||||
(unsigned long long)group->group_id, idx, pos, buf_size);
|
||||
return (int)pos;
|
||||
|
||||
truncated: {
|
||||
const char marker[] = "\n[TRUNCATED]\n";
|
||||
size_t marker_len = sizeof(marker) - 1;
|
||||
if (marker_len > pos) marker_len = pos;
|
||||
memcpy(buf + pos - marker_len, marker, marker_len);
|
||||
buf[pos] = '\0';
|
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "node dump truncated: group=%016llx written=%zu capacity=%zu",
|
||||
(unsigned long long)group->group_id, pos, buf_size);
|
||||
return (int)pos;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
// ===== update my nodeinfo =====
|
||||
@@ -1009,114 +1107,124 @@ int topo_node_init_self(struct UTUN_INSTANCE* instance) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Пересоздаёт local_node группы при изменении имени/подсетей/ключей, инкрементируя версию. */
|
||||
/* Готовит local_node целиком; отказ подготовки/публикации сохраняет прежний узел и маршруты. */
|
||||
int topo_group_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct TOPO_GROUP* group) {
|
||||
if (!instance || !group) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args"); return -1; }
|
||||
size_t name_len = 0;
|
||||
if (instance->name[0]) { name_len = strlen(instance->name); if (name_len > 63) name_len = 63; }
|
||||
int vc = 0, vc6 = 0;
|
||||
struct CFG_ROUTE_ENTRY* s = instance->config->my_subnets;
|
||||
while (s) { if (s->ip.family == AF_INET) vc++; else if (s->ip.family == AF_INET6) vc6++; s = s->next; }
|
||||
|
||||
// Для chat групп подсети не передаём
|
||||
if (group->group_type == TOPO_GROUP_TYPE_CHAT) { vc = 0; vc6 = 0; }
|
||||
|
||||
int changed = 1; uint8_t old_ver = 0;
|
||||
if (group->local_node) {
|
||||
struct TOPO_NODE* oni = topo_node_registry_find(instance->topo_groups, group->local_node->node_id);
|
||||
if (oni) {
|
||||
old_ver = oni->ver;
|
||||
changed = (vc != (group->local_node->subnets ? topo_list_count((struct _topo_head*)group->local_node->subnets->v4_subnets) : 0))
|
||||
|| (name_len != (oni->node_name ? strlen(oni->node_name) : 0))
|
||||
|| (name_len && strncmp(instance->name, oni->node_name ? oni->node_name : "", name_len))
|
||||
|| (memcmp(oni->public_key, instance->my_keys.public_key, SC_PUBKEY_SIZE) != 0)
|
||||
|| (vc6 != (group->local_node->subnets ? topo_list_count((struct _topo_head*)group->local_node->subnets->v6_subnets) : 0))
|
||||
|| (oni->client_type != instance->client_type)
|
||||
|| (oni->client_activity != instance->client_activity)
|
||||
|| (group->local_node->radio != group->radio_active)
|
||||
|| (group->local_node->phone_status != call_phone_status(instance))
|
||||
|| (memcmp(oni->ed25519_public_key, group->ed25519_public_key, SC_PUBKEY_SIZE) != 0);
|
||||
}
|
||||
if (!instance || !group || group->instance != instance || !instance->config || !instance->topo_groups) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local node update: invalid instance/group/config"); return -1;
|
||||
}
|
||||
if (!changed && group->local_node)
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "my nodeinfo unchanged (identity), ver=%d grp=%016llx",
|
||||
group->local_node->last_ver, (unsigned long long)group->group_id);
|
||||
|
||||
if (changed) {
|
||||
struct TOPO_NODE* ni = topo_node_registry_find(instance->topo_groups, instance->node_id);
|
||||
if (ni) topo_node_ref(ni); /* новая группа удерживает запись до освобождения старой */
|
||||
if (group->local_node) {
|
||||
if (instance->rt) route_delete(instance->rt, group->local_node);
|
||||
topo_nodeq_free_group_fields(instance->topo_groups, group->local_node);
|
||||
u_free(group->local_node);
|
||||
group->local_node = NULL;
|
||||
}
|
||||
|
||||
group->local_node = u_calloc(1, sizeof(struct TOPO_GROUP_NODE));
|
||||
if (!group->local_node) {
|
||||
if (ni) topo_node_registry_unref(instance->topo_groups, instance->node_id);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local group node allocation failed"); return -1;
|
||||
}
|
||||
struct TOPO_GROUP_NODE* lq = group->local_node;
|
||||
|
||||
int new_identity = ni == NULL;
|
||||
if (!ni) ni = u_calloc(1, sizeof(struct TOPO_NODE));
|
||||
if (!ni) { u_free(lq); group->local_node = NULL; return -1; }
|
||||
struct TOPO_NODE old_identity = *ni;
|
||||
ni->node_id = instance->node_id;
|
||||
ni->ver = (old_ver % 255) + 1;
|
||||
ni->client_type = instance->client_type;
|
||||
ni->client_activity = instance->client_activity;
|
||||
lq->radio = group->radio_active;
|
||||
lq->phone_status = call_phone_status(instance);
|
||||
memcpy(ni->public_key, instance->my_keys.public_key, SC_PUBKEY_SIZE);
|
||||
memcpy(ni->ed25519_public_key, group->ed25519_public_key, SC_PUBKEY_SIZE);
|
||||
ni->node_name = u_strdup(instance->name);
|
||||
if (!ni->node_name || topo_node_sign_self(instance, ni) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot publish local identity");
|
||||
u_free(ni->node_name); *ni = old_identity;
|
||||
if (new_identity) topo_node_free_raw(instance->topo_groups, ni);
|
||||
else topo_node_registry_unref(instance->topo_groups, instance->node_id);
|
||||
u_free(lq); group->local_node = NULL; return -1;
|
||||
}
|
||||
u_free(old_identity.node_name);
|
||||
if (new_identity) ni = topo_node_registry_store(instance->topo_groups, ni);
|
||||
if (!ni) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot register local identity"); return -1; }
|
||||
lq->node_id = ni->node_id;
|
||||
lq->last_ver = ni->ver;
|
||||
lq->last_timestamp = ni->timestamp;
|
||||
|
||||
if (group->group_type != TOPO_GROUP_TYPE_CHAT && (vc || vc6)) {
|
||||
struct TOPO_NODESUBNETS* r = u_calloc(1, sizeof(struct TOPO_NODESUBNETS));
|
||||
s = instance->config->my_subnets;
|
||||
while (s) {
|
||||
if (s->ip.family == AF_INET) {
|
||||
struct TOPO_SUBNET4* sub = memory_pool_alloc(group->instance->topo_groups->v4_subnet_pool);
|
||||
memcpy(sub->addr, &s->ip.addr.v4, 4); sub->prefix_length = s->netmask;
|
||||
sub->next = r->v4_subnets; r->v4_subnets = sub;
|
||||
} else if (s->ip.family == AF_INET6) {
|
||||
struct TOPO_SUBNET6* sub6 = memory_pool_alloc(group->instance->topo_groups->v6_subnet_pool);
|
||||
memcpy(sub6->addr, &s->ip.addr.v6, 16); sub6->prefix_length = s->netmask;
|
||||
sub6->next = r->v6_subnets; r->v6_subnets = sub6;
|
||||
}
|
||||
s = s->next;
|
||||
struct TOPO_GROUPS* groups = instance->topo_groups;
|
||||
struct TOPO_GROUP_NODE* old = group->local_node;
|
||||
struct TOPO_GROUP_NODE* lq = u_calloc(1, sizeof(*lq));
|
||||
if (!lq) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local node update: allocation failed"); return -1; }
|
||||
struct TOPO_NODE* ni = NULL;
|
||||
char* name = NULL;
|
||||
int new_identity = 0, retained = 0, routes_changed = 0, changed = 1, vc = 0, vc6 = 0;
|
||||
lq->node_id = instance->node_id; lq->radio = group->radio_active; lq->phone_status = call_phone_status(instance);
|
||||
if (group->group_type != TOPO_GROUP_TYPE_CHAT) {
|
||||
struct TOPO_SUBNET4** tail4 = NULL; struct TOPO_SUBNET6** tail6 = NULL;
|
||||
for (struct CFG_ROUTE_ENTRY* s = instance->config->my_subnets; s; s = s->next) {
|
||||
if ((s->ip.family != AF_INET && s->ip.family != AF_INET6) ||
|
||||
s->netmask > (s->ip.family == AF_INET ? 32 : 128)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local node update: invalid subnet family=%d prefix=%u", s->ip.family, s->netmask);
|
||||
goto fail;
|
||||
}
|
||||
if (!lq->subnets) {
|
||||
lq->subnets = u_calloc(1, sizeof(*lq->subnets));
|
||||
if (!lq->subnets) goto allocation_failed;
|
||||
tail4 = &lq->subnets->v4_subnets; tail6 = &lq->subnets->v6_subnets;
|
||||
}
|
||||
if (s->ip.family == AF_INET) {
|
||||
struct TOPO_SUBNET4* sub = memory_pool_alloc(groups->v4_subnet_pool);
|
||||
if (!sub) goto allocation_failed;
|
||||
*sub = (struct TOPO_SUBNET4){.prefix_length = s->netmask}; memcpy(sub->addr, &s->ip.addr.v4, 4);
|
||||
*tail4 = sub; tail4 = &sub->next; vc++;
|
||||
} else {
|
||||
struct TOPO_SUBNET6* sub = memory_pool_alloc(groups->v6_subnet_pool);
|
||||
if (!sub) goto allocation_failed;
|
||||
*sub = (struct TOPO_SUBNET6){.prefix_length = s->netmask}; memcpy(sub->addr, &s->ip.addr.v6, 16);
|
||||
*tail6 = sub; tail6 = &sub->next; vc6++;
|
||||
}
|
||||
lq->subnets = r;
|
||||
}
|
||||
|
||||
if (group->group_type == TOPO_GROUP_TYPE_CHAT)
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "my_nodeinfo updated: v4sub=%d v6sub=%d ver=%d grp=%016llx",
|
||||
vc, vc6, ni->ver, (unsigned long long)group->group_id);
|
||||
else
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "my_nodeinfo updated: v4sub=%d v6sub=%d ver=%d grp=%016llx",
|
||||
vc, vc6, ni->ver, (unsigned long long)group->group_id);
|
||||
if (group->group_type != TOPO_GROUP_TYPE_CHAT && instance->rt && !route_insert(instance->rt, lq))
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "failed to insert local routes");
|
||||
} else {
|
||||
struct TOPO_NODE* lni = topo_node_registry_find(instance->topo_groups, group->local_node->node_id);
|
||||
if (lni) group->local_node->last_ver = lni->ver;
|
||||
if (vc > UINT8_MAX || vc6 > UINT8_MAX) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local node update: subnet counters exceed wire limit v4=%d v6=%d", vc, vc6); goto fail;
|
||||
}
|
||||
}
|
||||
ni = topo_node_registry_find(groups, instance->node_id);
|
||||
changed = !old || !ni || strcmp(ni->node_name ? ni->node_name : "", instance->name) ||
|
||||
memcmp(ni->public_key, instance->my_keys.public_key, SC_PUBKEY_SIZE) ||
|
||||
memcmp(ni->ed25519_public_key, instance->my_ed25519_pubkey, SC_PUBKEY_SIZE) ||
|
||||
ni->client_type != instance->client_type || ni->client_activity != instance->client_activity ||
|
||||
old->radio != lq->radio || old->phone_status != lq->phone_status;
|
||||
if (!changed) {
|
||||
struct TOPO_SUBNET4* a4 = old->subnets ? old->subnets->v4_subnets : NULL;
|
||||
struct TOPO_SUBNET4* b4 = lq->subnets ? lq->subnets->v4_subnets : NULL;
|
||||
while (a4 && b4 && a4->prefix_length == b4->prefix_length && !memcmp(a4->addr, b4->addr, 4)) { a4 = a4->next; b4 = b4->next; }
|
||||
struct TOPO_SUBNET6* a6 = old->subnets ? old->subnets->v6_subnets : NULL;
|
||||
struct TOPO_SUBNET6* b6 = lq->subnets ? lq->subnets->v6_subnets : NULL;
|
||||
while (a6 && b6 && a6->prefix_length == b6->prefix_length && !memcmp(a6->addr, b6->addr, 16)) { a6 = a6->next; b6 = b6->next; }
|
||||
changed = a4 || b4 || a6 || b6;
|
||||
}
|
||||
if (!changed) {
|
||||
old->last_ver = ni->ver; old->last_timestamp = ni->timestamp;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "local node unchanged: group=%016llx ver=%u", (unsigned long long)group->group_id, ni->ver);
|
||||
goto unchanged;
|
||||
}
|
||||
name = u_strdup(instance->name);
|
||||
if (!name) goto allocation_failed;
|
||||
if (!ni) {
|
||||
ni = u_calloc(1, sizeof(*ni)); new_identity = 1;
|
||||
if (!ni) goto allocation_failed;
|
||||
} else { topo_node_registry_ref(groups, instance->node_id); retained = 1; }
|
||||
struct TOPO_NODE candidate = *ni;
|
||||
candidate.node_id = instance->node_id; candidate.ver = (ni->ver % 255) + 1;
|
||||
candidate.node_name = name; candidate.client_type = instance->client_type; candidate.client_activity = instance->client_activity;
|
||||
memcpy(candidate.public_key, instance->my_keys.public_key, SC_PUBKEY_SIZE);
|
||||
memcpy(candidate.ed25519_public_key, instance->my_ed25519_pubkey, SC_PUBKEY_SIZE);
|
||||
if (group->group_type != TOPO_GROUP_TYPE_CHAT && instance->rt) {
|
||||
routes_changed = 1;
|
||||
route_delete(instance->rt, old);
|
||||
if (vc && !route_insert(instance->rt, lq)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "local node update: route conflict/allocation group=%016llx",
|
||||
(unsigned long long)group->group_id); goto fail;
|
||||
}
|
||||
}
|
||||
if (topo_node_sign_self(instance, &candidate) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local node update: cannot publish group=%016llx", (unsigned long long)group->group_id); goto fail;
|
||||
}
|
||||
char* old_name = ni->node_name;
|
||||
*ni = candidate; name = NULL; u_free(old_name);
|
||||
if (new_identity) {
|
||||
struct TOPO_NODE* stored = topo_node_registry_store(groups, ni);
|
||||
if (!stored) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local node update: cannot register identity"); goto fail; }
|
||||
ni = stored; new_identity = 0; retained = 1;
|
||||
}
|
||||
lq->last_ver = ni->ver; lq->last_timestamp = ni->timestamp;
|
||||
if (old) { topo_nodeq_clear_paths(old); topo_nodeq_free_group_fields(groups, old); u_free(old); }
|
||||
group->local_node = lq;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "local node published: group=%016llx v4=%d v6=%d ver=%u timestamp=%llu",
|
||||
(unsigned long long)group->group_id, vc, vc6, ni->ver, (unsigned long long)ni->timestamp);
|
||||
return vc;
|
||||
allocation_failed:
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local node update: preparation allocation failed group=%016llx",
|
||||
(unsigned long long)group->group_id);
|
||||
fail:
|
||||
if (routes_changed) {
|
||||
route_delete(instance->rt, lq);
|
||||
if (old && old->subnets && old->subnets->v4_subnets && !route_insert(instance->rt, old))
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "local node update: previous routes restoration failed group=%016llx",
|
||||
(unsigned long long)group->group_id);
|
||||
}
|
||||
if (new_identity) topo_node_destroy(groups, ni);
|
||||
if (retained) topo_node_registry_unref(groups, instance->node_id);
|
||||
u_free(name);
|
||||
unchanged:
|
||||
if (lq->subnets) {
|
||||
free_v4_sub_list(groups->v4_subnet_pool, lq->subnets->v4_subnets);
|
||||
free_v6_sub_list(groups->v6_subnet_pool, lq->subnets->v6_subnets);
|
||||
u_free(lq->subnets);
|
||||
}
|
||||
u_free(lq);
|
||||
return changed ? -1 : vc;
|
||||
}
|
||||
|
||||
/* Пересобирает списки адресов своего узла из ETCP-сокетов; при изменении — подпись, БД, рассылка. */
|
||||
|
||||
@@ -130,6 +130,9 @@ struct TOPOMSG_REALITY_SOCK {
|
||||
uint8_t server_name[TOPO_REALITY_SNAME_MAX];
|
||||
} __attribute__((packed));
|
||||
|
||||
/* NODEINFO: этот заголовок, имя без NUL, SOCKMETA4/ADDR4/SOCKMETA6/ADDR6/REALITY,
|
||||
* опциональные SUBNET4/SUBNET6, затем hop_count ID. Счётчики — число элементов, имя до 63 байт.
|
||||
* Многобайтные числа wire копируются в нативном порядке; IP — сетевые байты, port в памяти — host order. */
|
||||
struct TOPOMSG_NODE {
|
||||
uint8_t flags; uint64_t group_id, node_id; uint8_t ver;
|
||||
uint64_t timestamp; // версия всей подписанной записи, Unix microseconds
|
||||
@@ -206,13 +209,13 @@ struct TOPO_NODEPATH {
|
||||
uint64_t exchange; // local_epoch сессии, в которой путь подтверждён NODEINFO
|
||||
};
|
||||
|
||||
/** Узел в контексте группы (per-group данные). Хранится в TOPO_GROUP->nodes (ll_queue). */
|
||||
/** Узел группы: удалённый хранится в TOPO_GROUP->nodes (ll_queue), локальный — отдельно в local_node. */
|
||||
struct TOPO_GROUP_NODE {
|
||||
struct ll_entry ll; // queue entry, хеш-ключ 8 байт → node_id
|
||||
uint64_t node_id; // идентификатор узла в этой группе
|
||||
struct TOPO_NODESUBNETS* subnets; // подсети узла (v4/v6), для route_insert()
|
||||
struct ll_queue* paths; // TOPO_NODEPATH{conn,hop_count,rtt} — маршруты
|
||||
uint8_t last_ver; // последняя версия NODEINFO (защита от stale, BGP)
|
||||
uint8_t last_ver; // диагностический циклический ver; свежесть определяется timestamp
|
||||
uint64_t last_timestamp; // последняя принятая версия записи в группе
|
||||
uint8_t conn_mgr_type; // CONN_TYPE_DIRECT/REVERSE/INDIRECT/NONE
|
||||
uint64_t conn_mgr_intermediaries[CONN_MGR_MAX_INTERMEDIARIES]; // посредники для INDIRECT
|
||||
@@ -228,8 +231,10 @@ struct TOPO_GROUP_NODE {
|
||||
// API — глобальный реестр TOPO_NODE
|
||||
/* Заимствованный указатель; ref нужен, если запись должна пережить удаление из группы. */
|
||||
struct TOPO_NODE* topo_node_registry_find(struct TOPO_GROUPS* groups, uint64_t node_id);
|
||||
/* Принять ni и вернуть запись с одной новой ссылкой. Может вернуть существующий объект.
|
||||
* При NULL входной ni остаётся у вызывающего. Подписанные записи сравниваются по timestamp. */
|
||||
/* Принять ni и вернуть запись с одной новой ссылкой. При NULL входной ni остаётся у вызывающего.
|
||||
* Более свежий подписанный снимок заменяет содержимое целиком, сохраняя адрес существующего объекта.
|
||||
* Старый/равный вход поглощается, возвращается прежняя запись; при двух timestamp=0 сливаются непустые bootstrap-списки.
|
||||
* Новая запись может быть заменена более свежим снимком SQLite перед регистрацией. */
|
||||
struct TOPO_NODE* topo_node_registry_store(struct TOPO_GROUPS* groups, struct TOPO_NODE* ni);
|
||||
/* Добавить/снять ссылку; последняя unref удаляет запись из реестра и освобождает её. */
|
||||
void topo_node_registry_ref(struct TOPO_GROUPS* groups, uint64_t node_id);
|
||||
@@ -244,17 +249,23 @@ struct TOPO_GROUP_NODE* topo_node_find_by_id(struct TOPO_GROUP* group, uint64_t
|
||||
void topo_nodeq_free_group_fields(struct TOPO_GROUPS* groups, struct TOPO_GROUP_NODE* nq);
|
||||
/* Освободить принадлежащие узлу записи путей и их контейнер; ETCP-соединения заимствованы. */
|
||||
void topo_nodeq_clear_paths(struct TOPO_GROUP_NODE* nq);
|
||||
/* Полное удаление узла из группы: отменяет влётные пробы, освобождает paths/subnets,
|
||||
* убирает из group->nodes и освобождает сам nq. Единственный корректный способ удаления узла. */
|
||||
/* Полное удаление узла из group->nodes: отменяет пробы, освобождает paths/subnets,
|
||||
* убирает из очереди и освобождает nq. local_node освобождает владелец группы отдельно. */
|
||||
void topo_nodeq_remove_node(struct TOPO_GROUP* group, struct TOPO_GROUP_NODE* nq);
|
||||
|
||||
/* Размер переменной части NODEINFO по счётчикам заголовка. */
|
||||
/* Размер переменной части NODEINFO или -1 при NULL/счётчиках подсетей без TOPO_FLAG_SEND_SUBNETS. */
|
||||
int topo_node_dyn_size(const struct TOPOMSG_NODE* msg);
|
||||
/* Собрать NODEINFO в out; длина или -1. Входные структуры остаются у вызывающего. */
|
||||
/* Собрать NODEINFO в out[out_max]; длина или -1. Входные структуры остаются у вызывающего.
|
||||
* Без TOPO_FLAG_SEND_SUBNETS обе секции подсетей отсутствуют, их счётчики равны 0. */
|
||||
int topo_node_serialize(struct TOPO_NODE* ni, struct TOPO_GROUP_NODE* nq,
|
||||
uint64_t group_id, uint8_t flags,
|
||||
uint8_t* out, size_t out_max, uint16_t cumulative_rtt);
|
||||
/* Разобрать NODEINFO; 0/-1. Выходные объекты выделены отдельно и передаются вызывающему. */
|
||||
/* Разобрать ровно len байт NODEINFO; 0/-1. group->instance->topo_groups содержит пулы списков.
|
||||
* out_ni обязателен, остальные выходы независимы и необязательны. Отсутствие выхода не меняет обход/проверки секций.
|
||||
* Перед разбором все переданные выходы обнуляются, прежние объекты не освобождаются.
|
||||
* При успехе node уничтожается topo_node_destroy, подсети возвращаются в пулы groups, hops освобождается u_free.
|
||||
* Пустые секции дают NULL/0; при ошибке выходы остаются NULL/0, частичные объекты освобождены.
|
||||
* Проверяет размер, профили и /0..32, /0..128; подпись и допустимость BGP-пути проверяет вызывающий. */
|
||||
int topo_node_deserialize(struct TOPO_GROUP* group, const uint8_t* data, size_t len, struct TOPO_NODE** out_ni, struct TOPO_NODESUBNETS** out_subnets,
|
||||
uint64_t** out_hop_list, uint8_t* out_hop_count,
|
||||
uint16_t* out_cumulative_rtt);
|
||||
@@ -265,15 +276,24 @@ uint64_t* topo_node_best_hop_list(struct TOPO_GROUP_NODE* nq, uint8_t* out_count
|
||||
/* Лучший живой путь: минимум переходов, затем ID соседа. Указатель заимствованный. */
|
||||
struct TOPO_NODEPATH* topo_node_best_path(const struct TOPO_GROUP_NODE* nq);
|
||||
|
||||
/* Обновить локальный узел группы и его анонс. 0/-1. */
|
||||
/* Подготовить и опубликовать local_node с актуальными подсетями/состоянием; число IPv4-подсетей или -1.
|
||||
* Учитывает адреса и префиксы, а не только счётчики; CHAT не анонсирует подсети и возвращает 0.
|
||||
* При отказе подготовки/подписи или конфликте IPv4 сохраняет прежние local_node и маршруты.
|
||||
* Вызывает обновление общей подписанной записи; рассылку NODEINFO выполняет вызывающий. */
|
||||
int topo_group_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct TOPO_GROUP* group);
|
||||
/* Создать подписанную локальную идентичность с отдельной ссылкой ядра. 0/-1. */
|
||||
int topo_node_init_self(struct UTUN_INSTANCE* instance);
|
||||
int topo_node_update_my_addresses(struct UTUN_INSTANCE* instance); /* 1=changed, 0=unchanged, -1=error */
|
||||
/* Пересобрать общие адреса из рабочих UDP/TCP-сокетов; 1=изменились, 0=прежние, -1=ошибка.
|
||||
* Новая запись подписывается/сохраняется перед заменой, затем рассылается во все группы.
|
||||
* Ошибка SQL-проекции даёт -1 и после применённого изменения; при неизменных адресах проекция также обновляется. */
|
||||
int topo_node_update_my_addresses(struct UTUN_INSTANCE* instance);
|
||||
/* Обработчик изменений локального сокета: обновляет адреса узла. */
|
||||
void topo_node_on_socket_changed(struct ETCP_SOCKET* sock, int event, void* arg);
|
||||
/* Вывести диагностику узлов группы в лог / текстовый буфер. */
|
||||
/* Вывести group->nodes в лог категории BGP на уровне INFO; отдельный local_node не добавляется. */
|
||||
void topo_node_dump_all(struct TOPO_GROUP* group);
|
||||
/* Записать дамп в buf: длина без NUL или -1 при ошибке форматирования. Ёмкость ограничена INT_MAX.
|
||||
* При усечении завершает запись сразу, заменяет хвост доступной частью "\n[TRUNCATED]\n" и возвращает реальную длину.
|
||||
* При buf != NULL и buf_size > 0 строка всегда завершена NUL. Некорректные аргументы дают 0 и пустую строку, если есть буфер. */
|
||||
int topo_node_format_all(struct TOPO_GROUP* group, char* buf, size_t buf_size);
|
||||
/* 1, если хотя бы в одной группе пора повторить ping этого узла; иначе 0. */
|
||||
int topo_node_ping_request_cbk(struct TOPO_GROUPS* groups, uint64_t node_id);
|
||||
@@ -286,15 +306,18 @@ uint16_t topo_get_chain_rtt(struct TOPO_GROUP_NODE* nq);
|
||||
/* Лимиты буфера подписываемых данных и полного сетевого NODEINFO. */
|
||||
#define TOPO_SIG_MSG_MAX_SIZE 8192
|
||||
#define TOPO_NODE_WIRE_MAX_SIZE 16384
|
||||
/* Канонические байты подписи: node_id, ver, timestamp, X25519-ключ, имя,
|
||||
* тип/активность и полные списки сокетов/адресов/Reality. Длина или -1. */
|
||||
/* Канонические байты: node_id(native), ver, timestamp(BE), X25519-ключ, имя до 63 байт + NUL,
|
||||
* тип/активность, пять счётчиков, SOCKMETA4/ADDR4/SOCKMETA6/ADDR6/REALITY. Порты здесь LE, SNI с NUL.
|
||||
* Групповые flags/id, подсети, hops и RTT не подписываются. Длина или -1, буфер при ошибке может быть заполнен частично. */
|
||||
int topo_node_build_sig_msg(struct TOPO_NODE* ni, uint8_t* buf, size_t buf_size);
|
||||
|
||||
/* Увеличить timestamp, подписать локальный NODEINFO и сохранить снимок в SQLite. 0/-1. */
|
||||
/* Увеличить timestamp (выше текущего/сохранённого), подписать локальный NODEINFO; при наличии SQLite сохранить снимок.
|
||||
* 0/-1; при ошибке возвращает прежние timestamp/подпись, остальные изменённые вызывающим поля не откатывает. */
|
||||
int topo_node_sign_self(struct UTUN_INSTANCE* instance, struct TOPO_NODE* ni);
|
||||
/* Проверить timestamp, привязку node_id к ключу и подпись. 0/-1. */
|
||||
/* Проверить 0 < timestamp <= INT64_MAX, node_id=derive(X25519) и подпись предоставленным Ed25519-ключом. 0/-1. */
|
||||
int topo_node_verify(const struct TOPO_NODE* ni);
|
||||
|
||||
/* Заимствованные списки: ni обязателен; подсетевые аксессоры допускают r=NULL. */
|
||||
static inline const struct TOPO_SOCKMETA4* topo_v4_sock_meta(const struct TOPO_NODE* ni) { return ni->v4_sock_meta; }
|
||||
static inline const struct TOPO_ADDR4* topo_v4_addrs(const struct TOPO_NODE* ni) { return ni->v4_addrs; }
|
||||
static inline const struct TOPO_SOCKMETA6* topo_v6_sock_meta(const struct TOPO_NODE* ni) { return ni->v6_sock_meta; }
|
||||
|
||||
+119
-145
@@ -1,178 +1,152 @@
|
||||
# topo_node
|
||||
# topo_node — модель, снимок и локальное состояние узла
|
||||
|
||||
## 1. Назначение
|
||||
## Два уровня записи
|
||||
|
||||
Модель данных узла топологии — структуры для представления узла в памяти, сериализации/
|
||||
десериализации в бинарный wire-формат для BGP-обмена, глобальный реестр идентичностей
|
||||
(refcounting) и диагностический дамп.
|
||||
TOPO_NODE содержит общую идентичность: node_id, диагностический ver, timestamp, ключи,
|
||||
Ed25519-подпись, имя, тип/активность, списки SOCKMETA4/ADDR4/SOCKMETA6/ADDR6/REALITY.
|
||||
Групповых flags, group_id, подсетей и путей в нём нет.
|
||||
|
||||
Модуль определяет два слоя:
|
||||
- **Память:** связанные списки (`TOPO_ADDR4`, `TOPO_SOCKMETA4`, `TOPO_SUBNET4` и др.) и две
|
||||
структуры узла — `TOPO_NODE` (глобальная идентичность) и `TOPO_GROUP_NODE` (per-group данные).
|
||||
- **Wire-формат:** packed-структуры (`TOPOMSG_NODE`, `TOPOMSG_ADDR4` и др.) для передачи по сети.
|
||||
TOPO_GROUP_NODE содержит node_id, подсети, пути, last_ver/last_timestamp, состояние conn_mgr,
|
||||
пробы связности, presence/up, radio/phone_status и заимствованный NCD handle.
|
||||
Удалённые записи находятся в group->nodes; локальная запись хранится отдельно в group->local_node.
|
||||
TOPO_NODEPATH содержит заимствованный ETCP_CONN, hop_count, cumulative_rtt, exchange
|
||||
и массив uint64_t[hop_count] непосредственно после структуры.
|
||||
|
||||
Сам модуль не содержит сетевой логики BGP-обмена — этим занимается `topo_group`. Здесь только
|
||||
данные и их упаковка/распаковка. Персистентность — в `topo_node_sqlite`.
|
||||
Все операции выполняются в uasync-потоке ядра. BGP-сессией/обменом управляет topo_group,
|
||||
хранением в общей SQLite — topo_node_sqlite. Этот модуль также готовит локальную запись,
|
||||
обновляет адреса и инициирует рассылку после их изменения.
|
||||
|
||||
Два уровня представления узла:
|
||||
- **TOPO_NODE** — глобальная идентичность (pubkeys, имя, адреса, self-signature). Хранится один
|
||||
раз в глобальном реестре `TOPO_GROUPS->node_registry` (hash по node_id), разделяется между
|
||||
группами через `group_ref_count`.
|
||||
- **TOPO_GROUP_NODE** — узел в контексте группы: `node_id`, `paths`, `subnets`, `last_ver`,
|
||||
`connectivity`, `conn_presence/conn_up`, `handle`. Хранится в `TOPO_GROUP->nodes`.
|
||||
## Реестр и память
|
||||
|
||||
## 2. Как пользоваться
|
||||
node_registry хранит отдельный ll_entry с node_id и указателем TOPO_NODE.
|
||||
registry_find возвращает заимствованный указатель. Для удержания записи берётся registry_ref;
|
||||
последний registry_unref удаляет registry entry, списки, имя и сам объект.
|
||||
|
||||
### Типовые сценарии
|
||||
registry_store принимает входной объект при успехе и возвращает запись с одной новой ссылкой:
|
||||
|
||||
**Сборка информации о себе:**
|
||||
```c
|
||||
topo_group_update_my_nodeinfo(instance, group);
|
||||
```
|
||||
Строит `group->local_node` (`TOPO_GROUP_NODE`) из конфига: сокеты, адреса (interface/NAT/real),
|
||||
подсети; при изменениях инкрементирует `ver`. Для chat-групп (`TOPO_GROUP_TYPE_CHAT`) подсети
|
||||
не включаются.
|
||||
- Более свежий подписанный timestamp заменяет всю запись, включая пустые списки, но адрес существующего объекта сохраняется.
|
||||
- Старый/равный вход освобождается, возвращается прежняя запись. Конфликт одинаковых timestamp логируется.
|
||||
- Для двух bootstrap-записей с timestamp=0 сливаются присутствующие списки; полноценная идентичность может обновить скалярные поля.
|
||||
- При новой регистрации более свежий/равный сохранённый снимок SQLite может заменить вход.
|
||||
- При NULL-результате вход остаётся у вызывающего.
|
||||
|
||||
**Сериализация для отправки по BGP:**
|
||||
```c
|
||||
struct TOPO_NODE* ni = topo_node_registry_find(groups, nq->node_id);
|
||||
uint8_t buf[4096];
|
||||
int len = topo_node_serialize(ni, nq, group_id, flags, buf, sizeof(buf), cumulative_rtt);
|
||||
```
|
||||
Запаковывает `TOPO_NODE` + per-group данные (`TOPO_GROUP_NODE`) в буфер: заголовок
|
||||
`TOPOMSG_NODE`, затем имя узла, v4/v6 sock_meta, v4/v6 адреса, подсети (если
|
||||
`TOPO_FLAG_SEND_SUBNETS`) и hop-лист (через `topo_node_best_hop_list`).
|
||||
У зарегистрированной записи используется registry_unref. Незарегистрированный результат parser/SQL
|
||||
уничтожается topo_node_destroy(groups, ni). Списки сокетов/адресов/подсетей принадлежат пулам groups,
|
||||
имя, REALITY, контейнер подсетей и отдельно возвращённый hop-list — u_malloc/u_calloc.
|
||||
Для освобождения pooled-списков нужен тот же контейнер groups.
|
||||
|
||||
**Десериализация при получении NODEINFO:**
|
||||
```c
|
||||
struct TOPO_NODE* ni = NULL;
|
||||
struct TOPO_NODESUBNETS* subnets = NULL;
|
||||
uint64_t* hop_list = NULL; uint8_t hop_count = 0;
|
||||
uint16_t cumulative_rtt = 0;
|
||||
topo_node_deserialize(group, data, data_len, &ni, &subnets, &hop_list, &hop_count, &cumulative_rtt);
|
||||
```
|
||||
Восстанавливает `TOPO_NODE` (через `u_calloc`), подсети и hop-лист. Освобождение:
|
||||
- `topo_node_destroy(groups, ni)` — освободить `TOPO_NODE` (списки адресов + имя).
|
||||
- `topo_nodeq_free_group_fields(groups, nq)` — освободить per-group поля (`subnets`, `paths`-элементы).
|
||||
topo_nodeq_free_group_fields снимает ссылку реестра, освобождает подсети и обнуляет handle;
|
||||
сам nq и пути остаются. topo_nodeq_clear_paths освобождает пути/контейнер, транспорт не закрывает.
|
||||
Удалённый узел удаляется через topo_nodeq_remove_node: отмена проб, удаление IPv4-маршрутов,
|
||||
удаление из очереди и освобождение полей/записи. local_node освобождает владелец группы отдельно.
|
||||
|
||||
**Поиск узла по ID:**
|
||||
```c
|
||||
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(group, node_id); // per-group
|
||||
struct TOPO_NODE* ni = topo_node_registry_find(groups, node_id); // глобальная идентичность
|
||||
```
|
||||
## Timestamp и подпись
|
||||
|
||||
**Дамп всех узлов:**
|
||||
```c
|
||||
topo_node_dump_all(group); // в лог
|
||||
topo_node_format_all(group, buf, size); // в строку
|
||||
```
|
||||
timestamp — Unix microseconds, версия всей подписанной записи; 0 обозначает bootstrap.
|
||||
ver — циклический счётчик 1..255, не фильтр свежести. Групповой last_timestamp хранит последнюю
|
||||
принятую версию в этой группе, last_ver используется для диагностики.
|
||||
|
||||
### Ключевые концепции
|
||||
topo_node_build_sig_msg создаёт канонические байты в таком порядке:
|
||||
|
||||
- **Глобальный реестр**: `TOPO_NODE` хранится один раз в `node_registry`; `topo_node_registry_store`
|
||||
дедуплицирует по `node_id` (возвращает существующий при совпадении). `registry_ref/unref`
|
||||
управляют `group_ref_count`; при 0 — `topo_node_destroy`.
|
||||
- **Путь**: `TOPO_NODEPATH = {conn, hop_count, cumulative_rtt}` + inline `hop_list[hop_count]`
|
||||
сразу после структуры. Полный hop-лист для отправки — `topo_node_best_hop_list`.
|
||||
- **Self-signature**: `x25519_self_sig` — Ed25519-подпись над каноническим сообщением
|
||||
(`topo_node_build_sig_msg`: pubkey || имя || client_type || client_activity || адреса).
|
||||
Ставится `topo_node_sign_self`, проверяется в `topo_group_process_nodeinfo`.
|
||||
- **Адреса трёх типов**: `TOPO_ADDR_INTERFACE` (LAN), `TOPO_ADDR_NAT` (после детекции),
|
||||
`TOPO_ADDR_REAL` (подтверждённый прямой). Для каждого сокета — до 3 адресов.
|
||||
- **TOPO_CONNECTIVITY** — локальное состояние связности (не передаётся по BGP): probe_status
|
||||
(NONE/IN_PROGRESS/DONE/DEFERRED), RTT и статусы interface/nat/real, `probe_deferred_wait`.
|
||||
- **TOPO_FLAG_SEND_SUBNETS** — если установлен, подсети сериализуются; для chat-групп сброшен.
|
||||
- **Wire-протокол**: заголовок `TOPOMSG_NODE` (packed) + динамика: `node_name`, `SOCKMETA4*N`,
|
||||
`ADDR4*N`, `SOCKMETA6*N`, `ADDR6*N`, опционально подсети, hop-лист. Размер динамики — `topo_node_dyn_size()`.
|
||||
| Поле/секция | Представление подписи |
|
||||
| --- | --- |
|
||||
| node_id, ver | uint64 в нативном порядке, затем байт ver |
|
||||
| timestamp | 8 байт BE |
|
||||
| public_key | X25519, 32 байта |
|
||||
| node_name | До 63 байт и NUL |
|
||||
| client_type, client_activity | По одному байту |
|
||||
| Счётчики | По байту: v4 sockets, v4 addrs, v6 sockets, v6 addrs, REALITY |
|
||||
| SOCKMETA4, ADDR4, SOCKMETA6, ADDR6 | Полные списки в текущем порядке; адреса сетевыми байтами, порты LE |
|
||||
| REALITY | socket_id, short_id[8], server_pubkey[32], version[3], SNI с NUL |
|
||||
|
||||
## 3. API
|
||||
SOCKMETA содержит id/config_type/mapping/inbound/source, каждый по байту.
|
||||
Групповые flags/id, подсети, hops и cumulative_rtt не входят в подпись.
|
||||
Ed25519-ключ передаётся в записи и используется при verify; он не добавляется отдельным полем канонического сообщения.
|
||||
|
||||
### Основные структуры
|
||||
topo_node_sign_self назначает timestamp строго выше текущего и сохранённого в SQLite,
|
||||
подписывает ключом instance->my_ed25519_privkey и сохраняет снимок, если БД есть.
|
||||
При ошибке восстанавливаются timestamp/подпись; другие изменённые вызывающим поля не откатываются.
|
||||
Поэтому локальные обновления подписывают временный candidate до замены общей записи.
|
||||
|
||||
| Структура | Назначение |
|
||||
|-----------|-----------|
|
||||
| `struct TOPO_NODE` | Глобальная идентичность узла: `flags`, `group_id`, `node_id`, `ver`, `public_key`, `ed25519_public_key`, `x25519_self_sig`, `client_type`, `client_activity`, `node_name`, списки v4/v6 sock_meta и addrs, `group_ref_count` |
|
||||
| `struct TOPO_GROUP_NODE` | Per-group запись: `node_id`, `subnets`, `paths` (queue TOPO_NODEPATH), `last_ver`, `conn_mgr_*`, `connectivity`, `conn_presence`, `conn_up`, `handle` (ncd) |
|
||||
| `struct TOPO_NODEPATH` | Путь через ETCP_CONN: `{conn, hop_count, cumulative_rtt}` + inline `hop_list[]` |
|
||||
| `struct TOPO_NODESUBNETS` | v4/v6 подсети узла (отдельный malloc, может быть NULL) |
|
||||
| `struct TOPO_CONNECTIVITY` | Связность: probe_status, RTT, статусы interface/nat/real, probe-тайминги |
|
||||
| `struct TOPOMSG_NODE` | Packed-заголовок wire-формата: счётчики, `hop_count`, `client_type/activity`, `cumulative_rtt` |
|
||||
| `struct TOPOMSG_ADDR4/6` | Packed wire одного адреса (addr, port, type, socket_id, protocol) |
|
||||
| `struct TOPOMSG_SOCKMETA4/6` | Packed wire сокет-метаданных (id, config_type, nat_type) |
|
||||
| `struct TOPOMSG_SUBNET4/6` | Packed wire подсети (addr, prefix_length) |
|
||||
topo_node_verify проверяет диапазон timestamp, node_id=derive(X25519 public_key)
|
||||
и Ed25519-подпись предоставленным ключом; возвращает 0/-1.
|
||||
Проверку права участия в группе и допустимости пути выполняет вызывающий BGP-слой.
|
||||
|
||||
### Вспомогательные типы в памяти (связанные списки)
|
||||
## NODEINFO wire
|
||||
|
||||
| Тип | Назначение |
|
||||
|-----|-----------|
|
||||
| `struct TOPO_SOCKMETA4/6` | Элемент списка сокет-метаданных (next*, id, config_type, nat_type) |
|
||||
| `struct TOPO_ADDR4/6` | Элемент списка адресов (next*, addr, port, type, socket_id, protocol) |
|
||||
| `struct TOPO_SUBNET4/6` | Элемент списка подсетей (next*, addr, prefix_length) |
|
||||
Сериализация ручная, без lib/serialize. Packed-заголовок TOPOMSG_NODE содержит поля записи,
|
||||
group_id/flags, счётчики, hop_count и cumulative_rtt. Далее идут:
|
||||
|
||||
Элементы списков аллоцируются из memory pool'ов (`topo_groups`) через `memory_pool_alloc/free`.
|
||||
| Порядок | Секция |
|
||||
| --- | --- |
|
||||
| 1 | node_name_len байт имени без NUL |
|
||||
| 2 | SOCKMETA4, затем ADDR4 |
|
||||
| 3 | SOCKMETA6, затем ADDR6 |
|
||||
| 4 | REALITY с фиксированным полем server_name[256] |
|
||||
| 5 | SUBNET4, затем SUBNET6, только с TOPO_FLAG_SEND_SUBNETS |
|
||||
| 6 | hop_count идентификаторов uint64_t |
|
||||
|
||||
### Константы
|
||||
Числа wire копируются в нативном порядке; IP — сетевые байты. Это представление отличается
|
||||
от канонического сообщения подписи (timestamp BE, порты LE, строки с NUL).
|
||||
Счётчики ограничены 255, имя — 63 байтами. dyn_size возвращает размер динамики или -1.
|
||||
|
||||
| Константа | Значение | Назначение |
|
||||
|-----------|---------|-----------|
|
||||
| `TOPO_ADDR_INTERFACE` / `TOPO_ADDR_NAT` / `TOPO_ADDR_REAL` | 0/1/2 | Тип адреса |
|
||||
| `TOPO_PROTO_UDP` / `TOPO_PROTO_TCP` | 0x01/0x02 | Транспорт |
|
||||
| `ADDR_TYPE_NETIF/DIRECT/NAT_EIM/NAT_STRICT` | 0/1/2/3 | Классификация адреса |
|
||||
| `PROBE_STATUS_NONE/IN_PROGRESS/DONE/DEFERRED` | 0/1/2/3 | Состояние пробы |
|
||||
| `PROBE_RESULT_UNKNOWN/REACHABLE/UNREACHABLE` | 0/1/2 | Результат пробы |
|
||||
| `CONN_TYPE_NONE/DIRECT/REVERSE/INDIRECT` | 0/1/2/3 | Стратегия соединения |
|
||||
| `CLIENT_TYPE_SERVER/DESKTOP/MOBILE` | 0/1/2 | Тип клиента |
|
||||
| `CLIENT_ACTIVITY_STANDBY/ACTIVE` | 0/1 | Режим |
|
||||
| `NCONN_DIRECT/INDIRECT/BGP` | 1/2/4 | Биты наличия соединений |
|
||||
| `TOPO_FLAG_SEND_SUBNETS` | 0x01 | Отправлять подсети |
|
||||
| `TOPO_GROUP_UTUN` | `0x8000000000000000ULL` | ID utun-группы по умолчанию |
|
||||
| `TOPO_GROUP_TYPE_UTUN/CHAT` | 1/2 | Тип группы |
|
||||
| `CONN_MGR_MAX_INTERMEDIARIES` | 3 | Максимум посредников |
|
||||
serialize получает ni и nq, оставляет владение у вызывающего и возвращает длину либо -1.
|
||||
Входные поля должны быть пригодны для публикации; serialize не проверяет подпись.
|
||||
Hop-list берётся один раз из выбранного живого пути. Radio и phone_status дополняют flags.
|
||||
Без TOPO_FLAG_SEND_SUBNETS счётчики подсетей равны 0; секции не записываются.
|
||||
|
||||
### Функции
|
||||
deserialize требует group->instance->topo_groups с пулами и обязательный out_ni.
|
||||
Остальные out_* независимы и необязательны. Проверяются точная длина, флаг/счётчики,
|
||||
профили сокетов и IPv4 /0..32, IPv6 /0..128. Все секции обходятся и проверяются независимо
|
||||
от запрошенных выходов. Подпись и допустимость BGP-пути проверяются отдельно.
|
||||
|
||||
**Реестр и refcounting:**
|
||||
- `topo_node_registry_find(groups, node_id)` — найти `TOPO_NODE` в глобальном реестре.
|
||||
- `topo_node_registry_store(groups, ni)` — сохранить с дедупликацией (возвращает существующий при совпадении).
|
||||
- `topo_node_registry_ref/unref(groups, node_id)` — инкремент/декремент `group_ref_count`.
|
||||
- `topo_node_destroy(groups, ni)` — освободить `TOPO_NODE` (списки адресов + имя).
|
||||
Перед разбором переданные выходы обнуляются, прежние объекты не освобождаются.
|
||||
Успех — 0 и атомарная выдача результата; ошибка — -1 и NULL/0 без частичных объектов.
|
||||
Пустые секции дают NULL/0. ni уничтожается topo_node_destroy, подсети передаются владельцу
|
||||
TOPO_GROUP_NODE, hop-list освобождается u_free.
|
||||
|
||||
**Per-group:**
|
||||
- `topo_node_find_by_id(group, node_id)` — найти `TOPO_GROUP_NODE` по node_id (hash).
|
||||
- `topo_nodeq_free_group_fields(groups, nq)` — освободить per-group поля (subnets, paths) + unref глобального TOPO_NODE.
|
||||
## Локальная публикация
|
||||
|
||||
**Сериализация:**
|
||||
- `topo_node_dyn_size(msg)` — размер динамической части по счётчикам заголовка.
|
||||
- `topo_node_serialize(ni, nq, group_id, flags, out, out_max, cumulative_rtt)` — сериализовать `TOPO_NODE` + per-group в буфер.
|
||||
- `topo_node_deserialize(group, data, len, &out_ni, &out_subnets, &out_hop_list, &out_hop_count, &out_cumulative_rtt)` — восстановить из wire-формата.
|
||||
topo_node_init_self создаёт подписанную идентичность с отдельной ссылкой ядра; 0/-1.
|
||||
|
||||
**Пути:**
|
||||
- `topo_node_best_hop_list(nq, &out_count, &out_rtt)` — лучший hop-лист (по min hop_count).
|
||||
topo_group_update_my_nodeinfo готовит local_node из имени, ключей, типа/активности,
|
||||
radio/phone_status и конфигурационных подсетей. Сравниваются адреса и префиксы,
|
||||
включая замену подсети при прежнем количестве. CHAT не анонсирует подсети.
|
||||
|
||||
**Подпись:**
|
||||
- `topo_node_build_sig_msg(ni, buf, buf_size)` — каноническое сообщение для Ed25519-подписи.
|
||||
- `topo_node_sign_self(instance, ni)` — подписать свой NODEINFO (`x25519_self_sig`).
|
||||
Новая запись/подсети готовятся до удаления прежнего local_node. IPv4 проходит route_insert
|
||||
с запретом пересечений; при конфликте или отказе подготовки/подписи прежние узел и маршруты сохраняются.
|
||||
Общая подписанная идентичность заменяется после успешного sign_self.
|
||||
Возврат — число IPv4-подсетей (CHAT: 0), при ошибке -1. Рассылку запускает вызывающий.
|
||||
|
||||
**Обновление о себе:**
|
||||
- `topo_group_update_my_nodeinfo(instance, group)` — перестроить `local_node`.
|
||||
- `topo_node_update_my_addresses(instance)` — обновить адреса.
|
||||
- `topo_node_on_socket_changed(sock, event, arg)` — обработчик изменений сокетов.
|
||||
topo_node_update_my_addresses отдельно собирает адреса рабочих UDP/TCP-сокетов.
|
||||
PRIVATE исключаются, UDP probe_only исключаются; LOCAL остаются доступными для локальных подключений.
|
||||
UDP публикует интерфейс и допустимый наблюдаемый NAT endpoint, IPv6 интерфейс — отдельным списком.
|
||||
TCP может публиковать внешний IP с port=0, если входящий порт неизвестен.
|
||||
REALITY-параметры берутся из глобальной конфигурации и связываются с socket_id.
|
||||
|
||||
**Диагностика:**
|
||||
- `topo_node_dump_all(group)` / `topo_node_format_all(group, buf, size)` — дамп узлов.
|
||||
Изменение подписывается/сохраняется до замены списков и рассылается во все группы.
|
||||
Неизменный набор не переподписывается, но SQL-проекция обновляется повторно.
|
||||
Возврат 1/0/-1; ошибка проекции может дать -1 уже после применённой подписанной записи.
|
||||
on_socket_changed инициирует этот пересчёт при событии сокета.
|
||||
|
||||
**Inline-аксессоры (.h):**
|
||||
- `topo_v4_sock_meta/addr`, `topo_v6_sock_meta/addr`, `topo_v4_subnets/topo_v6_subnets`, `topo_list_count`.
|
||||
## Пути, RTT и диагностика
|
||||
|
||||
### Зависимости
|
||||
best_path выбирает путь с conn->links_up, без close_requested, по минимуму hop_count,
|
||||
при равенстве — меньшему peer_node_id. READY проверяется отдельно владельцем групповой сессии.
|
||||
best_hop_list возвращает заимствованный inline-массив; out_count обязателен,
|
||||
out_rtt необязателен и получает RTT без локального линка. При отсутствии пути — NULL/0.
|
||||
|
||||
| Модуль | Использование |
|
||||
|--------|--------------|
|
||||
| `ll_queue.h` | `TOPO_GROUP_NODE` — запись в очереди (hash по node_id), `paths` — вложенная очередь |
|
||||
| `secure_channel.h` | `SC_PUBKEY_SIZE` (32) |
|
||||
| `mem.h` | `u_malloc`, `u_calloc`, `u_free` |
|
||||
| `memory_pool.h` | Элементы списков из пулов `topo_groups` |
|
||||
| `debug_config.h` | Логирование (BGP) |
|
||||
| `topo_group.h` | `struct TOPO_GROUP`, `TOPO_GROUPS` |
|
||||
| `route_lib.h` | `route_insert/route_delete` |
|
||||
| `utun_instance.h` | `struct UTUN_INSTANCE` |
|
||||
topo_get_chain_rtt складывает rtt_last локального соединения и cumulative_rtt.
|
||||
UINT16_MAX обозначает неизвестный путь/локальный RTT либо насыщение суммы.
|
||||
Единицы RTT и timebase-таймингов связности — 0.1 ms.
|
||||
|
||||
ping_request_cbk сообщает, пора ли повторить ping хотя бы в одной группе (TTL 24 часа).
|
||||
ping_update_rtt обновляет время ping и минимальные RTT во всех группах, SQL и callbacks;
|
||||
сам по себе не переводит статусы достижимости в REACHABLE.
|
||||
|
||||
dump_all пишет group->nodes в BGP/INFO. format_all форматирует ту же очередь в буфер:
|
||||
реальная длина без NUL, -1 при ошибке форматирования, некорректные аргументы дают 0.
|
||||
При наличии буфера строка завершена NUL; после первого усечения запись прекращается,
|
||||
хвост заменяется доступной частью маркера TRUNCATED. Ёмкость ограничена INT_MAX.
|
||||
|
||||
Полные объявления и контракты — в topo_node.h; общий жизненный цикл снимка — doc/node_snapshot.md.
|
||||
@@ -206,24 +206,35 @@ int topo_node_sqlite_member_exists(sqlite3* db, uint64_t node_id) {
|
||||
|
||||
int topo_node_sqlite_get_member_channels(sqlite3* db, uint64_t node_id,
|
||||
uint64_t** out_ch_ids, int* out_count) {
|
||||
if (!db || !out_ch_ids || !out_count) return -1;
|
||||
*out_ch_ids = NULL; *out_count = 0;
|
||||
if (out_ch_ids) *out_ch_ids = NULL;
|
||||
if (out_count) *out_count = 0;
|
||||
if (!db || !out_ch_ids || !out_count) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "member channels: invalid arguments"); return -1; }
|
||||
|
||||
sqlite3_stmt* s = NULL;
|
||||
if (sqlite3_prepare_v2(db,
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'peers\\_%' ESCAPE '\\'",
|
||||
-1, &s, NULL) != SQLITE_OK) return -1;
|
||||
-1, &s, NULL) != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "member channels: enumerate tables: %s", sqlite3_errmsg(db)); return -1;
|
||||
}
|
||||
|
||||
uint64_t* ids = NULL; int count = 0, cap = 0;
|
||||
char sql[256];
|
||||
while (sqlite3_step(s) == SQLITE_ROW) {
|
||||
int rc;
|
||||
while ((rc = sqlite3_step(s)) == SQLITE_ROW) {
|
||||
const char* tbl = (const char*)sqlite3_column_text(s, 0);
|
||||
if (!tbl || strncmp(tbl, "peers_", 6) != 0) continue;
|
||||
snprintf(sql, sizeof(sql), "SELECT 1 FROM \"%s\" WHERE node_id=?", tbl);
|
||||
sqlite3_stmt* m = NULL;
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &m, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_int64(m, 1, (sqlite3_int64)node_id);
|
||||
int is_member = (sqlite3_step(m) == SQLITE_ROW);
|
||||
int member_rc = sqlite3_prepare_v2(db, sql, -1, &m, NULL);
|
||||
if (member_rc == SQLITE_OK) member_rc = sqlite3_bind_int64(m, 1, (sqlite3_int64)node_id);
|
||||
if (member_rc == SQLITE_OK) member_rc = sqlite3_step(m);
|
||||
if (member_rc != SQLITE_ROW && member_rc != SQLITE_DONE) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "member channels: table=%s node=%016llx rc=%d: %s",
|
||||
tbl, (unsigned long long)node_id, member_rc, sqlite3_errmsg(db));
|
||||
sqlite3_finalize(m); u_free(ids); sqlite3_finalize(s); return -1;
|
||||
}
|
||||
{
|
||||
int is_member = member_rc == SQLITE_ROW;
|
||||
sqlite3_finalize(m);
|
||||
if (is_member) {
|
||||
uint64_t ch = strtoull(tbl + 6, NULL, 10);
|
||||
@@ -231,7 +242,10 @@ int topo_node_sqlite_get_member_channels(sqlite3* db, uint64_t node_id,
|
||||
if (count == cap) {
|
||||
cap = cap ? cap * 2 : 8;
|
||||
uint64_t* tmp = u_realloc(ids, (size_t)cap * sizeof(uint64_t));
|
||||
if (!tmp) { u_free(ids); sqlite3_finalize(s); return -1; }
|
||||
if (!tmp) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "member channels: allocation failed count=%d", count);
|
||||
u_free(ids); sqlite3_finalize(s); return -1;
|
||||
}
|
||||
ids = tmp;
|
||||
}
|
||||
ids[count++] = ch;
|
||||
@@ -239,6 +253,11 @@ int topo_node_sqlite_get_member_channels(sqlite3* db, uint64_t node_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rc != SQLITE_DONE) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "member channels: enumeration failed node=%016llx rc=%d: %s",
|
||||
(unsigned long long)node_id, rc, sqlite3_errmsg(db));
|
||||
u_free(ids); sqlite3_finalize(s); return -1;
|
||||
}
|
||||
sqlite3_finalize(s);
|
||||
*out_ch_ids = ids; *out_count = count;
|
||||
return 0;
|
||||
@@ -586,44 +605,47 @@ int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id,
|
||||
return rc == SQLITE_DONE ? 0 : -1;
|
||||
}
|
||||
|
||||
/* Удаляет канал и производные таблицы целиком; savepoint сохраняет внешнюю транзакцию. */
|
||||
int topo_node_sqlite_channel_delete(sqlite3* db, const char* channel_id) {
|
||||
if (!db || !channel_id || !channel_id[0]) return -1;
|
||||
|
||||
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
|
||||
char msg_tbl[80]; sanitize_ch_id(channel_id, msg_tbl, sizeof(msg_tbl));
|
||||
/* msg_<san> */
|
||||
char msg_full[80]; snprintf(msg_full, sizeof(msg_full), "msg_%s", msg_tbl);
|
||||
|
||||
/* 1. drop per-channel tables (индексы дропятся вместе с таблицей) */
|
||||
char sql[128];
|
||||
snprintf(sql, sizeof(sql), "DROP TABLE IF EXISTS \"%s\"", peers_tbl);
|
||||
sqlite3_exec(db, sql, NULL, NULL, NULL);
|
||||
snprintf(sql, sizeof(sql), "DROP TABLE IF EXISTS \"%s\"", msg_full);
|
||||
sqlite3_exec(db, sql, NULL, NULL, NULL);
|
||||
snprintf(sql, sizeof(sql), "DROP TABLE IF EXISTS \"feedback_%s\"", msg_tbl);
|
||||
if (sqlite3_exec(db, sql, NULL, NULL, NULL) != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "feedback: delete channel=%s: %s", channel_id, sqlite3_errmsg(db)); return -1;
|
||||
if (!db || !channel_id || !channel_id[0]) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel delete: invalid arguments"); return -1;
|
||||
}
|
||||
int rc = sqlite3_exec(db, "SAVEPOINT delete_topo_channel", NULL, NULL, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel delete savepoint: channel=%s rc=%d: %s", channel_id, rc, sqlite3_errmsg(db)); return -1;
|
||||
}
|
||||
|
||||
/* 2. delete channels row (вместе с канальными privkey) */
|
||||
sqlite3_stmt* st = NULL;
|
||||
if (sqlite3_prepare_v2(db, "DELETE FROM channels WHERE channel_id=?", -1, &st, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_text(st, 1, channel_id, -1, SQLITE_STATIC);
|
||||
sqlite3_step(st);
|
||||
sqlite3_finalize(st);
|
||||
char san[64], sql[512]; sanitize_ch_id(channel_id, san, sizeof(san));
|
||||
snprintf(sql, sizeof(sql), "DROP TABLE IF EXISTS \"peers_%s\"; DROP TABLE IF EXISTS \"msg_%s\";"
|
||||
" DROP TABLE IF EXISTS \"feedback_%s\"", san, san, san);
|
||||
if ((rc = sqlite3_exec(db, sql, NULL, NULL, NULL)) != SQLITE_OK) goto fail;
|
||||
rc = sqlite3_prepare_v2(db, "DELETE FROM channels WHERE channel_id=?", -1, &st, NULL);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_bind_text(st, 1, channel_id, -1, SQLITE_STATIC);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_step(st);
|
||||
if (rc != SQLITE_DONE) goto fail;
|
||||
sqlite3_finalize(st); st = NULL;
|
||||
rc = sqlite3_prepare_v2(db, "SELECT 1 FROM sqlite_master WHERE type='table' AND name='merkle_tree_hash'", -1, &st, NULL);
|
||||
if (rc != SQLITE_OK) goto fail;
|
||||
rc = sqlite3_step(st);
|
||||
if (rc != SQLITE_ROW && rc != SQLITE_DONE) goto fail;
|
||||
int has_merkle = rc == SQLITE_ROW;
|
||||
sqlite3_finalize(st); st = NULL;
|
||||
if (has_merkle) {
|
||||
rc = sqlite3_prepare_v2(db, "DELETE FROM merkle_tree_hash WHERE substr(namespace,instr(namespace,':')+1)=?", -1, &st, NULL);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_bind_text(st, 1, channel_id, -1, SQLITE_STATIC);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_step(st);
|
||||
if (rc != SQLITE_DONE) goto fail;
|
||||
sqlite3_finalize(st); st = NULL;
|
||||
}
|
||||
|
||||
/* 3. delete merkle tree hashes for this namespace */
|
||||
sqlite3_stmt* mh = NULL;
|
||||
if (sqlite3_prepare_v2(db, "DELETE FROM merkle_tree_hash WHERE substr(namespace,instr(namespace,':')+1)=?", -1, &mh, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_text(mh, 1, channel_id, -1, SQLITE_STATIC);
|
||||
sqlite3_step(mh);
|
||||
sqlite3_finalize(mh);
|
||||
}
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "topo_node_sqlite_channel_delete: ch=%s peers=%s msg=%s",
|
||||
channel_id, peers_tbl, msg_full);
|
||||
if ((rc = sqlite3_exec(db, "RELEASE delete_topo_channel", NULL, NULL, NULL)) != SQLITE_OK) goto fail;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "channel deleted: channel=%s tables=peers/msg/feedback merkle=%d", channel_id, has_merkle);
|
||||
return 0;
|
||||
fail:
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel delete failed: channel=%s rc=%d: %s", channel_id, rc, sqlite3_errmsg(db));
|
||||
sqlite3_finalize(st);
|
||||
if (sqlite3_exec(db, "ROLLBACK TO delete_topo_channel; RELEASE delete_topo_channel", NULL, NULL, NULL) != SQLITE_OK)
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel delete rollback failed: channel=%s: %s", channel_id, sqlite3_errmsg(db));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Упрощённый плейсхолдер мембера от topo_group: только node_id + pubkeys, source=1.
|
||||
@@ -724,11 +746,16 @@ int topo_node_sqlite_member_owner_put(sqlite3* db, const char* channel_id, uint6
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Записывает уже проверенную вызывающим идентичность; любой отказ SQL возвращается вызывающему. */
|
||||
int topo_node_sqlite_node_update_verified(sqlite3* db, uint64_t node_id,
|
||||
const char* name, const uint8_t* x25519, const uint8_t* ed25519, uint64_t update_ts, time_t created_at) {
|
||||
if (!db || !x25519 || !ed25519) return -1;
|
||||
if (!db || !x25519 || !ed25519) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "verified node write: invalid arguments node=%016llx", (unsigned long long)node_id);
|
||||
return -1;
|
||||
}
|
||||
sqlite3_stmt* stmt = NULL;
|
||||
if (sqlite3_prepare_v2(db,
|
||||
const char* stage = "prepare";
|
||||
int rc = sqlite3_prepare_v2(db,
|
||||
"INSERT INTO nodes(node_id, name, x25519_pubkey, ed25519_pubkey, created_at, update_ts, online)"
|
||||
" VALUES(?,?,?,?,?,?,1)"
|
||||
" ON CONFLICT(node_id) DO UPDATE SET"
|
||||
@@ -739,15 +766,25 @@ int topo_node_sqlite_node_update_verified(sqlite3* db, uint64_t node_id,
|
||||
" created_at=COALESCE(nodes.created_at, excluded.created_at),"
|
||||
" update_ts=CASE WHEN nodes.update_ts IS NULL OR excluded.update_ts > nodes.update_ts"
|
||||
" THEN excluded.update_ts ELSE nodes.update_ts END",
|
||||
-1, &stmt, NULL) != SQLITE_OK) return -1;
|
||||
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id);
|
||||
sqlite3_bind_text(stmt, 2, name && name[0] ? name : "", -1, SQLITE_STATIC);
|
||||
sqlite3_bind_blob(stmt, 3, x25519, 32, SQLITE_STATIC);
|
||||
sqlite3_bind_blob(stmt, 4, ed25519, 32, SQLITE_STATIC);
|
||||
sqlite3_bind_int64(stmt, 5, (sqlite3_int64)created_at);
|
||||
sqlite3_bind_int64(stmt, 6, (sqlite3_int64)update_ts);
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
-1, &stmt, NULL);
|
||||
if (rc == SQLITE_OK) {
|
||||
stage = "bind";
|
||||
rc = sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_bind_text(stmt, 2, name && name[0] ? name : "", -1, SQLITE_STATIC);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_bind_blob(stmt, 3, x25519, 32, SQLITE_STATIC);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_bind_blob(stmt, 4, ed25519, 32, SQLITE_STATIC);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(stmt, 5, (sqlite3_int64)created_at);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(stmt, 6, (sqlite3_int64)update_ts);
|
||||
}
|
||||
if (rc == SQLITE_OK) { stage = "step"; rc = sqlite3_step(stmt); }
|
||||
if (rc != SQLITE_DONE) DEBUG_ERROR(DEBUG_CATEGORY_BGP, "verified node write: node=%016llx stage=%s rc=%d: %s",
|
||||
(unsigned long long)node_id, stage, rc, sqlite3_errmsg(db));
|
||||
int final_rc = sqlite3_finalize(stmt);
|
||||
if (final_rc != SQLITE_OK && rc == SQLITE_DONE) DEBUG_ERROR(DEBUG_CATEGORY_BGP,
|
||||
"verified node write: node=%016llx stage=finalize rc=%d: %s", (unsigned long long)node_id, final_rc, sqlite3_errmsg(db));
|
||||
if (rc != SQLITE_DONE || final_rc != SQLITE_OK) return -1;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "verified node stored: node=%016llx timestamp=%llu changed=%d",
|
||||
(unsigned long long)node_id, (unsigned long long)update_ts, sqlite3_changes(db));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -763,7 +800,10 @@ int topo_node_sqlite_member_get_join(sqlite3* db, const char* channel_id, uint64
|
||||
int rc = -1;
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const uint8_t* sig = (const uint8_t*)sqlite3_column_blob(stmt, 0);
|
||||
if (sig) { memcpy(join_sig_out, sig, PEERS_JOIN_SIG_SIZE); *join_ts_out = (uint64_t)sqlite3_column_int64(stmt, 1); rc = 0; }
|
||||
if (sig && sqlite3_column_bytes(stmt, 0) == PEERS_JOIN_SIG_SIZE) {
|
||||
memcpy(join_sig_out, sig, PEERS_JOIN_SIG_SIZE); *join_ts_out = (uint64_t)sqlite3_column_int64(stmt, 1); rc = 0;
|
||||
} else DEBUG_WARN(DEBUG_CATEGORY_BGP, "invalid stored join signature channel=%s node=%016llx bytes=%d",
|
||||
channel_id, (unsigned long long)node_id, sqlite3_column_bytes(stmt, 0));
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
return rc;
|
||||
@@ -802,15 +842,18 @@ int topo_node_sqlite_member_in_channel(sqlite3* db, const char* channel_id, uint
|
||||
}
|
||||
|
||||
int topo_node_sqlite_member_del(sqlite3* db, const char* channel_id, uint64_t node_id) {
|
||||
if (!db || !channel_id) return -1;
|
||||
if (!db || !channel_id) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "member delete: invalid arguments"); return -1; }
|
||||
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
|
||||
char sql[256];
|
||||
snprintf(sql, sizeof(sql), "DELETE FROM \"%s\" WHERE node_id=?", peers_tbl);
|
||||
sqlite3_stmt* stmt = NULL;
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1;
|
||||
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id);
|
||||
sqlite3_step(stmt); sqlite3_finalize(stmt);
|
||||
return 0;
|
||||
int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_DONE) DEBUG_ERROR(DEBUG_CATEGORY_BGP, "member delete: channel=%s node=%016llx rc=%d: %s",
|
||||
channel_id, (unsigned long long)node_id, rc, sqlite3_errmsg(db));
|
||||
sqlite3_finalize(stmt);
|
||||
return rc == SQLITE_DONE ? 0 : -1;
|
||||
}
|
||||
|
||||
int topo_node_sqlite_channel_get(sqlite3* db, const char* channel_id,
|
||||
@@ -823,6 +866,12 @@ int topo_node_sqlite_channel_get(sqlite3* db, const char* channel_id,
|
||||
" FROM channels WHERE channel_id=?", -1, &stmt, NULL) != SQLITE_OK) return -1;
|
||||
sqlite3_bind_text(stmt, 1, channel_id, -1, SQLITE_STATIC);
|
||||
if (sqlite3_step(stmt) != SQLITE_ROW) { sqlite3_finalize(stmt); return -1; }
|
||||
if ((x25519_pub && sqlite3_column_bytes(stmt, 2) != 32) ||
|
||||
(ed25519_pub && sqlite3_column_bytes(stmt, 3) != 32) || (signature && sqlite3_column_bytes(stmt, 4) != 64)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid stored channel keys/signature channel=%s sizes=%d/%d/%d", channel_id,
|
||||
sqlite3_column_bytes(stmt, 2), sqlite3_column_bytes(stmt, 3), sqlite3_column_bytes(stmt, 4));
|
||||
sqlite3_finalize(stmt); return -1;
|
||||
}
|
||||
if (name_out && name_sz > 0) {
|
||||
const unsigned char* txt = sqlite3_column_text(stmt, 0);
|
||||
if (txt) snprintf(name_out, name_sz, "%s", txt); else name_out[0] = '\0';
|
||||
@@ -884,14 +933,16 @@ int topo_node_sqlite_channel_set_privs(sqlite3* db, const char* channel_id,
|
||||
}
|
||||
|
||||
int topo_node_sqlite_node_set_online(sqlite3* db, uint64_t node_id, int online) {
|
||||
if (!db) return -1;
|
||||
if (!db) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node online: database is NULL"); return -1; }
|
||||
sqlite3_stmt* stmt = NULL;
|
||||
if (sqlite3_prepare_v2(db, "UPDATE nodes SET online=? WHERE node_id=?", -1, &stmt, NULL) != SQLITE_OK) return -1;
|
||||
sqlite3_bind_int(stmt, 1, online ? 1 : 0);
|
||||
sqlite3_bind_int64(stmt, 2, (sqlite3_int64)node_id);
|
||||
sqlite3_step(stmt);
|
||||
int rc = sqlite3_prepare_v2(db, "UPDATE nodes SET online=? WHERE node_id=?", -1, &stmt, NULL);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_bind_int(stmt, 1, online ? 1 : 0);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(stmt, 2, (sqlite3_int64)node_id);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_step(stmt);
|
||||
if (rc != SQLITE_DONE) DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node online: node=%016llx online=%d rc=%d: %s",
|
||||
(unsigned long long)node_id, online, rc, sqlite3_errmsg(db));
|
||||
sqlite3_finalize(stmt);
|
||||
return 0;
|
||||
return rc == SQLITE_DONE ? 0 : -1;
|
||||
}
|
||||
|
||||
int topo_node_sqlite_node_get_online(sqlite3* db, uint64_t node_id) {
|
||||
@@ -928,75 +979,80 @@ int topo_node_sqlite_get_ed25519_pubkey(sqlite3* db, uint64_t node_id, uint8_t p
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Пересчитывает канальные роли/хранение/удаление; при отказе откатывает всю проекцию узла. */
|
||||
int topo_node_sqlite_nodeinfo_updated(sqlite3* db, uint64_t node_id) {
|
||||
if (!db) return -1;
|
||||
|
||||
int best_at = -1;
|
||||
{
|
||||
sqlite3_stmt* s = NULL;
|
||||
if (sqlite3_prepare_v2(db,
|
||||
"SELECT addr_type FROM node_addresses WHERE node_id=?", -1, &s, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id);
|
||||
while (sqlite3_step(s) == SQLITE_ROW) {
|
||||
int at = sqlite3_column_int(s, 0);
|
||||
if (at == ADDR_TYPE_DIRECT) { best_at = ADDR_TYPE_DIRECT; break; }
|
||||
if (best_at < 0 || at < best_at) best_at = at;
|
||||
}
|
||||
sqlite3_finalize(s);
|
||||
}
|
||||
if (!db) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node classification: database is NULL"); return -1; }
|
||||
int rc = sqlite3_exec(db, "SAVEPOINT node_classification", NULL, NULL, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node classification savepoint: rc=%d: %s", rc, sqlite3_errmsg(db)); return -1;
|
||||
}
|
||||
|
||||
sqlite3_stmt *scan = NULL, *read = NULL, *write = NULL;
|
||||
const char* stage = "addresses";
|
||||
int best_at = -1;
|
||||
rc = sqlite3_prepare_v2(db, "SELECT addr_type FROM node_addresses WHERE node_id=?", -1, &read, NULL);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(read, 1, (sqlite3_int64)node_id);
|
||||
if (rc != SQLITE_OK) goto fail;
|
||||
while ((rc = sqlite3_step(read)) == SQLITE_ROW) {
|
||||
int at = sqlite3_column_int(read, 0);
|
||||
if (at == ADDR_TYPE_DIRECT) { best_at = ADDR_TYPE_DIRECT; break; }
|
||||
if (best_at < 0 || at < best_at) best_at = at;
|
||||
}
|
||||
if (rc != SQLITE_DONE && rc != SQLITE_ROW) goto fail;
|
||||
sqlite3_finalize(read); read = NULL;
|
||||
static const int at2type[] = { [ADDR_TYPE_NETIF]=1, [ADDR_TYPE_DIRECT]=1, [ADDR_TYPE_NAT_EIM]=2,
|
||||
[ADDR_TYPE_NAT_STRICT]=1, [ADDR_TYPE_NAT]=2, [ADDR_TYPE_CONTACT]=2, [ADDR_TYPE_NAT_EIM_UNVERIFIED]=2 };
|
||||
int base_type = best_at < 0 || (unsigned)best_at >= sizeof(at2type) / sizeof(at2type[0]) ? 0 : at2type[best_at];
|
||||
if (best_at >= 0 && (unsigned)best_at >= sizeof(at2type) / sizeof(at2type[0]))
|
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "invalid projected address type=%d node=%016llx", best_at, (unsigned long long)node_id);
|
||||
|
||||
sqlite3_stmt* s = NULL;
|
||||
if (sqlite3_prepare_v2(db,
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'peers\\_%' ESCAPE '\\'",
|
||||
-1, &s, NULL) != SQLITE_OK) return -1;
|
||||
|
||||
char sql[256];
|
||||
while (sqlite3_step(s) == SQLITE_ROW) {
|
||||
const char* tbl = (const char*)sqlite3_column_text(s, 0);
|
||||
|
||||
int node_type = base_type; int is_storage = 0; int is_deleted = 0;
|
||||
snprintf(sql, sizeof(sql),
|
||||
"SELECT adm_tags FROM \"%s\" WHERE node_id=?", tbl);
|
||||
sqlite3_stmt* a = NULL;
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &a, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_int64(a, 1, (sqlite3_int64)node_id);
|
||||
if (sqlite3_step(a) == SQLITE_ROW) {
|
||||
const char* tags = (const char*)sqlite3_column_text(a, 0);
|
||||
if (tags) {
|
||||
char buf[64];
|
||||
if (json_flat_get(tags, "supernode", buf, sizeof(buf)) == 0 && strcmp(buf, "yes") == 0) node_type = 4;
|
||||
if (best_at == ADDR_TYPE_DIRECT && base_type == 1) {
|
||||
if (json_flat_get(tags, "storage", buf, sizeof(buf)) == 0 && strcmp(buf, "yes") == 0) is_storage = 1;
|
||||
}
|
||||
is_deleted = adm_tags_deleted(tags);
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(a);
|
||||
}
|
||||
|
||||
snprintf(sql, sizeof(sql),
|
||||
"UPDATE \"%s\" SET node_type=?, storage=?, deleted=? WHERE node_id=?", tbl);
|
||||
sqlite3_stmt* u = NULL;
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &u, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_int(u, 1, node_type);
|
||||
sqlite3_bind_int(u, 2, is_storage);
|
||||
sqlite3_bind_int(u, 3, is_deleted);
|
||||
sqlite3_bind_int64(u, 4, (sqlite3_int64)node_id);
|
||||
sqlite3_step(u); sqlite3_finalize(u);
|
||||
}
|
||||
stage = "membership tables";
|
||||
rc = sqlite3_prepare_v2(db, "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'peers\\_%' ESCAPE '\\'",
|
||||
-1, &scan, NULL);
|
||||
if (rc != SQLITE_OK) goto fail;
|
||||
while ((rc = sqlite3_step(scan)) == SQLITE_ROW) {
|
||||
const char* table = (const char*)sqlite3_column_text(scan, 0);
|
||||
int node_type = base_type, storage = 0, deleted = 0;
|
||||
char sql[256]; snprintf(sql, sizeof(sql), "SELECT adm_tags FROM \"%s\" WHERE node_id=?", table);
|
||||
stage = "member tags";
|
||||
rc = sqlite3_prepare_v2(db, sql, -1, &read, NULL);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(read, 1, (sqlite3_int64)node_id);
|
||||
if (rc != SQLITE_OK) goto fail;
|
||||
rc = sqlite3_step(read);
|
||||
if (rc == SQLITE_ROW) {
|
||||
const char* tags = (const char*)sqlite3_column_text(read, 0);
|
||||
char value[64];
|
||||
if (json_flat_get(tags, "supernode", value, sizeof(value)) == 0 && !strcmp(value, "yes")) node_type = 4;
|
||||
if (best_at == ADDR_TYPE_DIRECT && json_flat_get(tags, "storage", value, sizeof(value)) == 0 && !strcmp(value, "yes"))
|
||||
storage = 1;
|
||||
deleted = adm_tags_deleted(tags);
|
||||
} else if (rc != SQLITE_DONE) goto fail;
|
||||
sqlite3_finalize(read); read = NULL;
|
||||
snprintf(sql, sizeof(sql), "UPDATE \"%s\" SET node_type=?,storage=?,deleted=? WHERE node_id=?", table);
|
||||
stage = "member projection";
|
||||
rc = sqlite3_prepare_v2(db, sql, -1, &write, NULL);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_bind_int(write, 1, node_type);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_bind_int(write, 2, storage);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_bind_int(write, 3, deleted);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(write, 4, (sqlite3_int64)node_id);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_step(write);
|
||||
if (rc != SQLITE_DONE) goto fail;
|
||||
sqlite3_finalize(write); write = NULL;
|
||||
stage = "membership tables";
|
||||
}
|
||||
sqlite3_finalize(s);
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "nodeinfo_updated: node=%016llx best_at=%d base_type=%d",
|
||||
if (rc != SQLITE_DONE) goto fail;
|
||||
sqlite3_finalize(scan); scan = NULL;
|
||||
stage = "release";
|
||||
if ((rc = sqlite3_exec(db, "RELEASE node_classification", NULL, NULL, NULL)) != SQLITE_OK) goto fail;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "node classification stored: node=%016llx best_at=%d base_type=%d",
|
||||
(unsigned long long)node_id, best_at, base_type);
|
||||
return 0;
|
||||
fail:
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node classification failed: node=%016llx stage=%s rc=%d: %s",
|
||||
(unsigned long long)node_id, stage, rc, sqlite3_errmsg(db));
|
||||
sqlite3_finalize(write); sqlite3_finalize(read); sqlite3_finalize(scan);
|
||||
if (sqlite3_exec(db, "ROLLBACK TO node_classification; RELEASE node_classification", NULL, NULL, NULL) != SQLITE_OK)
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node classification rollback failed: node=%016llx: %s",
|
||||
(unsigned long long)node_id, sqlite3_errmsg(db));
|
||||
return -1;
|
||||
}
|
||||
|
||||
void topo_node_sqlite_update_rtt(sqlite3* db, uint64_t node_id, uint16_t rtt) {
|
||||
@@ -1111,157 +1167,157 @@ struct TOPO_NODE* topo_node_sqlite_snapshot_load(sqlite3* db, struct TOPO_GROUPS
|
||||
}
|
||||
|
||||
struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* groups, uint64_t node_id) {
|
||||
if (!db || !groups) return NULL;
|
||||
if (!db || !groups || !groups->instance || groups->instance->topo_groups != groups) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node load: invalid database/groups node=%016llx", (unsigned long long)node_id); return NULL;
|
||||
}
|
||||
struct TOPO_NODE* snapshot = topo_node_sqlite_snapshot_load(db, groups, node_id);
|
||||
if (snapshot) return snapshot;
|
||||
|
||||
struct TOPO_NODE* ni = u_calloc(1, sizeof(*ni));
|
||||
if (!ni) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node load: allocation failed node=%016llx", (unsigned long long)node_id); return NULL; }
|
||||
sqlite3_stmt* s = NULL;
|
||||
uint8_t pubkey[32] = {0}, ed_pubkey[32] = {0};
|
||||
char* name = NULL;
|
||||
uint8_t client_type = 0;
|
||||
int found = 0;
|
||||
{
|
||||
sqlite3_stmt* s = NULL;
|
||||
if (sqlite3_prepare_v2(db, "SELECT name,x25519_pubkey,ed25519_pubkey,client_type FROM nodes WHERE node_id=?",
|
||||
-1, &s, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id);
|
||||
if (sqlite3_step(s) == SQLITE_ROW) {
|
||||
const char* n = (const char*)sqlite3_column_text(s, 0);
|
||||
if (n) name = u_strdup(n);
|
||||
const void* pk = sqlite3_column_blob(s, 1);
|
||||
if (pk && sqlite3_column_bytes(s, 1) >= 32) memcpy(pubkey, pk, 32);
|
||||
const void* ep = sqlite3_column_blob(s, 2);
|
||||
if (ep && sqlite3_column_bytes(s, 2) >= 32) memcpy(ed_pubkey, ep, 32);
|
||||
client_type = (uint8_t)sqlite3_column_int(s, 3);
|
||||
found = 1;
|
||||
}
|
||||
sqlite3_finalize(s);
|
||||
}
|
||||
}
|
||||
if (!found) { u_free(name); return NULL; }
|
||||
|
||||
struct TOPO_ADDR4* v4_head = NULL; struct TOPO_ADDR4* v4_tail = NULL;
|
||||
struct TOPO_SOCKMETA4* m4_head = NULL; struct TOPO_SOCKMETA4* m4_tail = NULL;
|
||||
struct TOPO_ADDR6* v6_head = NULL; struct TOPO_ADDR6* v6_tail = NULL;
|
||||
struct TOPO_SOCKMETA6* m6_head = NULL; struct TOPO_SOCKMETA6* m6_tail = NULL;
|
||||
struct TOPO_REALITY_SOCK* r_head = NULL; struct TOPO_REALITY_SOCK* r_tail = NULL;
|
||||
{
|
||||
sqlite3_stmt* s = NULL;
|
||||
if (sqlite3_prepare_v2(db, "SELECT name,x25519_pubkey,ed25519_pubkey,client_type FROM nodes WHERE node_id=?",
|
||||
-1, &s, NULL) != SQLITE_OK) goto sql_failed;
|
||||
if (sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id) != SQLITE_OK) goto sql_failed;
|
||||
int rc = sqlite3_step(s);
|
||||
if (rc == SQLITE_ROW) {
|
||||
const char* n = (const char*)sqlite3_column_text(s, 0);
|
||||
if (n) { name = u_strdup(n); if (!name) goto allocation_failed; }
|
||||
const void* pk = sqlite3_column_blob(s, 1);
|
||||
if (pk && sqlite3_column_bytes(s, 1) >= 32) memcpy(pubkey, pk, 32);
|
||||
const void* ep = sqlite3_column_blob(s, 2);
|
||||
if (ep && sqlite3_column_bytes(s, 2) >= 32) memcpy(ed_pubkey, ep, 32);
|
||||
client_type = (uint8_t)sqlite3_column_int(s, 3);
|
||||
found = 1;
|
||||
} else if (rc != SQLITE_DONE) goto sql_failed;
|
||||
sqlite3_finalize(s); s = NULL;
|
||||
}
|
||||
if (!found) { DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "node load: missing node=%016llx", (unsigned long long)node_id); goto failed; }
|
||||
|
||||
{
|
||||
if (sqlite3_prepare_v2(db,
|
||||
"SELECT address,port,addr_type,socket_id,protocol,config_type,nat_mapping,options,address_kind FROM node_addresses"
|
||||
" WHERE node_id=? AND family=4 ORDER BY addr_type",
|
||||
-1, &s, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id);
|
||||
while (sqlite3_step(s) == SQLITE_ROW) {
|
||||
const void* addr = sqlite3_column_blob(s, 0);
|
||||
if (!addr || sqlite3_column_bytes(s, 0) < 4) continue;
|
||||
int port = sqlite3_column_int(s, 1);
|
||||
int sock_id_raw = sqlite3_column_int(s, 3);
|
||||
uint8_t sock_id = (uint8_t)sock_id_raw;
|
||||
uint8_t type = (uint8_t)sqlite3_column_int(s, 8);
|
||||
struct TOPO_ADDR4* a4 = memory_pool_alloc(groups->v4_addr_pool);
|
||||
if (!a4) continue;
|
||||
memset(a4, 0, sizeof(*a4));
|
||||
int proto = sqlite3_column_int(s, 4);
|
||||
memcpy(a4->addr, addr, 4); a4->port = (uint16_t)port;
|
||||
a4->type = type; a4->socket_id = sock_id; a4->protocol = (uint8_t)proto;
|
||||
if (!v4_head) v4_head = v4_tail = a4;
|
||||
else { v4_tail->next = a4; v4_tail = a4; }
|
||||
-1, &s, NULL) != SQLITE_OK) goto sql_failed;
|
||||
if (sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id) != SQLITE_OK) goto sql_failed;
|
||||
int rc;
|
||||
while ((rc = sqlite3_step(s)) == SQLITE_ROW) {
|
||||
const void* addr = sqlite3_column_blob(s, 0);
|
||||
if (!addr || sqlite3_column_bytes(s, 0) < 4) continue;
|
||||
int port = sqlite3_column_int(s, 1);
|
||||
int sock_id_raw = sqlite3_column_int(s, 3);
|
||||
uint8_t sock_id = (uint8_t)sock_id_raw;
|
||||
uint8_t type = (uint8_t)sqlite3_column_int(s, 8);
|
||||
struct TOPO_ADDR4* a4 = memory_pool_alloc(groups->v4_addr_pool);
|
||||
if (!a4) goto allocation_failed;
|
||||
memset(a4, 0, sizeof(*a4));
|
||||
int proto = sqlite3_column_int(s, 4);
|
||||
memcpy(a4->addr, addr, 4); a4->port = (uint16_t)port;
|
||||
a4->type = type; a4->socket_id = sock_id; a4->protocol = (uint8_t)proto;
|
||||
if (!v4_head) v4_head = v4_tail = a4;
|
||||
else { v4_tail->next = a4; v4_tail = a4; }
|
||||
|
||||
struct TOPO_SOCKMETA4* ex = m4_head;
|
||||
while (ex && ex->id != sock_id) ex = ex->next;
|
||||
if (!ex) {
|
||||
struct TOPO_SOCKMETA4* m4 = memory_pool_alloc(groups->v4_sock_meta_pool);
|
||||
if (m4) {
|
||||
m4->id = sock_id;
|
||||
m4->config_type = (uint8_t)sqlite3_column_int(s, 5);
|
||||
/* Без подписанного snapshot профиль из SQL-проекции не считается доказательством. */
|
||||
memset(&m4->address_profile, 0, sizeof(m4->address_profile));
|
||||
m4->next = NULL;
|
||||
if (!m4_head) m4_head = m4_tail = m4;
|
||||
else { m4_tail->next = m4; m4_tail = m4; }
|
||||
}
|
||||
}
|
||||
if (sqlite3_column_type(s, 7) == SQLITE_BLOB
|
||||
&& sqlite3_column_bytes(s, 7) == TOPO_ADDR_REALITY_OPTS_SIZE
|
||||
&& (proto & TOPO_PROTO_REALITY)) {
|
||||
const struct TOPO_ADDR_REALITY_OPTS* o = (const struct TOPO_ADDR_REALITY_OPTS*)sqlite3_column_blob(s, 7);
|
||||
struct TOPO_REALITY_SOCK* r = u_calloc(1, sizeof(*r));
|
||||
if (r) {
|
||||
r->socket_id = sock_id;
|
||||
memcpy(r->short_id, o->short_id, 8);
|
||||
memcpy(r->server_pubkey, o->server_pubkey, 32);
|
||||
memcpy(r->version, o->version, 3);
|
||||
memcpy(r->server_name, o->server_name, TOPO_REALITY_SNAME_MAX);
|
||||
r->server_name[TOPO_REALITY_SNAME_MAX - 1] = 0;
|
||||
if (!r_head) r_head = r_tail = r;
|
||||
else { r_tail->next = r; r_tail = r; }
|
||||
}
|
||||
}
|
||||
struct TOPO_SOCKMETA4* ex = m4_head;
|
||||
while (ex && ex->id != sock_id) ex = ex->next;
|
||||
if (!ex) {
|
||||
struct TOPO_SOCKMETA4* m4 = memory_pool_alloc(groups->v4_sock_meta_pool);
|
||||
if (!m4) goto allocation_failed;
|
||||
m4->id = sock_id;
|
||||
m4->config_type = (uint8_t)sqlite3_column_int(s, 5);
|
||||
/* Без подписанного snapshot профиль из SQL-проекции не считается доказательством. */
|
||||
memset(&m4->address_profile, 0, sizeof(m4->address_profile));
|
||||
m4->next = NULL;
|
||||
if (!m4_head) m4_head = m4_tail = m4;
|
||||
else { m4_tail->next = m4; m4_tail = m4; }
|
||||
}
|
||||
if (sqlite3_column_type(s, 7) == SQLITE_BLOB
|
||||
&& sqlite3_column_bytes(s, 7) == TOPO_ADDR_REALITY_OPTS_SIZE
|
||||
&& (proto & TOPO_PROTO_REALITY)) {
|
||||
const struct TOPO_ADDR_REALITY_OPTS* o = (const struct TOPO_ADDR_REALITY_OPTS*)sqlite3_column_blob(s, 7);
|
||||
struct TOPO_REALITY_SOCK* r = u_calloc(1, sizeof(*r));
|
||||
if (!r) goto allocation_failed;
|
||||
r->socket_id = sock_id;
|
||||
memcpy(r->short_id, o->short_id, 8);
|
||||
memcpy(r->server_pubkey, o->server_pubkey, 32);
|
||||
memcpy(r->version, o->version, 3);
|
||||
memcpy(r->server_name, o->server_name, TOPO_REALITY_SNAME_MAX);
|
||||
r->server_name[TOPO_REALITY_SNAME_MAX - 1] = 0;
|
||||
if (!r_head) r_head = r_tail = r;
|
||||
else { r_tail->next = r; r_tail = r; }
|
||||
}
|
||||
sqlite3_finalize(s);
|
||||
}
|
||||
if (rc != SQLITE_DONE) goto sql_failed;
|
||||
sqlite3_finalize(s); s = NULL;
|
||||
}
|
||||
/* IPv6 addresses */
|
||||
struct TOPO_ADDR6* v6_head = NULL; struct TOPO_ADDR6* v6_tail = NULL;
|
||||
struct TOPO_SOCKMETA6* m6_head = NULL; struct TOPO_SOCKMETA6* m6_tail = NULL;
|
||||
{
|
||||
sqlite3_stmt* s = NULL;
|
||||
if (sqlite3_prepare_v2(db,
|
||||
"SELECT address,port,addr_type,socket_id,protocol,config_type,nat_mapping,options,address_kind FROM node_addresses"
|
||||
" WHERE node_id=? AND family=6 ORDER BY addr_type",
|
||||
-1, &s, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id);
|
||||
while (sqlite3_step(s) == SQLITE_ROW) {
|
||||
const void* addr = sqlite3_column_blob(s, 0);
|
||||
if (!addr || sqlite3_column_bytes(s, 0) < 16) continue;
|
||||
int port = sqlite3_column_int(s, 1);
|
||||
int sock_id_raw = sqlite3_column_int(s, 3);
|
||||
uint8_t sock_id = (uint8_t)sock_id_raw;
|
||||
uint8_t type = (uint8_t)sqlite3_column_int(s, 8);
|
||||
struct TOPO_ADDR6* a6 = memory_pool_alloc(groups->v6_addr_pool);
|
||||
if (!a6) continue;
|
||||
memset(a6, 0, sizeof(*a6));
|
||||
int proto6 = sqlite3_column_int(s, 4);
|
||||
memcpy(a6->addr, addr, 16); a6->port = (uint16_t)port;
|
||||
a6->type = type; a6->socket_id = sock_id; a6->protocol = (uint8_t)proto6;
|
||||
if (!v6_head) v6_head = v6_tail = a6;
|
||||
else { v6_tail->next = a6; v6_tail = a6; }
|
||||
-1, &s, NULL) != SQLITE_OK) goto sql_failed;
|
||||
if (sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id) != SQLITE_OK) goto sql_failed;
|
||||
int rc;
|
||||
while ((rc = sqlite3_step(s)) == SQLITE_ROW) {
|
||||
const void* addr = sqlite3_column_blob(s, 0);
|
||||
if (!addr || sqlite3_column_bytes(s, 0) < 16) continue;
|
||||
int port = sqlite3_column_int(s, 1);
|
||||
int sock_id_raw = sqlite3_column_int(s, 3);
|
||||
uint8_t sock_id = (uint8_t)sock_id_raw;
|
||||
uint8_t type = (uint8_t)sqlite3_column_int(s, 8);
|
||||
struct TOPO_ADDR6* a6 = memory_pool_alloc(groups->v6_addr_pool);
|
||||
if (!a6) goto allocation_failed;
|
||||
memset(a6, 0, sizeof(*a6));
|
||||
int proto6 = sqlite3_column_int(s, 4);
|
||||
memcpy(a6->addr, addr, 16); a6->port = (uint16_t)port;
|
||||
a6->type = type; a6->socket_id = sock_id; a6->protocol = (uint8_t)proto6;
|
||||
if (!v6_head) v6_head = v6_tail = a6;
|
||||
else { v6_tail->next = a6; v6_tail = a6; }
|
||||
|
||||
struct TOPO_SOCKMETA6* ex = m6_head;
|
||||
while (ex && ex->id != sock_id) ex = ex->next;
|
||||
if (!ex) {
|
||||
struct TOPO_SOCKMETA6* m6 = memory_pool_alloc(groups->v6_sock_meta_pool);
|
||||
if (m6) {
|
||||
m6->id = sock_id;
|
||||
m6->config_type = (uint8_t)sqlite3_column_int(s, 5);
|
||||
memset(&m6->address_profile, 0, sizeof(m6->address_profile));
|
||||
m6->next = NULL;
|
||||
if (!m6_head) m6_head = m6_tail = m6;
|
||||
else { m6_tail->next = m6; m6_tail = m6; }
|
||||
}
|
||||
}
|
||||
if (sqlite3_column_type(s, 7) == SQLITE_BLOB
|
||||
&& sqlite3_column_bytes(s, 7) == TOPO_ADDR_REALITY_OPTS_SIZE
|
||||
&& (proto6 & TOPO_PROTO_REALITY)) {
|
||||
const struct TOPO_ADDR_REALITY_OPTS* o = (const struct TOPO_ADDR_REALITY_OPTS*)sqlite3_column_blob(s, 7);
|
||||
struct TOPO_REALITY_SOCK* r = u_calloc(1, sizeof(*r));
|
||||
if (r) {
|
||||
r->socket_id = sock_id;
|
||||
memcpy(r->short_id, o->short_id, 8);
|
||||
memcpy(r->server_pubkey, o->server_pubkey, 32);
|
||||
memcpy(r->version, o->version, 3);
|
||||
memcpy(r->server_name, o->server_name, TOPO_REALITY_SNAME_MAX);
|
||||
r->server_name[TOPO_REALITY_SNAME_MAX - 1] = 0;
|
||||
if (!r_head) r_head = r_tail = r;
|
||||
else { r_tail->next = r; r_tail = r; }
|
||||
}
|
||||
}
|
||||
struct TOPO_SOCKMETA6* ex = m6_head;
|
||||
while (ex && ex->id != sock_id) ex = ex->next;
|
||||
if (!ex) {
|
||||
struct TOPO_SOCKMETA6* m6 = memory_pool_alloc(groups->v6_sock_meta_pool);
|
||||
if (!m6) goto allocation_failed;
|
||||
m6->id = sock_id;
|
||||
m6->config_type = (uint8_t)sqlite3_column_int(s, 5);
|
||||
memset(&m6->address_profile, 0, sizeof(m6->address_profile));
|
||||
m6->next = NULL;
|
||||
if (!m6_head) m6_head = m6_tail = m6;
|
||||
else { m6_tail->next = m6; m6_tail = m6; }
|
||||
}
|
||||
if (sqlite3_column_type(s, 7) == SQLITE_BLOB
|
||||
&& sqlite3_column_bytes(s, 7) == TOPO_ADDR_REALITY_OPTS_SIZE
|
||||
&& (proto6 & TOPO_PROTO_REALITY)) {
|
||||
const struct TOPO_ADDR_REALITY_OPTS* o = (const struct TOPO_ADDR_REALITY_OPTS*)sqlite3_column_blob(s, 7);
|
||||
struct TOPO_REALITY_SOCK* r = u_calloc(1, sizeof(*r));
|
||||
if (!r) goto allocation_failed;
|
||||
r->socket_id = sock_id;
|
||||
memcpy(r->short_id, o->short_id, 8);
|
||||
memcpy(r->server_pubkey, o->server_pubkey, 32);
|
||||
memcpy(r->version, o->version, 3);
|
||||
memcpy(r->server_name, o->server_name, TOPO_REALITY_SNAME_MAX);
|
||||
r->server_name[TOPO_REALITY_SNAME_MAX - 1] = 0;
|
||||
if (!r_head) r_head = r_tail = r;
|
||||
else { r_tail->next = r; r_tail = r; }
|
||||
}
|
||||
sqlite3_finalize(s);
|
||||
}
|
||||
if (rc != SQLITE_DONE) goto sql_failed;
|
||||
sqlite3_finalize(s); s = NULL;
|
||||
}
|
||||
if (!v4_head && !v6_head) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "node load: no bootstrap addresses node=%016llx", (unsigned long long)node_id); goto failed;
|
||||
}
|
||||
if (!v4_head && !v6_head) { u_free(name); DEBUG_WARN(DEBUG_CATEGORY_BGP, "node_load: node 0x%016llx has no addresses (may be stub/seed — will retry when addresses arrive)", (unsigned long long)node_id); return NULL; }
|
||||
|
||||
struct TOPO_NODE* ni = u_calloc(1, sizeof(struct TOPO_NODE));
|
||||
if (!ni) { u_free(name); return NULL; }
|
||||
ni->node_id = node_id; ni->ver = 1; ni->group_ref_count = 0;
|
||||
ni->client_type = client_type;
|
||||
memcpy(ni->public_key, pubkey, 32); memcpy(ni->ed25519_public_key, ed_pubkey, 32);
|
||||
@@ -1277,6 +1333,18 @@ struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* gr
|
||||
topo_list_count((struct _topo_head*)v4_head),
|
||||
topo_list_count((struct _topo_head*)v6_head));
|
||||
return ni;
|
||||
sql_failed:
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node load SQL failed node=%016llx rc=%d: %s",
|
||||
(unsigned long long)node_id, sqlite3_extended_errcode(db), sqlite3_errmsg(db));
|
||||
goto failed;
|
||||
allocation_failed:
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node load: bootstrap allocation failed node=%016llx", (unsigned long long)node_id);
|
||||
failed:
|
||||
sqlite3_finalize(s);
|
||||
ni->node_name = name; ni->v4_addrs = v4_head; ni->v4_sock_meta = m4_head;
|
||||
ni->v6_addrs = v6_head; ni->v6_sock_meta = m6_head; ni->reality_socks = r_head;
|
||||
topo_node_destroy(groups, ni);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int topo_node_sqlite_set_connected(sqlite3* db, const char* channel_id, uint64_t node_id, int connected) {
|
||||
@@ -1346,98 +1414,90 @@ int topo_node_sqlite_get_connect_peers(sqlite3* db, const char* channel_id,
|
||||
sqlite3_finalize(stmt); *out = peers; *out_count = count; return 0;
|
||||
}
|
||||
|
||||
int topo_node_sqlite_get_connected_peers(sqlite3* db, const char* channel_id,
|
||||
uint64_t** out_ids, int* out_count) {
|
||||
if (!db || !channel_id || !out_ids || !out_count) return -1;
|
||||
/* Один проход SQL: частичный массив никогда не публикуется при ошибке чтения/памяти. */
|
||||
static int node_ids_query(sqlite3* db, const char* sql, int exclude_self, uint64_t self,
|
||||
uint64_t** out_ids, int* out_count) {
|
||||
*out_ids = NULL; *out_count = 0;
|
||||
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
|
||||
char sql[192]; snprintf(sql, sizeof(sql), "SELECT node_id FROM \"%s\" WHERE connected=1 AND deleted=0", peers_tbl);
|
||||
sqlite3_stmt* st = NULL;
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) return -1;
|
||||
int cnt = 0;
|
||||
while (sqlite3_step(st) == SQLITE_ROW) cnt++;
|
||||
if (cnt == 0) { sqlite3_finalize(st); return 0; }
|
||||
uint64_t* ids = u_malloc((size_t)cnt * sizeof(uint64_t));
|
||||
if (!ids) { sqlite3_finalize(st); return -1; }
|
||||
sqlite3_reset(st); int i = 0;
|
||||
while (sqlite3_step(st) == SQLITE_ROW) ids[i++] = (uint64_t)sqlite3_column_int64(st, 0);
|
||||
uint64_t* ids = NULL;
|
||||
size_t count = 0, capacity = 0;
|
||||
int rc = sqlite3_prepare_v2(db, sql, -1, &st, NULL);
|
||||
if (rc == SQLITE_OK && exclude_self) rc = sqlite3_bind_int64(st, 1, (sqlite3_int64)self);
|
||||
if (rc != SQLITE_OK) goto fail;
|
||||
while ((rc = sqlite3_step(st)) == SQLITE_ROW) {
|
||||
if (count == capacity) {
|
||||
size_t next = capacity ? capacity * 2 : 16;
|
||||
if (next > UINT32_MAX / sizeof(*ids)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "peer ID list too large: count=%zu", count); goto fail;
|
||||
}
|
||||
uint64_t* resized = u_realloc(ids, next * sizeof(*ids));
|
||||
if (!resized) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "peer ID list allocation failed: count=%zu", count); goto fail; }
|
||||
ids = resized; capacity = next;
|
||||
}
|
||||
ids[count++] = (uint64_t)sqlite3_column_int64(st, 0);
|
||||
}
|
||||
if (rc != SQLITE_DONE) goto fail;
|
||||
sqlite3_finalize(st);
|
||||
*out_ids = ids; *out_count = cnt;
|
||||
*out_ids = ids; *out_count = (int)count;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "peer ID list read: count=%zu exclude_self=%d", count, exclude_self);
|
||||
return 0;
|
||||
fail:
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "peer ID query failed: rc=%d rows=%zu sql=%s: %s", rc, count, sql, sqlite3_errmsg(db));
|
||||
sqlite3_finalize(st); u_free(ids); return -1;
|
||||
}
|
||||
|
||||
int topo_node_sqlite_get_connected_peers(sqlite3* db, const char* channel_id, uint64_t** out_ids, int* out_count) {
|
||||
if (out_ids) *out_ids = NULL;
|
||||
if (out_count) *out_count = 0;
|
||||
if (!db || !channel_id || !out_ids || !out_count) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "connected peers: invalid arguments"); return -1;
|
||||
}
|
||||
char table[80], sql[192]; peers_table_name(channel_id, table, sizeof(table));
|
||||
snprintf(sql, sizeof(sql), "SELECT node_id FROM \"%s\" WHERE connected=1 AND deleted=0", table);
|
||||
return node_ids_query(db, sql, 0, 0, out_ids, out_count);
|
||||
}
|
||||
|
||||
int topo_node_sqlite_get_public_peers(sqlite3* db, const char* channel_id,
|
||||
uint64_t** out_ids, int* out_count, uint64_t self_node_id) {
|
||||
if (!db || !channel_id || !out_ids || !out_count) return -1;
|
||||
*out_ids = NULL; *out_count = 0;
|
||||
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
|
||||
char sql[384]; snprintf(sql, sizeof(sql),
|
||||
uint64_t** out_ids, int* out_count, uint64_t self_node_id) {
|
||||
if (out_ids) *out_ids = NULL;
|
||||
if (out_count) *out_count = 0;
|
||||
if (!db || !channel_id || !out_ids || !out_count) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "public peers: invalid arguments"); return -1;
|
||||
}
|
||||
char table[80], sql[512]; peers_table_name(channel_id, table, sizeof(table));
|
||||
snprintf(sql, sizeof(sql),
|
||||
"SELECT DISTINCT na.node_id FROM node_addresses na"
|
||||
" WHERE na.node_id!=?1 AND na.family IN (4,6) AND na.addr_type IN (1,2)"
|
||||
" AND EXISTS (SELECT 1 FROM nodes n WHERE n.node_id=na.node_id)"
|
||||
" AND NOT EXISTS (SELECT 1 FROM \"%s\" p WHERE p.node_id=na.node_id AND p.deleted=1)",
|
||||
peers_tbl);
|
||||
sqlite3_stmt* st = NULL;
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) return -1;
|
||||
sqlite3_bind_int64(st, 1, (sqlite3_int64)self_node_id);
|
||||
int cnt = 0;
|
||||
while (sqlite3_step(st) == SQLITE_ROW) cnt++;
|
||||
if (cnt == 0) { sqlite3_finalize(st); return 0; }
|
||||
uint64_t* ids = u_malloc((size_t)cnt * sizeof(uint64_t));
|
||||
if (!ids) { sqlite3_finalize(st); return -1; }
|
||||
sqlite3_reset(st); int i = 0;
|
||||
while (sqlite3_step(st) == SQLITE_ROW) ids[i++] = (uint64_t)sqlite3_column_int64(st, 0);
|
||||
sqlite3_finalize(st);
|
||||
*out_ids = ids; *out_count = cnt;
|
||||
return 0;
|
||||
" AND NOT EXISTS (SELECT 1 FROM \"%s\" p WHERE p.node_id=na.node_id AND p.deleted=1)", table);
|
||||
return node_ids_query(db, sql, 1, self_node_id, out_ids, out_count);
|
||||
}
|
||||
|
||||
int topo_node_sqlite_get_supernode_peers(sqlite3* db, const char* channel_id,
|
||||
uint64_t** out_ids, int* out_count) {
|
||||
if (!db || !channel_id || !out_ids || !out_count) return -1;
|
||||
*out_ids = NULL; *out_count = 0;
|
||||
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
|
||||
char sql[256]; snprintf(sql, sizeof(sql),
|
||||
"SELECT p.node_id FROM \"%s\" p WHERE p.node_type=4 AND p.deleted=0",
|
||||
peers_tbl);
|
||||
sqlite3_stmt* st = NULL;
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) return -1;
|
||||
int cnt = 0;
|
||||
while (sqlite3_step(st) == SQLITE_ROW) cnt++;
|
||||
if (cnt == 0) { sqlite3_finalize(st); return 0; }
|
||||
uint64_t* ids = u_malloc((size_t)cnt * sizeof(uint64_t));
|
||||
if (!ids) { sqlite3_finalize(st); return -1; }
|
||||
sqlite3_reset(st); int i = 0;
|
||||
while (sqlite3_step(st) == SQLITE_ROW) ids[i++] = (uint64_t)sqlite3_column_int64(st, 0);
|
||||
sqlite3_finalize(st);
|
||||
*out_ids = ids; *out_count = cnt;
|
||||
return 0;
|
||||
int topo_node_sqlite_get_supernode_peers(sqlite3* db, const char* channel_id, uint64_t** out_ids, int* out_count) {
|
||||
if (out_ids) *out_ids = NULL;
|
||||
if (out_count) *out_count = 0;
|
||||
if (!db || !channel_id || !out_ids || !out_count) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "supernode peers: invalid arguments"); return -1;
|
||||
}
|
||||
char table[80], sql[192]; peers_table_name(channel_id, table, sizeof(table));
|
||||
snprintf(sql, sizeof(sql), "SELECT node_id FROM \"%s\" WHERE node_type=4 AND deleted=0", table);
|
||||
return node_ids_query(db, sql, 0, 0, out_ids, out_count);
|
||||
}
|
||||
|
||||
int topo_node_sqlite_get_local_peers(sqlite3* db, const char* channel_id,
|
||||
uint64_t** out_ids, int* out_count, uint64_t self_node_id) {
|
||||
if (!db || !channel_id || !out_ids || !out_count) return -1;
|
||||
*out_ids = NULL; *out_count = 0;
|
||||
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
|
||||
char sql[384]; snprintf(sql, sizeof(sql),
|
||||
uint64_t** out_ids, int* out_count, uint64_t self_node_id) {
|
||||
if (out_ids) *out_ids = NULL;
|
||||
if (out_count) *out_count = 0;
|
||||
if (!db || !channel_id || !out_ids || !out_count) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local peers: invalid arguments"); return -1;
|
||||
}
|
||||
char table[80], sql[512]; peers_table_name(channel_id, table, sizeof(table));
|
||||
snprintf(sql, sizeof(sql),
|
||||
"SELECT DISTINCT na.node_id FROM node_addresses na"
|
||||
" WHERE na.node_id!=?1 AND na.family IN (4,6) AND na.addr_type IN (0,3)"
|
||||
" AND EXISTS (SELECT 1 FROM nodes n WHERE n.node_id=na.node_id)"
|
||||
" AND NOT EXISTS (SELECT 1 FROM \"%s\" p WHERE p.node_id=na.node_id AND p.deleted=1)",
|
||||
peers_tbl);
|
||||
sqlite3_stmt* st = NULL;
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) return -1;
|
||||
sqlite3_bind_int64(st, 1, (sqlite3_int64)self_node_id);
|
||||
int cnt = 0;
|
||||
while (sqlite3_step(st) == SQLITE_ROW) cnt++;
|
||||
if (cnt == 0) { sqlite3_finalize(st); return 0; }
|
||||
uint64_t* ids = u_malloc((size_t)cnt * sizeof(uint64_t));
|
||||
if (!ids) { sqlite3_finalize(st); return -1; }
|
||||
sqlite3_reset(st); int i = 0;
|
||||
while (sqlite3_step(st) == SQLITE_ROW) ids[i++] = (uint64_t)sqlite3_column_int64(st, 0);
|
||||
sqlite3_finalize(st);
|
||||
*out_ids = ids; *out_count = cnt;
|
||||
return 0;
|
||||
" AND NOT EXISTS (SELECT 1 FROM \"%s\" p WHERE p.node_id=na.node_id AND p.deleted=1)", table);
|
||||
return node_ids_query(db, sql, 1, self_node_id, out_ids, out_count);
|
||||
}
|
||||
|
||||
int topo_node_sqlite_get_node_type(sqlite3* db, const char* channel_id, uint64_t node_id) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* topo_node_sqlite — хранение подписанных снимков узлов, адресов, каналов и блоков мемберов в общей SQLite.
|
||||
* Модель/проверка подписей/registry — topo_node и member_sync; этот модуль реализует SQL-операции в uasync-потоке.
|
||||
* snapshot_put проверяет подпись и сохраняет только более свежий timestamp; node_load предпочитает snapshot.
|
||||
* Только snapshot_put/load проверяют подпись; остальные записи требуют проверенных вызывающим данных.
|
||||
* snapshot_put сохраняет более свежий timestamp; node_load предпочитает snapshot.
|
||||
* db/groups заимствованы. Возвращённый TOPO_NODE без registry ref: передать registry либо уничтожить через topo_node_destroy.
|
||||
* Массивы out_ids/out_ch_ids/out_peers принадлежат вызывающему и освобождаются u_free. */
|
||||
#ifndef TOPO_NODE_SQLITE_H
|
||||
@@ -19,41 +20,55 @@
|
||||
/* flags byte in wire format */
|
||||
#define PEERS_FLAG_HAS_JOIN 0x01
|
||||
|
||||
/* Создать/обновить nodes, node_addresses, node_snapshots, channels; 0/-1. db открывает и закрывает вызывающий. */
|
||||
int topo_node_sqlite_init(sqlite3* db);
|
||||
/* Проверить и сохранить полный подписанный NODEINFO без групповых полей; старый/равный timestamp даёт 0 без замены. */
|
||||
int topo_node_sqlite_snapshot_put(sqlite3* db, struct TOPO_NODE* ni);
|
||||
/* Максимальный сохранённый timestamp (Unix microseconds), в том числе после инвалидирования record; 0 — нет/ошибка. */
|
||||
uint64_t topo_node_sqlite_snapshot_timestamp(sqlite3* db, uint64_t node_id);
|
||||
/* Незарегистрированный проверенный снимок, включая снимок без адресов; NULL — нет/невалиден/ошибка.
|
||||
* groups и groups->instance->topo_groups должны обозначать один контейнер с пулами. */
|
||||
struct TOPO_NODE* topo_node_sqlite_snapshot_load(sqlite3* db, struct TOPO_GROUPS* groups, uint64_t node_id);
|
||||
|
||||
/* Персистентная запись адресов узла в node_addresses (источник истины — BGP/topo_group).
|
||||
* DELETE + INSERT из ni->v4_addrs/v6_addrs; mapping/inbound/source из подписанного SOCKMETA. */
|
||||
* DELETE + INSERT из ni->v4_addrs/v6_addrs под savepoint, включая пустой набор; 0/-1, отказ откатывает замену.
|
||||
* mapping/inbound/source берутся из SOCKMETA; проверку ni выполняет вызывающий. RTT при замене не сохраняется. */
|
||||
int topo_node_sqlite_addrs_put(sqlite3* db, uint64_t node_id, struct TOPO_NODE* ni);
|
||||
|
||||
/* Есть ли у узла запись мембера хотя бы в одной peers_<ch> (реальный мембер с ключами). */
|
||||
/* Есть ли строка хотя бы в одной peers_*: включая source=1 и deleted=1; 1/0, при ошибке также 0. */
|
||||
int topo_node_sqlite_member_exists(sqlite3* db, uint64_t node_id);
|
||||
|
||||
/* Список channel_id (uint64) всех peers_<ch>, где узел является членом. 0=ок. */
|
||||
/* Числовые ненулевые channel_id таблиц peers_*, содержащих строку узла; включает placeholders/deleted.
|
||||
* 0/-1; пустой результат NULL/0, при ошибке частичный массив не выдаётся. */
|
||||
int topo_node_sqlite_get_member_channels(sqlite3* db, uint64_t node_id,
|
||||
uint64_t** out_ch_ids, int* out_count);
|
||||
|
||||
/* Создать peers_<id> и сохранить канал; 0/-1. Ключи — по 32, signature — 64 байта, privkey необязательны.
|
||||
* При обновлении публичные ключи обязаны совпасть; имя/владелец/подпись заменяются, NULL privkey сохраняет прежний.
|
||||
* Подпись/пары ключей не проверяет. Табличный суффикс: первые 63 символа, вне [A-Za-z0-9_] заменены на '_'. */
|
||||
int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id,
|
||||
const char* name, uint64_t owner_node_id,
|
||||
const uint8_t* x25519_pub, const uint8_t* x25519_priv,
|
||||
const uint8_t* ed25519_pub, const uint8_t* ed25519_priv,
|
||||
const uint8_t* signature);
|
||||
|
||||
/* Подготовить локальные поля участника при создании и загрузке канала. */
|
||||
/* Добавить radio_muted=0 в существующую peers_<id>, если поля ещё нет; 0/-1, идемпотентно. */
|
||||
int topo_node_sqlite_member_local_init(sqlite3* db, const char* channel_id);
|
||||
|
||||
/* Полное локальное удаление канала: DROP peers_<id>/msg_<id>, DELETE channels,
|
||||
* Удаляются feedback_<channel_id> и merkle_tree_hash всех datasets этого канала. 0=ок. */
|
||||
* Удаляются feedback_<id> и merkle_tree_hash всех datasets канала (если таблица есть).
|
||||
* 0/-1; savepoint откатывает в том числе DROP TABLE, внешняя транзакция сохраняется. */
|
||||
int topo_node_sqlite_channel_delete(sqlite3* db, const char* channel_id);
|
||||
|
||||
/* Упрощённый плейсхолдер мембера от topo_group (source=1, не участвует в merkle-хеше). */
|
||||
/* Вставить source=1 с нулевой join_sig; существующую строку не менять. 0/-1; ключи по 32 байта.
|
||||
* Плейсхолдер не участвует в Merkle-хеше; канал/peers_<id> должны уже существовать. */
|
||||
int topo_node_sqlite_member_placeholder_put(sqlite3* db, const char* channel_id, uint64_t node_id,
|
||||
const uint8_t* x25519_pubkey, const uint8_t* ed25519_pubkey);
|
||||
|
||||
/* Обновляет только блок мембера (keys/join/update_sig/update_ts/userinfo), не трогая блок владельца.
|
||||
* signed_by/signature — дерево приглашений: кто подписал pubkey узла (0 = корень, ключом группы). */
|
||||
* signed_by/signature — дерево приглашений: кто подписал pubkey узла (0 = корень, ключом группы).
|
||||
* 0/-1; подписи по 64, ключи по 32 байта. NULL join_sig даёт нулевую подпись, userinfo=NULL даёт ''.
|
||||
* Сбрасывает source=0; свежесть/подписи проверяет вызывающий, owner/local-поля сохраняются. */
|
||||
int topo_node_sqlite_member_block_put(sqlite3* db, const char* channel_id, uint64_t node_id,
|
||||
const uint8_t* join_sig, uint64_t join_ts,
|
||||
const uint8_t* update_sig, uint64_t update_ts,
|
||||
@@ -64,25 +79,33 @@ int topo_node_sqlite_member_block_put(sqlite3* db, const char* channel_id, uint6
|
||||
int topo_node_sqlite_member_get_sign(sqlite3* db, const char* channel_id, uint64_t node_id,
|
||||
uint64_t* signed_by_out, uint8_t signature_out[64]);
|
||||
|
||||
/* Есть ли узел в таблице мемберов канала peers_<ch_id> (source 0 или 1). 1=да, 0=нет. */
|
||||
/* Есть ли строка в peers_<id>, включая placeholders/deleted. 1/0; ошибка также даёт 0. */
|
||||
int topo_node_sqlite_member_in_channel(sqlite3* db, const char* channel_id, uint64_t node_id);
|
||||
|
||||
/* Обновляет только блок владельца (adm_tags/adm_tags_sig/storage). */
|
||||
/* Обновить owner-блок и deleted из adm_tags; 0/-1, отсутствующая строка даёт 0 без вставки.
|
||||
* adm_tags/adm_tags_sig могут быть NULL; ненулевая подпись — 64 байта, проверяется вызывающим. */
|
||||
int topo_node_sqlite_member_owner_put(sqlite3* db, const char* channel_id, uint64_t node_id,
|
||||
const char* adm_tags, const uint8_t* adm_tags_sig, int storage);
|
||||
|
||||
/* Прочитать ровно 64 байта join_sig и join_ts; 0/-1, выходы обязательны и при неуспехе не меняются. */
|
||||
int topo_node_sqlite_member_get_join(sqlite3* db, const char* channel_id, uint64_t node_id,
|
||||
uint8_t* join_sig_out, uint64_t* join_ts_out);
|
||||
|
||||
/* Физически удалить строку участника; 0/-1. Отсутствующая строка даёт 0; tombstone/Merkle не создаёт. */
|
||||
int topo_node_sqlite_member_del(sqlite3* db, const char* channel_id, uint64_t node_id);
|
||||
|
||||
/* Записать проверенные вызывающим ключи/имя в nodes; 0/-1. Пустое имя сохраняет прежнее при UPDATE.
|
||||
* Ключи заменяются независимо от update_ts; update_ts хранит максимум, created_at сохраняется, если не NULL.
|
||||
* Новая строка online=1, при UPDATE online сохраняется. Подпись и свежесть здесь не проверяются. */
|
||||
int topo_node_sqlite_node_update_verified(sqlite3* db, uint64_t node_id,
|
||||
const char* name, const uint8_t* x25519, const uint8_t* ed25519, uint64_t update_ts, time_t created_at);
|
||||
|
||||
/* Метаданные канала; 0/-1. Все выходы необязательны; name_sz включает NUL, ключи по 32, подпись 64 байта. */
|
||||
int topo_node_sqlite_channel_get(sqlite3* db, const char* channel_id,
|
||||
char* name_out, int name_sz, uint64_t* owner_node_id,
|
||||
uint8_t* x25519_pub, uint8_t* ed25519_pub, uint8_t* signature);
|
||||
|
||||
/* Прочитать первые 32 байта Ed25519 privkey; 0/-1, отсутствующий/короткий ключ даёт -1. */
|
||||
int topo_node_sqlite_channel_get_priv(sqlite3* db, const char* channel_id,
|
||||
uint8_t* ed25519_priv_out);
|
||||
|
||||
@@ -90,32 +113,40 @@ int topo_node_sqlite_channel_get_priv(sqlite3* db, const char* channel_id,
|
||||
int topo_node_sqlite_channel_get_privs(sqlite3* db, const char* channel_id,
|
||||
uint8_t* x25519_priv_out, uint8_t* ed25519_priv_out);
|
||||
|
||||
/* Записать оба канальных privkey (импорт прав админа). */
|
||||
/* Записать оба канальных privkey по 32 байта (импорт прав админа); 0/-1, без проверки пары/подписи.
|
||||
* Отсутствующая строка даёт 0 без вставки; наличие канала проверяет вызывающий. */
|
||||
int topo_node_sqlite_channel_set_privs(sqlite3* db, const char* channel_id,
|
||||
const uint8_t* x25519_priv, const uint8_t* ed25519_priv);
|
||||
|
||||
/* Установить online=0/1 существующей строки; 0/-1. Отсутствующая строка даёт 0 без вставки. */
|
||||
int topo_node_sqlite_node_set_online(sqlite3* db, uint64_t node_id, int online);
|
||||
/* online из nodes; 0, если записи нет или запрос не выполнен. */
|
||||
int topo_node_sqlite_node_get_online(sqlite3* db, uint64_t node_id);
|
||||
/* Скопировать ровно 32 байта Ed25519 из nodes: 0 — найден, -1 — отсутствует/другая длина/ошибка SQLite.
|
||||
* Значение байтов не определяет наличие ключа; криптографическую пригодность проверяет операция verify. */
|
||||
int topo_node_sqlite_get_ed25519_pubkey(sqlite3* db, uint64_t node_id, uint8_t pubkey_out[32]);
|
||||
|
||||
/** Пересчитывает node_type во всех peers_* таблицах на основе addr_type из node_addresses */
|
||||
/* Атомарно пересчитать node_type/storage/deleted во всех peers_* по адресам и adm_tags; 0/-1.
|
||||
* node_type: 0 нет адресов, 1 прямой/локальный/strict, 2 NAT/contact, 4 supernode=yes.
|
||||
* storage=yes действует только при наличии ADDR_TYPE_DIRECT, deleted берётся из adm_tags.deleted=yes. */
|
||||
int topo_node_sqlite_nodeinfo_updated(sqlite3* db, uint64_t node_id);
|
||||
|
||||
/** Обновляет RTT узла: node_addresses.rtt и все peers_*.node_RTT */
|
||||
/* Обновить node_addresses.rtt и все peers_*.node_RTT, единицы 0.1 ms; ошибки логируются, обновление неатомарно. */
|
||||
void topo_node_sqlite_update_rtt(sqlite3* db, uint64_t node_id, uint16_t rtt);
|
||||
|
||||
/**
|
||||
* @brief Загружает TOPO_NODE из SQLite (ref_count=0).
|
||||
* @param groups нужен для выделения адресов из memory_pool
|
||||
* @return TOPO_NODE* или NULL если узел не найден/нет адресов
|
||||
* @return TOPO_NODE* или NULL, если нет пригодной записи/адресов bootstrap либо при ошибке
|
||||
*
|
||||
* Сначала загружает и проверяет подписанный snapshot; при его отсутствии собирает сведения nodes/node_addresses.
|
||||
* Вызывающий передаёт владение через topo_node_registry_acquire() либо вызывает topo_node_destroy().
|
||||
* Snapshot может быть без адресов. Bootstrap требует адресов и получает timestamp=0, профиль UNKNOWN.
|
||||
* При отказе SQL/памяти частичная запись освобождается. groups->instance->topo_groups должен совпадать с groups.
|
||||
* Вызывающий передаёт владение через topo_node_registry_store() либо вызывает topo_node_destroy().
|
||||
*/
|
||||
struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* groups, uint64_t node_id);
|
||||
|
||||
/* Записать connected=0/1 существующего участника; 0/-1, без вставки отсутствующей строки. */
|
||||
int topo_node_sqlite_set_connected(sqlite3* db, const char* channel_id, uint64_t node_id, int connected);
|
||||
|
||||
/* Кандидат CHAT-подключения: роль канальная, тип устройства из подписанного snapshot.
|
||||
@@ -129,6 +160,10 @@ struct topo_connect_peer {
|
||||
* 0/-1; массив принадлежит вызывающему и освобождается u_free(). */
|
||||
int topo_node_sqlite_get_connect_peers(sqlite3* db, const char* channel_id,
|
||||
struct topo_connect_peer** out, size_t* out_count);
|
||||
/* Нижеследующие выборки: 0/-1, пусто/ошибка — NULL/0, частичный массив не выдаётся; освобождение u_free.
|
||||
* connected/supernode ограничены участниками канала, self не исключают, deleted=1 исключают.
|
||||
* public/local — глобальные адреса с записью nodes, исключены self и deleted=1 в указанном канале;
|
||||
* наличие членства в канале не требуется. public: addr_type 1/2, local: 0/3; фильтра port!=0 нет. */
|
||||
int topo_node_sqlite_get_connected_peers(sqlite3* db, const char* channel_id, uint64_t** out_ids, int* out_count);
|
||||
int topo_node_sqlite_get_public_peers(sqlite3* db, const char* channel_id, uint64_t** out_ids, int* out_count, uint64_t self_node_id);
|
||||
int topo_node_sqlite_get_local_peers(sqlite3* db, const char* channel_id, uint64_t** out_ids, int* out_count, uint64_t self_node_id);
|
||||
|
||||
@@ -1,38 +1,124 @@
|
||||
# topo_node_sqlite — SQLite-персистентность топологии
|
||||
# topo_node_sqlite — хранение топологии в SQLite
|
||||
|
||||
## 1. Назначение
|
||||
Обеспечивает сохранение и восстановление топологии P2P-сети (ноды, адреса, каналы, участники каналов) в SQLite-базе. База используется как основной источник данных для chatgui и восстановления состояния между запусками utun.
|
||||
## Назначение и владение
|
||||
|
||||
В отличие от LMDB-слоя (`topo_node_lmdb`), который хранит данные в бинарном формате BGP-сообщений, SQLite-слой работает с нормализованными таблицами и используется для клиентских целей — чтение данных chatgui, получение списка участников канала, проверка online-статуса.
|
||||
Модуль работает с общей SQLite ядра: подписанные снимки узлов, производные адреса/состояния,
|
||||
описания каналов и строки участников. Базу открывает и закрывает владелец ядра; все операции
|
||||
этого модуля выполняются в его uasync-потоке. Отдельного LMDB-хранилища топологии здесь нет.
|
||||
|
||||
## 2. Как пользоваться
|
||||
1. Получить готовый `sqlite3* db` (открывается извне, обычно в `chat_sync.c`)
|
||||
2. Вызвать `topo_node_sqlite_init(db)` для создания таблиц
|
||||
3. Сохранять/обновлять ноды через `topo_node_sqlite_node_put()`
|
||||
4. Сохранять каналы через `topo_node_sqlite_channel_put()` (автоматически создаёт таблицу `peers_<channel_id>`)
|
||||
5. Добавлять участников в канал через `topo_node_sqlite_member_put()`
|
||||
6. Читать участников из `peers_<channel_id>`; постраничная сетевая сериализация принадлежит `member_sync`.
|
||||
Проверку подписи выполняют только topo_node_sqlite_snapshot_put/load.
|
||||
Канальные метаданные, ключи участников и их версии проверяют вызывающие chat_sync/member_sync.
|
||||
SQL-функции записи блоков сами не проверяют подписи и не отбрасывают старые версии.
|
||||
|
||||
**Нюансы:**
|
||||
- Имена таблиц `peers_<channel_id>` санитизируются: все символы кроме `[a-zA-Z0-9_]` заменяются на `_`
|
||||
- Сетевой формат участников описан в `src/chat/member_sync_doc.md`; SQLite-слой не дублирует сериализатор.
|
||||
- Ключи в BLOB-полях всегда 32 байта (x25519/ed25519), подписи — 64 байта
|
||||
- Функции `online` и `get_ed25519_pubkey` — простые SELECT-запросы без JOIN
|
||||
Возвращённый TOPO_NODE не зарегистрирован и имеет нулевой refcount.
|
||||
Передайте владение через topo_node_registry_store либо вызовите topo_node_destroy(groups, node).
|
||||
Массивы идентификаторов и topo_connect_peer освобождаются через u_free.
|
||||
db, groups и входные строки/объекты остаются у вызывающего.
|
||||
|
||||
## 3. API
|
||||
## Таблицы и версии
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `topo_node_sqlite_init(db)` | Создаёт таблицы `nodes`, `node_addresses`, `channels` (если не существуют) |
|
||||
| `topo_node_sqlite_node_put(db, nq)` | Сохраняет ноду и её адреса (IPv4/IPv6) в транзакции; старые адреса удаляются |
|
||||
| `topo_node_sqlite_channel_put(...)` | Сохраняет канал в `channels` + создаёт таблицу `peers_<ch_id>` |
|
||||
| `topo_node_sqlite_member_put(...)` | Добавляет/обновляет участника в `peers_<ch_id>` (INSERT OR REPLACE) |
|
||||
| `topo_node_sqlite_member_get_join(db, ch_id, node_id, ...)` | Читает join_sig и join_ts участника канала |
|
||||
| `topo_node_sqlite_member_del(db, ch_id, node_id)` | Удаляет участника из `peers_<ch_id>` |
|
||||
| `topo_node_sqlite_node_update_verified(...)` | Обновляет ключи и имя ноды по условию `update_ts < новое_значение` (защита от stale-данных) |
|
||||
| `topo_node_sqlite_channel_get(...)` | Читает метаданные канала (имя, владелец, ключи, подпись) |
|
||||
| `topo_node_sqlite_node_set_online(db, id, online)` | Устанавливает флаг `online` для ноды |
|
||||
| `topo_node_sqlite_node_get_online(db, id)` | Возвращает 1 если нода online |
|
||||
| `topo_node_sqlite_get_ed25519_pubkey(db, id, out)` | Извлекает ed25519-публичный ключ ноды (32 байта) |
|
||||
| Таблица | Назначение |
|
||||
| --- | --- |
|
||||
| node_snapshots | node_id, максимальный timestamp, полный подписанный NODEINFO в BLOB record |
|
||||
| nodes | Проекция имени и ключей, online/last_seen, update_ts, created_at; не замена подписанному снимку |
|
||||
| node_addresses | IPv4/IPv6 endpoints, protocol, socket_id, address_kind, NAT-профиль, классификация, RTT и REALITY options |
|
||||
| channels | Имя, владелец, публичные/необязательные приватные ключи, подпись канала и локальные поля |
|
||||
| peers_<id> | Блок участника, блок владельца и локальные поля конкретного канала |
|
||||
|
||||
**Структуры:** внешние — `sqlite3*`, `struct TOPO_GROUP_NODE`, `struct TOPO_NODE`; собственных структур нет.
|
||||
topo_node_sqlite_init создаёт общие таблицы/индексы, обрабатывает прежние форматы проекции адресов
|
||||
и восстанавливает deleted из adm_tags. peers_<id> создаётся при channel_put;
|
||||
member_local_init идемпотентно добавляет radio_muted.
|
||||
|
||||
В снимке timestamp — Unix microseconds и версия всей подписанной записи.
|
||||
snapshot_put проверяет подпись и заменяет BLOB только при большем timestamp.
|
||||
Старый или равный timestamp даёт успех без замены. При смене формата BLOB может инвалидироваться
|
||||
без сброса сохранённого timestamp, чтобы новая локальная публикация оставалась новее.
|
||||
|
||||
node_update_verified записывает уже проверенные ключи и непустое имя независимо от update_ts.
|
||||
В nodes.update_ts сохраняется максимум прежнего и входного значения; это не фильтр замены ключей.
|
||||
created_at сохраняется, если уже не NULL. Новая строка получает online=1,
|
||||
обновление существующей строки online не меняет.
|
||||
|
||||
## Снимок и bootstrap
|
||||
|
||||
topo_node_sqlite_node_load сначала загружает и проверяет снимок. Валидный снимок может содержать
|
||||
пустой список адресов: это полноценное опубликованное состояние, а не повод вернуть старые адреса.
|
||||
|
||||
Если пригодного снимка нет, функция собирает bootstrap из nodes и node_addresses.
|
||||
Для этого требуется хотя бы один адрес. Ключи/имя копируются из проекции, timestamp остаётся 0,
|
||||
профили сокетов сбрасываются в UNKNOWN; сохранённая SQL-классификация не считается подписанным доказательством.
|
||||
IPv4/IPv6 сохраняются отдельно, REALITY восстанавливается из options, порт 0 сохраняется как известный TCP IP.
|
||||
|
||||
Пулы берутся из groups; groups->instance->topo_groups должен указывать на тот же контейнер.
|
||||
При ошибке SQL или памяти частичные списки, имя, REALITY и statement освобождаются; возвращается NULL.
|
||||
|
||||
## Адреса, классификация и RTT
|
||||
|
||||
addrs_put заменяет все адреса узла одним savepoint: DELETE, затем INSERT IPv4/IPv6.
|
||||
Пустой список очищает проекцию. При отказе замена откатывается.
|
||||
Профиль mapping/inbound/source берётся из SOCKMETA, REALITY — из записи соответствующего socket_id.
|
||||
Функция не проверяет подпись входного узла. Замена адресов не сохраняет прежний RTT.
|
||||
|
||||
address_kind — INTERFACE/NAT/REAL, отдельно от вычисляемого addr_type.
|
||||
Классификация учитывает IP, protocol, конфигурационный тип сокета и профиль доступности.
|
||||
Адрес с port=0 хранится, но не является готовым endpoint для подключения.
|
||||
|
||||
nodeinfo_updated под savepoint обновляет node_type, storage, deleted во всех peers_*:
|
||||
|
||||
| node_type | Основание |
|
||||
| --- | --- |
|
||||
| 0 | Нет адресов или классификация неизвестна |
|
||||
| 1 | NETIF, DIRECT или NAT_STRICT |
|
||||
| 2 | NAT_EIM, NAT, CONTACT или EIM_UNVERIFIED |
|
||||
| 4 | adm_tags.supernode=yes |
|
||||
|
||||
При выборе адреса DIRECT имеет приоритет, иначе берётся минимальный addr_type.
|
||||
storage=yes применяется только при DIRECT; deleted выводится из adm_tags.deleted=yes.
|
||||
При отказе одной канальной записи весь пересчёт узла откатывается, функция возвращает -1.
|
||||
|
||||
update_rtt обновляет node_addresses.rtt и peers_*.node_RTT в единицах 0.1 ms.
|
||||
Обновление этих таблиц неатомарно; ошибки логируются. Online и connected меняются отдельными функциями.
|
||||
|
||||
## Каналы и участники
|
||||
|
||||
channel_put требует публичные ключи по 32 байта и подпись 64 байта.
|
||||
При конфликте channel_id публичные ключи должны совпасть: меняются имя/владелец/подпись,
|
||||
переданные privkey заменяют прежние, NULL сохраняет их. Криптографическую проверку делает вызывающий.
|
||||
|
||||
Суффикс таблиц строится из первых 63 символов channel_id: вне [A-Za-z0-9_] заменяются на '_'.
|
||||
Штатные channel_id — десятичные uint64. Строка channels.channel_id хранит исходное значение.
|
||||
|
||||
member_placeholder_put вставляет source=1 с нулевой join_sig; существующую строку не меняет.
|
||||
member_block_put заменяет ключи/join/update/userinfo/дерево приглашений и устанавливает source=0,
|
||||
сохраняя owner-блок и локальные поля. NULL join_sig записывается как 64 нулевых байта.
|
||||
member_owner_put меняет adm_tags/подпись/storage/deleted без вставки отсутствующей строки.
|
||||
Placeholder не участвует в Merkle-хеше; сетевой формат и проверка версий принадлежат member_sync.
|
||||
|
||||
member_exists, member_in_channel, get_member_channels учитывают любое наличие строки,
|
||||
в том числе source=1 и deleted=1. Это не проверка полной подписанной записи участника.
|
||||
member_get_join требует ровно 64 байта подписи; channel_get проверяет размер запрошенных ключей/подписи.
|
||||
|
||||
member_del физически удаляет строку без tombstone и пересчёта Merkle.
|
||||
channel_delete удаляет peers/msg/feedback, канал с приватными ключами и хеши datasets его namespace
|
||||
(если таблица Merkle существует). Все действия, включая DROP TABLE, защищены savepoint:
|
||||
ошибка возвращает -1 и откатывает удаление, сохраняя внешнюю транзакцию.
|
||||
|
||||
## Выбор пиров
|
||||
|
||||
Все выборки возвращают принадлежащий вызывающему массив. Пустой результат — NULL/0.
|
||||
Ошибка чтения/памяти — -1 без публикации частичного массива.
|
||||
|
||||
| API | Выборка |
|
||||
| --- | --- |
|
||||
| get_connect_peers | Все неудалённые участники канала, включая self; connected, затем известный RTT/RTT/node_id |
|
||||
| get_connected_peers | connected=1, deleted=0 в указанном канале |
|
||||
| get_supernode_peers | node_type=4, deleted=0 в указанном канале |
|
||||
| get_public_peers | Глобальные адреса addr_type 1/2 с записью nodes, кроме self и удалённых в указанном канале |
|
||||
| get_local_peers | Аналогично public, но addr_type 0/3 |
|
||||
|
||||
Public/local не требуют членства в указанном канале и не фильтруют port=0.
|
||||
Для get_connect_peers роль supernode берётся из adm_tags, client_type — из сохранённого
|
||||
подписанного snapshot; отсутствие снимка даёт UINT8_MAX. Адреса выборку не ограничивают.
|
||||
Сам запрос не повторяет криптографическую проверку BLOB; подпись проверяется при сохранении снимка.
|
||||
|
||||
Полный список функций, размеры буферов и возвраты — в topo_node_sqlite.h.
|
||||
+67
-19
@@ -29,7 +29,6 @@
|
||||
|
||||
// Constants from spec (adjusted for completeness)
|
||||
#define MAX_INFLIGHT_BYTES 65536 // Initial window
|
||||
#define ACK_DELAY_TB 20 // ACK timer delay (2ms in 0.1ms units)
|
||||
#define BURST_DELAY_FACTOR 4 // Delay before burst
|
||||
#define BURST_SIZE 5 // Packets in burst (1 delayed + 4 burst)
|
||||
#define RTT_HISTORY_SIZE 10 // For jitter calc
|
||||
@@ -56,7 +55,7 @@ static void etcp_schedule_send(struct ETCP_CONN* conn, int delay_tb) {
|
||||
if (!conn || conn->close_requested || conn->state == 2 || conn->tx_token) return;
|
||||
if (!conn->initialized || conn->session_required || conn->reinit_pending) return;
|
||||
if (conn->tx_running && !delay_tb) return;
|
||||
int work = conn->input_send_q->count || conn->input_queue->count || conn->ack_till_pending || conn->ack_request_pending;
|
||||
int work = conn->input_send_q->count || conn->input_queue->count || conn->ack_till_deadline_us || conn->ack_request_pending;
|
||||
for (struct ETCP_LINK* link = conn->links; !work && link; link = link->next)
|
||||
work = link->burst_active || link->ack_reply_pending || (link->ack_q && link->ack_q->count);
|
||||
if (!work) return;
|
||||
@@ -463,6 +462,7 @@ void etcp_connection_close(struct ETCP_CONN* etcp) {
|
||||
|
||||
// Cancel active timers
|
||||
if (etcp->ack_resp_timer) { uasync_cancel_timeout(etcp->instance->ua, etcp->ack_resp_timer); etcp->ack_resp_timer = NULL; }
|
||||
etcp->ack_resp_deadline_us = etcp->ack_till_deadline_us = 0;
|
||||
if (etcp->ack_window_timer) { uasync_cancel_timeout(etcp->instance->ua, etcp->ack_window_timer); etcp->ack_window_timer = NULL; }
|
||||
|
||||
routing_del_conn(etcp);
|
||||
@@ -522,11 +522,12 @@ void etcp_conn_reset(struct ETCP_CONN* conn) {
|
||||
for (struct ETCP_LINK* link = conn->links; link; link = link->next) etcp_tx_link_reset(link, 0);
|
||||
etcp_cancel_send(conn);
|
||||
if (conn->ack_resp_timer) { uasync_cancel_timeout(conn->instance->ua, conn->ack_resp_timer); conn->ack_resp_timer = NULL; }
|
||||
conn->ack_resp_deadline_us = 0;
|
||||
if (conn->ack_window_timer) { uasync_cancel_timeout(conn->instance->ua, conn->ack_window_timer); conn->ack_window_timer = NULL; }
|
||||
queue_clear(conn->input_queue); queue_clear(conn->output_queue);
|
||||
queue_clear(conn->input_send_q); queue_clear(conn->input_wait_ack); queue_clear(conn->recv_q);
|
||||
if (conn->normalizer) pn_reset(conn->normalizer);
|
||||
conn->ack_till_pending = conn->ack_request_pending = 0;
|
||||
conn->ack_till_deadline_us = conn->ack_request_pending = 0;
|
||||
conn->next_tx_id = 1; conn->last_rx_id = conn->last_delivered_id = conn->rx_ack_till = 0;
|
||||
conn->got_initial_pkt = 0;
|
||||
conn->unacked_bytes = conn->rtt_last = conn->rtt_avg_10 = conn->jitter = conn->last_rtt_cb_time = 0;
|
||||
@@ -794,7 +795,7 @@ static void input_queue_try_resume(struct ETCP_CONN* etcp) {// при ACK
|
||||
if (etcp->unacked_bytes < etcp->optimal_inflight) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] resume input queue: unacked_bytes=%u input_len=%zu", etcp->log_name,
|
||||
etcp->unacked_bytes, etcp->input_queue->total_bytes);
|
||||
queue_resume_callback(etcp->input_queue);// и только когда больше нечего отправлять - забираем новый пакет
|
||||
queue_resume_callback(etcp->input_queue); // освобождённый общий бюджет допускает новые DATA и при непустой send_q
|
||||
}
|
||||
}
|
||||
|
||||
@@ -962,16 +963,48 @@ void etcp_on_link_down(struct ETCP_CONN* etcp, struct ETCP_LINK* down_link) {
|
||||
|
||||
static void ack_response_timer_cb(void* arg) {
|
||||
struct ETCP_CONN* conn = arg;
|
||||
conn->ack_resp_timer = NULL;
|
||||
etcp_conn_wake_sender(conn);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] ACK timer fired due_us=%llu now_us=%llu", conn->log_name,
|
||||
(unsigned long long)conn->ack_resp_deadline_us, (unsigned long long)get_time_us());
|
||||
conn->ack_resp_timer = NULL; conn->ack_resp_deadline_us = 0;
|
||||
etcp_conn_schedule_ack(conn);
|
||||
}
|
||||
|
||||
static void ack_schedule(struct ETCP_CONN* conn) {
|
||||
if (conn->ack_resp_timer || conn->close_requested) return;
|
||||
conn->ack_resp_timer = uasync_set_timeout(conn->instance->ua, ACK_DELAY_TB, conn, ack_response_timer_cb, "etcp_ack_resp");
|
||||
/* Один таймер обслуживает ближайший абсолютный срок: новый RX не отодвигает старые ACK. */
|
||||
void etcp_conn_schedule_ack(struct ETCP_CONN* conn) {
|
||||
if (!conn) return;
|
||||
uint64_t now = get_time_us(), deadline = 0;
|
||||
if (!conn->close_requested && conn->state != 2 && conn->initialized && !conn->session_required && !conn->reinit_pending) {
|
||||
deadline = conn->ack_till_deadline_us;
|
||||
for (struct ETCP_LINK* link = conn->links; link; link = link->next) {
|
||||
if (!link->initialized || !link->link_status) continue;
|
||||
if (link->ack_reply_pending) { deadline = now; break; }
|
||||
const struct ACK_PACKET* first = link->ack_q ? (struct ACK_PACKET*)link->ack_q->head : NULL;
|
||||
if (first && (!deadline || first->received_at_us + ETCP_ACK_DELAY_US < deadline))
|
||||
deadline = first->received_at_us + ETCP_ACK_DELAY_US;
|
||||
}
|
||||
}
|
||||
if (conn->ack_resp_timer && conn->ack_resp_deadline_us == deadline && deadline > now) return;
|
||||
if (conn->ack_resp_timer) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] ACK timer replace old_us=%llu next_us=%llu", conn->log_name,
|
||||
(unsigned long long)conn->ack_resp_deadline_us, (unsigned long long)deadline);
|
||||
uasync_cancel_timeout(conn->instance->ua, conn->ack_resp_timer); conn->ack_resp_timer = NULL;
|
||||
}
|
||||
conn->ack_resp_deadline_us = 0;
|
||||
if (!deadline) return;
|
||||
if (deadline <= now) { etcp_conn_wake_sender(conn); return; }
|
||||
int delay = (int)((deadline - now + 99) / 100);
|
||||
conn->ack_resp_timer = uasync_set_timeout(conn->instance->ua, delay, conn, ack_response_timer_cb, "etcp_ack_resp");
|
||||
if (!conn->ack_resp_timer) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] ACK delay timer allocation failed, requesting immediate send", conn->log_name);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] ACK timer OOM due_us=%llu delay_tb=%d; forcing urgent ACK", conn->log_name,
|
||||
(unsigned long long)deadline, delay);
|
||||
for (struct ETCP_LINK* link = conn->links; link; link = link->next)
|
||||
if (link->initialized && link->link_status && link->ack_q && link->ack_q->count) link->ack_reply_pending = 1;
|
||||
if (conn->ack_till_deadline_us) conn->ack_till_deadline_us = now;
|
||||
etcp_conn_wake_sender(conn);
|
||||
} else {
|
||||
conn->ack_resp_deadline_us = deadline;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] ACK timer arm due_us=%llu now_us=%llu delay_tb=%d", conn->log_name,
|
||||
(unsigned long long)deadline, (unsigned long long)now, delay);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1099,6 +1132,8 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* conn) {
|
||||
if (conn->tx_state) conn->tx_state = ETCP_TX_STATE_LINK_WAIT;
|
||||
return NULL;
|
||||
}
|
||||
etcp_conn_schedule_ack(conn);
|
||||
if (conn->close_requested) return NULL;
|
||||
struct ETCP_LINK* link = NULL;
|
||||
struct INFLIGHT_PACKET* packet = send_queue_select(conn, &link);
|
||||
if (!packet) {
|
||||
@@ -1109,14 +1144,26 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* conn) {
|
||||
conn->session_required || conn->reinit_pending) return NULL;
|
||||
if (pulled) packet = send_queue_select(conn, &link);
|
||||
}
|
||||
if (!link) {
|
||||
for (struct ETCP_LINK* candidate = conn->links; candidate; candidate = candidate->next)
|
||||
if (candidate->initialized && candidate->link_status &&
|
||||
(candidate->ack_reply_pending || (candidate->ack_q && candidate->ack_q->count))) {
|
||||
link = candidate; packet = NULL; break;
|
||||
}
|
||||
if (!link || !link->burst_active) {
|
||||
uint64_t now = get_time_us();
|
||||
struct ETCP_LINK* ack_link = NULL;
|
||||
uint64_t first_due = UINT64_MAX;
|
||||
for (struct ETCP_LINK* candidate = conn->links; candidate; candidate = candidate->next) {
|
||||
if (!candidate->initialized || !candidate->link_status) continue;
|
||||
const struct ACK_PACKET* first = candidate->ack_q ? (struct ACK_PACKET*)candidate->ack_q->head : NULL;
|
||||
if (!candidate->ack_reply_pending && !first) continue;
|
||||
uint64_t due = candidate->ack_reply_pending ? 0 : first->received_at_us + ETCP_ACK_DELAY_US;
|
||||
if (due <= now && (!ack_link || due < first_due)) { ack_link = candidate; first_due = due; }
|
||||
}
|
||||
if (ack_link) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] ACK due path=%u deadline_us=%llu now_us=%llu piggyback=%u", conn->log_name,
|
||||
ack_link->local_link_id, (unsigned long long)first_due, (unsigned long long)now, ack_link == link && packet != NULL);
|
||||
if (ack_link != link) packet = NULL;
|
||||
link = ack_link;
|
||||
}
|
||||
}
|
||||
if (!link && (conn->ack_till_pending || conn->ack_request_pending)) {
|
||||
if (!link && (conn->ack_request_pending ||
|
||||
(conn->ack_till_deadline_us && conn->ack_till_deadline_us <= get_time_us()))) {
|
||||
link = etcp_loadbalancer_select_link(conn, 0, NULL); packet = NULL;
|
||||
}
|
||||
if (!link) {
|
||||
@@ -1272,7 +1319,8 @@ void etcp_output_try_assembly(struct ETCP_CONN* etcp) {
|
||||
}
|
||||
|
||||
if (delivered_count > 0 && !etcp->close_requested && !etcp->reinit_pending) {
|
||||
etcp->ack_till_pending = 1; ack_schedule(etcp);
|
||||
if (!etcp->ack_till_deadline_us) etcp->ack_till_deadline_us = get_time_us() + ETCP_ACK_DELAY_US;
|
||||
etcp_conn_schedule_ack(etcp);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] delivered %u fragments (%u bytes), till=%u recv_q=%d",
|
||||
etcp->log_name, delivered_count, delivered_bytes, etcp->last_delivered_id, etcp->recv_q->count);
|
||||
}
|
||||
@@ -1475,7 +1523,7 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
pkt->link->rx_data_bytes += payload_len; pkt->link->rx_data_time_us = get_time_us();
|
||||
}
|
||||
etcp->got_initial_pkt = 1;
|
||||
if (etcp_tx_queue_ack(pkt->link, seq, attempt_id) == 0) ack_schedule(etcp);
|
||||
if (etcp_tx_queue_ack(pkt->link, seq, attempt_id) == 0) etcp_conn_schedule_ack(etcp);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] accepted DATA seq=%u attempt=%llu path=%u bytes=%u duplicate=%d",
|
||||
etcp->log_name, seq, (unsigned long long)attempt_no, pkt->link->local_link_id, payload_len, duplicate);
|
||||
if (distance == 1) etcp_output_try_assembly(etcp);
|
||||
|
||||
@@ -66,6 +66,7 @@ uint16_t get_current_timestamp(void);
|
||||
#define INFLIGHT_STATE_WAIT_SEND 1
|
||||
#define INFLIGHT_INITIAL_HASH_SIZE 1024
|
||||
#define ETCP_LINK_HISTORY_BYTES 32 // по одному биту для локального слота линка 0..255
|
||||
#define ETCP_ACK_DELAY_US 2000 // обычный ACK-only: срок от старейшей записи; piggyback и срочный ответ не ждут
|
||||
|
||||
#define MAX_INFLIGHT_SIZE 16384 // максимальное число элементов в inflight приёмной очереди (для предотвращения атак)
|
||||
#define ETCP_ACK_GAP_MAX_PACKETS 8192 // предел кумулятивного ACK за приём: 8192 × PACKET_DATA_SIZE(2048) = 16 MB (защита от DoS)
|
||||
@@ -197,9 +198,10 @@ struct ETCP_CONN {
|
||||
uint32_t optimal_inflight; // сумма индивидуальных окон; общий бюджет подгрузки новых DATA с учётом unacked_bytes
|
||||
|
||||
// Timers
|
||||
void* ack_resp_timer; // ACK send timer
|
||||
void* ack_resp_timer; // один таймер ближайшего срока ACK всех линков/логической границы
|
||||
uint64_t ack_resp_deadline_us; // абсолютный срок установленного таймера; 0 без таймера
|
||||
void* ack_window_timer; // запрос потерянного till, только при заполненном приёмном окне
|
||||
uint8_t ack_till_pending; // нужно передать текущую логическую границу по рабочему пути
|
||||
uint64_t ack_till_deadline_us; // срок передачи текущей логической границы по рабочему пути; 0 — нет запроса
|
||||
uint8_t ack_request_pending; // нужно запросить границу у пира
|
||||
void* down_timer; // удаление после непрерывного DOWN, включая первоначальное ожидание
|
||||
uint64_t down_deadline_tb; // сохраняется при наличии NCD handles после истечения таймера
|
||||
@@ -342,7 +344,9 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt);
|
||||
int etcp_int_send(struct ETCP_CONN* etcp, const void* data, uint16_t len);
|
||||
|
||||
/* Подготовить следующий пакет из pkt_pool либо NULL при ожидании/ошибке/отсутствии работы.
|
||||
* Сканирует send_q: готовые повторы имеют приоритет, заблокированные записи пропускаются.
|
||||
* Наступивший/срочный ACK своего пути имеет приоритет; готовый DATA этого пути забирает его piggyback.
|
||||
* Сканирует send_q: готовые повторы идут перед новыми DATA, заблокированные записи пропускаются.
|
||||
* Короткая измерительная burst-серия завершается до перехода к ACK другого пути.
|
||||
* При отсутствии кандидата допускает одну подгрузку в рамках общего/приёмного окна; callback может сделать reset/close.
|
||||
* Пакет с DATA содержит резерв попытки: etcp_loadbalancer_send завершает send/rollback и возвращает пакет в пул. */
|
||||
struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp);
|
||||
@@ -351,6 +355,11 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp);
|
||||
* Используется очередями, ACK/RTO, изменением окна и подтверждением сессии. close/reset отменяет запуск. */
|
||||
void etcp_conn_wake_sender(struct ETCP_CONN* conn);
|
||||
|
||||
/* Пересчитать единственный ACK-таймер по головам очередей и ack_till_deadline_us; просроченную работу передать sender.
|
||||
* Вызвать после RX/продвижения till, завершения send и смены доступных путей. NULL/закрытый conn допустимы.
|
||||
* Piggyback не ждёт сроков. При OOM таймера существующие флаги срочного ACK обеспечивают немедленный fallback. */
|
||||
void etcp_conn_schedule_ack(struct ETCP_CONN* conn);
|
||||
|
||||
// Process ACK receipt - remove acknowledged packet from inflight queues
|
||||
void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq);
|
||||
|
||||
|
||||
@@ -264,7 +264,7 @@ struct ETCP_LINK {
|
||||
void* shaper_timer;
|
||||
struct ll_queue* attempts; // Физические попытки, внутренний ключ (seq<<16)|attempt_no; ll.len учитывается в окне
|
||||
struct ll_queue* ack_q; // ACK только DATA этого линка, тот же составной ключ
|
||||
uint8_t ack_reply_pending; // Ответ на запрос till возвращается тем же путём, даже при пустой ACK-очереди
|
||||
uint8_t ack_reply_pending; // срочный ACK: ответ till тем же путём либо fallback при OOM таймера; обходит ACK_DELAY
|
||||
void* retrans_timer; // Срок старейшей принятой транспортом попытки этого канала
|
||||
uint64_t retrans_deadline_us; // Уже запланированная проверка: более ранний таймер можно сохранить
|
||||
uint32_t data_rtt_us; // Сглаженный RTT DATA->ACK этого пути; 0 до первого измерения
|
||||
|
||||
@@ -186,16 +186,17 @@ struct ETCP_TX_ATTEMPT* etcp_tx_prepare(struct ETCP_LINK* link, struct INFLIGHT_
|
||||
}
|
||||
|
||||
void etcp_tx_complete(struct ETCP_DGRAM* dgram, int success) {
|
||||
struct ETCP_CONN* conn = dgram->link->etcp;
|
||||
if (success) {
|
||||
dgram->link->ack_reply_pending = 0;
|
||||
dgram->link->etcp->ack_till_pending = 0;
|
||||
conn->ack_till_deadline_us = 0;
|
||||
if (dgram->ack_request) dgram->link->etcp->ack_request_pending = 0;
|
||||
}
|
||||
etcp_conn_schedule_ack(conn);
|
||||
struct ETCP_TX_ATTEMPT* attempt = dgram->attempt;
|
||||
if (!attempt) return;
|
||||
dgram->attempt = NULL;
|
||||
struct ETCP_LINK* link = dgram->link;
|
||||
struct ETCP_CONN* conn = link->etcp;
|
||||
struct INFLIGHT_PACKET* packet = logical_find(conn, attempt->seq);
|
||||
if (!success) { attempt_retire(link, attempt, 0, 0); return; }
|
||||
if (!packet || packet->state != INFLIGHT_STATE_WAIT_SEND) {
|
||||
|
||||
@@ -54,6 +54,7 @@ uint32_t etcp_tx_rto_us(const struct ETCP_LINK* link);
|
||||
struct ETCP_TX_ATTEMPT* etcp_tx_prepare(struct ETCP_LINK* link, struct INFLIGHT_PACKET* packet);
|
||||
/* Завершить синхронный send: success=1 принят транспортом, 0 — снять резерв без сетевой потери.
|
||||
* Принятие DATA фиксирует историю каналов и RR; повторно использованный слот начинает новый обход.
|
||||
* После потребления ACK пересчитывает ближайший срок оставшихся очередей; успешный пакет снимает pending till/urgent этого пути.
|
||||
* Вызвать ровно один раз до освобождения dgram и до следующего poll; dgram->attempt обнуляется. */
|
||||
void etcp_tx_complete(struct ETCP_DGRAM* dgram, int success);
|
||||
/* Принятый ACK: seq подтверждает логическую доставку; внутренний attempt_id=(seq<<16)|номер обновляет только свой линк.
|
||||
@@ -66,7 +67,8 @@ void etcp_tx_complete(struct ETCP_DGRAM* dgram, int success);
|
||||
* Непредставимый интервал не создаёт оценку BW. Пустой flight заново начинает приёмную базу, исключая простой. */
|
||||
void etcp_tx_ack(struct ETCP_LINK* link, uint32_t seq, uint64_t attempt_id, uint32_t receiver_delay_us,
|
||||
uint64_t peer_rx_bytes, uint64_t peer_rx_time_us);
|
||||
/* Поставить ACK уже сохранённого/доставленного DATA в очередь этого линка; 0/-1, ошибка логируется. */
|
||||
/* Поставить ACK уже сохранённого/доставленного DATA в очередь этого линка; 0/-1, ошибка логируется.
|
||||
* received_at_us сохраняется для RTT и срока ACK-only; вызывающий затем запускает etcp_conn_schedule_ack(). */
|
||||
int etcp_tx_queue_ack(struct ETCP_LINK* link, uint32_t seq, uint64_t attempt_id);
|
||||
|
||||
#endif
|
||||
Reference in new issue
Block a user