Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37417ba49d | ||
|
|
cca3511503 | ||
|
|
d70cdbb040 | ||
|
|
c3f946fb63 | ||
|
|
93de0aa591 | ||
|
|
0c2eeb8820 | ||
|
|
637b21da9e | ||
|
|
c0935aba6c | ||
|
|
6bf267924d | ||
|
|
5318476cdf | ||
|
|
24294b22f6 | ||
|
|
43d95016ae | ||
|
|
41e0736b75 | ||
|
|
1d29cab751 | ||
|
|
d0f5a2970f | ||
|
|
03701f525f | ||
|
|
89b1a01448 | ||
|
|
e3b32cf4d3 | ||
|
|
2d93dd66c8 | ||
|
|
85a91a1320 | ||
|
|
102816d0dc | ||
|
|
c65940f499 | ||
|
|
79bdafc9a7 | ||
|
|
cb1021f231 | ||
|
|
58503907b5 | ||
|
|
666507d3fe | ||
|
|
f0f20c158b | ||
|
|
ed5ccd1ffb | ||
|
|
65611790fa | ||
|
|
e74240f0dd | ||
|
|
e438e19f9e | ||
|
|
de7841f33a | ||
|
|
a9a6b6bb10 | ||
|
|
3f874a80ab | ||
|
|
1f4749a1b9 | ||
|
|
28e5950d29 | ||
|
|
3c3d9a65d9 | ||
|
|
c7ac42eb5f | ||
|
|
62954ca0a9 | ||
|
|
1889d23240 | ||
|
|
f5c93266dc | ||
|
|
861d81815f | ||
|
|
9dafb4c31e | ||
|
|
19a4505a03 | ||
|
|
46f5ff7eca | ||
|
|
7628f77b93 | ||
|
|
118ab04788 | ||
|
|
bfb83f7459 | ||
|
|
797c0736a7 | ||
|
|
8756f561be | ||
|
|
7139969acd | ||
|
|
59d307f98b | ||
|
|
90ad64a106 | ||
|
|
4914348557 | ||
|
|
55fa5c7759 | ||
|
|
0b5a710fe6 | ||
|
|
f1f533a552 | ||
|
|
03490cfc00 | ||
|
|
ba14e31e09 | ||
|
|
5b850ea5da | ||
|
|
2fba5c31c4 | ||
|
|
4f168c93f6 | ||
|
|
6c938ed2ba | ||
|
|
2cb85cbc3d | ||
|
|
754c08e9c2 | ||
|
|
71ccccc2d5 | ||
|
|
c6d1ebeeaf | ||
|
|
eea0ed0a48 | ||
|
|
0738dcfd5a | ||
|
|
4862c30261 | ||
|
|
3c5d82a6ee | ||
|
|
389ef00da6 | ||
|
|
487cb8718e | ||
|
|
42d96f118d | ||
|
|
8962f58ea4 | ||
|
|
1f541d6432 | ||
|
|
e66156eb49 | ||
|
|
2cd38988e6 | ||
|
|
d58b532741 | ||
|
|
4716e54069 | ||
|
|
aa73e9d8cb | ||
|
|
993c265125 | ||
|
|
5ca0bbf9e7 | ||
|
|
0ef7ee597c | ||
|
|
7d460ca156 | ||
|
|
b738b2ff1b | ||
|
|
cc5289e37c | ||
|
|
0c5f25c96d | ||
|
|
b53758bdbe | ||
|
|
f5b1c3e789 | ||
|
|
0edd21e335 | ||
|
|
e0c38676e7 | ||
|
|
fefd2bcc52 | ||
|
|
8427e18c98 | ||
|
|
7329fa65f1 | ||
|
|
de8cce521a | ||
|
|
f06ebd2ab4 | ||
|
|
a97a38d867 | ||
|
|
aff3b2d00f | ||
|
|
ccdbab95a8 | ||
|
|
a01e2f0899 | ||
|
|
dfdec06a17 | ||
|
|
393a641e18 | ||
|
|
53d41d5c62 | ||
|
|
c60ba6eaec | ||
|
|
c5123e4baf | ||
|
|
928c2fe057 | ||
|
|
b5b6875bc5 | ||
|
|
7c953c1929 | ||
|
|
1c09ad2e5a | ||
|
|
3284dffaaf | ||
|
|
c893285d71 | ||
|
|
75028f278a | ||
|
|
7fc1b5419f | ||
|
|
dee9889423 | ||
|
|
35871891db | ||
|
|
373e4b33c7 | ||
|
|
090e2f28e4 | ||
|
|
a2c7f1e6cb | ||
|
|
21da640ebe | ||
|
|
80177c9b3e | ||
|
|
8f13f0ec4d | ||
|
|
5420e414ce | ||
|
|
69112ffde1 | ||
|
|
89d675f15d | ||
|
|
914f252b12 | ||
|
|
a7b20f268f | ||
|
|
c0207ad41d | ||
|
|
8051dfe4e5 | ||
|
|
22c24d135e | ||
|
|
d8c8c1c5b0 | ||
|
|
231081dd2f | ||
|
|
62b3695a77 | ||
|
|
0e2b0e28d8 | ||
|
|
e50ca9978e | ||
|
|
b667d6f352 | ||
|
|
6717c863e7 | ||
|
|
2bbdf8195f | ||
|
|
73f707ef9b | ||
|
|
34683f0500 | ||
|
|
150e9977df |
No files matched your search
+7
-1
@@ -1,5 +1,7 @@
|
||||
# Build directory (out-of-tree build)
|
||||
/build/
|
||||
build-win/
|
||||
tools/chatgui/build/
|
||||
|
||||
# Build outputs
|
||||
*.o
|
||||
@@ -59,7 +61,7 @@ Makefile.in
|
||||
.cache/
|
||||
|
||||
# Debug outputs
|
||||
core
|
||||
/core
|
||||
vgcore.*
|
||||
callgrind.out.*
|
||||
|
||||
@@ -96,6 +98,9 @@ utun.conf
|
||||
tools/bping/bping
|
||||
tools/proxy/udp_proxy
|
||||
|
||||
# Reference projects (not part of utun)
|
||||
tools/tdesktop-dev/
|
||||
|
||||
# Debug/temp artifacts
|
||||
*.bak
|
||||
*.debug
|
||||
@@ -109,3 +114,4 @@ build_win.log
|
||||
/wintun.dll
|
||||
src/lwip_orig/
|
||||
lib/timeout/
|
||||
utun.pid
|
||||
@@ -15,7 +15,7 @@ This file contains essential information for AI coding agents working in the uTu
|
||||
**Repository:** uTun - Secure VPN tunnel with ETCP protocol
|
||||
**Language:** C (C99)
|
||||
**Build System:** GNU Autotools (autoconf/automake)
|
||||
**Cryptography:** TinyCrypt + OpenSSL (AES-CCM, ECC, SHA256)
|
||||
**Cryptography:** OpenSSL (AES-CCM, X25519, SHA256)
|
||||
|
||||
## Build Commands
|
||||
|
||||
@@ -56,6 +56,16 @@ cd tests && make # Build only tests
|
||||
./build_direct.sh # Компиляция вручную с x86_64-w64-mingw32-gcc
|
||||
```
|
||||
|
||||
### ASAN Build (AddressSanitizer, Linux)
|
||||
```bash
|
||||
./build.sh --asan -j4 # lib/ + src/ only (tests not ASAN-compatible)
|
||||
```
|
||||
Запуск с ASAN (detect_leaks отключены чтобы не спамить при shutdown):
|
||||
```bash
|
||||
ASAN_OPTIONS=detect_leaks=0:halt_on_error=0 ./src/utun -f -p /tmp/utun.pid -l /tmp/utun.log
|
||||
```
|
||||
Краш-логи ASAN пишутся в `/tmp/utun_asan.<pid>`.
|
||||
|
||||
## Test Commands
|
||||
|
||||
### Run All Tests
|
||||
@@ -142,14 +152,14 @@ gcc -I../src -I../lib -I../tinycrypt/lib/include \
|
||||
### Key Sizes
|
||||
| Constant | Value | Description |
|
||||
|----------|-------|-------------|
|
||||
| `SC_PRIVKEY_SIZE` | 32 | ECC private key |
|
||||
| `SC_PUBKEY_SIZE` | 64 | ECC public key |
|
||||
| `SC_PRIVKEY_SIZE` | 32 | X25519 private key |
|
||||
| `SC_PUBKEY_SIZE` | 32 | X25519 public key |
|
||||
| `SC_NONCE_SIZE` | 13 | CCM nonce (exactly 13 bytes) |
|
||||
| `SC_SESSION_KEY_SIZE` | 16 | AES-128 session key |
|
||||
| `SC_TAG_SIZE` | 16 | CCM auth tag |
|
||||
| `SC_CRC32_SIZE` | 4 | CRC32 checksum |
|
||||
| `SC_PUBKEY_ENC_SALT_SIZE` | 8 | Salt for pubkey obfuscation |
|
||||
| `SC_PUBKEY_ENC_SIZE` | 72 | Total pubkey+salt block sent unencrypted |
|
||||
| `SC_PUBKEY_ENC_SIZE` | 40 | Total pubkey+salt block sent unencrypted |
|
||||
|
||||
### Using Secure Channel (secure_channel.h)
|
||||
```c
|
||||
@@ -184,11 +194,12 @@ sc_obfuscate_pubkey(salt, peer_pubkey_bin, my_pubkey_bin, obfuscated_output);
|
||||
### Debug Levels (по возрастанию)
|
||||
`none < error < warn < info < debug < trace`
|
||||
|
||||
### Debug Categories (21 категория)
|
||||
### Debug Categories (25 категорий)
|
||||
```
|
||||
NONE=0, UASYNC=1, LL_QUEUE=2, CONNECTION=3, ETCP=4, CRYPTO=5, MEMORY=6,
|
||||
TIMING=7, CONFIG=8, TUN=9, ROUTING=10, TIMERS=11, NORMALIZER=12, BGP=13,
|
||||
SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
|
||||
SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20,
|
||||
KEEPALIVE=21, ETCPROUTE=22, BBR=23, ETCP_DUMP=24
|
||||
```
|
||||
|
||||
### Настройка отладки
|
||||
@@ -205,12 +216,14 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
|
||||
|
||||
### Directory Structure
|
||||
```
|
||||
├── lib/ # Core libraries (13 .c + 14 .h)
|
||||
├── src/ # Main source code (28 .c + 23 .h)
|
||||
├── tests/ # 31+ test programs
|
||||
├── lib/ # Core libraries (17 .c + 17 .h)
|
||||
├── src/ # Main source code (42 .c + 44 .h root + 17 в поддиректориях)
|
||||
├── tests/ # 65 .c файлов, 55 в check_PROGRAMS
|
||||
├── doc/ # Technical Specifications
|
||||
├── tools/ # Auxiliary tools
|
||||
│ ├── etcpmon/ # GUI монитор ETCP
|
||||
│ ├── chatgui/ # GUI чат (Qt 5, отдельная сборка через CMake)
|
||||
│ ├── tdesktop-dev/ # Референс: Telegram Desktop (только для изучения)
|
||||
│ ├── proxy/ # UDP прокси для тестов
|
||||
│ └── bping/ # BPing (bandwidth ping)
|
||||
├── tinycrypt/ # TinyCrypt crypto library (external)
|
||||
@@ -233,20 +246,37 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
|
||||
- `etcp_connections.c/h` - Socket and link management, INIT handshake, keepalive, PING/PONG
|
||||
- `etcp_loadbalancer.c/h` - Multi-link load balancing with traffic shaper
|
||||
- `etcp_debug.c/h` - ETCP packet dump/formatting
|
||||
- `etcp_router.c/h` - ETCP router/multiplexer (мультиплексирование каналов)
|
||||
- `etcp_connect.c/h` - ETCP outbound connections
|
||||
- `etcp_dump.c/h` - ETCP дамп/декодирование пакетов
|
||||
- `etcp_bbr.c/h` - BBR congestion control for ETCP
|
||||
- `pkt_normalizer.c/h` - Packet fragmentation/reassembly (packer/unpacker)
|
||||
- `packet_dump.c/h` - Packet hex dump utility
|
||||
- `firewall.c/h` - Firewall rules (per-interface filtering)
|
||||
- `dummynet.c/h` - Network emulator integrated into utun (for testing)
|
||||
|
||||
**Routing (src/)**
|
||||
- `routing.c/h` - Routing table management (local routes)
|
||||
- `route_lib.c/h` - Routing library utilities
|
||||
- `route_bgp.c/h` - BGP-style route exchange between peers
|
||||
**BBR (src/BBR/)**
|
||||
- `bbr_v3.c` - BBR congestion control algorithm v3
|
||||
|
||||
**STCP (src/)**
|
||||
- `stcp.c/h` - STCP протокол (Secure TCP)
|
||||
- `stcp_server.c/h` - STCP сервер
|
||||
- `stcp_client.c/h` - STCP клиент
|
||||
- `stcp_link.c/h` - STCP линк-уровень
|
||||
|
||||
**Топология / Маршрутизация (src/)**
|
||||
- `routing.c/h` - Таблица маршрутов (локальные)
|
||||
- `route_lib.c/h` - Библиотека маршрутизации
|
||||
- `route6_lib.c/h` - IPv6 маршрутизация
|
||||
- `route_ping.c/h` - Route ping probing (NAT check, liveness)
|
||||
- `route_node.c/h` - Route node (peer) management
|
||||
- `route_connectivity.c/h` - Проверка связности маршрутов
|
||||
- `topo_group.c/h` - BGP-подобный обмен маршрутами между узлами
|
||||
- `topo_node.c/h` - Управление узлами (peer management)
|
||||
- `topo_node_lmdb.c/h` - LMDB-персистентность узлов
|
||||
- `conn_mgr.c/h` - Менеджер соединений
|
||||
|
||||
**Crypto (src/)**
|
||||
- `secure_channel.c/h` - AES-CCM encryption with ECC key exchange, pubkey obfuscation
|
||||
- `secure_channel.c/h` - AES-CCM encryption with X25519 key exchange, pubkey obfuscation
|
||||
- `crc32.c/h` - CRC32 checksums
|
||||
|
||||
**NAT (src/)**
|
||||
@@ -260,6 +290,23 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
|
||||
**Control (src/)**
|
||||
- `control_server.c/h` - Control/monitoring server (etcpmon backend API)
|
||||
|
||||
**Транспорт сообщений (src/)**
|
||||
- `msg_transport.c/h` - Транспорт сообщений (чат, команды)
|
||||
- `db_sync.c/h` - Синхронизация БД между узлами
|
||||
|
||||
**Прокси (src/proxy/)**
|
||||
- `socks_proxy.c/h` - SOCKS5 прокси
|
||||
- `udp_proxy.c/h` - UDP прокси
|
||||
- `icmp_proxy.c/h` - ICMP прокси
|
||||
- `tcp_proxy_server.c/h` - TCP прокси (серверная сторона)
|
||||
- `tcp_proxy_client.c/h` - TCP прокси (клиентская сторона)
|
||||
|
||||
**lwIP TCP (src/lwip_tcp/)**
|
||||
- `lwip_tcp.c/h` - lwIP TCP стек
|
||||
- `lwip_tcp_out.c` / `lwip_tcp_in.c` - Исходящий/входящий трафик
|
||||
- `lwip_pbuf.c/h` - Пакетные буферы lwIP
|
||||
- `lwip_tcp_priv.h`, `lwip_tcp_opts.h` - Внутренние настройки lwIP
|
||||
|
||||
**Libraries (lib/)**
|
||||
- `u_async.c/h` - Async event loop (epoll/poll/select, timers via timeout_heap)
|
||||
- `ll_queue.c/h` - Lock-free linked list queue with callbacks, hash index, threshold waiter
|
||||
@@ -274,6 +321,9 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
|
||||
- `myip.c` - My IP helper (no header)
|
||||
- `socket_compat.c/h` - Cross-platform socket compatibility (Linux/FreeBSD/Windows)
|
||||
- `platform_compat.c/h` - Cross-platform compatibility layer (byte order, time, random)
|
||||
- `radix.c/h` - Radix tree for IP routing lookups
|
||||
- `tcp_io.c/h` - TCP I/O abstraction (Windows IOCP, Linux epoll)
|
||||
- `wintun.h` - Wintun API header for Windows TUN driver
|
||||
|
||||
**Memory Pools in UTUN_INSTANCE:**
|
||||
- `data_pool` — для данных пакетов (payload), используется input_queue/output_queue фрагментами
|
||||
@@ -285,7 +335,7 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
|
||||
- **LL_QUEUE:** Lock-free queue with auto-callback, hash index lookup, threshold waiter
|
||||
- **Memory Pool:** Fast allocation for hot-path objects (packets, inflight entries, fragments)
|
||||
- **ETCP:** TCP-like reliable protocol with encryption, multi-link, load balancing
|
||||
- **Secure Channel:** AES-CCM + ECC key exchange, nonce-based encryption, pubkey obfuscation
|
||||
- **Secure Channel:** AES-CCM + X25519 key exchange, nonce-based encryption, pubkey obfuscation
|
||||
|
||||
## Queue Usage Rules
|
||||
|
||||
@@ -296,7 +346,8 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
|
||||
|
||||
### Запись в очередь
|
||||
- Очереди забивать нельзя. Добавляй следующий элемент только когда очередь стала пустой.
|
||||
- Используй `queue_wait_threshold` для ожидания освобождения очереди до заданного порога.
|
||||
- Порог задаётся через `queue_set_threshold(q, max_packets, max_bytes)`.
|
||||
- Используй `queue_waiter_wait` для ожидания освобождения очереди до заданного порога.
|
||||
|
||||
### Чтение из очереди
|
||||
- Используй `queue_set_callback`: при вызове callback обработай один или несколько элементов, потом вызови `queue_resume_callback`.
|
||||
@@ -321,6 +372,40 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
|
||||
Всегда когда начинаешь диагностику ознакомься со скиллом (используй skills)
|
||||
Диагностика и поиск ошибок - это в первую очередь продумывание отладочных механизмов которые покажут понятную картину и точное представление об ошибке. Рассуждать надо как диагностикой добиться точной картины. И как приавльнее добавить диагностику чтобы не спамила лишними сообщениями и была понятной, логичной и информативной.
|
||||
|
||||
Важное правило отладки: вся отладка сводится к тому чтобы сделать удобные логи которые наглядно показывают поведение и проблемы. без лишнего мусора, компактно и по существу. очень желательно с деталями которые сильно повышают качество отладки.
|
||||
при отладке нельзя: гадать и долго пытаться разбирать код пытаяясь найти причину. гораздо надёжнее с помощью логов понято точное поведение.
|
||||
Если логов много - записывай в файл и потом анализируй.
|
||||
Если видишь спам-логи - подумай как их выборочно отключить чтобы не мешали.
|
||||
|
||||
Для отладки добавляй в DEBUG_CATEGORY_DEBUG диагностические сообщения где надо. И включи эту категорию в настройках. Убирай только после проверки (путём запуска) когда все ошибки устранены.
|
||||
сообщение выводится по или - либо debug_set_level(DEBUG_LEVEL_TRACE) - выводится ВСЁ независимо от настроек по категориям. Тоесть берется max(global level, category lavel)
|
||||
|
||||
Твой бич - ты постоянно гадаешь и анализируешь код. Что приводит к снежному кобу ошибок и неверных гипотез. 10 раз повторяю - только логи логи логи и никакого гадания. Лоооги!!! правильные логи покажут всё с предельной точностью. Вся суть отладки - информативные логи
|
||||
И смотри логи. не задавливай их grep-ом. лучше больше. единственное с чем борись - это бесполезные спам логи.
|
||||
Но полезные смотри всегда, и всегда оставляй логи которые выводятся нечасто.
|
||||
Частые логи - это трафик которые >100 раз повторяются. Но которые мало раз обязательно оставлять и выводить.
|
||||
можешь в тесте включить логи в нужный интервал чтобы не спамить. debug_set_level(DEBUG_LEVEL_TRACE) и выключить debug_set_level(DEBUG_LEVEL_NONE)
|
||||
Если видишь проблему и ее решение неочевидно то выстраивай диагностику вокруг неё пока не будет очевидно где и что происходит не так. Это базовое и обязательное требование к отладке.
|
||||
Подробные логи ты должен выводить не обрезая всегда и анализировать. Если лог большой - выведи в файл и анализируй файл.
|
||||
Рассуждение должно быть примерно таким:
|
||||
- данные повреждаются при отправке.
|
||||
- где и почему непонятно. надо продумать ключевые точки где получим максимум полезной информации и не было большого объёма вывода лога.
|
||||
- где лучше? каждый пакет логировать - много, но будет предельно точная картина
|
||||
- Еще хорошо бы время - так мы заодно сможем найти проблемы с производительностью, найти проблемные места застревания кода.
|
||||
- Но это большой объём. Можно ли без него? можно но это будет малоэффективно и хороших альтернатив пока не видно.
|
||||
- Значит логируем весь трафик, а заодно включим полную трассировку функций.
|
||||
- Запустили. Записали весь процесс в файл. Теперь можно анализировать. Давай сперва посчитаю количество строк с дампом: ... 50000.
|
||||
- давай сделаю простой скрипт который дамп прочитает и сохранит в файл. И сверю с оригинальным файлом
|
||||
- Давай заодно возьму произвольный фрагмент лога и изучу еа предмет явных проблем. Особенно инициализацию и освобождение ресурсов.
|
||||
Сколько времени занимает, нет ли заклиниваний, нет ли ошибок или странностей
|
||||
|
||||
### Диагностические сообщения (DEBUG_WARN DEBUG_ERROR)
|
||||
Сообщения должны говорить что случилось и с какими деталями.
|
||||
Обязательно выводить подробные собощения об ошибках (даже если нужен небольшой дополнительный код чтобы эти детали сформировать и привести в читаемый вид).
|
||||
Полезны подробности об инициализации-завершении которые помогают понять внутренние состояния
|
||||
Диагностические сообщения - важный момент. надо про диагностику помнить.
|
||||
Для хорошей диагностики надо проанализировать как архитектор - какие есть нюансы в архитектуре и что полезно будет видеть в дебаг выводе чтобы понять полную картину происходящего.
|
||||
|
||||
### Прочие правила:
|
||||
- sed для редактирования исходников - запрещено
|
||||
- Проверяй на дублирование кода - не сделано ли это уже в другом месте
|
||||
@@ -337,11 +422,162 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
|
||||
- Для каждого отладочного вывода подумай какая из доступной информация будет полезна чтобы можно было наиболее завершенно оценить состояние алгоритма и состояний влияющих на алгоритм.
|
||||
|
||||
|
||||
## chatgui (GUI Chat Client)
|
||||
|
||||
Chatgui — десктопный GUI-чат на Qt 6 (Qt 5 fallback), отдельный проект внутри репозитория. Не связан с autotools-сборкой utun.
|
||||
|
||||
В chat gui интегрированы библиотеки utun. Чат и библиотеки работают в разных потоках. Поэтому нужно использовать семафоры, сокеты или другие механизмы синхронизации (uasync_post, uasync_memsync, uasync_get_wakeup_fd)
|
||||
|
||||
База данных sqlite в chatgui: можно писать (update) из потока utun / instance. можно только читать из gui потока.
|
||||
|
||||
### Технологии
|
||||
- **Язык:** C++20
|
||||
- **Фреймворк:** Qt 6 (предпочитаемый) или Qt 5.15+ (Widgets + Network)
|
||||
- **Сборка:** CMake 3.16+
|
||||
- **БД:** SQLite3 (WAL mode, встроенный `db/sqlite3.c` amalgamation)
|
||||
- **Анимации:** rlottie (C API) + zlib (gzip-декомпрессия TGS)
|
||||
|
||||
### Сборка
|
||||
```bash
|
||||
# Установка зависимостей
|
||||
sudo apt install librlottie-dev zlib1g-dev qt6-base-dev
|
||||
# или qtbase5-dev для Qt 5 fallback
|
||||
|
||||
# Сборка
|
||||
cd tools/chatgui && mkdir -p build && cd build
|
||||
cmake .. && make -j4
|
||||
./chatgui
|
||||
```
|
||||
|
||||
### Структура файлов (src/)
|
||||
|
||||
| Файл | Назначение |
|
||||
|------|-----------|
|
||||
| `main.cpp` | Точка входа, инициализация QApplication |
|
||||
| `mainwindow.h/cpp` | Главное окно, QSplitter (ChannelList \| MessageList \| AccountList), трей |
|
||||
| `chatview.h/cpp` | QListView с фоновым изображением, hover-сигнал, drag-to-select |
|
||||
| `messagelist.h/cpp` | QStandardItemModel, stub-данные, инициализация анимаций, hover→activate |
|
||||
| `messagedelegate.h/cpp` | QStyledItemDelegate: бабблы, текст, цитаты, реакции, оверлей анимаций |
|
||||
| `emoji.h/cpp` | EmojiData/EmojiCategory/EmojiType, 4 категории, `g_animatedEmojiMap` |
|
||||
| `emojipanel.h/cpp` | Эмодзи-пикер: QTabWidget + QGridLayout, EmojiButton с hover-анимацией |
|
||||
| `emojitabbar.h/cpp` | Кастомный QTabBar для эмодзи-панели |
|
||||
| `inputbar.h/cpp` | Поле ввода (QTextEdit), вставка эмодзи, отправка по Enter |
|
||||
| `lottieicon.h/cpp` | Загрузка TGS (gzip→zlib), рендер кадров через `lottie_animation_render` |
|
||||
| `animtimer.h/cpp` | Синглтон-таймер 30fps, refcount activate/deactivate, сигнал ticked |
|
||||
| `channellist.h/cpp` `channeldelegate.h/cpp` | Список каналов с аватарами и превью |
|
||||
| `accountlist.h/cpp` | Список аккаунтов/пользователей |
|
||||
| `joindialog.h/cpp` | Диалог присоединения к группе/каналу |
|
||||
| `invitedialog.h/cpp` | Диалог приглашения пользователя |
|
||||
| `invite_link.h/cpp` | Логика invite-ссылок |
|
||||
| `settingsdialog.h/cpp` | Окно настроек |
|
||||
| `creategroupdialog.h/cpp` | Диалог создания группы |
|
||||
| `networksettingspage.h/cpp` | Страница сетевых настроек |
|
||||
| `qrcode_utils.h/cpp` | Утилиты для QR-кодов |
|
||||
| `debug_ui.h` | Отладочный UI |
|
||||
|
||||
### Другие поддиректории
|
||||
- `db/` — SQLite3 amalgamation (`sqlite3.c/h`), `db_manager.h/cpp` (схема: nodes, node_addresses, channels, msg_<channel>, peers_<channel>, local_identity, accounts, ui_state)
|
||||
- `transport/` — интеграция с uTun: `gui_bridge.h/gui_bridge_impl.cpp` (Qt signal-based обмен), `chat_core.h/c` (управление), `chat_sync.h/c` (синхронизация), `db_sync_stub.c` (заглушка БД), `node_config.h/cpp`, `config_updater.h/cpp`, `utun_node.h/cpp`, `topo_node_sqlite.h/c`
|
||||
- `resources/` — `bg.jpg`, `chatgui.qrc` (встраивает 5 TGS в бинарник), `animations/*.tgs`
|
||||
|
||||
### Система анимированных эмодзи
|
||||
|
||||
#### Формат
|
||||
TGS = gzip-сжатый Lottie JSON. Холст 512×512, 60fps, 90–180 кадров. Декомпрессия через zlib (`inflateInit2` с `16+MAX_WBITS`).
|
||||
|
||||
#### Ключевые компоненты
|
||||
```
|
||||
LottieIcon — загрузка TGS, рендер кадров (rlottie C API)
|
||||
↓
|
||||
AnimTimer (синглтон) — таймер 33ms, refcount activate/deactivate
|
||||
├── в сообщениях: → MessageDelegate::paint()
|
||||
│ drawAnimatedEmojiOverlay() — QTextLayout → позиции эмодзи → drawImage()
|
||||
│ drawStatusBar() — анимированные реакции вместо drawText()
|
||||
│
|
||||
├── в панели: → EmojiButton (наследует QPushButton)
|
||||
│ per-button QTimer, enterEvent/leaveEvent, собственный paintEvent
|
||||
│
|
||||
└── активация: → ChatView::hoveredIndexChanged
|
||||
MessageList проверяет текст/реакции сообщения на g_animatedEmojiMap
|
||||
есть анимация → AnimTimer::activate() / нет → deactivate()
|
||||
```
|
||||
|
||||
#### Инициализация
|
||||
1. `MessageList` конструктор: загружает все `EmojiType::Animation` из `builtinEmojiSet()`, создаёт `LottieIcon`, регистрирует в `AnimTimer`
|
||||
2. **Должно быть ДО создания `EmojiPanel`** — иначе `EmojiButton` получит `nullptr`
|
||||
3. `g_animatedEmojiMap` заполняется при старте (Unicode → animPath)
|
||||
|
||||
#### Текущий маппинг (5 эмодзи)
|
||||
| Unicode | Shortcode | TGS-файл |
|
||||
|---------|-----------|----------|
|
||||
| ✨ `U+2728` | `:sparkles:` | `sparkles_emoji.tgs` |
|
||||
| 🏳️ `U+1F3F3` | `:flag:` | `white_flag_emoji.tgs` |
|
||||
| 👋 `U+1F44B` | `:wave:` | `greeting.tgs` |
|
||||
| ✋ `U+270B` | `:raised_hand:` | `stop.tgs` |
|
||||
| 🤖 `U+1F916` | `:robot:` | `robot.tgs` |
|
||||
|
||||
#### Рендеринг в сообщениях
|
||||
- Текст рисуется через `QPainter::drawText(Qt::TextWordWrap)`
|
||||
- `drawAnimatedEmojiOverlay()` повторяет layout через `QTextLayout` с теми же параметрами
|
||||
- Для каждого эмодзи из `g_animatedEmojiMap` находит пиксельную позицию через `QTextLine::cursorToX()`
|
||||
- Рендерит кадр через `LottieIcon::renderFrame(currentFrame(), size)` и рисует `drawImage()`
|
||||
- Размер эмодзи масштабирован ×1.3 относительно высоты строки
|
||||
- Аналогично для реакций в `drawStatusBar()`
|
||||
|
||||
#### Drag-to-select
|
||||
- Одиночный клик снимает выделение (если было), не стартует новое
|
||||
- Движение ≥5px от точки нажатия стартует выделение (`m_selStart`/`m_selEnd`)
|
||||
- Ctrl+C копирует: для одного сообщения — выделенный фрагмент, для нескольких — `[time] author: text`
|
||||
|
||||
### Важные правила chatgui
|
||||
- `CMAKE_AUTORCC ON` обязателен для компиляции `.qrc`-ресурсов
|
||||
- Иконки должны быть зарегистрированы в `AnimTimer` ДО создания `EmojiPanel`
|
||||
- `EmojiButton` НЕ использует `Q_OBJECT` (в анонимном namespace) — таймер через лямбду, каст через `dynamic_cast`
|
||||
- Координаты `indexAt()`: `viewport()->mapFrom(this, event->pos())`
|
||||
|
||||
---
|
||||
|
||||
## tdesktop-dev (референсный проект Telegram Desktop)
|
||||
|
||||
**Расположение:** `tools/tdesktop-dev/` — полный клон [Telegram Desktop](https://github.com/telegramdesktop/tdesktop).
|
||||
|
||||
**Статус:** референс. Не собирается, не модифицируется, в `.gitignore`.
|
||||
|
||||
**Назначение:**
|
||||
- Источник TGS-анимаций (`Telegram/Resources/animations/` — 99+ файлов)
|
||||
- Образец использования rlottie/lib_lottie API
|
||||
- Архитектурный референс для GUI-компонентов
|
||||
|
||||
**Правила:**
|
||||
- НЕ модифицировать файлы внутри `tdesktop-dev/`
|
||||
- НЕ коммитить изменения из `tdesktop-dev/`
|
||||
- НЕ ссылаться в сборке на код/библиотеки из tdesktop-dev
|
||||
- Можно копировать TGS-файлы в `tools/chatgui/resources/animations/`
|
||||
|
||||
**Полезные поддиректории:**
|
||||
- `Telegram/Resources/animations/` — TGS-анимации (dice, reactions, emoji, UI)
|
||||
- `Telegram/lib_lottie/` — C++ обёртка над rlottie (SinglePlayer, MultiPlayer, Icon, FrameGenerator)
|
||||
- `Telegram/ThirdParty/rlottie/` — submodule rlottie (обычно не выкачан)
|
||||
|
||||
**rlottie C API (ключевые функции):**
|
||||
```c
|
||||
Lottie_Animation *lottie_animation_from_data(const char *data, const char *key, const char *resource_path);
|
||||
void lottie_animation_get_size(const Lottie_Animation *anim, size_t *w, size_t *h);
|
||||
size_t lottie_animation_get_totalframe(const Lottie_Animation *anim);
|
||||
double lottie_animation_get_framerate(const Lottie_Animation *anim);
|
||||
void lottie_animation_render(Lottie_Animation *anim, size_t frame, uint32_t *buf, size_t w, size_t h, size_t stride);
|
||||
void lottie_animation_destroy(Lottie_Animation *anim);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Documentation Files
|
||||
- `/doc/etcp_protocol.txt` - ETCP протокол (формат кодограмм, ACK, handshake, keepalive)
|
||||
- `/doc/etcp_arch.md` - ETCP архитектура
|
||||
- `/doc/etcp_config.txt` - Конфигурация ETCP
|
||||
- `/doc/etcp_router_arch.md` - ETCP роутер архитектура
|
||||
- `/doc/route_p2pconn.txt` - Route peer-to-peer соединения
|
||||
- `/src/route_bgp.txt` - BGP обмен маршрутами (дизайн-документ)
|
||||
|
||||
## Runtime
|
||||
- Запуск utun от root (для tun): `/home/vnc1/proj/utun3/utun_start.sh`
|
||||
@@ -353,7 +589,7 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
|
||||
- **Commit Messages:** Use imperative mood, concise (50-72 chars)
|
||||
- **Language:** Mix of English (technical) and Russian (business logic)
|
||||
- **Tags:** Version tags follow vX.Y.Z format
|
||||
- "cp" or "кп" in prompt = do commit and push (всех изменений на текущий момент, не откатывая)
|
||||
- "cp" or "кп" in prompt = do commit and push (всех изменений на текущий момент, не откатывая). Запрещено откатывать "лишние" изменения сделаные вне сессии (если сильно надо - сделать бэкап). если нет дополнительных указаний - комить всё что изменилось как есть.
|
||||
|
||||
## Quick Start for New Features
|
||||
1. Add new source file to `src/Makefile.am` under `utun_SOURCES`
|
||||
@@ -362,5 +598,14 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
|
||||
4. Run `make check` after changes
|
||||
5. Commit with descriptive message in appropriate language
|
||||
|
||||
## chatgui: отправка сообщений через xdotool
|
||||
|
||||
1. **Запуск:** `setsid env DISPLAY=:1.0 QT_ACCESSIBILITY=1 ./chatgui & disown` (иначе SIGTERM убьёт процесс при таймауте bash)
|
||||
2. **Окно:** `0x4600006 "Chat"` (WM_CLASS "chatgui"), 900×600. Не путать с `0x4800001` (10×10, временное)
|
||||
3. **Фокус:** клик в область InputBar (x=400, y=570 относительно окна) перед вводом
|
||||
4. **Отправка:** `xdotool type --window WID "text"` + `xdotool key --window WID Return`
|
||||
5. **Проверка:** `sqlite3 chats.db "SELECT ... FROM msg_ch_GENERAL"` — сообщения в per-channel таблицах
|
||||
6. **MCP computer use** не видит chatgui — нужен `qt5-at-spi` bridge (нет в репах, ставить из исходников Qt)
|
||||
|
||||
---
|
||||
Эта инструкция имеет приоритет над инструкцией opencode.
|
||||
@@ -1,253 +0,0 @@
|
||||
========================================================================
|
||||
uTun3 AUDIT FINDINGS — Сводный отчёт
|
||||
========================================================================
|
||||
|
||||
Аудит от 14.05.2026 — обход основных модулей
|
||||
(use-after-free, out-of-bounds, утечки, reentrancy)
|
||||
|
||||
========================================================================
|
||||
HIGH (19 проблем)
|
||||
========================================================================
|
||||
|
||||
1. u_async.c:137-165 use-after-free
|
||||
Частичный успех u_realloc в socket_array_add_internal: если часть
|
||||
реаллоков прошла, а часть нет — старые массивы уже освобождены,
|
||||
новые не выделены, sa->sockets/sa->fd_to_index/sa->index_to_fd
|
||||
указывают на освобождённую память.
|
||||
|
||||
2. u_async.c:842,1167 логическая ошибка
|
||||
process_posted_tasks никогда не вызывается на Linux.
|
||||
В epoll-пути: drain_wakeup_pipe не вызывает handle_wakeup/process_posted_tasks.
|
||||
В poll-пути: wakeup-слот (i==0) обрабатывается с drain_wakeup_pipe и continue,
|
||||
минуя зарегистрированный коллбэк.
|
||||
Следствие: uasync_post из другого потока не доставляет задачи — posted_tasks
|
||||
накапливаются бесконечно.
|
||||
|
||||
3. u_async.c:1383,1398,1418 утечка памяти
|
||||
При сбое создания wakeup (pipe/socket) в uasync_create не освобождаются:
|
||||
ua->sockets, ua->timeout_heap, ua->timeout_pool, ua->epoll_fd.
|
||||
|
||||
4. memory_pool.c:51-69 double-free
|
||||
Нет детекта double-free. Повторный free создаёт цикл во freelist,
|
||||
memory_pool_alloc выдаёт один и тот же блок двум потребителям.
|
||||
|
||||
5. etcp.c:679-683 утечка памяти
|
||||
input_queue_cb: при исчерпании inflight_pool вызывается
|
||||
queue_entry_free(in_pkt) без queue_dgram_free(&in_pkt->ll).
|
||||
dgram из data_pool утекает.
|
||||
|
||||
6. etcp_connections.c:87,876-926 use-after-free
|
||||
burst_resp_timer не отменяется в etcp_link_close.
|
||||
Коллбэк burst_resp_timeout_cb срабатывает на освобождённом link.
|
||||
|
||||
7. etcp_connections.c:846-854 use-after-free
|
||||
stats_timer не отменяется при провале insert_link в etcp_link_new.
|
||||
link_stats_timer_cb срабатывает на освобождённом link.
|
||||
|
||||
8. etcp_connections.c:879-886 use-after-free
|
||||
Ветка conn==NULL в etcp_link_close пропускает отмену ВСЕХ таймеров
|
||||
(stats_timer, init_timer, shaper_timer, keepalive_timer, burst_resp_timer).
|
||||
Любой из них сработает на освобождённом link.
|
||||
|
||||
9. etcp_connections.c:1388,1531,1561 out-of-bounds read
|
||||
INIT request парсинг: проверка pkt_len < 12 должна быть pkt_len < 26
|
||||
(23 байта заголовка). Пакет с длиной 12-25 пройдёт проверку,
|
||||
но читает за границей расшифрованных данных.
|
||||
|
||||
10. pkt_normalizer.c:33 null deref
|
||||
if (!pn) pn->alloc_errors++ — разыменование NULL после u_calloc.
|
||||
|
||||
11. pkt_normalizer.c:300 null deref
|
||||
pn_buf_renew в цикле without проверки возврата → memcpy(NULL, ...)
|
||||
при сбое memory_pool_alloc.
|
||||
|
||||
12. eim_nat.c:80-93 логическая ошибка
|
||||
Переполнение uint16_t порта: next_port с 65535→0, затем порты
|
||||
ниже port_start выделяются (включая порт 0). Соединения нерабочие.
|
||||
|
||||
13. eim_nat.c (весь файл) логическая ошибка
|
||||
Нет механизма истечения/удаления NAT-записей. Таблица навсегда
|
||||
исчерпывается при долгой работе, eim_nat_egress начинает отказывать.
|
||||
|
||||
14. route_bgp.c:749,762-766 утечка памяти
|
||||
route_bgp_process_nodeinfo: при пересоздании nodeinfo1 старая
|
||||
очередь paths сохраняется в локальную переменную, но затем
|
||||
перезаписывается новой queue_new() — утечка.
|
||||
|
||||
15. route_bgp.c:904-907 утечка памяти
|
||||
route_bgp_send_nodeinfo: при провале etcp_send не освобождаются
|
||||
p (u_malloc) и e (queue_entry_new).
|
||||
|
||||
16. route_ping.c:126-134 утечка памяти
|
||||
route_ping_cancel_for_conn: nat_check_arg (p->arg, u_calloc) не
|
||||
освобождается. В нормальном и таймаутном путях коллбэк освобождает,
|
||||
но cancel-путь коллбэк не вызывает.
|
||||
|
||||
17. ll_queue.c:321,439 reentrancy
|
||||
Синхронный вызов коллбэка при первом queue_data_put (count==1)
|
||||
без отложенного возобновления через uasync → рекурсивный
|
||||
ack_timeout_check с устаревшим указателем current.
|
||||
|
||||
18. secure_channel.c:379,711 потенциальный OOB write
|
||||
sc_encrypt пишет в caller buffer без параметра capacity.
|
||||
Контракт API опасен: вызывающий должен сам гарантировать размер буфера.
|
||||
|
||||
19. secure_channel.c:432,756 потенциальный OOB write
|
||||
sc_decrypt — аналогично, нет проверки capacity выходного буфера.
|
||||
|
||||
|
||||
========================================================================
|
||||
MEDIUM (18 проблем)
|
||||
========================================================================
|
||||
|
||||
20. u_async.c:591-639 use-after-free
|
||||
Stale handle в uasync_cancel_timeout/uasync_call_soon_cancel.
|
||||
Нет инвалидации (generation counter), пул может переиспользовать память.
|
||||
|
||||
21. memory_pool.c:8,38,61,95 race condition
|
||||
Глобальная g_total_free изменяется без блокировок, общая для всех пулов.
|
||||
|
||||
22. memory_pool.c:51-69 потенциальный OOB write
|
||||
Нет проверки принадлежности блока пулу. Free в чужой пул вызывает
|
||||
memset с чужим object_size — частичная очистка или запись за границу.
|
||||
|
||||
23. timeout_heap.c:43-45 утечка / несоответствие документации
|
||||
Документация обещает free() при free_callback==NULL, код этого не делает.
|
||||
|
||||
24. etcp.c:718-763 reentrancy
|
||||
Вложенный ack_timeout_check (через input_send_q_cb→etcp_request_pkt→
|
||||
wait_ack_cb) обрабатывает не тот entry, т.к. outer loop использует
|
||||
current, который мог быть перемещён/удалён внутренним вызовом.
|
||||
|
||||
25. etcp.c:747,944 утечка / дрейф счётчиков
|
||||
Непроверенный queue_data_put_with_index: при провале счётчики
|
||||
(retransmissions_count, inflight_bytes) не откатываются.
|
||||
|
||||
26. etcp_connections.c:1731,1763 out-of-bounds read
|
||||
INIT_RESPONSE парсинг: нужно >=19 байт, проверяется только >=3.
|
||||
|
||||
27. etcp_connections.c:748-753 use-after-free + бесконечный цикл
|
||||
etcp_socket_remove: при link->conn==NULL remove_link пропускается,
|
||||
num_channels не уменьшается, цикл перечитывает освобождённый conn->links[0].
|
||||
|
||||
28. etcp_connections.c:1736-1738 логическая ошибка
|
||||
link_status перезаписывается значением remote_keepalive, игнорируя
|
||||
локальный recv_keepalive. Временно помечает link UP даже когда
|
||||
локальный приём мёртв.
|
||||
|
||||
29. pkt_normalizer.c:43 integer overflow/underflow
|
||||
frag_size = mtu - ACK_REZERV - UDP_HDR_SIZE - UDP_SC_HDR_SIZE.
|
||||
При mtu < 158 беззнаковое вычитание даёт ~65379 → огромные буферы.
|
||||
|
||||
30. pkt_normalizer.c:240 утечка памяти
|
||||
При провале queue_data_put фрагмент и dgram не освобождаются.
|
||||
|
||||
31. eim_nat.c:97,173 null deref
|
||||
Нет проверки ctx->initialized/ctx->table в egress/ingress.
|
||||
|
||||
32. eim_nat.c:268,293 логическая ошибка
|
||||
Статические пробросы (port forward) вне port range недоступны.
|
||||
|
||||
33. secure_channel.c:66 key hygiene
|
||||
Нет sc_free_ctx — сессионный ключ не зануляется при уничтожении контекста.
|
||||
|
||||
34. secure_channel.c:322,620 key hygiene
|
||||
ECDH shared secret остаётся на стеке незанулённым после
|
||||
sc_derive_session_key.
|
||||
|
||||
35. secure_channel.c:352,680 nonce entropy
|
||||
При постоянном провале random_bytes seed остаётся 0 (static init).
|
||||
Nonce становится полностью предсказуемым (counter+timestamp).
|
||||
|
||||
36. route_bgp.c:1122 утечка памяти
|
||||
route_bgp_send_nat_info: etcp_send без проверки → pkt/e утекают при провале.
|
||||
|
||||
37. route_bgp.c:1157 утечка памяти (аналогично #36)
|
||||
route_bgp_send_nat_check_req: etcp_send без проверки.
|
||||
|
||||
38. config_parser.c:1055-1066 логическая ошибка
|
||||
update_config_keys дописывает [global] секцию в конец файла.
|
||||
При повторном вызове получается дубликат секции, парсер может сбойнуть.
|
||||
|
||||
|
||||
========================================================================
|
||||
LOW (13 проблем)
|
||||
========================================================================
|
||||
|
||||
39. u_async.c:1594-1617 утечка (abort)
|
||||
abort() в destroy до освобождения posted_tasks и мьютекса.
|
||||
|
||||
40. u_async.c:1608 race condition
|
||||
posted_tasks_head читается без posted_lock в destroy.
|
||||
|
||||
41. memory_pool.c:47 truncation
|
||||
size_t → uint32_t в u_calloc_impl (object_size > UINT32_MAX).
|
||||
|
||||
42. memory_pool.h:16 signedness
|
||||
int free_count может обернуться при двойном освобождении.
|
||||
|
||||
43. timeout_heap.c:31 dead code
|
||||
freed_count никогда не обновляется, timeout_heap_get_freed_count не реализован.
|
||||
|
||||
44. timeout_heap.c:66-67 integer overflow
|
||||
capacity*2 и sizeof(TimeoutEntry)*new_cap могут переполниться.
|
||||
|
||||
45. ll_queue.c:106-127 dangling pointer
|
||||
queue_free освобождает hash_table, но не чистит entry->hash_next.
|
||||
|
||||
46. pkt_normalizer.c:374 десинхронизация потока
|
||||
При провале ll_alloc_lldgram 2 байта заголовка уже consumed,
|
||||
но фрагмент сброшен → потеря данных.
|
||||
|
||||
47. eim_nat.c:105,181 unaligned access (ARM)
|
||||
*(uint16_t*)(ip_data + 6) — невыровненный доступ на ARM.
|
||||
|
||||
48. secure_channel.c:108-131 избыточность
|
||||
CRC32 поверх CCM (аутентифицированное шифрование) избыточен.
|
||||
|
||||
49. secure_channel.h:51-52 dead code
|
||||
Неиспользуемые поля send_nonce[13]/recv_nonce[13].
|
||||
|
||||
50. config_parser.c:312 undefined behavior
|
||||
~0U << (32 - cidr) при cidr==0 — сдвиг на ширину типа.
|
||||
|
||||
51. route_ping.c:227 integer overflow
|
||||
uint16_t avg_rtt = timeout_ms * 10 — переполнение при timeout_ms > 6553.
|
||||
|
||||
|
||||
========================================================================
|
||||
Файлы без ошибок
|
||||
========================================================================
|
||||
|
||||
- route_node.c — чисто
|
||||
- tun_if.c — чисто
|
||||
- etcp_api.c — только порядок queue_entry_free/dgram_free (LOW, см. etcp.c Issue #7 старого аудита)
|
||||
- etcp_loadbalancer.c — не аудирован (250 строк)
|
||||
|
||||
|
||||
========================================================================
|
||||
Рекомендуемый порядок исправления
|
||||
========================================================================
|
||||
|
||||
1. КРИТИЧЕСКИ ФУНКЦИОНАЛЬНЫЕ:
|
||||
u_async.c #2 (posted_tasks не работают на Linux)
|
||||
u_async.c #3 (утечки при сбое старта)
|
||||
|
||||
2. USE-AFTER-FREE:
|
||||
etcp_connections.c #6,#7,#8,#5 (висячие таймеры)
|
||||
u_async.c #1 (частичный realloc)
|
||||
ll_queue.c #17 (reentrancy)
|
||||
|
||||
3. УТЕЧКИ ПАМЯТИ:
|
||||
etcp.c #5 (dgram из data_pool)
|
||||
route_bgp.c #14,#15,#36,#37
|
||||
route_ping.c #16
|
||||
|
||||
4. OOB / NULL DEREF:
|
||||
etcp_connections.c #9,#26 (INIT bounds)
|
||||
pkt_normalizer.c #10,#11 (null deref)
|
||||
|
||||
5. ЛОГИКА:
|
||||
eim_nat.c #12,#13 (порты + expiry)
|
||||
memory_pool.c #4 (double-free)
|
||||
config_parser.c #38 (дубликат секции)
|
||||
@@ -1,28 +0,0 @@
|
||||
# Bootstrap Makefile for utun
|
||||
# All build artifacts (.o) go to build/ directory
|
||||
# Final binary (utun) is copied to project root
|
||||
|
||||
BUILD_DIR = build
|
||||
|
||||
all:
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
@if test ! -f $(BUILD_DIR)/config.status || test ./configure -nt $(BUILD_DIR)/config.status; then \
|
||||
echo "Configuring in $(BUILD_DIR)/..."; \
|
||||
cd $(BUILD_DIR) && ../configure; \
|
||||
fi
|
||||
@$(MAKE) -C $(BUILD_DIR)
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILD_DIR)
|
||||
|
||||
distclean: clean
|
||||
-rm -f config.status config.log config.h stamp-h1
|
||||
-rm -rf autom4te.cache/
|
||||
|
||||
check: all
|
||||
@$(MAKE) -C $(BUILD_DIR) check
|
||||
|
||||
install:
|
||||
@$(MAKE) -C $(BUILD_DIR) install
|
||||
|
||||
.PHONY: all clean distclean check install
|
||||
@@ -28,4 +28,4 @@ if not exist "%MSYS2_PATH%\msys2_shell.cmd" (
|
||||
REM Launch MSYS2 UCRT64 shell and run build script
|
||||
echo Starting MSYS2 UCRT64 environment...
|
||||
rem call "%MSYS2_PATH%\msys2_shell.cmd" -ucrt64 -here -c "bash build.sh > build_win.log 2>&1"
|
||||
call "%MSYS2_PATH%\msys2_shell.cmd" -ucrt64 -here -defterm -no-start -c "bash build.sh 2>&1 | tee build_win.log"
|
||||
call "%MSYS2_PATH%\msys2_shell.cmd" -ucrt64 -here -defterm -no-start -c "set -o pipefail; bash build.sh 2>&1 | tee build_win.log"
|
||||
@@ -5,6 +5,8 @@
|
||||
# -h, --help Show this help
|
||||
# --clean Clean before build
|
||||
# --full Full rebuild (autoreconf + configure + make)
|
||||
# --asan Build with AddressSanitizer (debug)
|
||||
# --hardened Build with compiler security hardening flags
|
||||
# -j[N] Build with N parallel jobs (default: 4)
|
||||
|
||||
set -e # Exit on error
|
||||
@@ -21,8 +23,11 @@ fi
|
||||
# Parse arguments
|
||||
CLEAN=0
|
||||
FULL_REBUILD=0
|
||||
USE_ASAN=0
|
||||
USE_HARDENED=0
|
||||
JOBS="-j4"
|
||||
MAKE_ARGS=""
|
||||
HARDEN_FLAGS=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
@@ -32,6 +37,8 @@ while [[ $# -gt 0 ]]; do
|
||||
echo " -h, --help Show this help"
|
||||
echo " --clean Clean before build"
|
||||
echo " --full Full rebuild (autoreconf + configure + make)"
|
||||
echo " --asan Build with AddressSanitizer"
|
||||
echo " --hardened Build with compiler security hardening flags"
|
||||
echo " -j[N] Build with N parallel jobs (default: 4)"
|
||||
exit 0
|
||||
;;
|
||||
@@ -43,6 +50,14 @@ while [[ $# -gt 0 ]]; do
|
||||
FULL_REBUILD=1
|
||||
shift
|
||||
;;
|
||||
--asan)
|
||||
USE_ASAN=1
|
||||
shift
|
||||
;;
|
||||
--hardened)
|
||||
USE_HARDENED=1
|
||||
shift
|
||||
;;
|
||||
-j*)
|
||||
JOBS="$1"
|
||||
shift
|
||||
@@ -122,13 +137,43 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
# Generate Makefiles if needed
|
||||
if [ ! -f Makefile ]; then
|
||||
echo ""
|
||||
echo "Running ./configure..."
|
||||
if [ "$USE_ASAN" -eq 1 ]; then
|
||||
./configure CFLAGS="-g -Og -fsanitize=address -fno-omit-frame-pointer -fno-pie" LDFLAGS="-no-pie"
|
||||
elif [ "$USE_HARDENED" -eq 1 ]; then
|
||||
./configure --enable-hardening
|
||||
else
|
||||
./configure
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build (all .o files go to build/ directory, binary copied to project root)
|
||||
echo ""
|
||||
echo "Compiling with $JOBS..."
|
||||
BUILD_SUCCESS=0
|
||||
make $JOBS $MAKE_ARGS 2>&1 | tee build.log
|
||||
if [ ${PIPESTATUS[0]} -eq 0 ]; then
|
||||
if [ "$USE_ASAN" -eq 1 ]; then
|
||||
echo "(AddressSanitizer enabled — lib/ and src/ only, skipping tests)"
|
||||
AF="-fsanitize=address -fno-omit-frame-pointer -g -Og"
|
||||
AL="-fsanitize=address"
|
||||
(cd lib && make clean $JOBS $MAKE_ARGS 2>&1 && make "CFLAGS+=$AF" "LDFLAGS+=$AL" $JOBS $MAKE_ARGS 2>&1) || { echo "lib build failed"; exit 1; }
|
||||
(cd src && make clean $JOBS $MAKE_ARGS 2>&1 && make "CFLAGS+=$AF" "LDFLAGS+=$AL" $JOBS $MAKE_ARGS 2>&1) || { echo "src build failed"; exit 1; }
|
||||
BUILD_SUCCESS=1
|
||||
elif [ "$USE_HARDENED" -eq 1 ]; then
|
||||
echo "(Hardening flags enabled)"
|
||||
HF="-D_FORTIFY_SOURCE=2 -fstack-protector-strong -fstack-clash-protection -Wformat=2 -Wformat-overflow=2 -fPIE"
|
||||
HL="-Wl,-z,relro -Wl,-z,now -pie"
|
||||
(cd lib && make clean $JOBS $MAKE_ARGS 2>&1 && make "CFLAGS+=$HF" "LDFLAGS+=$HL" $JOBS $MAKE_ARGS 2>&1) || { echo "lib build failed"; exit 1; }
|
||||
(cd src && make clean $JOBS $MAKE_ARGS 2>&1 && make "CFLAGS+=$HF" "LDFLAGS+=$HL" $JOBS $MAKE_ARGS 2>&1) || { echo "src build failed"; exit 1; }
|
||||
(cd tests && make clean $JOBS $MAKE_ARGS 2>&1 && make "CFLAGS+=$HF" "LDFLAGS+=$HL" $JOBS $MAKE_ARGS 2>&1) || { echo "tests build failed"; exit 1; }
|
||||
BUILD_SUCCESS=1
|
||||
else
|
||||
make $JOBS $MAKE_ARGS 2>&1 | tee build.log
|
||||
if [ ${PIPESTATUS[0]} -eq 0 ]; then
|
||||
BUILD_SUCCESS=1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $BUILD_SUCCESS -eq 1 ]; then
|
||||
|
||||
+37
-6
@@ -1,8 +1,24 @@
|
||||
#!/bin/sh
|
||||
#pkg install -y autoconf openssl
|
||||
|
||||
set -e
|
||||
|
||||
# Проверка и установка зависимостей
|
||||
need_install=""
|
||||
|
||||
for cmd in autoreconf aclocal automake libtoolize; do
|
||||
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||
need_install=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$need_install" ]; then
|
||||
echo "Устанавливаются необходимые пакеты..."
|
||||
pkg update
|
||||
pkg install -y autoconf automake libtool pkgconf openssl
|
||||
fi
|
||||
|
||||
mkdir -p m4
|
||||
set -e
|
||||
|
||||
echo "=== Полная пересборка uTun ==="
|
||||
|
||||
@@ -12,18 +28,33 @@ make distclean 2>/dev/null || true
|
||||
rm -f config.h config.status config.log
|
||||
rm -f configure.log autoreconf.log build.log
|
||||
|
||||
# 2. Автогенерация (autoreconf)
|
||||
echo "[2/4] Автогенерация (autoreconf -fi)..."
|
||||
# 2. Генерация configure
|
||||
echo "[2/4] Автогенерация..."
|
||||
autoreconf -fi 2>&1 | tee autoreconf.log
|
||||
|
||||
# Проверяем, появился ли configure
|
||||
if [ ! -f configure ]; then
|
||||
echo "Ошибка: autoreconf не создал configure."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
chmod +x configure
|
||||
|
||||
# 3. Конфигурация
|
||||
echo "[3/4] Конфигурация..."
|
||||
./configure --prefix=/usr/local 2>&1 | tee configure.log
|
||||
|
||||
# Определяем количество ядер
|
||||
if command -v nproc >/dev/null 2>&1; then
|
||||
JOBS=$(nproc)
|
||||
else
|
||||
JOBS=$(sysctl -n hw.ncpu)
|
||||
fi
|
||||
|
||||
# 4. Сборка
|
||||
echo "[4/4] Сборка..."
|
||||
make -j$(nproc) 2>&1 | tee build.log
|
||||
make -j"$JOBS" 2>&1 | tee build.log
|
||||
|
||||
echo ""
|
||||
echo
|
||||
echo "=== Готово ==="
|
||||
echo "Бинарный файл: src/utun"
|
||||
+1
-1
@@ -70,7 +70,7 @@ echo "Building utun.exe..."
|
||||
cd src
|
||||
EXE_NEEDS_REBUILD=0
|
||||
|
||||
CORE_SOURCES="utun.c utun_instance.c config_parser.c config_updater.c route_lib.c route_bgp.c routing.c tun_if.c tun_route.c etcp.c etcp_connections.c etcp_loadbalancer.c secure_channel.c crc32.c pkt_normalizer.c etcp_api.c tun_windows.c"
|
||||
CORE_SOURCES="utun.c utun_instance.c config_parser.c config_updater.c route_lib.c topo_bgp.c routing.c tun_if.c tun_route.c etcp.c etcp_connections.c etcp_loadbalancer.c secure_channel.c crc32.c pkt_normalizer.c etcp_api.c tun_windows.c"
|
||||
|
||||
for src in $CORE_SOURCES; do
|
||||
if [ -f "$src" ]; then
|
||||
|
||||
+1
-1
@@ -30,4 +30,4 @@ REM Launch MSYS2 UCRT64 shell and run build script with --full flag
|
||||
echo Starting MSYS2 UCRT64 environment for FULL REBUILD...
|
||||
echo Running: bash build.sh --full
|
||||
echo.
|
||||
call "%MSYS2_PATH%\msys2_shell.cmd" -ucrt64 -here -defterm -no-start -c "bash build.sh --full 2>&1 | tee build_full.log"
|
||||
call "%MSYS2_PATH%\msys2_shell.cmd" -ucrt64 -here -defterm -no-start -c "set -o pipefail; bash build.sh --full 2>&1 | tee build_full.log"
|
||||
File diff suppressed because it is too large.
Load diff
@@ -55,6 +55,20 @@ fi
|
||||
|
||||
AM_CONDITIONAL([USE_OPENSSL], [test "x$with_openssl" = "xyes"])
|
||||
|
||||
# ==================== HARDENING ====================
|
||||
AC_ARG_ENABLE([hardening],
|
||||
AS_HELP_STRING([--enable-hardening], [Enable compiler security hardening (FORTIFY, stack protector, PIE, RELRO)]),
|
||||
[enable_hardening=$enableval],
|
||||
[enable_hardening=no])
|
||||
|
||||
if test "x$enable_hardening" = "xyes"; then
|
||||
HARDEN_CFLAGS="-D_FORTIFY_SOURCE=2 -fstack-protector-strong -fstack-clash-protection -Wformat=2 -Wformat-overflow=2 -fPIE"
|
||||
HARDEN_LDFLAGS="-Wl,-z,relro -Wl,-z,now -pie"
|
||||
CFLAGS="$CFLAGS $HARDEN_CFLAGS"
|
||||
LDFLAGS="$LDFLAGS $HARDEN_LDFLAGS"
|
||||
AC_MSG_NOTICE([Hardening flags enabled: $HARDEN_CFLAGS])
|
||||
fi
|
||||
|
||||
# ==================== НАДЁЖНЫЙ ДЕТЕКТ WINDOWS ====================
|
||||
AC_MSG_CHECKING([for Windows])
|
||||
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
|
||||
|
||||
Executable
+284
@@ -0,0 +1,284 @@
|
||||
#!/bin/bash
|
||||
# cross-build-win.sh — кросс-компиляция uTun для Windows 10 из Linux
|
||||
# Использование: ./cross-build-win.sh [--clean]
|
||||
set -euo pipefail
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
step() { echo -e "${CYAN}===${NC} $*"; }
|
||||
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARNING]${NC} $*"; }
|
||||
fatal() { echo -e "${RED}FATAL:${NC} $*" >&2; exit 1; }
|
||||
|
||||
trap 'fatal "script failed at line $LINENO (exit code: $?)"' ERR
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
BUILD_DIR="$SCRIPT_DIR/build-win"
|
||||
PREFIX="$BUILD_DIR/prefix"
|
||||
|
||||
MINGW_CC="x86_64-w64-mingw32-gcc"
|
||||
MINGW_AR="x86_64-w64-mingw32-ar"
|
||||
MINGW_RANLIB="x86_64-w64-mingw32-ranlib"
|
||||
|
||||
MINGW_PKGS="gcc-mingw-w64-x86-64-posix g++-mingw-w64-x86-64-posix"
|
||||
OPENSSL_VER="3.0.16"
|
||||
OPENSSL_URL="https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_VER}/openssl-${OPENSSL_VER}.tar.gz"
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 0. Аргументы
|
||||
# -------------------------------------------------------------------
|
||||
CLEAN=0
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--clean) CLEAN=1; shift ;;
|
||||
*) fatal "unknown option: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "$CLEAN" -eq 1 ]; then
|
||||
echo -e "${YELLOW}Cleaning build directory: $BUILD_DIR${NC}"
|
||||
rm -rf "$BUILD_DIR"
|
||||
fi
|
||||
|
||||
echo -e "${CYAN}==============================================${NC}"
|
||||
echo -e "${CYAN} uTun Cross-Compilation for Windows 10${NC}"
|
||||
echo -e "${CYAN}==============================================${NC}"
|
||||
echo ""
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 1. Проверка системы
|
||||
# -------------------------------------------------------------------
|
||||
step "Checking system..."
|
||||
if [ "$(uname -s)" != "Linux" ]; then
|
||||
fatal "This script only runs on Linux. Detected: $(uname -s)"
|
||||
fi
|
||||
if [ "$(uname -m)" != "x86_64" ]; then
|
||||
fatal "This script requires x86_64 architecture. Detected: $(uname -m)"
|
||||
fi
|
||||
|
||||
if ! command -v apt-get &>/dev/null; then
|
||||
fatal "apt-get not found. Only Debian/Ubuntu are supported."
|
||||
fi
|
||||
ok "System: Linux x86_64 with apt"
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 2. Установка пакетов
|
||||
# -------------------------------------------------------------------
|
||||
step "Installing required packages..."
|
||||
|
||||
need_install=()
|
||||
for pkg in $MINGW_PKGS; do
|
||||
if ! dpkg -s "$pkg" &>/dev/null 2>&1; then
|
||||
need_install+=("$pkg")
|
||||
fi
|
||||
done
|
||||
if ! command -v wget &>/dev/null && ! command -v curl &>/dev/null; then
|
||||
need_install+=("wget")
|
||||
fi
|
||||
|
||||
if [ ${#need_install[@]} -gt 0 ]; then
|
||||
echo " Installing: ${need_install[*]}"
|
||||
sudo apt-get update -qq || fatal "apt-get update failed"
|
||||
sudo apt-get install -y -qq "${need_install[@]}" || fatal "apt-get install failed"
|
||||
ok "Packages installed: ${need_install[*]}"
|
||||
else
|
||||
ok "All packages already installed"
|
||||
fi
|
||||
|
||||
# Проверка компилятора
|
||||
for tool in "$MINGW_CC" "$MINGW_AR" "$MINGW_RANLIB"; do
|
||||
if ! command -v "$tool" &>/dev/null; then
|
||||
fatal "$tool not found after package installation"
|
||||
fi
|
||||
done
|
||||
ok "Cross-compiler: $($MINGW_CC --version | head -1)"
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 3. Кросс-компиляция OpenSSL
|
||||
# -------------------------------------------------------------------
|
||||
OPENSSL_SRC="$BUILD_DIR/openssl-${OPENSSL_VER}"
|
||||
LIBCRYPTO="$PREFIX/lib/libcrypto.a"
|
||||
|
||||
if [ -f "$LIBCRYPTO" ]; then
|
||||
ok "OpenSSL already built: $LIBCRYPTO"
|
||||
else
|
||||
step "Building OpenSSL ${OPENSSL_VER} for Windows..."
|
||||
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
TARBALL="$BUILD_DIR/openssl-${OPENSSL_VER}.tar.gz"
|
||||
if [ ! -f "$TARBALL" ]; then
|
||||
echo " Downloading OpenSSL ${OPENSSL_VER}..."
|
||||
if command -v wget &>/dev/null; then
|
||||
wget -q --show-progress -O "$TARBALL" "$OPENSSL_URL" || fatal "wget download failed for $OPENSSL_URL"
|
||||
else
|
||||
curl -L --progress-bar -o "$TARBALL" "$OPENSSL_URL" || fatal "curl download failed for $OPENSSL_URL"
|
||||
fi
|
||||
ok "Downloaded: $TARBALL"
|
||||
fi
|
||||
|
||||
if [ ! -d "$OPENSSL_SRC" ]; then
|
||||
echo " Extracting..."
|
||||
tar xzf "$TARBALL" -C "$BUILD_DIR" || fatal "tar extract failed"
|
||||
fi
|
||||
|
||||
echo " Configuring OpenSSL for mingw64..."
|
||||
cd "$OPENSSL_SRC"
|
||||
./Configure --cross-compile-prefix=x86_64-w64-mingw32- \
|
||||
mingw64 no-shared no-tests \
|
||||
--prefix="$PREFIX" --libdir="$PREFIX/lib" --openssldir="$PREFIX/ssl" \
|
||||
|| fatal "OpenSSL configure failed (see output above)"
|
||||
ok "OpenSSL configured"
|
||||
|
||||
echo " Compiling OpenSSL..."
|
||||
make -j"$(nproc)" || fatal "OpenSSL make failed"
|
||||
echo " Installing OpenSSL..."
|
||||
make install_sw >/dev/null || fatal "OpenSSL make install_sw failed"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# OpenSSL может установить в lib64/ вместо lib/ — поправим
|
||||
if [ ! -f "$LIBCRYPTO" ] && [ -f "$PREFIX/lib64/libcrypto.a" ]; then
|
||||
ln -sf lib64 "$PREFIX/lib" 2>/dev/null || cp -r "$PREFIX/lib64" "$PREFIX/lib" 2>/dev/null || true
|
||||
fi
|
||||
if [ ! -f "$LIBCRYPTO" ]; then
|
||||
fatal "libcrypto.a was not found at $PREFIX/lib/ or $PREFIX/lib64/"
|
||||
fi
|
||||
ok "OpenSSL built: $LIBCRYPTO"
|
||||
fi
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 4. Сборка libuasync.a
|
||||
# -------------------------------------------------------------------
|
||||
step "Building libuasync.a..."
|
||||
|
||||
LIB_OBJDIR="$BUILD_DIR/lib-objs"
|
||||
mkdir -p "$LIB_OBJDIR"
|
||||
|
||||
# Все .c файлы из lib/Makefile.am + myip.c (реализация get_my_ip)
|
||||
LIB_SOURCES=(
|
||||
u_async.c ll_queue.c memory_pool.c debug_config.c timeout_heap.c
|
||||
sha256.c mem.c serialize.c swm_min.c getmyip.c radix.c tcp_io.c
|
||||
myip.c platform_compat.c socket_compat.c
|
||||
liblmdb/mdb.c liblmdb/midl.c
|
||||
)
|
||||
|
||||
LIB_CFLAGS="-D_ISOC99_SOURCE -DDEBUG_OUTPUT_STDERR -DUSE_OPENSSL"
|
||||
LIB_CFLAGS+=" -I$SCRIPT_DIR/lib -I$SCRIPT_DIR/src -I$SCRIPT_DIR/lib/liblmdb"
|
||||
LIB_CFLAGS+=" -I$PREFIX/include"
|
||||
LIB_CFLAGS+=" -g -O2 -Wno-deprecated-declarations"
|
||||
|
||||
for src in "${LIB_SOURCES[@]}"; do
|
||||
obj="$LIB_OBJDIR/$(basename "${src%.c}.o")"
|
||||
echo " CC lib/$src"
|
||||
"$MINGW_CC" $LIB_CFLAGS -c "$SCRIPT_DIR/lib/$src" -o "$obj" \
|
||||
|| fatal "compilation failed: lib/$src"
|
||||
done
|
||||
|
||||
"$MINGW_AR" crs "$BUILD_DIR/libuasync.a" "$LIB_OBJDIR"/*.o \
|
||||
|| fatal "ar failed for libuasync.a"
|
||||
"$MINGW_RANLIB" "$BUILD_DIR/libuasync.a" 2>/dev/null || true
|
||||
ok "libuasync.a ($(wc -c < "$BUILD_DIR/libuasync.a") bytes)"
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 5. Сборка объектных файлов для utun.exe
|
||||
# -------------------------------------------------------------------
|
||||
step "Compiling utun source files..."
|
||||
|
||||
SRC_OBJDIR="$BUILD_DIR/src-objs"
|
||||
mkdir -p "$SRC_OBJDIR"
|
||||
|
||||
# Все .c файлы из src/Makefile.am (utun_CORE_SOURCES)
|
||||
SRC_SOURCES=(
|
||||
utun.c utun_instance.c config_parser.c config_updater.c
|
||||
route_lib.c route6_lib.c topo_bgp.c route_ping.c
|
||||
topo_node.c topo_node_lmdb.c route_connectivity.c conn_mgr.c
|
||||
routing.c tun_if.c tun_route.c tun_linux.c tun_freebsd.c tun_windows.c
|
||||
etcp.c etcp_connections.c etcp_bbr.c etcp_loadbalancer.c
|
||||
etcp_debug.c etcp_dump.c secure_channel.c crc32.c
|
||||
stcp_link.c stcp.c stcp_server.c stcp_client.c
|
||||
pkt_normalizer.c packet_dump.c etcp_api.c
|
||||
control_server.c msg_transport.c
|
||||
firewall.c eim_nat.c nat_transport.c dummynet.c
|
||||
etcp_router.c
|
||||
proxy/udp_proxy.c proxy/socks_proxy.c
|
||||
lwip_tcp/lwip_pbuf.c
|
||||
)
|
||||
|
||||
SRC_CFLAGS="-DUSE_OPENSSL"
|
||||
SRC_CFLAGS+=" -I$SCRIPT_DIR/src -I$SCRIPT_DIR/lib -I$SCRIPT_DIR/lib/liblmdb"
|
||||
SRC_CFLAGS+=" -I$SCRIPT_DIR/tinycrypt/lib/include -I$SCRIPT_DIR/tinycrypt/lib/source"
|
||||
SRC_CFLAGS+=" -I$PREFIX/include"
|
||||
SRC_CFLAGS+=" -g -O2 -Wno-deprecated-declarations"
|
||||
|
||||
TOTAL=${#SRC_SOURCES[@]}
|
||||
CURRENT=0
|
||||
FAILED=()
|
||||
|
||||
for src in "${SRC_SOURCES[@]}"; do
|
||||
CURRENT=$((CURRENT + 1))
|
||||
# Имя объектного файла: заменяем / на _ (для proxy/ и lwip_tcp/)
|
||||
obj_name="$(echo "$src" | tr '/' '_')"
|
||||
obj="$SRC_OBJDIR/${obj_name%.c}.o"
|
||||
printf " [%3d/%3d] CC src/%s\n" "$CURRENT" "$TOTAL" "$src"
|
||||
if ! "$MINGW_CC" $SRC_CFLAGS -c "$SCRIPT_DIR/src/$src" -o "$obj" 2>&1; then
|
||||
FAILED+=("src/$src")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#FAILED[@]} -gt 0 ]; then
|
||||
echo ""
|
||||
fatal "compilation failed for: ${FAILED[*]}"
|
||||
fi
|
||||
ok "All $TOTAL source files compiled"
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 6. Линковка utun.exe
|
||||
# -------------------------------------------------------------------
|
||||
step "Linking utun.exe..."
|
||||
|
||||
"$MINGW_CC" -o "$SCRIPT_DIR/utun.exe" \
|
||||
"$SRC_OBJDIR"/*.o \
|
||||
"$BUILD_DIR/libuasync.a" \
|
||||
"$LIBCRYPTO" \
|
||||
-lws2_32 -liphlpapi -ladvapi32 -lbcrypt -lcrypt32 -lpthread -lm \
|
||||
-static \
|
||||
|| fatal "linking failed (see undefined references above)"
|
||||
|
||||
ok "utun.exe ($(wc -c < "$SCRIPT_DIR/utun.exe") bytes)"
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 7. Копирование wintun.dll и финал
|
||||
# -------------------------------------------------------------------
|
||||
echo ""
|
||||
if [ -f "$SCRIPT_DIR/lib/wintun.dll" ]; then
|
||||
cp "$SCRIPT_DIR/lib/wintun.dll" "$SCRIPT_DIR/"
|
||||
ok "wintun.dll copied to project root"
|
||||
else
|
||||
warn "wintun.dll not found in lib/ — place it manually alongside utun.exe"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}==============================================${NC}"
|
||||
echo -e "${GREEN} Build successful!${NC}"
|
||||
echo -e "${GREEN} Binary: utun.exe${NC}"
|
||||
echo -e "${GREEN}==============================================${NC}"
|
||||
echo ""
|
||||
|
||||
if [ -f "$SCRIPT_DIR/lib/wintun.dll" ]; then
|
||||
echo "Run on Windows:"
|
||||
echo " 1. Install Wintun driver (https://www.wintun.net)"
|
||||
echo " 2. Copy utun.exe + wintun.dll to the same directory"
|
||||
echo " 3. Run: utun.exe -f -p utun.pid -l utun.log"
|
||||
else
|
||||
echo "Run on Windows:"
|
||||
echo " 1. Install Wintun driver (https://www.wintun.net)"
|
||||
echo " 2. Copy utun.exe + wintun.dll to the same directory"
|
||||
echo " 3. Run: utun.exe -f -p utun.pid -l utun.log"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,35 @@
|
||||
План доработок
|
||||
[ ] продумать архитектуру репликации таблицы узлов группы
|
||||
[ ] добавить узлы хелперы (для проксирования трафика)
|
||||
|
||||
|
||||
Категории узлов:
|
||||
- узлы без нат []
|
||||
- узлы с nat [restricted]
|
||||
|
||||
Узлы за nat сами подключаются к прямым
|
||||
|
||||
Узлы за eim nat сами подключаются и сообщают обновление адреса.
|
||||
|
||||
Прямые узлы должны сами должны объединиться в сеть следующим образом:
|
||||
|
||||
Инит: база содержит RTT по узлам.
|
||||
выбираем n=16 узлов с минимальным rtt. запускаем подключение к ним.
|
||||
синхронизируем роутинг таблицу с ними (обмен маршрутами должен инициироваться сразу после подключения).
|
||||
при потере связи узлы через nat должны найти альтернативы.
|
||||
|
||||
в таблице обновляется
|
||||
|
||||
как обмен завершен
|
||||
|
||||
Нужно собирать и обновлять информацию о том
|
||||
- к каким публичным нодам подключены nat узлы.
|
||||
|
||||
- метрика доступности узла - как давно не менялся ip:port - собираем с клиентов. вместе с рейтигном (каждый клиент по узлу фолрмирует свой рейтинг кандидата в суперузлы и отправляет подписанный рейтинг узлу)
|
||||
рейтинг включает:
|
||||
- число разных часов фигурирует в замерах
|
||||
- число разных дат фигурирует в замерах
|
||||
- гистограмма rtt: 0-2ms, 2-4ms, 4-6ms, 6-10ms, 10-13ms, 14-18ms, 18-25ms, 25-35ms, 35-50ms, 50-75ms, 75-100ms, 100-130ms, 130-160ms, 160+ms
|
||||
- гистограмма потерь: 0-1% 1-2% 2-4% 4-8% >8% (обновляется каждый час)
|
||||
|
||||
Сделать подключение к группе
|
||||
+32
-8
@@ -22,12 +22,9 @@ utun поддерживает встроенный ipv4 роутинг, може
|
||||
Есть статические подключения (которые прописаны в конфиге) и есть динамические - которые устанавливаются в процессе по необходимости.
|
||||
Обмен маршрутами происходит только через статические подключения.
|
||||
|
||||
toto:
|
||||
- задать ограничение bandwidth для каждого сокета (общее).
|
||||
|
||||
|
||||
## etcp_connections:
|
||||
- обслуживает encrypt/decrypt и установку защищенного подключения.
|
||||
- обслуживает encrypt/decrypt и установку защищенного подключения per-link.
|
||||
- для одного etcp соединения может испольоваться несколько подключений одновременно (load balancing / filover)
|
||||
|
||||
## etcp_loadbalancer:
|
||||
@@ -50,12 +47,39 @@ toto:
|
||||
- обеспечивает ретрансмиссии при передаче и сборку в правильной последовательности при приёме
|
||||
- взаимодействует с pkt_normalizer и с udp сокетами для отправки-получения пакетов
|
||||
|
||||
## etcp_metric:
|
||||
- обеспечивает обновление метрик каналов etcp_connections
|
||||
- вызывается при получении ack
|
||||
## stcp_* :
|
||||
- аналогично etcp только упрощенный вариант, используя tcp в качестве транспорта.
|
||||
|
||||
## etcp_router:
|
||||
- устанавливает связь между узлами, маршрутизирует трафик через цепочку узлов
|
||||
|
||||
## route_lib, route6_lib :
|
||||
- управляет таблицами маршрутизации (ipv4/ipv6 lookup)
|
||||
|
||||
## route_node:
|
||||
- хранит список доступных узлов (полученные от соседей) и их адреса/pubkeys для подключения.
|
||||
- содержит апи для поиска-чтения-обновления узла
|
||||
|
||||
## route_bgp:
|
||||
- составляет карту маршрутизации между узлами и обновляет ее при подключении-отключении узлов
|
||||
|
||||
## route_ping:
|
||||
- модуль для определения типа nat (strict или eim) используя вспомогательный узел
|
||||
|
||||
Библиотеки:
|
||||
## uasync
|
||||
- модуль для асинхронной работы.
|
||||
реализует сокеты, таймауты и сигналы из других потоков
|
||||
- быстро работает с большим числом таймаутов (timeout_heap)
|
||||
|
||||
## ll_queue
|
||||
- модуль работы с очередями, использует uasync.
|
||||
- реализует fifo/lifo очереди, быстрый поиск по индексу, различные callback - сигнализация о состояниях очереди,
|
||||
- congestion control: если много желающих записать -> очередь round-robin
|
||||
|
||||
|
||||
Тесты:
|
||||
# Правила работы с очередями в тестах:
|
||||
- для добавления в очередь надо использовать queue_wait_threshold(q,0,0,arg) и добавлять по одному пакету, каждый раз дожидаясь когда очередь станет пустой.
|
||||
- для добавления в очередь надо использовать queue_waiter_wait и queue_set_threshold, добавлять по одному пакету, каждый раз дожидаясь когда очередь станет пустой.
|
||||
- для получения надо использовать queue_set_callback + queue_resume_callback
|
||||
|
||||
@@ -136,7 +136,7 @@ link_state: 0=just init, 1=handshake, 2=reconnect, 3=connected.
|
||||
- local_link_id: uint8_t (0-255), назначается отправителем
|
||||
- sock_id: uint8_t, уникальный ID сокета отправителя
|
||||
- only_local: uint8_t, флаг "только локальные подключения"
|
||||
- type: uint8_t, тип сокета (CFG_SERVER_TYPE_PUBLIC/NAT/PRIVATE)
|
||||
- type: uint8_t, тип сокета (CFG_SERVER_TYPE_UNKNOWN=0/PUBLIC=1/NAT=2/PRIVATE=3/LOCAL=4)
|
||||
- random_padding: случайные байты до размера handshake (защита от fingerprinting)
|
||||
- salt + obfuscated_pubkey: публичный ключ с обфускацией (не шифруется AES,
|
||||
но скрывается XOR с salt и собственным публичным ключом). Передаётся в конце пакета
|
||||
|
||||
-164
@@ -1,164 +0,0 @@
|
||||
config.h
|
||||
lib\debug_config.c
|
||||
lib\debug_config.h
|
||||
lib\ll_queue.c
|
||||
lib\ll_queue.h
|
||||
lib\mem.c
|
||||
lib\mem.h
|
||||
lib\memory_pool.c
|
||||
lib\memory_pool.h
|
||||
lib\platform_compat.c
|
||||
lib\platform_compat.h
|
||||
lib\sha256.c
|
||||
lib\sha256.h
|
||||
lib\socket_compat.c
|
||||
lib\socket_compat.h
|
||||
lib\timeout_heap.c
|
||||
lib\timeout_heap.h
|
||||
lib\u_async.c
|
||||
lib\u_async.h
|
||||
lib\wintun.h
|
||||
net_emulator\net_emulator.c
|
||||
net_emulator\net_emulator.h
|
||||
src\config_parser.c
|
||||
src\config_parser.h
|
||||
src\config_updater.c
|
||||
src\config_updater.h
|
||||
src\control_server.c
|
||||
src\control_server.h
|
||||
src\crc32.c
|
||||
src\crc32.h
|
||||
src\dummynet.c
|
||||
src\dummynet.h
|
||||
src\etcp.c
|
||||
src\etcp.h
|
||||
src\etcp_api.c
|
||||
src\etcp_api.h
|
||||
src\etcp_connections.c
|
||||
src\etcp_connections.h
|
||||
src\etcp_debug.c
|
||||
src\etcp_debug.h
|
||||
src\etcp_loadbalancer.c
|
||||
src\etcp_loadbalancer.h
|
||||
src\packet_dump.c
|
||||
src\packet_dump.h
|
||||
src\pkt_normalizer.c
|
||||
src\pkt_normalizer.h
|
||||
src\route_bgp.c
|
||||
src\route_bgp.h
|
||||
src\route_lib.c
|
||||
src\route_lib.h
|
||||
src\routing.c
|
||||
src\routing.h
|
||||
src\secure_channel.c
|
||||
src\secure_channel.h
|
||||
src\tun_if.c
|
||||
src\tun_if.h
|
||||
src\tun_linux.c
|
||||
src\tun_route.c
|
||||
src\tun_route.h
|
||||
src\tun_windows.c
|
||||
src\utun.c
|
||||
src\utun_instance.c
|
||||
src\utun_instance.h
|
||||
tests\bench_timeout_heap.c
|
||||
tests\bench_uasync_timeouts.c
|
||||
tests\debug_full_test.c
|
||||
tests\debug_performance.c
|
||||
tests\debug_simple.c
|
||||
tests\detailed_test.c
|
||||
tests\simple_test.c
|
||||
tests\test_bgp_route_exchange.c
|
||||
tests\test_config_debug.c
|
||||
tests\test_control_server.c
|
||||
tests\test_control_simple.c
|
||||
tests\test_crash_debug.c
|
||||
tests\test_crypto.c
|
||||
tests\test_debug_categories.c
|
||||
tests\test_dummynet.c
|
||||
tests\test_ecc_encrypt.c
|
||||
tests\test_etcp_100_packets.c
|
||||
tests\test_etcp_api.c
|
||||
tests\test_etcp_crypto.c
|
||||
tests\test_etcp_dummynet.c
|
||||
tests\test_etcp_exit.c
|
||||
tests\test_etcp_link_id.c
|
||||
tests\test_etcp_minimal.c
|
||||
tests\test_etcp_simple_traffic.c
|
||||
tests\test_etcp_two_instances.c
|
||||
tests\test_intensive_memory_pool.c
|
||||
tests\test_intensive_memory_pool_new.c
|
||||
tests\test_ll_queue.c
|
||||
tests\test_memory_pool_and_config.c
|
||||
tests\test_minimal.c
|
||||
tests\test_minimal_exit.c
|
||||
tests\test_offset.c
|
||||
tests\test_packet_dump.c
|
||||
tests\test_pkt_normalizer_etcp.c
|
||||
tests\test_pkt_normalizer_standalone.c
|
||||
tests\test_poll_exact.c
|
||||
tests\test_poll_multi.c
|
||||
tests\test_route_lib.c
|
||||
tests\test_routing_mesh.c
|
||||
tests\test_simple.c
|
||||
tests\test_simple2.c
|
||||
tests\test_socket.c
|
||||
tests\test_utils.h
|
||||
tests\test_u_async_comprehensive.c
|
||||
tests\test_u_async_performance.c
|
||||
tests\track_test.c
|
||||
tests\working_crypto_test.c
|
||||
tinycrypt\lib\include\tinycrypt\aes.h
|
||||
tinycrypt\lib\include\tinycrypt\cbc_mode.h
|
||||
tinycrypt\lib\include\tinycrypt\ccm_mode.h
|
||||
tinycrypt\lib\include\tinycrypt\cmac_mode.h
|
||||
tinycrypt\lib\include\tinycrypt\constants.h
|
||||
tinycrypt\lib\include\tinycrypt\ctr_mode.h
|
||||
tinycrypt\lib\include\tinycrypt\ctr_prng.h
|
||||
tinycrypt\lib\include\tinycrypt\ecc.h
|
||||
tinycrypt\lib\include\tinycrypt\ecc_dh.h
|
||||
tinycrypt\lib\include\tinycrypt\ecc_dsa.h
|
||||
tinycrypt\lib\include\tinycrypt\ecc_platform_specific.h
|
||||
tinycrypt\lib\include\tinycrypt\hmac.h
|
||||
tinycrypt\lib\include\tinycrypt\hmac_prng.h
|
||||
tinycrypt\lib\include\tinycrypt\sha256.h
|
||||
tinycrypt\lib\include\tinycrypt\utils.h
|
||||
tinycrypt\lib\source\aes_decrypt.c
|
||||
tinycrypt\lib\source\aes_encrypt.c
|
||||
tinycrypt\lib\source\cbc_mode.c
|
||||
tinycrypt\lib\source\ccm_mode.c
|
||||
tinycrypt\lib\source\cmac_mode.c
|
||||
tinycrypt\lib\source\ctr_mode.c
|
||||
tinycrypt\lib\source\ctr_prng.c
|
||||
tinycrypt\lib\source\ecc.c
|
||||
tinycrypt\lib\source\ecc_dh.c
|
||||
tinycrypt\lib\source\ecc_dsa.c
|
||||
tinycrypt\lib\source\ecc_platform_specific.c
|
||||
tinycrypt\lib\source\hmac.c
|
||||
tinycrypt\lib\source\hmac_prng.c
|
||||
tinycrypt\lib\source\sha256.c
|
||||
tinycrypt\lib\source\utils.c
|
||||
tinycrypt\tests\test_aes.c
|
||||
tinycrypt\tests\test_cbc_mode.c
|
||||
tinycrypt\tests\test_ccm_mode.c
|
||||
tinycrypt\tests\test_client_server.c
|
||||
tinycrypt\tests\test_cmac_mode.c
|
||||
tinycrypt\tests\test_ctr_mode.c
|
||||
tinycrypt\tests\test_ctr_prng.c
|
||||
tinycrypt\tests\test_ecc_dh.c
|
||||
tinycrypt\tests\test_ecc_dsa.c
|
||||
tinycrypt\tests\test_ecc_utils.c
|
||||
tinycrypt\tests\test_hmac.c
|
||||
tinycrypt\tests\test_hmac_prng.c
|
||||
tinycrypt\tests\test_sha256.c
|
||||
tinycrypt\tests\include\test_ecc_utils.h
|
||||
tinycrypt\tests\include\test_utils.h
|
||||
tools\bping\bping.c
|
||||
tools\etcpmon\etcpmon_client.c
|
||||
tools\etcpmon\etcpmon_client.h
|
||||
tools\etcpmon\etcpmon_graph.c
|
||||
tools\etcpmon\etcpmon_graph.h
|
||||
tools\etcpmon\etcpmon_gui.c
|
||||
tools\etcpmon\etcpmon_gui.h
|
||||
tools\etcpmon\etcpmon_main.c
|
||||
tools\etcpmon\etcpmon_protocol.h
|
||||
+7
-2
@@ -28,14 +28,19 @@ libuasync_a_SOURCES = \
|
||||
serialize.c \
|
||||
serialize.h \
|
||||
tcp_io.c \
|
||||
tcp_io.h
|
||||
tcp_io.h \
|
||||
liblmdb/mdb.c \
|
||||
liblmdb/midl.c \
|
||||
liblmdb/lmdb.h \
|
||||
liblmdb/midl.h
|
||||
|
||||
libuasync_a_CFLAGS = \
|
||||
-D_ISOC99_SOURCE \
|
||||
-DDEBUG_OUTPUT_STDERR \
|
||||
-g \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/lib
|
||||
-I$(top_srcdir)/lib \
|
||||
-I$(srcdir)/liblmdb
|
||||
|
||||
# Clean build directory
|
||||
clean-local:
|
||||
|
||||
+25
-28
@@ -128,6 +128,8 @@ static const struct {
|
||||
{"keepalive", DEBUG_CATEGORY_KEEPALIVE},
|
||||
{"etcp_route", DEBUG_CATEGORY_ETCPROUTE},
|
||||
{"bbr", DEBUG_CATEGORY_BBR},
|
||||
{"etcp_dump", DEBUG_CATEGORY_ETCP_DUMP},
|
||||
{"connectivity", DEBUG_CATEGORY_CONNECTIVITY},
|
||||
{"all", DEBUG_CATEGORY_ALL},
|
||||
{NULL, DEBUG_CATEGORY_NONE}
|
||||
};
|
||||
@@ -136,13 +138,14 @@ static const struct {
|
||||
const char* name;
|
||||
debug_level_t level;
|
||||
} g_level_table[] = {
|
||||
{"none", DEBUG_LEVEL_NONE},
|
||||
{"error", DEBUG_LEVEL_ERROR},
|
||||
{"warn", DEBUG_LEVEL_WARN},
|
||||
{"info", DEBUG_LEVEL_INFO},
|
||||
{"debug", DEBUG_LEVEL_DEBUG},
|
||||
{"trace", DEBUG_LEVEL_TRACE},
|
||||
{NULL, DEBUG_LEVEL_NONE}
|
||||
{"disabled", DEBUG_LEVEL_DISABLED},
|
||||
{"none", DEBUG_LEVEL_NONE},
|
||||
{"error", DEBUG_LEVEL_ERROR},
|
||||
{"warn", DEBUG_LEVEL_WARN},
|
||||
{"info", DEBUG_LEVEL_INFO},
|
||||
{"debug", DEBUG_LEVEL_DEBUG},
|
||||
{"trace", DEBUG_LEVEL_TRACE},
|
||||
{NULL, DEBUG_LEVEL_NONE}
|
||||
};
|
||||
|
||||
debug_category_t get_category_by_name(const char* name) {
|
||||
@@ -162,7 +165,7 @@ debug_level_t debug_level_from_name(const char* name) {
|
||||
return g_level_table[i].level;
|
||||
}
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid debug level '%s' (valid: none,error,warn,info,debug,trace)", name);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid debug level '%s' (valid: disabled,none,error,warn,info,debug,trace)", name);
|
||||
return DEBUG_LEVEL_INFO; /* fallback */
|
||||
}
|
||||
|
||||
@@ -220,15 +223,13 @@ void debug_config_init(void) {
|
||||
|
||||
// Dual output defaults
|
||||
g_debug_config.file_output = NULL; // No file by default
|
||||
g_debug_config.file_level = DEBUG_LEVEL_TRACE; // File gets everything
|
||||
g_debug_config.console_level = DEBUG_LEVEL_INFO; // Console gets INFO+
|
||||
g_debug_config.console_enabled = 1; // Console enabled
|
||||
g_debug_config.thread_marker = 0; // No thread marker by default
|
||||
}
|
||||
|
||||
/* Set debug level */
|
||||
void debug_set_level(debug_level_t level) {
|
||||
g_debug_config.level = level;
|
||||
g_debug_config.console_level = level;
|
||||
}
|
||||
|
||||
/* Set debug level for specific category (0 = disabled, otherwise uses that level) */
|
||||
@@ -280,16 +281,6 @@ void debug_set_output_file(const char* file_path) {
|
||||
}
|
||||
|
||||
/* Set console log level */
|
||||
void debug_set_console_level(debug_level_t level) {
|
||||
g_debug_config.console_level = level;
|
||||
}
|
||||
|
||||
/* Set file log level */
|
||||
void debug_set_file_level(debug_level_t level) {
|
||||
g_debug_config.file_level = level;
|
||||
}
|
||||
|
||||
/* Enable/disable console output */
|
||||
void debug_enable_console(int enable) {
|
||||
g_debug_config.console_enabled = enable;
|
||||
}
|
||||
@@ -343,6 +334,9 @@ int debug_should_output(debug_level_t level, debug_category_t category_idx) {
|
||||
}
|
||||
|
||||
debug_level_t cat_level = g_debug_config.category_levels[category_idx];
|
||||
if (cat_level == DEBUG_LEVEL_DISABLED) {
|
||||
return 0;
|
||||
}
|
||||
if (cat_level == DEBUG_LEVEL_NONE) {
|
||||
cat_level = g_debug_config.level;
|
||||
}
|
||||
@@ -395,6 +389,12 @@ void debug_output(debug_level_t level, debug_category_t category_idx,
|
||||
offset += snprintf(buffer + offset, remaining, "[%s-%03ld.%03ld] ", time_str, tv.tv_usec / 1000, tv.tv_usec % 1000);
|
||||
remaining = BUFFER_SIZE - offset;
|
||||
|
||||
/* Add thread marker if set */
|
||||
if (g_debug_config.thread_marker > 0) {
|
||||
offset += snprintf(buffer + offset, remaining, "[%d] ", g_debug_config.thread_marker);
|
||||
remaining = BUFFER_SIZE - offset;
|
||||
}
|
||||
|
||||
/* Add level */
|
||||
const char* level_name = get_level_name(level);
|
||||
offset += snprintf(buffer + offset, remaining, "[%s] ", level_name);
|
||||
@@ -433,15 +433,12 @@ void debug_output(debug_level_t level, debug_category_t category_idx,
|
||||
va_end(args);
|
||||
|
||||
/* Output to console if enabled and level is sufficient */
|
||||
if (g_debug_config.console_enabled && level <= g_debug_config.console_level) {
|
||||
fprintf(stdout, "%s", buffer);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
/* Output to file if open and level is sufficient */
|
||||
if (g_debug_config.file_output && level <= g_debug_config.file_level) {
|
||||
if (g_debug_config.file_output) {
|
||||
fprintf(g_debug_config.file_output, "%s", buffer);
|
||||
fflush(g_debug_config.file_output);
|
||||
} else if (g_debug_config.console_enabled) {
|
||||
fprintf(stdout, "%s", buffer);
|
||||
fflush(stdout);
|
||||
}
|
||||
#undef BUFFER_SIZE
|
||||
}
|
||||
|
||||
+14
-13
@@ -26,12 +26,13 @@ extern "C" {
|
||||
|
||||
/* Debug levels */
|
||||
typedef enum {
|
||||
DEBUG_LEVEL_NONE = 0, // No debug output
|
||||
DEBUG_LEVEL_ERROR = 1, // Errors only
|
||||
DEBUG_LEVEL_WARN = 2, // Warnings and errors
|
||||
DEBUG_LEVEL_INFO = 3, // Info, warnings, errors
|
||||
DEBUG_LEVEL_DEBUG = 4, // Full debug output
|
||||
DEBUG_LEVEL_TRACE = 5 // Trace everything
|
||||
DEBUG_LEVEL_DISABLED = -1, // Category completely disabled, never outputs
|
||||
DEBUG_LEVEL_NONE = 0, // Fallback to global level
|
||||
DEBUG_LEVEL_ERROR = 1, // Errors only
|
||||
DEBUG_LEVEL_WARN = 2, // Warnings and errors
|
||||
DEBUG_LEVEL_INFO = 3, // Info, warnings, errors
|
||||
DEBUG_LEVEL_DEBUG = 4, // Full debug output
|
||||
DEBUG_LEVEL_TRACE = 5 // Trace everything
|
||||
} debug_level_t;
|
||||
|
||||
/* Debug categories as indices (0-based) */
|
||||
@@ -61,7 +62,9 @@ typedef int debug_category_t;
|
||||
#define DEBUG_CATEGORY_KEEPALIVE 21 // Link keepalive logic
|
||||
#define DEBUG_CATEGORY_ETCPROUTE 22 // ETCP routing
|
||||
#define DEBUG_CATEGORY_BBR 23 // BBR congestion control
|
||||
#define DEBUG_CATEGORY_COUNT 24 // Total number of categories
|
||||
#define DEBUG_CATEGORY_ETCP_DUMP 24 // ETCP packet dump
|
||||
#define DEBUG_CATEGORY_CONNECTIVITY 25 // Connection/handshake lifecycle
|
||||
#define DEBUG_CATEGORY_COUNT 26 // Total number of categories
|
||||
#define DEBUG_CATEGORY_ALL (-1) // special value for all categories
|
||||
|
||||
/* Debug configuration structure */
|
||||
@@ -75,9 +78,8 @@ typedef struct {
|
||||
|
||||
// Dual output support
|
||||
FILE* file_output; // File handle for log file (NULL if not open)
|
||||
debug_level_t file_level; // Log level for file output
|
||||
debug_level_t console_level; // Log level for console output
|
||||
int console_enabled; // Enable console output
|
||||
int thread_marker; // Thread identification (0=none, e.g. 2 for uasync)
|
||||
} debug_config_t;
|
||||
|
||||
/* Global debug configuration */
|
||||
@@ -87,6 +89,7 @@ extern debug_config_t g_debug_config;
|
||||
void debug_config_init(void);
|
||||
|
||||
/* Set debug level */
|
||||
// итоговый level = max (global level - здесь задается, category level)
|
||||
void debug_set_level(debug_level_t level);
|
||||
|
||||
/* Set debug level for specific category (0 = disabled, otherwise uses that level) */
|
||||
@@ -106,10 +109,8 @@ void debug_enable_function_name(int enable);
|
||||
void debug_enable_file_line(int enable);
|
||||
void debug_set_output_file(const char* file_path);
|
||||
|
||||
/* Dual output configuration */
|
||||
void debug_set_console_level(debug_level_t level);
|
||||
void debug_set_file_level(debug_level_t level);
|
||||
void debug_enable_file_output(const char* file_path, int truncate);
|
||||
/* Dual output configuration */
|
||||
void debug_enable_file_output(const char* file_path, int truncate);
|
||||
void debug_disable_file_output(void);
|
||||
int debug_reopen_log(void);
|
||||
void debug_enable_console(int enable);
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
mtest
|
||||
mtest[23456]
|
||||
testdb
|
||||
mdb_copy
|
||||
mdb_stat
|
||||
mdb_dump
|
||||
mdb_load
|
||||
*.lo
|
||||
*.[ao]
|
||||
*.so
|
||||
*.exe
|
||||
*[~#]
|
||||
*.bak
|
||||
*.orig
|
||||
*.rej
|
||||
*.gcov
|
||||
*.gcda
|
||||
*.gcno
|
||||
core
|
||||
core.*
|
||||
valgrind.*
|
||||
man/
|
||||
html/
|
||||
@@ -0,0 +1,317 @@
|
||||
LMDB 0.9 Change Log
|
||||
|
||||
LMDB 0.9.36 Engineering
|
||||
ITS#7772 - limit sub-page growth when re-adding existing data
|
||||
ITS#8335 - more input validation for put(MDB_MULTIPLE)
|
||||
ITS#8386 - be a bit more precise that mdb_get retrieves data in intro.doc
|
||||
ITS#8590 - suppress SIGPIPE on Mac OSX for mdb_copy/compact
|
||||
ITS#8739 - don't use fdatasync on FreeBSD 11.0 and older
|
||||
ITS#8824 - mdb_dump: cleanup check for MDB_SUCCESS
|
||||
ITS#9388 - fix mdb_stat page counts for dupsort DBs
|
||||
ITS#10054 - fix write of 2GB values
|
||||
ITS#10108 - mdb_dump: fix comment
|
||||
ITS#10454 - fix metapage sync on Mac OSX
|
||||
ITS#10504 - silence linker waarning on Mac OSX
|
||||
ITS#10522 - reject 0-length values in DUPSORT DBs
|
||||
|
||||
LMDB 0.9.35 Release (2026/01/27)
|
||||
ITS#10434 - Fix typo affecting Mac OSX
|
||||
|
||||
LMDB 0.9.34 Release (2026/01/26)
|
||||
ITS#9564 - fix race condition freeing spilled pages at end of transaction
|
||||
ITS#10222 - Update mdb_dump(1) and mdb_load(1) man pages for append (-a) option
|
||||
ITS#10275 - mdb_load: add -Q option to use NOSYNC
|
||||
ITS#10296 - fix fdatasync on MacOS
|
||||
ITS#10342 - fix memleak in mdb_txn_begin for nested txns
|
||||
ITS#10346 - fix mdb_env_copy2 with values > (2GB-16)
|
||||
ITS#10355 - fix mplay build on musl
|
||||
ITS#10396 - fix mdb_cursor_del0 with multiple DUPSORT cursors
|
||||
ITS#10419 - add support for NetBSD
|
||||
ITS#10420 - add support for Haiku
|
||||
ITS#10421 - mdb_load: check for malicious input
|
||||
|
||||
LMDB 0.9.33 Release (2024/05/21)
|
||||
ITS#9037 mdb_page_search: fix error code when DBI record is missing
|
||||
ITS#10198 For win32, stop passing ignored parameter
|
||||
ITS#10212 Fix meta page usage by read only tools
|
||||
|
||||
LMDB 0.9.32 Release (2024/01/29)
|
||||
ITS#9378 - Add ability to replay log and replay log tool
|
||||
ITS#10095 - partial revert of ITS#9278. The patch was incorrect and introduced numerous race conditions.
|
||||
ITS#10125 - mdb_load: fix cursor reinit in Append mode
|
||||
ITS#10137 - Allow users to define MDB_IDL_LOGN
|
||||
|
||||
LMDB 0.9.31 Release (2023/07/10)
|
||||
ITS#8447 - Fix cursor_put(MDB_CURRENT) on DUPSORT DB with different sized data
|
||||
|
||||
LMDB 0.9.30 Release (2023/02/08)
|
||||
ITS#9806 - LMDB page_split: key threshold depends on page size
|
||||
ITS#9916 - avoid gcc optimization bug on sparc64 linux
|
||||
ITS#9919 - Mark infrequently used functions as cold
|
||||
ITS#9723 - clear C_EOF on cursor with MDB_FIRST_DUP
|
||||
ITS#9030 - Use sys/cachectl.h rather than asm/cachectl.h on mips
|
||||
|
||||
LMDB 0.9.29 Release (2021/03/16)
|
||||
ITS#9461 refix ITS#9376
|
||||
ITS#9500 fix regression from ITS#8662
|
||||
|
||||
LMDB 0.9.28 Release (2021/02/04)
|
||||
ITS#8662 add -a append option to mdb_load
|
||||
|
||||
LMDB 0.9.27 Release (2020/10/26)
|
||||
ITS#9376 fix repeated DUPSORT cursor deletes
|
||||
|
||||
LMDB 0.9.26 Release (2020/08/11)
|
||||
ITS#9278 fix robust mutex cleanup for FreeBSD
|
||||
|
||||
LMDB 0.9.25 Release (2020/01/30)
|
||||
ITS#9068 fix mdb_dump/load backslashes in printable content
|
||||
ITS#9118 add MAP_NOSYNC for FreeBSD
|
||||
ITS#9155 free mt_spill_pgs in non-nested txn on end
|
||||
|
||||
LMDB 0.9.24 Release (2019/07/24)
|
||||
ITS#8969 Tweak mdb_page_split
|
||||
ITS#8975 WIN32 fix writemap set_mapsize crash
|
||||
ITS#9007 Fix loose pages in WRITEMAP
|
||||
|
||||
LMDB 0.9.23 Release (2018/12/19)
|
||||
ITS#8756 Fix loose pages in dirty list
|
||||
ITS#8831 Fix mdb_load flag init
|
||||
ITS#8844 Fix mdb_env_close in forked process
|
||||
Documentation
|
||||
ITS#8857 mdb_cursor_del doesn't invalidate cursor
|
||||
ITS#8908 GET_MULTIPLE etc don't change passed in key
|
||||
|
||||
LMDB 0.9.22 Release (2018/03/22)
|
||||
Fix MDB_DUPSORT alignment bug (ITS#8819)
|
||||
Fix regression with new db from 0.9.19 (ITS#8760)
|
||||
Fix liblmdb to build on Solaris (ITS#8612)
|
||||
Fix delete behavior with DUPSORT DB (ITS#8622)
|
||||
Fix mdb_cursor_get/mdb_cursor_del behavior (ITS#8722)
|
||||
|
||||
LMDB 0.9.21 Release (2017/06/01)
|
||||
Fix xcursor after cursor_del (ITS#8622)
|
||||
|
||||
LMDB 0.9.20 (Withdrawn)
|
||||
Fix mdb_load with escaped plaintext (ITS#8558)
|
||||
Fix mdb_cursor_last / mdb_put interaction (ITS#8557)
|
||||
|
||||
LMDB 0.9.19 Release (2016/12/28)
|
||||
Fix mdb_env_cwalk cursor init (ITS#8424)
|
||||
Fix robust mutexes on Solaris 10/11 (ITS#8339)
|
||||
Tweak Win32 error message buffer
|
||||
Fix MDB_GET_BOTH on non-dup record (ITS#8393)
|
||||
Optimize mdb_drop
|
||||
Fix xcursors after mdb_cursor_del (ITS#8406)
|
||||
Fix MDB_NEXT_DUP after mdb_cursor_del (ITS#8412)
|
||||
Fix mdb_cursor_put resetting C_EOF (ITS#8489)
|
||||
Fix mdb_env_copyfd2 to return EPIPE on SIGPIPE (ITS#8504)
|
||||
Fix mdb_env_copy with empty DB (ITS#8209)
|
||||
Fix behaviors with fork (ITS#8505)
|
||||
Fix mdb_dbi_open with mainDB cursors (ITS#8542)
|
||||
Fix robust mutexes on kFreeBSD (ITS#8554)
|
||||
Fix utf8_to_utf16 error checks (ITS#7992)
|
||||
Fix F_NOCACHE on MacOS, error is non-fatal (ITS#7682)
|
||||
Build
|
||||
Make shared lib suffix overridable (ITS#8481)
|
||||
Documentation
|
||||
Cleanup doxygen nits
|
||||
Note reserved vs actual mem/disk usage
|
||||
|
||||
|
||||
LMDB 0.9.18 Release (2016/02/05)
|
||||
Fix robust mutex detection on glibc 2.10-11 (ITS#8330)
|
||||
Fix page_search_root assert on FreeDB (ITS#8336)
|
||||
Fix MDB_APPENDDUP vs. rewrite(single item) (ITS#8334)
|
||||
Fix mdb_copy of large files on Windows
|
||||
Fix subcursor move after delete (ITS#8355)
|
||||
Fix mdb_midl_shirnk off-by-one (ITS#8363)
|
||||
Check for utf8_to_utf16 failures (ITS#7992)
|
||||
Catch strdup failure in mdb_dbi_open
|
||||
Build
|
||||
Additional makefile var tweaks (ITS#8169)
|
||||
Documentation
|
||||
Add Getting Started page
|
||||
Update WRITEMAP description
|
||||
|
||||
|
||||
LMDB 0.9.17 Release (2015/11/30)
|
||||
Fix ITS#7377 catch calloc failure
|
||||
Fix ITS#8237 regression from ITS#7589
|
||||
Fix ITS#8238 page_split for DUPFIXED pages
|
||||
Fix ITS#8221 MDB_PAGE_FULL on delete/rebalance
|
||||
Fix ITS#8258 rebalance/split assert
|
||||
Fix ITS#8263 cursor_put cursor tracking
|
||||
Fix ITS#8264 cursor_del cursor tracking
|
||||
Fix ITS#8310 cursor_del cursor tracking
|
||||
Fix ITS#8299 mdb_del cursor tracking
|
||||
Fix ITS#8300 mdb_del cursor tracking
|
||||
Fix ITS#8304 mdb_del cursor tracking
|
||||
Fix ITS#7771 fakepage cursor tracking
|
||||
Fix ITS#7789 ensure mapsize >= pages in use
|
||||
Fix ITS#7971 mdb_txn_renew0() new reader slots
|
||||
Fix ITS#7969 use __sync_synchronize on non-x86
|
||||
Fix ITS#8311 page_split from update_key
|
||||
Fix ITS#8312 loose pages in nested txn
|
||||
Fix ITS#8313 mdb_rebalance dummy cursor
|
||||
Fix ITS#8315 dirty_room in nested txn
|
||||
Fix ITS#8323 dirty_list in nested txn
|
||||
Fix ITS#8316 page_merge cursor tracking
|
||||
Fix ITS#8321 cursor tracking
|
||||
Fix ITS#8319 mdb_load error messages
|
||||
Fix ITS#8320 mdb_load plaintext input
|
||||
Added mdb_txn_id() (ITS#7994)
|
||||
Added robust mutex support
|
||||
Miscellaneous cleanup/simplification
|
||||
Build
|
||||
Create install dirs if needed (ITS#8256)
|
||||
Fix ThreadProc decl on Win32/MSVC (ITS#8270)
|
||||
Added ssize_t typedef for MSVC (ITS#8067)
|
||||
Use ANSI apis on Windows (ITS#8069)
|
||||
Use O_SYNC if O_DSYNC,MDB_DSYNC are not defined (ITS#7209)
|
||||
Allow passing AR to make (ITS#8168)
|
||||
Allow passing mandir to make install (ITS#8169)
|
||||
|
||||
LMDB 0.9.16 Release (2015/08/14)
|
||||
Fix cursor EOF bug (ITS#8190)
|
||||
Fix handling of subDB records (ITS#8181)
|
||||
Fix mdb_midl_shrink() usage (ITS#8200)
|
||||
|
||||
LMDB 0.9.15 Release (2015/06/19)
|
||||
Fix txn init (ITS#7961,#7987)
|
||||
Fix MDB_PREV_DUP (ITS#7955,#7671)
|
||||
Fix compact of empty env (ITS#7956)
|
||||
Fix mdb_copy file mode
|
||||
Fix mdb_env_close() after failed mdb_env_open()
|
||||
Fix mdb_rebalance collapsing root (ITS#8062)
|
||||
Fix mdb_load with large values (ITS#8066)
|
||||
Fix to retry writes on EINTR (ITS#8106)
|
||||
Fix mdb_cursor_del on empty DB (ITS#8109)
|
||||
Fix MDB_INTEGERDUP key compare (ITS#8117)
|
||||
Fix error handling (ITS#7959,#8157,etc.)
|
||||
Fix race conditions (ITS#7969,7970)
|
||||
Added workaround for fdatasync bug in ext3fs
|
||||
Build
|
||||
Don't use -fPIC for static lib
|
||||
Update .gitignore (ITS#7952,#7953)
|
||||
Cleanup for "make test" (ITS#7841), "make clean", mtest*.c
|
||||
Misc. Android/Windows cleanup
|
||||
Documentation
|
||||
Fix MDB_APPEND doc
|
||||
Fix MDB_MAXKEYSIZE doc (ITS#8156)
|
||||
Fix mdb_cursor_put,mdb_cursor_del EACCES description
|
||||
Fix mdb_env_sync(MDB_RDONLY env) doc (ITS#8021)
|
||||
Clarify MDB_WRITEMAP doc (ITS#8021)
|
||||
Clarify mdb_env_open doc
|
||||
Clarify mdb_dbi_open doc
|
||||
|
||||
LMDB 0.9.14 Release (2014/09/20)
|
||||
Fix to support 64K page size (ITS#7713)
|
||||
Fix to persist decreased as well as increased mapsizes (ITS#7789)
|
||||
Fix cursor bug when deleting last node of a DUPSORT key
|
||||
Fix mdb_env_info to return FIXEDMAP address
|
||||
Fix ambiguous error code from writing to closed DBI (ITS#7825)
|
||||
Fix mdb_copy copying past end of file (ITS#7886)
|
||||
Fix cursor bugs from page_merge/rebalance
|
||||
Fix to dirty fewer pages in deletes (mdb_page_loose())
|
||||
Fix mdb_dbi_open creating subDBs (ITS#7917)
|
||||
Fix mdb_cursor_get(_DUP) with single value (ITS#7913)
|
||||
Fix Windows compat issues in mtests (ITS#7879)
|
||||
Add compacting variant of mdb_copy
|
||||
Add BigEndian integer key compare code
|
||||
Add mdb_dump/mdb_load utilities
|
||||
|
||||
LMDB 0.9.13 Release (2014/06/18)
|
||||
Fix mdb_page_alloc unlimited overflow page search
|
||||
Documentation
|
||||
Re-fix MDB_CURRENT doc (ITS#7793)
|
||||
Fix MDB_GET_MULTIPLE/MDB_NEXT_MULTIPLE doc
|
||||
|
||||
LMDB 0.9.12 Release (2014/06/13)
|
||||
Fix MDB_GET_BOTH regression (ITS#7875,#7681)
|
||||
Fix MDB_MULTIPLE writing multiple keys (ITS#7834)
|
||||
Fix mdb_rebalance (ITS#7829)
|
||||
Fix mdb_page_split (ITS#7815)
|
||||
Fix md_entries count (ITS#7861,#7828,#7793)
|
||||
Fix MDB_CURRENT (ITS#7793)
|
||||
Fix possible crash on Windows DLL detach
|
||||
Misc code cleanup
|
||||
Documentation
|
||||
mdb_cursor_put: cursor moves on error (ITS#7771)
|
||||
|
||||
|
||||
LMDB 0.9.11 Release (2014/01/15)
|
||||
Add mdb_env_set_assert() (ITS#7775)
|
||||
Fix: invalidate txn on page allocation errors (ITS#7377)
|
||||
Fix xcursor tracking in mdb_cursor_del0() (ITS#7771)
|
||||
Fix corruption from deletes (ITS#7756)
|
||||
Fix Windows/MSVC build issues
|
||||
Raise safe limit of max MDB_MAXKEYSIZE
|
||||
Misc code cleanup
|
||||
Documentation
|
||||
Remove spurious note about non-overlapping flags (ITS#7665)
|
||||
|
||||
LMDB 0.9.10 Release (2013/11/12)
|
||||
Add MDB_NOMEMINIT option
|
||||
Fix mdb_page_split() again (ITS#7589)
|
||||
Fix MDB_NORDAHEAD definition (ITS#7734)
|
||||
Fix mdb_cursor_del() positioning (ITS#7733)
|
||||
Partial fix for larger page sizes (ITS#7713)
|
||||
Fix Windows64/MSVC build issues
|
||||
|
||||
LMDB 0.9.9 Release (2013/10/24)
|
||||
Add mdb_env_get_fd()
|
||||
Add MDB_NORDAHEAD option
|
||||
Add MDB_NOLOCK option
|
||||
Avoid wasting space in mdb_page_split() (ITS#7589)
|
||||
Fix mdb_page_merge() cursor fixup (ITS#7722)
|
||||
Fix mdb_cursor_del() on last delete (ITS#7718)
|
||||
Fix adding WRITEMAP on existing env (ITS#7715)
|
||||
Fix nested txns (ITS#7515)
|
||||
Fix mdb_env_copy() O_DIRECT bug (ITS#7682)
|
||||
Fix mdb_cursor_set(SET_RANGE) return code (ITS#7681)
|
||||
Fix mdb_rebalance() cursor fixup (ITS#7701)
|
||||
Misc code cleanup
|
||||
Documentation
|
||||
Note that by default, readers need write access
|
||||
|
||||
|
||||
LMDB 0.9.8 Release (2013/09/09)
|
||||
Allow mdb_env_set_mapsize() on an open environment
|
||||
Fix mdb_dbi_flags() (ITS#7672)
|
||||
Fix mdb_page_unspill() in nested txns
|
||||
Fix mdb_cursor_get(CURRENT|NEXT) after a delete
|
||||
Fix mdb_cursor_get(DUP) to always return key (ITS#7671)
|
||||
Fix mdb_cursor_del() to always advance to next item (ITS#7670)
|
||||
Fix mdb_cursor_set(SET_RANGE) for tree with single page (ITS#7681)
|
||||
Fix mdb_env_copy() retry open if O_DIRECT fails (ITS#7682)
|
||||
Tweak mdb_page_spill() to be less aggressive
|
||||
Documentation
|
||||
Update caveats since mdb_reader_check() added in 0.9.7
|
||||
|
||||
LMDB 0.9.7 Release (2013/08/17)
|
||||
Don't leave stale lockfile on failed RDONLY open (ITS#7664)
|
||||
Fix mdb_page_split() ref beyond cursor depth
|
||||
Fix read txn data race (ITS#7635)
|
||||
Fix mdb_rebalance (ITS#7536, #7538)
|
||||
Fix mdb_drop() (ITS#7561)
|
||||
Misc DEBUG macro fixes
|
||||
Add MDB_NOTLS envflag
|
||||
Add mdb_env_copyfd()
|
||||
Add mdb_txn_env() (ITS#7660)
|
||||
Add mdb_dbi_flags() (ITS#7661)
|
||||
Add mdb_env_get_maxkeysize()
|
||||
Add mdb_env_reader_list()/mdb_env_reader_check()
|
||||
Add mdb_page_spill/unspill, remove hard txn size limit
|
||||
Use shorter names for semaphores (ITS#7615)
|
||||
Build
|
||||
Fix install target (ITS#7656)
|
||||
Documentation
|
||||
Misc updates for cursors, DB handles, data lifetime
|
||||
|
||||
LMDB 0.9.6 Release (2013/02/25)
|
||||
Many fixes/enhancements
|
||||
|
||||
LMDB 0.9.5 Release (2012/11/30)
|
||||
Renamed from libmdb to liblmdb
|
||||
Many fixes/enhancements
|
||||
@@ -0,0 +1,20 @@
|
||||
Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted only as authorized by the OpenLDAP
|
||||
Public License.
|
||||
|
||||
A copy of this license is available in the file LICENSE in the
|
||||
top-level directory of the distribution or, alternatively, at
|
||||
<http://www.OpenLDAP.org/license.html>.
|
||||
|
||||
OpenLDAP is a registered trademark of the OpenLDAP Foundation.
|
||||
|
||||
Individual files and/or contributed packages may be copyright by
|
||||
other parties and/or subject to additional restrictions.
|
||||
|
||||
This work also contains materials derived from public sources.
|
||||
|
||||
Additional information about OpenLDAP can be obtained at
|
||||
<http://www.openldap.org/>.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,47 @@
|
||||
The OpenLDAP Public License
|
||||
Version 2.8, 17 August 2003
|
||||
|
||||
Redistribution and use of this software and associated documentation
|
||||
("Software"), with or without modification, are permitted provided
|
||||
that the following conditions are met:
|
||||
|
||||
1. Redistributions in source form must retain copyright statements
|
||||
and notices,
|
||||
|
||||
2. Redistributions in binary form must reproduce applicable copyright
|
||||
statements and notices, this list of conditions, and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution, and
|
||||
|
||||
3. Redistributions must contain a verbatim copy of this document.
|
||||
|
||||
The OpenLDAP Foundation may revise this license from time to time.
|
||||
Each revision is distinguished by a version number. You may use
|
||||
this Software under terms of this license revision or under the
|
||||
terms of any subsequent revision of the license.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE OPENLDAP FOUNDATION AND ITS
|
||||
CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
||||
SHALL THE OPENLDAP FOUNDATION, ITS CONTRIBUTORS, OR THE AUTHOR(S)
|
||||
OR OWNER(S) OF THE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The names of the authors and copyright holders must not be used in
|
||||
advertising or otherwise to promote the sale, use or other dealing
|
||||
in this Software without specific, written prior permission. Title
|
||||
to copyright in this Software shall at all times remain with copyright
|
||||
holders.
|
||||
|
||||
OpenLDAP is a registered trademark of the OpenLDAP Foundation.
|
||||
|
||||
Copyright 1999-2003 The OpenLDAP Foundation, Redwood City,
|
||||
California, USA. All Rights Reserved. Permission to copy and
|
||||
distribute verbatim copies of this document is granted.
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright 2015-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
/** @page starting Getting Started
|
||||
|
||||
LMDB is compact, fast, powerful, and robust and implements a simplified
|
||||
variant of the BerkeleyDB (BDB) API. (BDB is also very powerful, and verbosely
|
||||
documented in its own right.) After reading this page, the main
|
||||
\ref mdb documentation should make sense. Thanks to Bert Hubert
|
||||
for creating the
|
||||
<a href="https://github.com/ahupowerdns/ahutils/blob/master/lmdb-semantics.md">
|
||||
initial version</a> of this writeup.
|
||||
|
||||
Everything starts with an environment, created by #mdb_env_create().
|
||||
Once created, this environment must also be opened with #mdb_env_open().
|
||||
|
||||
#mdb_env_open() gets passed a name which is interpreted as a directory
|
||||
path. Note that this directory must exist already, it is not created
|
||||
for you. Within that directory, a lock file and a storage file will be
|
||||
generated. If you don't want to use a directory, you can pass the
|
||||
#MDB_NOSUBDIR option, in which case the path you provided is used
|
||||
directly as the data file, and another file with a "-lock" suffix
|
||||
added will be used for the lock file.
|
||||
|
||||
Once the environment is open, a transaction can be created within it
|
||||
using #mdb_txn_begin(). Transactions may be read-write or read-only,
|
||||
and read-write transactions may be nested. A transaction must only
|
||||
be used by one thread at a time. Transactions are always required,
|
||||
even for read-only access. The transaction provides a consistent
|
||||
view of the data.
|
||||
|
||||
Once a transaction has been created, a database can be opened within it
|
||||
using #mdb_dbi_open(). If only one database will ever be used in the
|
||||
environment, a NULL can be passed as the database name. For named
|
||||
databases, the #MDB_CREATE flag must be used to create the database
|
||||
if it doesn't already exist. Also, #mdb_env_set_maxdbs() must be
|
||||
called after #mdb_env_create() and before #mdb_env_open() to set the
|
||||
maximum number of named databases you want to support.
|
||||
|
||||
Note: a single transaction can open multiple databases. Generally
|
||||
databases should only be opened once, by the first transaction in
|
||||
the process. After the first transaction completes, the database
|
||||
handles can freely be used by all subsequent transactions.
|
||||
|
||||
Within a transaction, #mdb_get() can retrieve and #mdb_put() can store single
|
||||
key/value pairs if that is all you need to do (but see \ref Cursors
|
||||
below if you want to do more).
|
||||
|
||||
A key/value pair is expressed as two #MDB_val structures. This struct
|
||||
has two fields, \c mv_size and \c mv_data. The data is a \c void pointer to
|
||||
an array of \c mv_size bytes.
|
||||
|
||||
Because LMDB is very efficient (and usually zero-copy), the data returned
|
||||
in an #MDB_val structure may be memory-mapped straight from disk. In
|
||||
other words <b>look but do not touch</b> (or free() for that matter).
|
||||
Once a transaction is closed, the values can no longer be used, so
|
||||
make a copy if you need to keep them after that.
|
||||
|
||||
@section Cursors Cursors
|
||||
|
||||
To do more powerful things, we must use a cursor.
|
||||
|
||||
Within the transaction, a cursor can be created with #mdb_cursor_open().
|
||||
With this cursor we can store/retrieve/delete (multiple) values using
|
||||
#mdb_cursor_get(), #mdb_cursor_put(), and #mdb_cursor_del().
|
||||
|
||||
#mdb_cursor_get() positions itself depending on the cursor operation
|
||||
requested, and for some operations, on the supplied key. For example,
|
||||
to list all key/value pairs in a database, use operation #MDB_FIRST for
|
||||
the first call to #mdb_cursor_get(), and #MDB_NEXT on subsequent calls,
|
||||
until the end is hit.
|
||||
|
||||
To retrieve all keys starting from a specified key value, use #MDB_SET.
|
||||
For more cursor operations, see the \ref mdb docs.
|
||||
|
||||
When using #mdb_cursor_put(), either the function will position the
|
||||
cursor for you based on the \b key, or you can use operation
|
||||
#MDB_CURRENT to use the current position of the cursor. Note that
|
||||
\b key must then match the current position's key.
|
||||
|
||||
@subsection summary Summarizing the Opening
|
||||
|
||||
So we have a cursor in a transaction which opened a database in an
|
||||
environment which is opened from a filesystem after it was
|
||||
separately created.
|
||||
|
||||
Or, we create an environment, open it from a filesystem, create a
|
||||
transaction within it, open a database within that transaction,
|
||||
and create a cursor within all of the above.
|
||||
|
||||
Got it?
|
||||
|
||||
@section thrproc Threads and Processes
|
||||
|
||||
LMDB uses POSIX locks on files, and these locks have issues if one
|
||||
process opens a file multiple times. Because of this, do not
|
||||
#mdb_env_open() a file multiple times from a single process. Instead,
|
||||
share the LMDB environment that has opened the file across all threads.
|
||||
Otherwise, if a single process opens the same environment multiple times,
|
||||
closing it once will remove all the locks held on it, and the other
|
||||
instances will be vulnerable to corruption from other processes.
|
||||
|
||||
Also note that a transaction is tied to one thread by default using
|
||||
Thread Local Storage. If you want to pass read-only transactions across
|
||||
threads, you can use the #MDB_NOTLS option on the environment.
|
||||
|
||||
@section txns Transactions, Rollbacks, etc.
|
||||
|
||||
To actually get anything done, a transaction must be committed using
|
||||
#mdb_txn_commit(). Alternatively, all of a transaction's operations
|
||||
can be discarded using #mdb_txn_abort(). In a read-only transaction,
|
||||
any cursors will \b not automatically be freed. In a read-write
|
||||
transaction, all cursors will be freed and must not be used again.
|
||||
|
||||
For read-only transactions, obviously there is nothing to commit to
|
||||
storage. The transaction still must eventually be aborted to close
|
||||
any database handle(s) opened in it, or committed to keep the
|
||||
database handles around for reuse in new transactions.
|
||||
|
||||
In addition, as long as a transaction is open, a consistent view of
|
||||
the database is kept alive, which requires storage. A read-only
|
||||
transaction that no longer requires this consistent view should
|
||||
be terminated (committed or aborted) when the view is no longer
|
||||
needed (but see below for an optimization).
|
||||
|
||||
There can be multiple simultaneously active read-only transactions
|
||||
but only one that can write. Once a single read-write transaction
|
||||
is opened, all further attempts to begin one will block until the
|
||||
first one is committed or aborted. This has no effect on read-only
|
||||
transactions, however, and they may continue to be opened at any time.
|
||||
|
||||
@section dupkeys Duplicate Keys
|
||||
|
||||
#mdb_get() and #mdb_put() respectively have no and only some support
|
||||
for multiple key/value pairs with identical keys. If there are multiple
|
||||
values for a key, #mdb_get() will only return the first value.
|
||||
|
||||
When multiple values for one key are required, pass the #MDB_DUPSORT
|
||||
flag to #mdb_dbi_open(). In an #MDB_DUPSORT database, by default
|
||||
#mdb_put() will not replace the value for a key if the key existed
|
||||
already. Instead it will add the new value to the key. In addition,
|
||||
#mdb_del() will pay attention to the value field too, allowing for
|
||||
specific values of a key to be deleted.
|
||||
|
||||
Finally, additional cursor operations become available for
|
||||
traversing through and retrieving duplicate values.
|
||||
|
||||
@section optim Some Optimization
|
||||
|
||||
If you frequently begin and abort read-only transactions, as an
|
||||
optimization, it is possible to only reset and renew a transaction.
|
||||
|
||||
#mdb_txn_reset() releases any old copies of data kept around for
|
||||
a read-only transaction. To reuse this reset transaction, call
|
||||
#mdb_txn_renew() on it. Any cursors in this transaction must also
|
||||
be renewed using #mdb_cursor_renew().
|
||||
|
||||
Note that #mdb_txn_reset() is similar to #mdb_txn_abort() and will
|
||||
close any databases you opened within the transaction.
|
||||
|
||||
To permanently free a transaction, reset or not, use #mdb_txn_abort().
|
||||
|
||||
@section cleanup Cleaning Up
|
||||
|
||||
For read-only transactions, any cursors created within it must
|
||||
be closed using #mdb_cursor_close().
|
||||
|
||||
It is very rarely necessary to close a database handle, and in
|
||||
general they should just be left open.
|
||||
|
||||
@section onward The Full API
|
||||
|
||||
The full \ref mdb documentation lists further details, like how to:
|
||||
|
||||
\li size a database (the default limits are intentionally small)
|
||||
\li drop and clean a database
|
||||
\li detect and report errors
|
||||
\li optimize (bulk) loading speed
|
||||
\li (temporarily) reduce robustness to gain even more speed
|
||||
\li gather statistics about the database
|
||||
\li define custom sort orders
|
||||
|
||||
*/
|
||||
+1608
File diff suppressed because it is too large.
Load diff
+10504
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,55 @@
|
||||
.TH MDB_COPY 1 "2014/07/01" "LMDB 0.9.14"
|
||||
.\" Copyright 2012-2021 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.SH NAME
|
||||
mdb_copy \- LMDB environment copy tool
|
||||
.SH SYNOPSIS
|
||||
.B mdb_copy
|
||||
[\c
|
||||
.BR \-V ]
|
||||
[\c
|
||||
.BR \-c ]
|
||||
[\c
|
||||
.BR \-n ]
|
||||
.B srcpath
|
||||
[\c
|
||||
.BR dstpath ]
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mdb_copy
|
||||
utility copies an LMDB environment. The environment can
|
||||
be copied regardless of whether it is currently in use.
|
||||
No lockfile is created, since it gets recreated at need.
|
||||
|
||||
If
|
||||
.I dstpath
|
||||
is specified it must be the path of an empty directory
|
||||
for storing the backup. Otherwise, the backup will be
|
||||
written to stdout.
|
||||
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-V
|
||||
Write the library version number to the standard output, and exit.
|
||||
.TP
|
||||
.BR \-c
|
||||
Compact while copying. Only current data pages will be copied; freed
|
||||
or unused pages will be omitted from the copy. This option will
|
||||
slow down the backup process as it is more CPU-intensive.
|
||||
Currently it fails if the environment has suffered a page leak.
|
||||
.TP
|
||||
.BR \-n
|
||||
Open LDMB environment(s) which do not use subdirectories.
|
||||
|
||||
.SH DIAGNOSTICS
|
||||
Exit status is zero if no errors occur.
|
||||
Errors result in a non-zero exit status and
|
||||
a diagnostic message being written to standard error.
|
||||
.SH CAVEATS
|
||||
This utility can trigger significant file size growth if run
|
||||
in parallel with write transactions, because pages which they
|
||||
free during copying cannot be reused until the copy is done.
|
||||
.SH "SEE ALSO"
|
||||
.BR mdb_stat (1)
|
||||
.SH AUTHOR
|
||||
Howard Chu of Symas Corporation <http://www.symas.com>
|
||||
@@ -0,0 +1,82 @@
|
||||
/* mdb_copy.c - memory-mapped database backup tool */
|
||||
/*
|
||||
* Copyright 2012-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#define MDB_STDOUT GetStdHandle(STD_OUTPUT_HANDLE)
|
||||
#else
|
||||
#define MDB_STDOUT 1
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
static void
|
||||
sighandle(int sig)
|
||||
{
|
||||
}
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int rc;
|
||||
MDB_env *env;
|
||||
const char *progname = argv[0], *act;
|
||||
unsigned flags = MDB_RDONLY;
|
||||
unsigned cpflags = 0;
|
||||
|
||||
for (; argc > 1 && argv[1][0] == '-'; argc--, argv++) {
|
||||
if (argv[1][1] == 'n' && argv[1][2] == '\0')
|
||||
flags |= MDB_NOSUBDIR;
|
||||
else if (argv[1][1] == 'c' && argv[1][2] == '\0')
|
||||
cpflags |= MDB_CP_COMPACT;
|
||||
else if (argv[1][1] == 'V' && argv[1][2] == '\0') {
|
||||
printf("%s\n", MDB_VERSION_STRING);
|
||||
exit(0);
|
||||
} else
|
||||
argc = 0;
|
||||
}
|
||||
|
||||
if (argc<2 || argc>3) {
|
||||
fprintf(stderr, "usage: %s [-V] [-c] [-n] srcpath [dstpath]\n", progname);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
#ifdef SIGPIPE
|
||||
signal(SIGPIPE, sighandle);
|
||||
#endif
|
||||
#ifdef SIGHUP
|
||||
signal(SIGHUP, sighandle);
|
||||
#endif
|
||||
signal(SIGINT, sighandle);
|
||||
signal(SIGTERM, sighandle);
|
||||
|
||||
act = "opening environment";
|
||||
rc = mdb_env_create(&env);
|
||||
if (rc == MDB_SUCCESS) {
|
||||
rc = mdb_env_open(env, argv[1], flags, 0600);
|
||||
}
|
||||
if (rc == MDB_SUCCESS) {
|
||||
act = "copying";
|
||||
if (argc == 2)
|
||||
rc = mdb_env_copyfd2(env, MDB_STDOUT, cpflags);
|
||||
else
|
||||
rc = mdb_env_copy2(env, argv[2], cpflags);
|
||||
}
|
||||
if (rc)
|
||||
fprintf(stderr, "%s: %s failed, error %d (%s)\n",
|
||||
progname, act, rc, mdb_strerror(rc));
|
||||
mdb_env_close(env);
|
||||
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
.TH MDB_DUMP 1 "2015/09/30" "LMDB 0.9.17"
|
||||
.\" Copyright 2014-2021 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.SH NAME
|
||||
mdb_dump \- LMDB environment export tool
|
||||
.SH SYNOPSIS
|
||||
.B mdb_dump
|
||||
[\c
|
||||
.BR \-V ]
|
||||
[\c
|
||||
.BI \-f \ file\fR]
|
||||
[\c
|
||||
.BR \-l ]
|
||||
[\c
|
||||
.BR \-n ]
|
||||
[\c
|
||||
.BR \-p ]
|
||||
[\c
|
||||
.BR \-a \ |
|
||||
.BI \-s \ subdb\fR]
|
||||
.BR \ envpath
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mdb_dump
|
||||
utility reads a database and writes its contents to the
|
||||
standard output using a portable flat-text format
|
||||
understood by the
|
||||
.BR mdb_load (1)
|
||||
utility.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-V
|
||||
Write the library version number to the standard output, and exit.
|
||||
.TP
|
||||
.BR \-f \ file
|
||||
Write to the specified file instead of to the standard output.
|
||||
.TP
|
||||
.BR \-l
|
||||
List the databases stored in the environment. Just the
|
||||
names will be listed, no data will be output.
|
||||
.TP
|
||||
.BR \-n
|
||||
Dump an LMDB database which does not use subdirectories.
|
||||
.TP
|
||||
.BR \-p
|
||||
If characters in either the key or data items are printing characters (as
|
||||
defined by isprint(3)), output them directly. This option permits users to
|
||||
use standard text editors and tools to modify the contents of databases.
|
||||
|
||||
Note: different systems may have different notions about what characters
|
||||
are considered printing characters, and databases dumped in this manner may
|
||||
be less portable to external systems.
|
||||
.TP
|
||||
.BR \-a
|
||||
Dump all of the subdatabases in the environment.
|
||||
.TP
|
||||
.BR \-s \ subdb
|
||||
Dump a specific subdatabase. If no database is specified, only the main database is dumped.
|
||||
.SH DIAGNOSTICS
|
||||
Exit status is zero if no errors occur.
|
||||
Errors result in a non-zero exit status and
|
||||
a diagnostic message being written to standard error.
|
||||
|
||||
Dumping databases that use user-defined comparison functions will output
|
||||
records with the ordering imposed by those comparison functions. If
|
||||
.B mdb_load
|
||||
is invoked without including the
|
||||
.B -a
|
||||
option when reloading those records, the new databases will likely be
|
||||
damaged beyond repair, permitting neither record storage nor retrieval.
|
||||
|
||||
.SH "SEE ALSO"
|
||||
.BR mdb_load (1)
|
||||
.SH AUTHOR
|
||||
Howard Chu of Symas Corporation <http://www.symas.com>
|
||||
@@ -0,0 +1,319 @@
|
||||
/* mdb_dump.c - memory-mapped database dump tool */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define Z "I"
|
||||
#else
|
||||
#define Z "z"
|
||||
#endif
|
||||
|
||||
#define PRINT 1
|
||||
static int mode;
|
||||
|
||||
typedef struct flagbit {
|
||||
int bit;
|
||||
char *name;
|
||||
} flagbit;
|
||||
|
||||
flagbit dbflags[] = {
|
||||
{ MDB_REVERSEKEY, "reversekey" },
|
||||
{ MDB_DUPSORT, "dupsort" },
|
||||
{ MDB_INTEGERKEY, "integerkey" },
|
||||
{ MDB_DUPFIXED, "dupfixed" },
|
||||
{ MDB_INTEGERDUP, "integerdup" },
|
||||
{ MDB_REVERSEDUP, "reversedup" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
static volatile sig_atomic_t gotsig;
|
||||
|
||||
static void dumpsig( int sig )
|
||||
{
|
||||
gotsig=1;
|
||||
}
|
||||
|
||||
static const char hexc[] = "0123456789abcdef";
|
||||
|
||||
static void hex(unsigned char c)
|
||||
{
|
||||
putchar(hexc[c >> 4]);
|
||||
putchar(hexc[c & 0xf]);
|
||||
}
|
||||
|
||||
static void text(MDB_val *v)
|
||||
{
|
||||
unsigned char *c, *end;
|
||||
|
||||
putchar(' ');
|
||||
c = v->mv_data;
|
||||
end = c + v->mv_size;
|
||||
while (c < end) {
|
||||
if (isprint(*c)) {
|
||||
if (*c == '\\')
|
||||
putchar('\\');
|
||||
putchar(*c);
|
||||
} else {
|
||||
putchar('\\');
|
||||
hex(*c);
|
||||
}
|
||||
c++;
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
static void byte(MDB_val *v)
|
||||
{
|
||||
unsigned char *c, *end;
|
||||
|
||||
putchar(' ');
|
||||
c = v->mv_data;
|
||||
end = c + v->mv_size;
|
||||
while (c < end) {
|
||||
hex(*c++);
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
/* Dump in BDB-compatible format */
|
||||
static int dumpit(MDB_txn *txn, MDB_dbi dbi, char *name)
|
||||
{
|
||||
MDB_cursor *mc;
|
||||
MDB_stat ms;
|
||||
MDB_val key, data;
|
||||
MDB_envinfo info;
|
||||
unsigned int flags;
|
||||
int rc, i;
|
||||
|
||||
rc = mdb_dbi_flags(txn, dbi, &flags);
|
||||
if (rc) return rc;
|
||||
|
||||
rc = mdb_stat(txn, dbi, &ms);
|
||||
if (rc) return rc;
|
||||
|
||||
rc = mdb_env_info(mdb_txn_env(txn), &info);
|
||||
if (rc) return rc;
|
||||
|
||||
printf("VERSION=3\n");
|
||||
printf("format=%s\n", mode & PRINT ? "print" : "bytevalue");
|
||||
if (name)
|
||||
printf("database=%s\n", name);
|
||||
printf("type=btree\n");
|
||||
printf("mapsize=%" Z "u\n", info.me_mapsize);
|
||||
if (info.me_mapaddr)
|
||||
printf("mapaddr=%p\n", info.me_mapaddr);
|
||||
printf("maxreaders=%u\n", info.me_maxreaders);
|
||||
|
||||
if (flags & MDB_DUPSORT)
|
||||
printf("duplicates=1\n");
|
||||
|
||||
for (i=0; dbflags[i].bit; i++)
|
||||
if (flags & dbflags[i].bit)
|
||||
printf("%s=1\n", dbflags[i].name);
|
||||
|
||||
printf("db_pagesize=%d\n", ms.ms_psize);
|
||||
printf("HEADER=END\n");
|
||||
|
||||
rc = mdb_cursor_open(txn, dbi, &mc);
|
||||
if (rc) return rc;
|
||||
|
||||
while ((rc = mdb_cursor_get(mc, &key, &data, MDB_NEXT)) == MDB_SUCCESS) {
|
||||
if (gotsig) {
|
||||
rc = EINTR;
|
||||
break;
|
||||
}
|
||||
if (mode & PRINT) {
|
||||
text(&key);
|
||||
text(&data);
|
||||
} else {
|
||||
byte(&key);
|
||||
byte(&data);
|
||||
}
|
||||
}
|
||||
printf("DATA=END\n");
|
||||
if (rc == MDB_NOTFOUND)
|
||||
rc = MDB_SUCCESS;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void usage(char *prog)
|
||||
{
|
||||
fprintf(stderr, "usage: %s [-V] [-f output] [-l] [-n] [-p] [-a|-s subdb] dbpath\n", prog);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int i, rc;
|
||||
MDB_env *env;
|
||||
MDB_txn *txn;
|
||||
MDB_dbi dbi;
|
||||
char *prog = argv[0];
|
||||
char *envname;
|
||||
char *subname = NULL;
|
||||
int alldbs = 0, envflags = 0, list = 0;
|
||||
|
||||
if (argc < 2) {
|
||||
usage(prog);
|
||||
}
|
||||
|
||||
/* -a: dump all subDBs
|
||||
* -s: dump only the named subDB
|
||||
* -n: use NOSUBDIR flag on env_open
|
||||
* -p: use printable characters
|
||||
* -f: write to file instead of stdout
|
||||
* -V: print version and exit
|
||||
* (default) dump only the main DB
|
||||
*/
|
||||
while ((i = getopt(argc, argv, "af:lnps:V")) != EOF) {
|
||||
switch(i) {
|
||||
case 'V':
|
||||
printf("%s\n", MDB_VERSION_STRING);
|
||||
exit(0);
|
||||
break;
|
||||
case 'l':
|
||||
list = 1;
|
||||
/*FALLTHROUGH*/
|
||||
case 'a':
|
||||
if (subname)
|
||||
usage(prog);
|
||||
alldbs++;
|
||||
break;
|
||||
case 'f':
|
||||
if (freopen(optarg, "w", stdout) == NULL) {
|
||||
fprintf(stderr, "%s: %s: reopen: %s\n",
|
||||
prog, optarg, strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
case 'n':
|
||||
envflags |= MDB_NOSUBDIR;
|
||||
break;
|
||||
case 'p':
|
||||
mode |= PRINT;
|
||||
break;
|
||||
case 's':
|
||||
if (alldbs)
|
||||
usage(prog);
|
||||
subname = optarg;
|
||||
break;
|
||||
default:
|
||||
usage(prog);
|
||||
}
|
||||
}
|
||||
|
||||
if (optind != argc - 1)
|
||||
usage(prog);
|
||||
|
||||
#ifdef SIGPIPE
|
||||
signal(SIGPIPE, dumpsig);
|
||||
#endif
|
||||
#ifdef SIGHUP
|
||||
signal(SIGHUP, dumpsig);
|
||||
#endif
|
||||
signal(SIGINT, dumpsig);
|
||||
signal(SIGTERM, dumpsig);
|
||||
|
||||
envname = argv[optind];
|
||||
rc = mdb_env_create(&env);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (alldbs || subname) {
|
||||
mdb_env_set_maxdbs(env, 2);
|
||||
}
|
||||
|
||||
rc = mdb_env_open(env, envname, envflags | MDB_RDONLY, 0664);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
rc = mdb_open(txn, subname, 0, &dbi);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
|
||||
if (alldbs) {
|
||||
MDB_cursor *cursor;
|
||||
MDB_val key;
|
||||
int count = 0;
|
||||
|
||||
rc = mdb_cursor_open(txn, dbi, &cursor);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
while ((rc = mdb_cursor_get(cursor, &key, NULL, MDB_NEXT_NODUP)) == 0) {
|
||||
char *str;
|
||||
MDB_dbi db2;
|
||||
if (memchr(key.mv_data, '\0', key.mv_size))
|
||||
continue;
|
||||
count++;
|
||||
str = malloc(key.mv_size+1);
|
||||
memcpy(str, key.mv_data, key.mv_size);
|
||||
str[key.mv_size] = '\0';
|
||||
rc = mdb_open(txn, str, 0, &db2);
|
||||
if (rc == MDB_SUCCESS) {
|
||||
if (list) {
|
||||
printf("%s\n", str);
|
||||
list++;
|
||||
} else {
|
||||
rc = dumpit(txn, db2, str);
|
||||
if (rc)
|
||||
break;
|
||||
}
|
||||
mdb_close(env, db2);
|
||||
}
|
||||
free(str);
|
||||
if (rc) continue;
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
if (!count) {
|
||||
fprintf(stderr, "%s: %s does not contain multiple databases\n", prog, envname);
|
||||
rc = MDB_NOTFOUND;
|
||||
} else if (rc == MDB_NOTFOUND) {
|
||||
rc = MDB_SUCCESS;
|
||||
}
|
||||
} else {
|
||||
rc = dumpit(txn, dbi, subname);
|
||||
}
|
||||
if (rc && rc != MDB_NOTFOUND)
|
||||
fprintf(stderr, "%s: %s: %s\n", prog, envname, mdb_strerror(rc));
|
||||
|
||||
mdb_close(env, dbi);
|
||||
txn_abort:
|
||||
mdb_txn_abort(txn);
|
||||
env_close:
|
||||
mdb_env_close(env);
|
||||
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
.TH MDB_LOAD 1 "2015/09/30" "LMDB 0.9.17"
|
||||
.\" Copyright 2014-2021 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.SH NAME
|
||||
mdb_load \- LMDB environment import tool
|
||||
.SH SYNOPSIS
|
||||
.B mdb_load
|
||||
[\c
|
||||
.BR \-V ]
|
||||
[\c
|
||||
.BR \-a ]
|
||||
[\c
|
||||
.BI \-f \ file\fR]
|
||||
[\c
|
||||
.BR \-n ]
|
||||
[\c
|
||||
.BI \-s \ subdb\fR]
|
||||
[\c
|
||||
.BR \-N ]
|
||||
[\c
|
||||
.BR \-Q ]
|
||||
[\c
|
||||
.BR \-T ]
|
||||
.BR \ envpath
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mdb_load
|
||||
utility reads from the standard input and loads it into the
|
||||
LMDB environment
|
||||
.BR envpath .
|
||||
|
||||
The input to
|
||||
.B mdb_load
|
||||
must be in the output format specified by the
|
||||
.BR mdb_dump (1)
|
||||
utility or as specified by the
|
||||
.B -T
|
||||
option below.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-V
|
||||
Write the library version number to the standard output, and exit.
|
||||
.TP
|
||||
.BR \-a
|
||||
Append all records in the order they appear in the input. The input is assumed to already be
|
||||
in correctly sorted order and no sorting or checking for redundant values will be performed.
|
||||
This option must be used to reload data that was produced by running
|
||||
.B mdb_dump
|
||||
on a database that uses custom compare functions.
|
||||
.TP
|
||||
.BR \-f \ file
|
||||
Read from the specified file instead of from the standard input.
|
||||
.TP
|
||||
.BR \-n
|
||||
Load an LMDB database which does not use subdirectories.
|
||||
.TP
|
||||
.BR \-s \ subdb
|
||||
Load a specific subdatabase. If no database is specified, data is loaded into the main database.
|
||||
.TP
|
||||
.BR \-N
|
||||
Don't overwrite existing records when loading into an already existing database; just skip them.
|
||||
.TP
|
||||
.BR \-Q
|
||||
Quick mode, uses MDB_NOSYNC for faster loading. Forces sync with mdb_env_sync() before exiting.
|
||||
.TP
|
||||
.BR \-T
|
||||
Load data from simple text files. The input must be paired lines of text, where the first
|
||||
line of the pair is the key item, and the second line of the pair is its corresponding
|
||||
data item.
|
||||
|
||||
A simple escape mechanism, where newline and backslash (\\) characters are special, is
|
||||
applied to the text input. Newline characters are interpreted as record separators.
|
||||
Backslash characters in the text will be interpreted in one of two ways: If the backslash
|
||||
character precedes another backslash character, the pair will be interpreted as a literal
|
||||
backslash. If the backslash character precedes any other character, the two characters
|
||||
following the backslash will be interpreted as a hexadecimal specification of a single
|
||||
character; for example, \\0a is a newline character in the ASCII character set.
|
||||
|
||||
For this reason, any backslash or newline characters that naturally occur in the text
|
||||
input must be escaped to avoid misinterpretation by
|
||||
.BR mdb_load .
|
||||
|
||||
.SH DIAGNOSTICS
|
||||
Exit status is zero if no errors occur.
|
||||
Errors result in a non-zero exit status and
|
||||
a diagnostic message being written to standard error.
|
||||
|
||||
.SH "SEE ALSO"
|
||||
.BR mdb_dump (1)
|
||||
.SH AUTHOR
|
||||
Howard Chu of Symas Corporation <http://www.symas.com>
|
||||
@@ -0,0 +1,515 @@
|
||||
/* mdb_load.c - memory-mapped database load tool */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <unistd.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define PRINT 1
|
||||
#define NOHDR 2
|
||||
static int mode;
|
||||
|
||||
static char *subname = NULL;
|
||||
|
||||
static size_t lineno;
|
||||
static int version;
|
||||
|
||||
static int flags;
|
||||
|
||||
static char *prog;
|
||||
|
||||
static int Eof;
|
||||
|
||||
static MDB_envinfo info;
|
||||
|
||||
static MDB_val kbuf, dbuf;
|
||||
static MDB_val k0buf;
|
||||
|
||||
#ifdef _WIN32
|
||||
#define Z "I"
|
||||
#else
|
||||
#define Z "z"
|
||||
#endif
|
||||
|
||||
#define STRLENOF(s) (sizeof(s)-1)
|
||||
|
||||
typedef struct flagbit {
|
||||
int bit;
|
||||
char *name;
|
||||
int len;
|
||||
} flagbit;
|
||||
|
||||
#define S(s) s, STRLENOF(s)
|
||||
|
||||
flagbit dbflags[] = {
|
||||
{ MDB_REVERSEKEY, S("reversekey") },
|
||||
{ MDB_DUPSORT, S("dupsort") },
|
||||
{ MDB_INTEGERKEY, S("integerkey") },
|
||||
{ MDB_DUPFIXED, S("dupfixed") },
|
||||
{ MDB_INTEGERDUP, S("integerdup") },
|
||||
{ MDB_REVERSEDUP, S("reversedup") },
|
||||
{ 0, NULL, 0 }
|
||||
};
|
||||
|
||||
static void readhdr(void)
|
||||
{
|
||||
char *ptr;
|
||||
|
||||
flags = 0;
|
||||
while (fgets(dbuf.mv_data, dbuf.mv_size, stdin) != NULL) {
|
||||
lineno++;
|
||||
if (!strncmp(dbuf.mv_data, "VERSION=", STRLENOF("VERSION="))) {
|
||||
version=atoi((char *)dbuf.mv_data+STRLENOF("VERSION="));
|
||||
if (version > 3) {
|
||||
fprintf(stderr, "%s: line %" Z "d: unsupported VERSION %d\n",
|
||||
prog, lineno, version);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "HEADER=END", STRLENOF("HEADER=END"))) {
|
||||
break;
|
||||
} else if (!strncmp(dbuf.mv_data, "format=", STRLENOF("format="))) {
|
||||
if (!strncmp((char *)dbuf.mv_data+STRLENOF("FORMAT="), "print", STRLENOF("print")))
|
||||
mode |= PRINT;
|
||||
else if (strncmp((char *)dbuf.mv_data+STRLENOF("FORMAT="), "bytevalue", STRLENOF("bytevalue"))) {
|
||||
fprintf(stderr, "%s: line %" Z "d: unsupported FORMAT %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("FORMAT="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "database=", STRLENOF("database="))) {
|
||||
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
|
||||
if (ptr) *ptr = '\0';
|
||||
if (subname) free(subname);
|
||||
subname = strdup((char *)dbuf.mv_data+STRLENOF("database="));
|
||||
} else if (!strncmp(dbuf.mv_data, "type=", STRLENOF("type="))) {
|
||||
if (strncmp((char *)dbuf.mv_data+STRLENOF("type="), "btree", STRLENOF("btree"))) {
|
||||
fprintf(stderr, "%s: line %" Z "d: unsupported type %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("type="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "mapaddr=", STRLENOF("mapaddr="))) {
|
||||
int i;
|
||||
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
|
||||
if (ptr) *ptr = '\0';
|
||||
i = sscanf((char *)dbuf.mv_data+STRLENOF("mapaddr="), "%p", &info.me_mapaddr);
|
||||
if (i != 1) {
|
||||
fprintf(stderr, "%s: line %" Z "d: invalid mapaddr %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("mapaddr="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "mapsize=", STRLENOF("mapsize="))) {
|
||||
int i;
|
||||
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
|
||||
if (ptr) *ptr = '\0';
|
||||
i = sscanf((char *)dbuf.mv_data+STRLENOF("mapsize="), "%" Z "u", &info.me_mapsize);
|
||||
if (i != 1) {
|
||||
fprintf(stderr, "%s: line %" Z "d: invalid mapsize %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("mapsize="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "maxreaders=", STRLENOF("maxreaders="))) {
|
||||
int i;
|
||||
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
|
||||
if (ptr) *ptr = '\0';
|
||||
i = sscanf((char *)dbuf.mv_data+STRLENOF("maxreaders="), "%u", &info.me_maxreaders);
|
||||
if (i != 1) {
|
||||
fprintf(stderr, "%s: line %" Z "d: invalid maxreaders %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("maxreaders="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else {
|
||||
int i;
|
||||
for (i=0; dbflags[i].bit; i++) {
|
||||
if (!strncmp(dbuf.mv_data, dbflags[i].name, dbflags[i].len) &&
|
||||
((char *)dbuf.mv_data)[dbflags[i].len] == '=') {
|
||||
flags |= dbflags[i].bit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dbflags[i].bit) {
|
||||
ptr = memchr(dbuf.mv_data, '=', dbuf.mv_size);
|
||||
if (!ptr) {
|
||||
fprintf(stderr, "%s: line %" Z "d: unexpected format\n",
|
||||
prog, lineno);
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
*ptr = '\0';
|
||||
fprintf(stderr, "%s: line %" Z "d: unrecognized keyword ignored: %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void badend(void)
|
||||
{
|
||||
fprintf(stderr, "%s: line %" Z "d: unexpected end of input\n",
|
||||
prog, lineno);
|
||||
}
|
||||
|
||||
static int unhex(unsigned char *c2)
|
||||
{
|
||||
int x, c;
|
||||
x = *c2++ & 0x4f;
|
||||
if (x & 0x40)
|
||||
x -= 55;
|
||||
c = x << 4;
|
||||
x = *c2 & 0x4f;
|
||||
if (x & 0x40)
|
||||
x -= 55;
|
||||
c |= x;
|
||||
return c;
|
||||
}
|
||||
|
||||
static int readline(MDB_val *out, MDB_val *buf)
|
||||
{
|
||||
unsigned char *c1, *c2, *end;
|
||||
size_t len, l2;
|
||||
int c;
|
||||
|
||||
if (!(mode & NOHDR)) {
|
||||
c = fgetc(stdin);
|
||||
if (c == EOF) {
|
||||
Eof = 1;
|
||||
return EOF;
|
||||
}
|
||||
if (c != ' ') {
|
||||
lineno++;
|
||||
if (fgets(buf->mv_data, buf->mv_size, stdin) == NULL) {
|
||||
badend:
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
if (c == 'D' && !strncmp(buf->mv_data, "ATA=END", STRLENOF("ATA=END")))
|
||||
return EOF;
|
||||
goto badend;
|
||||
}
|
||||
}
|
||||
if (fgets(buf->mv_data, buf->mv_size, stdin) == NULL) {
|
||||
Eof = 1;
|
||||
return EOF;
|
||||
}
|
||||
lineno++;
|
||||
|
||||
c1 = buf->mv_data;
|
||||
len = strlen((char *)c1);
|
||||
if (!len) {
|
||||
/* This can only happen with an intentionally invalid input
|
||||
* with a NUL byte after the leading SPACE
|
||||
*/
|
||||
goto badend;
|
||||
}
|
||||
l2 = len;
|
||||
|
||||
/* Is buffer too short? */
|
||||
while (c1[len-1] != '\n') {
|
||||
buf->mv_data = realloc(buf->mv_data, buf->mv_size*2);
|
||||
if (!buf->mv_data) {
|
||||
Eof = 1;
|
||||
fprintf(stderr, "%s: line %" Z "d: out of memory, line too long\n",
|
||||
prog, lineno);
|
||||
return EOF;
|
||||
}
|
||||
c1 = buf->mv_data;
|
||||
c1 += l2;
|
||||
if (fgets((char *)c1, buf->mv_size+1, stdin) == NULL) {
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
buf->mv_size *= 2;
|
||||
len = strlen((char *)c1);
|
||||
l2 += len;
|
||||
}
|
||||
c1 = c2 = buf->mv_data;
|
||||
len = l2;
|
||||
c1[--len] = '\0';
|
||||
end = c1 + len;
|
||||
|
||||
if (mode & PRINT) {
|
||||
while (c2 < end) {
|
||||
if (*c2 == '\\') {
|
||||
if (c2[1] == '\\') {
|
||||
*c1++ = *c2;
|
||||
} else {
|
||||
if (c2+3 > end || !isxdigit(c2[1]) || !isxdigit(c2[2])) {
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
*c1++ = unhex(++c2);
|
||||
}
|
||||
c2 += 2;
|
||||
} else {
|
||||
/* copies are redundant when no escapes were used */
|
||||
*c1++ = *c2++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* odd length not allowed */
|
||||
if (len & 1) {
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
while (c2 < end) {
|
||||
if (!isxdigit(*c2) || !isxdigit(c2[1])) {
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
*c1++ = unhex(c2);
|
||||
c2 += 2;
|
||||
}
|
||||
}
|
||||
c2 = out->mv_data = buf->mv_data;
|
||||
out->mv_size = c1 - c2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void usage(void)
|
||||
{
|
||||
fprintf(stderr, "usage: %s [-V] [-a] [-f input] [-n] [-s name] [-N] [-T] dbpath\n", prog);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static int greater(const MDB_val *a, const MDB_val *b)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int i, rc;
|
||||
MDB_env *env;
|
||||
MDB_txn *txn;
|
||||
MDB_cursor *mc;
|
||||
MDB_dbi dbi;
|
||||
char *envname;
|
||||
int envflags = MDB_NOSYNC, putflags = 0;
|
||||
int dohdr = 0, append = 0;
|
||||
MDB_val prevk;
|
||||
|
||||
prog = argv[0];
|
||||
|
||||
if (argc < 2) {
|
||||
usage();
|
||||
}
|
||||
|
||||
/* -a: append records in input order
|
||||
* -f: load file instead of stdin
|
||||
* -n: use NOSUBDIR flag on env_open
|
||||
* -s: load into named subDB
|
||||
* -N: use NOOVERWRITE on puts
|
||||
* -Q: quick mode using NOSYNC
|
||||
* -T: read plaintext
|
||||
* -V: print version and exit
|
||||
*/
|
||||
while ((i = getopt(argc, argv, "af:ns:NQTV")) != EOF) {
|
||||
switch(i) {
|
||||
case 'V':
|
||||
printf("%s\n", MDB_VERSION_STRING);
|
||||
exit(0);
|
||||
break;
|
||||
case 'a':
|
||||
append = 1;
|
||||
break;
|
||||
case 'f':
|
||||
if (freopen(optarg, "r", stdin) == NULL) {
|
||||
fprintf(stderr, "%s: %s: reopen: %s\n",
|
||||
prog, optarg, strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
case 'n':
|
||||
envflags |= MDB_NOSUBDIR;
|
||||
break;
|
||||
case 's':
|
||||
subname = strdup(optarg);
|
||||
break;
|
||||
case 'N':
|
||||
putflags = MDB_NOOVERWRITE|MDB_NODUPDATA;
|
||||
break;
|
||||
case 'Q':
|
||||
envflags |= MDB_NOSYNC;
|
||||
break;
|
||||
case 'T':
|
||||
mode |= NOHDR | PRINT;
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
}
|
||||
}
|
||||
|
||||
if (optind != argc - 1)
|
||||
usage();
|
||||
|
||||
dbuf.mv_size = 4096;
|
||||
dbuf.mv_data = malloc(dbuf.mv_size);
|
||||
|
||||
if (!(mode & NOHDR))
|
||||
readhdr();
|
||||
|
||||
envname = argv[optind];
|
||||
rc = mdb_env_create(&env);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
mdb_env_set_maxdbs(env, 2);
|
||||
|
||||
if (info.me_maxreaders)
|
||||
mdb_env_set_maxreaders(env, info.me_maxreaders);
|
||||
|
||||
if (info.me_mapsize)
|
||||
mdb_env_set_mapsize(env, info.me_mapsize);
|
||||
|
||||
if (info.me_mapaddr)
|
||||
envflags |= MDB_FIXEDMAP;
|
||||
|
||||
rc = mdb_env_open(env, envname, envflags, 0664);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
kbuf.mv_size = mdb_env_get_maxkeysize(env) * 2 + 2;
|
||||
kbuf.mv_data = malloc(kbuf.mv_size * 2);
|
||||
k0buf.mv_size = kbuf.mv_size;
|
||||
k0buf.mv_data = (char *)kbuf.mv_data + kbuf.mv_size;
|
||||
prevk.mv_data = k0buf.mv_data;
|
||||
|
||||
while(!Eof) {
|
||||
MDB_val key, data;
|
||||
int batch = 0;
|
||||
flags = 0;
|
||||
int appflag;
|
||||
|
||||
if (!dohdr) {
|
||||
dohdr = 1;
|
||||
} else if (!(mode & NOHDR))
|
||||
readhdr();
|
||||
|
||||
rc = mdb_txn_begin(env, NULL, 0, &txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
rc = mdb_dbi_open(txn, subname, flags|MDB_CREATE, &dbi);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_dbi_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
prevk.mv_size = 0;
|
||||
if (append) {
|
||||
mdb_set_compare(txn, dbi, greater);
|
||||
if (flags & MDB_DUPSORT)
|
||||
mdb_set_dupsort(txn, dbi, greater);
|
||||
}
|
||||
|
||||
rc = mdb_cursor_open(txn, dbi, &mc);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
|
||||
while(1) {
|
||||
rc = readline(&key, &kbuf);
|
||||
if (rc) /* rc == EOF */
|
||||
break;
|
||||
|
||||
rc = readline(&data, &dbuf);
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: line %" Z "d: failed to read key value\n", prog, lineno);
|
||||
goto txn_abort;
|
||||
}
|
||||
|
||||
if (append) {
|
||||
appflag = MDB_APPEND;
|
||||
if (flags & MDB_DUPSORT) {
|
||||
if (prevk.mv_size == key.mv_size && !memcmp(prevk.mv_data, key.mv_data, key.mv_size))
|
||||
appflag = MDB_CURRENT|MDB_APPENDDUP;
|
||||
else {
|
||||
memcpy(prevk.mv_data, key.mv_data, key.mv_size);
|
||||
prevk.mv_size = key.mv_size;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
appflag = 0;
|
||||
}
|
||||
rc = mdb_cursor_put(mc, &key, &data, putflags|appflag);
|
||||
if (rc == MDB_KEYEXIST && putflags)
|
||||
continue;
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: line %" Z "d: mdb_cursor_put failed, error %d %s\n", prog, lineno, rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
batch++;
|
||||
if (batch == 100) {
|
||||
rc = mdb_txn_commit(txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: line %" Z "d: txn_commit: %s\n",
|
||||
prog, lineno, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
rc = mdb_txn_begin(env, NULL, 0, &txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
rc = mdb_cursor_open(txn, dbi, &mc);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
if (append) {
|
||||
MDB_val k, d;
|
||||
mdb_cursor_get(mc, &k, &d, MDB_LAST);
|
||||
memcpy(prevk.mv_data, k.mv_data, k.mv_size);
|
||||
prevk.mv_size = k.mv_size;
|
||||
}
|
||||
batch = 0;
|
||||
}
|
||||
}
|
||||
rc = mdb_txn_commit(txn);
|
||||
txn = NULL;
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: line %" Z "d: txn_commit: %s\n",
|
||||
prog, lineno, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
if (envflags & MDB_NOSYNC) {
|
||||
rc = mdb_env_sync(env, 1);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_sync failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
}
|
||||
mdb_dbi_close(env, dbi);
|
||||
}
|
||||
|
||||
txn_abort:
|
||||
mdb_txn_abort(txn);
|
||||
env_close:
|
||||
mdb_env_close(env);
|
||||
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
.TH MDB_STAT 1 "2015/09/30" "LMDB 0.9.17"
|
||||
.\" Copyright 2012-2021 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.SH NAME
|
||||
mdb_stat \- LMDB environment status tool
|
||||
.SH SYNOPSIS
|
||||
.B mdb_stat
|
||||
[\c
|
||||
.BR \-V ]
|
||||
[\c
|
||||
.BR \-e ]
|
||||
[\c
|
||||
.BR \-f [ f [ f ]]]
|
||||
[\c
|
||||
.BR \-n ]
|
||||
[\c
|
||||
.BR \-r [ r ]]
|
||||
[\c
|
||||
.BR \-a \ |
|
||||
.BI \-s \ subdb\fR]
|
||||
.BR \ envpath
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mdb_stat
|
||||
utility displays the status of an LMDB environment.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-V
|
||||
Write the library version number to the standard output, and exit.
|
||||
.TP
|
||||
.BR \-e
|
||||
Display information about the database environment.
|
||||
.TP
|
||||
.BR \-f
|
||||
Display information about the environment freelist.
|
||||
If \fB\-ff\fP is given, summarize each freelist entry.
|
||||
If \fB\-fff\fP is given, display the full list of page IDs in the freelist.
|
||||
.TP
|
||||
.BR \-n
|
||||
Display the status of an LMDB database which does not use subdirectories.
|
||||
.TP
|
||||
.BR \-r
|
||||
Display information about the environment reader table.
|
||||
Shows the process ID, thread ID, and transaction ID for each active
|
||||
reader slot. The process ID and transaction ID are in decimal, the
|
||||
thread ID is in hexadecimal. The transaction ID is displayed as "-"
|
||||
if the reader does not currently have a read transaction open.
|
||||
If \fB\-rr\fP is given, check for stale entries in the reader
|
||||
table and clear them. The reader table will be printed again
|
||||
after the check is performed.
|
||||
.TP
|
||||
.BR \-a
|
||||
Display the status of all of the subdatabases in the environment.
|
||||
.TP
|
||||
.BR \-s \ subdb
|
||||
Display the status of a specific subdatabase.
|
||||
.SH DIAGNOSTICS
|
||||
Exit status is zero if no errors occur.
|
||||
Errors result in a non-zero exit status and
|
||||
a diagnostic message being written to standard error.
|
||||
.SH "SEE ALSO"
|
||||
.BR mdb_copy (1)
|
||||
.SH AUTHOR
|
||||
Howard Chu of Symas Corporation <http://www.symas.com>
|
||||
@@ -0,0 +1,263 @@
|
||||
/* mdb_stat.c - memory-mapped database status tool */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define Z "I"
|
||||
#else
|
||||
#define Z "z"
|
||||
#endif
|
||||
|
||||
static void prstat(MDB_stat *ms)
|
||||
{
|
||||
#if 0
|
||||
printf(" Page size: %u\n", ms->ms_psize);
|
||||
#endif
|
||||
printf(" Tree depth: %u\n", ms->ms_depth);
|
||||
printf(" Branch pages: %"Z"u\n", ms->ms_branch_pages);
|
||||
printf(" Leaf pages: %"Z"u\n", ms->ms_leaf_pages);
|
||||
printf(" Overflow pages: %"Z"u\n", ms->ms_overflow_pages);
|
||||
printf(" Entries: %"Z"u\n", ms->ms_entries);
|
||||
}
|
||||
|
||||
static void usage(char *prog)
|
||||
{
|
||||
fprintf(stderr, "usage: %s [-V] [-n] [-e] [-r[r]] [-f[f[f]]] [-a|-s subdb] dbpath\n", prog);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int i, rc;
|
||||
MDB_env *env;
|
||||
MDB_txn *txn;
|
||||
MDB_dbi dbi;
|
||||
MDB_stat mst;
|
||||
MDB_envinfo mei;
|
||||
char *prog = argv[0];
|
||||
char *envname;
|
||||
char *subname = NULL;
|
||||
int alldbs = 0, envinfo = 0, envflags = 0, freinfo = 0, rdrinfo = 0;
|
||||
|
||||
if (argc < 2) {
|
||||
usage(prog);
|
||||
}
|
||||
|
||||
/* -a: print stat of main DB and all subDBs
|
||||
* -s: print stat of only the named subDB
|
||||
* -e: print env info
|
||||
* -f: print freelist info
|
||||
* -r: print reader info
|
||||
* -n: use NOSUBDIR flag on env_open
|
||||
* -V: print version and exit
|
||||
* (default) print stat of only the main DB
|
||||
*/
|
||||
while ((i = getopt(argc, argv, "Vaefnrs:")) != EOF) {
|
||||
switch(i) {
|
||||
case 'V':
|
||||
printf("%s\n", MDB_VERSION_STRING);
|
||||
exit(0);
|
||||
break;
|
||||
case 'a':
|
||||
if (subname)
|
||||
usage(prog);
|
||||
alldbs++;
|
||||
break;
|
||||
case 'e':
|
||||
envinfo++;
|
||||
break;
|
||||
case 'f':
|
||||
freinfo++;
|
||||
break;
|
||||
case 'n':
|
||||
envflags |= MDB_NOSUBDIR;
|
||||
break;
|
||||
case 'r':
|
||||
rdrinfo++;
|
||||
break;
|
||||
case 's':
|
||||
if (alldbs)
|
||||
usage(prog);
|
||||
subname = optarg;
|
||||
break;
|
||||
default:
|
||||
usage(prog);
|
||||
}
|
||||
}
|
||||
|
||||
if (optind != argc - 1)
|
||||
usage(prog);
|
||||
|
||||
envname = argv[optind];
|
||||
rc = mdb_env_create(&env);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (alldbs || subname) {
|
||||
mdb_env_set_maxdbs(env, 4);
|
||||
}
|
||||
|
||||
rc = mdb_env_open(env, envname, envflags | MDB_RDONLY, 0664);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
if (envinfo) {
|
||||
(void)mdb_env_stat(env, &mst);
|
||||
(void)mdb_env_info(env, &mei);
|
||||
printf("Environment Info\n");
|
||||
printf(" Map address: %p\n", mei.me_mapaddr);
|
||||
printf(" Map size: %"Z"u\n", mei.me_mapsize);
|
||||
printf(" Page size: %u\n", mst.ms_psize);
|
||||
printf(" Max pages: %"Z"u\n", mei.me_mapsize / mst.ms_psize);
|
||||
printf(" Number of pages used: %"Z"u\n", mei.me_last_pgno+1);
|
||||
printf(" Last transaction ID: %"Z"u\n", mei.me_last_txnid);
|
||||
printf(" Max readers: %u\n", mei.me_maxreaders);
|
||||
printf(" Number of readers used: %u\n", mei.me_numreaders);
|
||||
}
|
||||
|
||||
if (rdrinfo) {
|
||||
printf("Reader Table Status\n");
|
||||
rc = mdb_reader_list(env, (MDB_msg_func *)fputs, stdout);
|
||||
if (rdrinfo > 1) {
|
||||
int dead;
|
||||
mdb_reader_check(env, &dead);
|
||||
printf(" %d stale readers cleared.\n", dead);
|
||||
rc = mdb_reader_list(env, (MDB_msg_func *)fputs, stdout);
|
||||
}
|
||||
if (!(subname || alldbs || freinfo))
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
if (freinfo) {
|
||||
MDB_cursor *cursor;
|
||||
MDB_val key, data;
|
||||
size_t pages = 0, *iptr;
|
||||
|
||||
printf("Freelist Status\n");
|
||||
dbi = 0;
|
||||
rc = mdb_cursor_open(txn, dbi, &cursor);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
rc = mdb_stat(txn, dbi, &mst);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
prstat(&mst);
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
iptr = data.mv_data;
|
||||
pages += *iptr;
|
||||
if (freinfo > 1) {
|
||||
char *bad = "";
|
||||
size_t pg, prev;
|
||||
ssize_t i, j, span = 0;
|
||||
j = *iptr++;
|
||||
for (i = j, prev = 1; --i >= 0; ) {
|
||||
pg = iptr[i];
|
||||
if (pg <= prev)
|
||||
bad = " [bad sequence]";
|
||||
prev = pg;
|
||||
pg += span;
|
||||
for (; i >= span && iptr[i-span] == pg; span++, pg++) ;
|
||||
}
|
||||
printf(" Transaction %"Z"u, %"Z"d pages, maxspan %"Z"d%s\n",
|
||||
*(size_t *)key.mv_data, j, span, bad);
|
||||
if (freinfo > 2) {
|
||||
for (--j; j >= 0; ) {
|
||||
pg = iptr[j];
|
||||
for (span=1; --j >= 0 && iptr[j] == pg+span; span++) ;
|
||||
printf(span>1 ? " %9"Z"u[%"Z"d]\n" : " %9"Z"u\n",
|
||||
pg, span);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
printf(" Free pages: %"Z"u\n", pages);
|
||||
}
|
||||
|
||||
rc = mdb_open(txn, subname, 0, &dbi);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
|
||||
rc = mdb_stat(txn, dbi, &mst);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
printf("Status of %s\n", subname ? subname : "Main DB");
|
||||
prstat(&mst);
|
||||
|
||||
if (alldbs) {
|
||||
MDB_cursor *cursor;
|
||||
MDB_val key;
|
||||
|
||||
rc = mdb_cursor_open(txn, dbi, &cursor);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
while ((rc = mdb_cursor_get(cursor, &key, NULL, MDB_NEXT_NODUP)) == 0) {
|
||||
char *str;
|
||||
MDB_dbi db2;
|
||||
if (memchr(key.mv_data, '\0', key.mv_size))
|
||||
continue;
|
||||
str = malloc(key.mv_size+1);
|
||||
memcpy(str, key.mv_data, key.mv_size);
|
||||
str[key.mv_size] = '\0';
|
||||
rc = mdb_open(txn, str, 0, &db2);
|
||||
if (rc == MDB_SUCCESS)
|
||||
printf("Status of %s\n", str);
|
||||
free(str);
|
||||
if (rc) continue;
|
||||
rc = mdb_stat(txn, db2, &mst);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
prstat(&mst);
|
||||
mdb_close(env, db2);
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
}
|
||||
|
||||
if (rc == MDB_NOTFOUND)
|
||||
rc = MDB_SUCCESS;
|
||||
|
||||
mdb_close(env, dbi);
|
||||
txn_abort:
|
||||
mdb_txn_abort(txn);
|
||||
env_close:
|
||||
mdb_env_close(env);
|
||||
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
/** @file midl.c
|
||||
* @brief ldap bdb back-end ID List functions */
|
||||
/* $OpenLDAP$ */
|
||||
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
|
||||
*
|
||||
* Copyright 2000-2021 The OpenLDAP Foundation.
|
||||
* Portions Copyright 2001-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include "midl.h"
|
||||
|
||||
/** @defgroup internal LMDB Internals
|
||||
* @{
|
||||
*/
|
||||
/** @defgroup idls ID List Management
|
||||
* @{
|
||||
*/
|
||||
#define CMP(x,y) ( (x) < (y) ? -1 : (x) > (y) )
|
||||
|
||||
unsigned mdb_midl_search( MDB_IDL ids, MDB_ID id )
|
||||
{
|
||||
/*
|
||||
* binary search of id in ids
|
||||
* if found, returns position of id
|
||||
* if not found, returns first position greater than id
|
||||
*/
|
||||
unsigned base = 0;
|
||||
unsigned cursor = 1;
|
||||
int val = 0;
|
||||
unsigned n = ids[0];
|
||||
|
||||
while( 0 < n ) {
|
||||
unsigned pivot = n >> 1;
|
||||
cursor = base + pivot + 1;
|
||||
val = CMP( ids[cursor], id );
|
||||
|
||||
if( val < 0 ) {
|
||||
n = pivot;
|
||||
|
||||
} else if ( val > 0 ) {
|
||||
base = cursor;
|
||||
n -= pivot + 1;
|
||||
|
||||
} else {
|
||||
return cursor;
|
||||
}
|
||||
}
|
||||
|
||||
if( val > 0 ) {
|
||||
++cursor;
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
#if 0 /* superseded by append/sort */
|
||||
int mdb_midl_insert( MDB_IDL ids, MDB_ID id )
|
||||
{
|
||||
unsigned x, i;
|
||||
|
||||
x = mdb_midl_search( ids, id );
|
||||
assert( x > 0 );
|
||||
|
||||
if( x < 1 ) {
|
||||
/* internal error */
|
||||
return -2;
|
||||
}
|
||||
|
||||
if ( x <= ids[0] && ids[x] == id ) {
|
||||
/* duplicate */
|
||||
assert(0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( ++ids[0] >= MDB_IDL_DB_MAX ) {
|
||||
/* no room */
|
||||
--ids[0];
|
||||
return -2;
|
||||
|
||||
} else {
|
||||
/* insert id */
|
||||
for (i=ids[0]; i>x; i--)
|
||||
ids[i] = ids[i-1];
|
||||
ids[x] = id;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
MDB_IDL mdb_midl_alloc(int num)
|
||||
{
|
||||
MDB_IDL ids = malloc((num+2) * sizeof(MDB_ID));
|
||||
if (ids) {
|
||||
*ids++ = num;
|
||||
*ids = 0;
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
void mdb_midl_free(MDB_IDL ids)
|
||||
{
|
||||
if (ids)
|
||||
free(ids-1);
|
||||
}
|
||||
|
||||
void mdb_midl_shrink( MDB_IDL *idp )
|
||||
{
|
||||
MDB_IDL ids = *idp;
|
||||
if (*(--ids) > MDB_IDL_UM_MAX &&
|
||||
(ids = realloc(ids, (MDB_IDL_UM_MAX+2) * sizeof(MDB_ID))))
|
||||
{
|
||||
*ids++ = MDB_IDL_UM_MAX;
|
||||
*idp = ids;
|
||||
}
|
||||
}
|
||||
|
||||
static int mdb_midl_grow( MDB_IDL *idp, int num )
|
||||
{
|
||||
MDB_IDL idn = *idp-1;
|
||||
/* grow it */
|
||||
idn = realloc(idn, (*idn + num + 2) * sizeof(MDB_ID));
|
||||
if (!idn)
|
||||
return ENOMEM;
|
||||
*idn++ += num;
|
||||
*idp = idn;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_midl_need( MDB_IDL *idp, unsigned num )
|
||||
{
|
||||
MDB_IDL ids = *idp;
|
||||
num += ids[0];
|
||||
if (num > ids[-1]) {
|
||||
num = (num + num/4 + (256 + 2)) & -256;
|
||||
if (!(ids = realloc(ids-1, num * sizeof(MDB_ID))))
|
||||
return ENOMEM;
|
||||
*ids++ = num - 2;
|
||||
*idp = ids;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_midl_append( MDB_IDL *idp, MDB_ID id )
|
||||
{
|
||||
MDB_IDL ids = *idp;
|
||||
/* Too big? */
|
||||
if (ids[0] >= ids[-1]) {
|
||||
if (mdb_midl_grow(idp, MDB_IDL_UM_MAX))
|
||||
return ENOMEM;
|
||||
ids = *idp;
|
||||
}
|
||||
ids[0]++;
|
||||
ids[ids[0]] = id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app )
|
||||
{
|
||||
MDB_IDL ids = *idp;
|
||||
/* Too big? */
|
||||
if (ids[0] + app[0] >= ids[-1]) {
|
||||
if (mdb_midl_grow(idp, app[0]))
|
||||
return ENOMEM;
|
||||
ids = *idp;
|
||||
}
|
||||
memcpy(&ids[ids[0]+1], &app[1], app[0] * sizeof(MDB_ID));
|
||||
ids[0] += app[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_midl_append_range( MDB_IDL *idp, MDB_ID id, unsigned n )
|
||||
{
|
||||
MDB_ID *ids = *idp, len = ids[0];
|
||||
/* Too big? */
|
||||
if (len + n > ids[-1]) {
|
||||
if (mdb_midl_grow(idp, n | MDB_IDL_UM_MAX))
|
||||
return ENOMEM;
|
||||
ids = *idp;
|
||||
}
|
||||
ids[0] = len + n;
|
||||
ids += len;
|
||||
while (n)
|
||||
ids[n--] = id++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mdb_midl_xmerge( MDB_IDL idl, MDB_IDL merge )
|
||||
{
|
||||
MDB_ID old_id, merge_id, i = merge[0], j = idl[0], k = i+j, total = k;
|
||||
idl[0] = (MDB_ID)-1; /* delimiter for idl scan below */
|
||||
old_id = idl[j];
|
||||
while (i) {
|
||||
merge_id = merge[i--];
|
||||
for (; old_id < merge_id; old_id = idl[--j])
|
||||
idl[k--] = old_id;
|
||||
idl[k--] = merge_id;
|
||||
}
|
||||
idl[0] = total;
|
||||
}
|
||||
|
||||
/* Quicksort + Insertion sort for small arrays */
|
||||
|
||||
#define SMALL 8
|
||||
#define MIDL_SWAP(a,b) { itmp=(a); (a)=(b); (b)=itmp; }
|
||||
|
||||
void
|
||||
mdb_midl_sort( MDB_IDL ids )
|
||||
{
|
||||
/* Max possible depth of int-indexed tree * 2 items/level */
|
||||
int istack[sizeof(int)*CHAR_BIT * 2];
|
||||
int i,j,k,l,ir,jstack;
|
||||
MDB_ID a, itmp;
|
||||
|
||||
ir = (int)ids[0];
|
||||
l = 1;
|
||||
jstack = 0;
|
||||
for(;;) {
|
||||
if (ir - l < SMALL) { /* Insertion sort */
|
||||
for (j=l+1;j<=ir;j++) {
|
||||
a = ids[j];
|
||||
for (i=j-1;i>=1;i--) {
|
||||
if (ids[i] >= a) break;
|
||||
ids[i+1] = ids[i];
|
||||
}
|
||||
ids[i+1] = a;
|
||||
}
|
||||
if (jstack == 0) break;
|
||||
ir = istack[jstack--];
|
||||
l = istack[jstack--];
|
||||
} else {
|
||||
k = (l + ir) >> 1; /* Choose median of left, center, right */
|
||||
MIDL_SWAP(ids[k], ids[l+1]);
|
||||
if (ids[l] < ids[ir]) {
|
||||
MIDL_SWAP(ids[l], ids[ir]);
|
||||
}
|
||||
if (ids[l+1] < ids[ir]) {
|
||||
MIDL_SWAP(ids[l+1], ids[ir]);
|
||||
}
|
||||
if (ids[l] < ids[l+1]) {
|
||||
MIDL_SWAP(ids[l], ids[l+1]);
|
||||
}
|
||||
i = l+1;
|
||||
j = ir;
|
||||
a = ids[l+1];
|
||||
for(;;) {
|
||||
do i++; while(ids[i] > a);
|
||||
do j--; while(ids[j] < a);
|
||||
if (j < i) break;
|
||||
MIDL_SWAP(ids[i],ids[j]);
|
||||
}
|
||||
ids[l+1] = ids[j];
|
||||
ids[j] = a;
|
||||
jstack += 2;
|
||||
if (ir-i+1 >= j-l) {
|
||||
istack[jstack] = ir;
|
||||
istack[jstack-1] = i;
|
||||
ir = j-1;
|
||||
} else {
|
||||
istack[jstack] = j-1;
|
||||
istack[jstack-1] = l;
|
||||
l = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id )
|
||||
{
|
||||
/*
|
||||
* binary search of id in ids
|
||||
* if found, returns position of id
|
||||
* if not found, returns first position greater than id
|
||||
*/
|
||||
unsigned base = 0;
|
||||
unsigned cursor = 1;
|
||||
int val = 0;
|
||||
unsigned n = (unsigned)ids[0].mid;
|
||||
|
||||
while( 0 < n ) {
|
||||
unsigned pivot = n >> 1;
|
||||
cursor = base + pivot + 1;
|
||||
val = CMP( id, ids[cursor].mid );
|
||||
|
||||
if( val < 0 ) {
|
||||
n = pivot;
|
||||
|
||||
} else if ( val > 0 ) {
|
||||
base = cursor;
|
||||
n -= pivot + 1;
|
||||
|
||||
} else {
|
||||
return cursor;
|
||||
}
|
||||
}
|
||||
|
||||
if( val > 0 ) {
|
||||
++cursor;
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id )
|
||||
{
|
||||
unsigned x, i;
|
||||
|
||||
x = mdb_mid2l_search( ids, id->mid );
|
||||
|
||||
if( x < 1 ) {
|
||||
/* internal error */
|
||||
return -2;
|
||||
}
|
||||
|
||||
if ( x <= ids[0].mid && ids[x].mid == id->mid ) {
|
||||
/* duplicate */
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( ids[0].mid >= MDB_IDL_UM_MAX ) {
|
||||
/* too big */
|
||||
return -2;
|
||||
|
||||
} else {
|
||||
/* insert id */
|
||||
ids[0].mid++;
|
||||
for (i=(unsigned)ids[0].mid; i>x; i--)
|
||||
ids[i] = ids[i-1];
|
||||
ids[x] = *id;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id )
|
||||
{
|
||||
/* Too big? */
|
||||
if (ids[0].mid >= MDB_IDL_UM_MAX) {
|
||||
return -2;
|
||||
}
|
||||
ids[0].mid++;
|
||||
ids[ids[0].mid] = *id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
/** @} */
|
||||
@@ -0,0 +1,188 @@
|
||||
/** @file midl.h
|
||||
* @brief LMDB ID List header file.
|
||||
*
|
||||
* This file was originally part of back-bdb but has been
|
||||
* modified for use in libmdb. Most of the macros defined
|
||||
* in this file are unused, just left over from the original.
|
||||
*
|
||||
* This file is only used internally in libmdb and its definitions
|
||||
* are not exposed publicly.
|
||||
*/
|
||||
/* $OpenLDAP$ */
|
||||
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
|
||||
*
|
||||
* Copyright 2000-2021 The OpenLDAP Foundation.
|
||||
* Portions Copyright 2001-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
#ifndef _MDB_MIDL_H_
|
||||
#define _MDB_MIDL_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** @defgroup internal LMDB Internals
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** @defgroup idls ID List Management
|
||||
* @{
|
||||
*/
|
||||
/** A generic unsigned ID number. These were entryIDs in back-bdb.
|
||||
* Preferably it should have the same size as a pointer.
|
||||
*/
|
||||
typedef size_t MDB_ID;
|
||||
|
||||
/** An IDL is an ID List, a sorted array of IDs. The first
|
||||
* element of the array is a counter for how many actual
|
||||
* IDs are in the list. In the original back-bdb code, IDLs are
|
||||
* sorted in ascending order. For libmdb IDLs are sorted in
|
||||
* descending order.
|
||||
*/
|
||||
typedef MDB_ID *MDB_IDL;
|
||||
|
||||
/* IDL sizes - likely should be even bigger
|
||||
* limiting factors: sizeof(ID), thread stack size
|
||||
*/
|
||||
#ifndef MDB_IDL_LOGN
|
||||
#define MDB_IDL_LOGN 16 /* DB_SIZE is 2^16, UM_SIZE is 2^17 */
|
||||
#endif
|
||||
#define MDB_IDL_DB_SIZE (1<<MDB_IDL_LOGN)
|
||||
#define MDB_IDL_UM_SIZE (1<<(MDB_IDL_LOGN+1))
|
||||
|
||||
#define MDB_IDL_DB_MAX (MDB_IDL_DB_SIZE-1)
|
||||
#define MDB_IDL_UM_MAX (MDB_IDL_UM_SIZE-1)
|
||||
|
||||
#define MDB_IDL_SIZEOF(ids) (((ids)[0]+1) * sizeof(MDB_ID))
|
||||
#define MDB_IDL_IS_ZERO(ids) ( (ids)[0] == 0 )
|
||||
#define MDB_IDL_CPY( dst, src ) (memcpy( dst, src, MDB_IDL_SIZEOF( src ) ))
|
||||
#define MDB_IDL_FIRST( ids ) ( (ids)[1] )
|
||||
#define MDB_IDL_LAST( ids ) ( (ids)[(ids)[0]] )
|
||||
|
||||
/** Current max length of an #mdb_midl_alloc()ed IDL */
|
||||
#define MDB_IDL_ALLOCLEN( ids ) ( (ids)[-1] )
|
||||
|
||||
/** Append ID to IDL. The IDL must be big enough. */
|
||||
#define mdb_midl_xappend(idl, id) do { \
|
||||
MDB_ID *xidl = (idl), xlen = ++(xidl[0]); \
|
||||
xidl[xlen] = (id); \
|
||||
} while (0)
|
||||
|
||||
/** Search for an ID in an IDL.
|
||||
* @param[in] ids The IDL to search.
|
||||
* @param[in] id The ID to search for.
|
||||
* @return The index of the first ID greater than or equal to \b id.
|
||||
*/
|
||||
unsigned mdb_midl_search( MDB_IDL ids, MDB_ID id );
|
||||
|
||||
/** Allocate an IDL.
|
||||
* Allocates memory for an IDL of the given size.
|
||||
* @return IDL on success, NULL on failure.
|
||||
*/
|
||||
MDB_IDL mdb_midl_alloc(int num);
|
||||
|
||||
/** Free an IDL.
|
||||
* @param[in] ids The IDL to free.
|
||||
*/
|
||||
void mdb_midl_free(MDB_IDL ids);
|
||||
|
||||
/** Shrink an IDL.
|
||||
* Return the IDL to the default size if it has grown larger.
|
||||
* @param[in,out] idp Address of the IDL to shrink.
|
||||
*/
|
||||
void mdb_midl_shrink(MDB_IDL *idp);
|
||||
|
||||
/** Make room for num additional elements in an IDL.
|
||||
* @param[in,out] idp Address of the IDL.
|
||||
* @param[in] num Number of elements to make room for.
|
||||
* @return 0 on success, ENOMEM on failure.
|
||||
*/
|
||||
int mdb_midl_need(MDB_IDL *idp, unsigned num);
|
||||
|
||||
/** Append an ID onto an IDL.
|
||||
* @param[in,out] idp Address of the IDL to append to.
|
||||
* @param[in] id The ID to append.
|
||||
* @return 0 on success, ENOMEM if the IDL is too large.
|
||||
*/
|
||||
int mdb_midl_append( MDB_IDL *idp, MDB_ID id );
|
||||
|
||||
/** Append an IDL onto an IDL.
|
||||
* @param[in,out] idp Address of the IDL to append to.
|
||||
* @param[in] app The IDL to append.
|
||||
* @return 0 on success, ENOMEM if the IDL is too large.
|
||||
*/
|
||||
int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app );
|
||||
|
||||
/** Append an ID range onto an IDL.
|
||||
* @param[in,out] idp Address of the IDL to append to.
|
||||
* @param[in] id The lowest ID to append.
|
||||
* @param[in] n Number of IDs to append.
|
||||
* @return 0 on success, ENOMEM if the IDL is too large.
|
||||
*/
|
||||
int mdb_midl_append_range( MDB_IDL *idp, MDB_ID id, unsigned n );
|
||||
|
||||
/** Merge an IDL onto an IDL. The destination IDL must be big enough.
|
||||
* @param[in] idl The IDL to merge into.
|
||||
* @param[in] merge The IDL to merge.
|
||||
*/
|
||||
void mdb_midl_xmerge( MDB_IDL idl, MDB_IDL merge );
|
||||
|
||||
/** Sort an IDL.
|
||||
* @param[in,out] ids The IDL to sort.
|
||||
*/
|
||||
void mdb_midl_sort( MDB_IDL ids );
|
||||
|
||||
/** An ID2 is an ID/pointer pair.
|
||||
*/
|
||||
typedef struct MDB_ID2 {
|
||||
MDB_ID mid; /**< The ID */
|
||||
void *mptr; /**< The pointer */
|
||||
} MDB_ID2;
|
||||
|
||||
/** An ID2L is an ID2 List, a sorted array of ID2s.
|
||||
* The first element's \b mid member is a count of how many actual
|
||||
* elements are in the array. The \b mptr member of the first element is unused.
|
||||
* The array is sorted in ascending order by \b mid.
|
||||
*/
|
||||
typedef MDB_ID2 *MDB_ID2L;
|
||||
|
||||
/** Search for an ID in an ID2L.
|
||||
* @param[in] ids The ID2L to search.
|
||||
* @param[in] id The ID to search for.
|
||||
* @return The index of the first ID2 whose \b mid member is greater than or equal to \b id.
|
||||
*/
|
||||
unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id );
|
||||
|
||||
|
||||
/** Insert an ID2 into a ID2L.
|
||||
* @param[in,out] ids The ID2L to insert into.
|
||||
* @param[in] id The ID2 to insert.
|
||||
* @return 0 on success, -1 if the ID was already present in the ID2L.
|
||||
*/
|
||||
int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id );
|
||||
|
||||
/** Append an ID2 into a ID2L.
|
||||
* @param[in,out] ids The ID2L to append into.
|
||||
* @param[in] id The ID2 to append.
|
||||
* @return 0 on success, -2 if the ID2L is too big.
|
||||
*/
|
||||
int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id );
|
||||
|
||||
/** @} */
|
||||
/** @} */
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* _MDB_MIDL_H_ */
|
||||
@@ -0,0 +1,580 @@
|
||||
/* mplay.c - memory-mapped database log replay */
|
||||
/*
|
||||
* Copyright 2011-2023 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
#define MDB_SCNy(t) "z" #t
|
||||
|
||||
#define SCMP(s) s, (sizeof(s)-1)
|
||||
char inbuf[8192];
|
||||
char *dbuf, *kbuf;
|
||||
size_t dbufsize;
|
||||
int maxkey;
|
||||
|
||||
#define SOFF(s) (sizeof(s)+1)
|
||||
|
||||
#define MAXENVS 16
|
||||
#define MAXTXNS 16
|
||||
#define MAXCRSS 16
|
||||
|
||||
#define MAXPIDS 16
|
||||
|
||||
typedef struct crspair {
|
||||
void *tcrs; /* scanned text pointer */
|
||||
MDB_cursor *rcrs;
|
||||
} crspair;
|
||||
|
||||
typedef struct txnpair {
|
||||
void *ttxn; /* scanned text pointer */
|
||||
MDB_txn *rtxn;
|
||||
crspair cursors[MAXCRSS];
|
||||
int ncursors;
|
||||
} txnpair;
|
||||
|
||||
typedef struct envpair {
|
||||
void *tenv;
|
||||
MDB_env *renv;
|
||||
txnpair txns[MAXTXNS];
|
||||
int ntxns;
|
||||
} envpair;
|
||||
|
||||
envpair envs[MAXENVS];
|
||||
int nenvs;
|
||||
|
||||
envpair *lastenv;
|
||||
txnpair *lasttxn;
|
||||
crspair *lastcrs;
|
||||
|
||||
typedef struct pidpair {
|
||||
int tpid;
|
||||
pid_t rpid;
|
||||
int fdout, fdin;
|
||||
} pidpair;
|
||||
|
||||
pidpair *lastpid;
|
||||
|
||||
pidpair pids[MAXPIDS];
|
||||
int npids;
|
||||
|
||||
unsigned long lcount;
|
||||
|
||||
static int unhex(unsigned char *c2)
|
||||
{
|
||||
int x, c;
|
||||
x = *c2++ & 0x4f;
|
||||
if (x & 0x40)
|
||||
x -= 55;
|
||||
c = x << 4;
|
||||
x = *c2 & 0x4f;
|
||||
if (x & 0x40)
|
||||
x -= 55;
|
||||
c |= x;
|
||||
return c;
|
||||
}
|
||||
|
||||
int inhex(char *in, char *out)
|
||||
{
|
||||
char *c2 = out;
|
||||
while (isxdigit(*in)) {
|
||||
*c2++ = unhex((unsigned char *)in);
|
||||
in += 2;
|
||||
}
|
||||
return c2 - out;
|
||||
}
|
||||
|
||||
static void addenv(void *tenv, MDB_env *renv)
|
||||
{
|
||||
assert(nenvs < MAXENVS);
|
||||
envs[nenvs].tenv = tenv;
|
||||
envs[nenvs].renv = renv;
|
||||
envs[nenvs].ntxns = 0;
|
||||
lastenv = envs+nenvs;
|
||||
nenvs++;
|
||||
}
|
||||
|
||||
static envpair *findenv(void *tenv)
|
||||
{
|
||||
int i;
|
||||
if (!lastenv || lastenv->tenv != tenv) {
|
||||
for (i=0; i<nenvs; i++)
|
||||
if (envs[i].tenv == tenv)
|
||||
break;
|
||||
assert(i < nenvs);
|
||||
lastenv = &envs[i];
|
||||
}
|
||||
return lastenv;
|
||||
}
|
||||
|
||||
static void delenv(envpair *ep)
|
||||
{
|
||||
int i = ep - envs;
|
||||
for (; i<nenvs-1; i++)
|
||||
envs[i] = envs[i+1];
|
||||
nenvs--;
|
||||
lastenv = NULL;
|
||||
}
|
||||
|
||||
static void addtxn(void *tenv, void *ttxn, MDB_txn *rtxn)
|
||||
{
|
||||
envpair *ep;
|
||||
txnpair *tp;
|
||||
|
||||
ep = findenv(tenv);
|
||||
assert(ep->ntxns < MAXTXNS);
|
||||
tp = ep->txns+ep->ntxns;
|
||||
tp->ttxn = ttxn;
|
||||
tp->rtxn = rtxn;
|
||||
tp->ncursors = 0;
|
||||
ep->ntxns++;
|
||||
lasttxn = tp;
|
||||
}
|
||||
|
||||
static txnpair *findtxn(void *ttxn)
|
||||
{
|
||||
int i, j;
|
||||
if (lasttxn && lasttxn->ttxn == ttxn)
|
||||
return lasttxn;
|
||||
if (lastenv) {
|
||||
for (i=0; i<lastenv->ntxns; i++) {
|
||||
if (lastenv->txns[i].ttxn == ttxn) {
|
||||
lasttxn = lastenv->txns+i;
|
||||
return lasttxn;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i=0; i<nenvs; i++) {
|
||||
if (envs+i == lastenv) continue;
|
||||
for (j=0; j<envs[i].ntxns; j++) {
|
||||
if (envs[i].txns[j].ttxn == ttxn) {
|
||||
lastenv = envs+i;
|
||||
lasttxn = envs[i].txns+j;
|
||||
return lasttxn;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(0); /* should have found it */
|
||||
}
|
||||
|
||||
static void deltxn(txnpair *tp)
|
||||
{
|
||||
int i = tp - lastenv->txns;
|
||||
for (; i<lastenv->ntxns-1; i++)
|
||||
lastenv->txns[i] = lastenv->txns[i+1];
|
||||
lastenv->ntxns--;
|
||||
lasttxn = NULL;
|
||||
}
|
||||
|
||||
static void addcrs(txnpair *tp, void *tcrs, MDB_cursor *rcrs)
|
||||
{
|
||||
int j = tp->ncursors;
|
||||
assert(tp->ncursors < MAXCRSS);
|
||||
|
||||
tp->cursors[j].tcrs = tcrs;
|
||||
tp->cursors[j].rcrs = rcrs;
|
||||
tp->ncursors++;
|
||||
lastcrs = tp->cursors+j;
|
||||
}
|
||||
|
||||
static crspair *findcrs(void *tcrs)
|
||||
{
|
||||
int i, j, k;
|
||||
envpair *ep;
|
||||
txnpair *tp;
|
||||
crspair *cp;
|
||||
if (lastcrs && lastcrs->tcrs == tcrs)
|
||||
return lastcrs;
|
||||
if (lasttxn) {
|
||||
for (k=0, cp=lasttxn->cursors; k<lasttxn->ncursors; k++, cp++) {
|
||||
if (cp->tcrs == tcrs) {
|
||||
lastcrs = cp;
|
||||
return lastcrs;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastenv) {
|
||||
for (j=0, tp=lastenv->txns; j<lastenv->ntxns; j++, tp++){
|
||||
if (tp == lasttxn)
|
||||
continue;
|
||||
for (k=0, cp = tp->cursors; k<tp->ncursors; k++, cp++) {
|
||||
if (cp->tcrs == tcrs) {
|
||||
lastcrs = cp;
|
||||
lasttxn = tp;
|
||||
return lastcrs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i=0, ep=envs; i<nenvs; i++, ep++) {
|
||||
for (j=0, tp=ep->txns; j<ep->ntxns; j++, tp++) {
|
||||
if (tp == lasttxn)
|
||||
continue;
|
||||
for (k=0, cp = tp->cursors; k<tp->ncursors; k++, cp++) {
|
||||
if (cp->tcrs == tcrs) {
|
||||
lastcrs = cp;
|
||||
lasttxn = tp;
|
||||
lastenv = ep;
|
||||
return lastcrs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(0); /* should have found it already */
|
||||
}
|
||||
|
||||
static void delcrs(void *tcrs)
|
||||
{
|
||||
int i;
|
||||
crspair *cp = findcrs(tcrs);
|
||||
mdb_cursor_close(cp->rcrs);
|
||||
for (i = cp - lasttxn->cursors; i<lasttxn->ncursors-1; i++)
|
||||
lasttxn->cursors[i] = lasttxn->cursors[i+1];
|
||||
lasttxn->ncursors--;
|
||||
lastcrs = NULL;
|
||||
}
|
||||
|
||||
void child()
|
||||
{
|
||||
int rc;
|
||||
MDB_val key, data;
|
||||
char *ptr;
|
||||
|
||||
while (fgets(inbuf, sizeof(inbuf), stdin)) {
|
||||
ptr = inbuf;
|
||||
if (!strncmp(ptr, SCMP("exit")))
|
||||
break;
|
||||
|
||||
if (!strncmp(ptr, SCMP("mdb_env_create"))) {
|
||||
void *tenv;
|
||||
MDB_env *renv;
|
||||
sscanf(ptr+SOFF("mdb_env_create"), "%p", &tenv);
|
||||
E(mdb_env_create(&renv));
|
||||
addenv(tenv, renv);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_env_set_maxdbs"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
unsigned int maxdbs;
|
||||
sscanf(ptr+SOFF("mdb_env_set_maxdbs"), "%p, %u", &tenv, &maxdbs);
|
||||
ep = findenv(tenv);
|
||||
E(mdb_env_set_maxdbs(ep->renv, maxdbs));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_env_set_mapsize"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
size_t mapsize;
|
||||
sscanf(ptr+SOFF("mdb_env_set_mapsize"), "%p, %"MDB_SCNy(u), &tenv, &mapsize);
|
||||
ep = findenv(tenv);
|
||||
E(mdb_env_set_mapsize(ep->renv, mapsize));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_env_open"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
char *path;
|
||||
int len;
|
||||
unsigned int flags, mode;
|
||||
sscanf(ptr+SOFF("mdb_env_open"), "%p, %n", &tenv, &len);
|
||||
path = ptr+SOFF("mdb_env_open")+len;
|
||||
ptr = strchr(path, ',');
|
||||
*ptr++ = '\0';
|
||||
sscanf(ptr, "%u, %o", &flags, &mode);
|
||||
ep = findenv(tenv);
|
||||
E(mdb_env_open(ep->renv, path, flags, mode));
|
||||
if (!maxkey) {
|
||||
maxkey = mdb_env_get_maxkeysize(ep->renv);
|
||||
kbuf = malloc(maxkey+2);
|
||||
dbuf = malloc(maxkey+2);
|
||||
dbufsize = maxkey;
|
||||
}
|
||||
} else if (!strncmp(ptr, SCMP("mdb_env_close"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
sscanf(ptr+SOFF("mdb_env_close"), "%p", &tenv);
|
||||
ep = findenv(tenv);
|
||||
mdb_env_close(ep->renv);
|
||||
delenv(ep);
|
||||
if (!nenvs) /* if no other envs left, this process is done */
|
||||
break;
|
||||
} else if (!strncmp(ptr, SCMP("mdb_txn_begin"))) {
|
||||
unsigned int flags;
|
||||
void *tenv, *ttxn;
|
||||
envpair *ep;
|
||||
MDB_txn *rtxn;
|
||||
sscanf(ptr+SOFF("mdb_txn_begin"), "%p, %*p, %u = %p", &tenv, &flags, &ttxn);
|
||||
ep = findenv(tenv);
|
||||
E(mdb_txn_begin(ep->renv, NULL, flags, &rtxn));
|
||||
addtxn(tenv, ttxn, rtxn);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_txn_commit"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
sscanf(ptr+SOFF("mdb_txn_commit"), "%p", &ttxn);
|
||||
tp = findtxn(ttxn);
|
||||
E(mdb_txn_commit(tp->rtxn));
|
||||
deltxn(tp);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_txn_abort"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
sscanf(ptr+SOFF("mdb_txn_abort"), "%p", &ttxn);
|
||||
tp = findtxn(ttxn);
|
||||
mdb_txn_abort(tp->rtxn);
|
||||
deltxn(tp);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_dbi_open"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
char *dbname;
|
||||
unsigned int flags;
|
||||
unsigned int tdbi;
|
||||
MDB_dbi dbi;
|
||||
sscanf(ptr+SOFF("mdb_dbi_open"), "%p, ", &ttxn);
|
||||
dbname = strchr(ptr+SOFF("mdb_dbi_open"), ',');
|
||||
dbname += 2;
|
||||
ptr = strchr(dbname, ',');
|
||||
*ptr++ = '\0';
|
||||
if (!strcmp(dbname, "(null)"))
|
||||
dbname = NULL;
|
||||
sscanf(ptr, "%u = %u", &flags, &tdbi);
|
||||
tp = findtxn(ttxn);
|
||||
E(mdb_dbi_open(tp->rtxn, dbname, flags, &dbi));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_dbi_close"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
unsigned int tdbi;
|
||||
sscanf(ptr+SOFF("mdb_dbi_close"), "%p, %u", &tenv, &tdbi);
|
||||
ep = findenv(tenv);
|
||||
mdb_dbi_close(ep->renv, tdbi);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_cursor_open"))) {
|
||||
void *ttxn, *tcrs;
|
||||
txnpair *tp;
|
||||
MDB_cursor *rcrs;
|
||||
unsigned int tdbi;
|
||||
sscanf(ptr+SOFF("mdb_cursor_open"), "%p, %u = %p", &ttxn, &tdbi, &tcrs);
|
||||
tp = findtxn(ttxn);
|
||||
E(mdb_cursor_open(tp->rtxn, tdbi, &rcrs));
|
||||
addcrs(tp, tcrs, rcrs);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_cursor_close"))) {
|
||||
void *tcrs;
|
||||
sscanf(ptr+SOFF("mdb_cursor_close"), "%p", &tcrs);
|
||||
delcrs(tcrs);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_cursor_put"))) {
|
||||
void *tcrs;
|
||||
crspair *cp;
|
||||
unsigned int flags;
|
||||
int len;
|
||||
sscanf(ptr+SOFF("mdb_cursor_put"), "%p, ", &tcrs);
|
||||
cp = findcrs(tcrs);
|
||||
ptr = strchr(ptr+SOFF("mdb_cursor_put"), ',');
|
||||
sscanf(ptr+1, "%"MDB_SCNy(u)",", &key.mv_size);
|
||||
if (key.mv_size) {
|
||||
ptr = strchr(ptr, '[');
|
||||
inhex(ptr+1, kbuf);
|
||||
key.mv_data = kbuf;
|
||||
ptr += key.mv_size * 2 + 2;
|
||||
}
|
||||
ptr = strchr(ptr+1, ',');
|
||||
sscanf(ptr+1, "%"MDB_SCNy(u)"%n", &data.mv_size, &len);
|
||||
if (data.mv_size > dbufsize) {
|
||||
dbuf = realloc(dbuf, data.mv_size+2);
|
||||
assert(dbuf != NULL);
|
||||
dbufsize = data.mv_size;
|
||||
}
|
||||
ptr += len+1;
|
||||
if (*ptr == '[') {
|
||||
inhex(ptr+1, dbuf);
|
||||
data.mv_data = dbuf;
|
||||
ptr += data.mv_size * 2 + 2;
|
||||
} else {
|
||||
sprintf(dbuf, "%09ld", (long)mdb_txn_id(lasttxn->rtxn));
|
||||
}
|
||||
sscanf(ptr+1, "%u", &flags);
|
||||
E(mdb_cursor_put(cp->rcrs, &key, &data, flags));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_cursor_del"))) {
|
||||
void *tcrs;
|
||||
crspair *cp;
|
||||
unsigned int flags;
|
||||
sscanf(ptr+SOFF("mdb_cursor_del"), "%p, %u", &tcrs, &flags);
|
||||
cp = findcrs(tcrs);
|
||||
E(mdb_cursor_del(cp->rcrs, flags));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_put"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
unsigned int tdbi, flags;
|
||||
int len;
|
||||
sscanf(ptr+SOFF("mdb_put"),"%p, %u, %"MDB_SCNy(u), &ttxn, &tdbi, &key.mv_size);
|
||||
tp = findtxn(ttxn);
|
||||
ptr = strchr(ptr+SOFF("mdb_put"), '[');
|
||||
inhex(ptr+1, kbuf);
|
||||
key.mv_data = kbuf;
|
||||
ptr += key.mv_size * 2 + 2;
|
||||
sscanf(ptr+1, "%"MDB_SCNy(u)"%n", &data.mv_size, &len);
|
||||
if (data.mv_size > dbufsize) {
|
||||
dbuf = realloc(dbuf, data.mv_size+2);
|
||||
assert(dbuf != NULL);
|
||||
dbufsize = data.mv_size;
|
||||
}
|
||||
ptr += len+1;
|
||||
if (*ptr == '[') {
|
||||
inhex(ptr+1, dbuf);
|
||||
ptr += data.mv_size * 2 + 2;
|
||||
} else {
|
||||
sprintf(dbuf, "%09ld", (long)mdb_txn_id(tp->rtxn));
|
||||
}
|
||||
data.mv_data = dbuf;
|
||||
sscanf(ptr+1, "%u", &flags);
|
||||
RES(MDB_KEYEXIST,mdb_put(tp->rtxn, tdbi, &key, &data, flags));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_del"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
unsigned int tdbi;
|
||||
int len;
|
||||
sscanf(ptr+SOFF("mdb_del"),"%p, %u, %"MDB_SCNy(u), &ttxn, &tdbi, &key.mv_size);
|
||||
tp = findtxn(ttxn);
|
||||
ptr = strchr(ptr+SOFF("mdb_del"), '[');
|
||||
inhex(ptr+1, kbuf);
|
||||
key.mv_data = kbuf;
|
||||
ptr += key.mv_size * 2 + 2;
|
||||
sscanf(ptr+1, "%"MDB_SCNy(u)"%n", &data.mv_size, &len);
|
||||
if (data.mv_size > dbufsize) {
|
||||
dbuf = realloc(dbuf, data.mv_size+2);
|
||||
assert(dbuf != NULL);
|
||||
dbufsize = data.mv_size;
|
||||
}
|
||||
ptr += len+1;
|
||||
if (*ptr == '[') {
|
||||
inhex(ptr+1, dbuf);
|
||||
} else {
|
||||
sprintf(dbuf, "%09ld", (long)mdb_txn_id(tp->rtxn));
|
||||
}
|
||||
data.mv_data = dbuf;
|
||||
RES(MDB_NOTFOUND,mdb_del(tp->rtxn, tdbi, &key, &data));
|
||||
}
|
||||
write(1, "\n", 1);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static pidpair *addpid(int tpid)
|
||||
{
|
||||
int fdout[2], fdin[2];
|
||||
pid_t pid;
|
||||
assert(npids < MAXPIDS);
|
||||
pids[npids].tpid = tpid;
|
||||
pipe(fdout);
|
||||
pipe(fdin);
|
||||
if ((pid = fork()) == 0) {
|
||||
/* child */
|
||||
fflush(stdin);
|
||||
fflush(stdout);
|
||||
dup2(fdout[0], 0);
|
||||
dup2(fdin[1], 1);
|
||||
child();
|
||||
return 0; /* NOTREACHED */
|
||||
} else {
|
||||
pids[npids].rpid = pid;
|
||||
pids[npids].fdout = fdout[1];
|
||||
pids[npids].fdin = fdin[0];
|
||||
lastpid = pids+npids;
|
||||
npids++;
|
||||
return lastpid;
|
||||
}
|
||||
}
|
||||
|
||||
static pidpair *findpid(int tpid)
|
||||
{
|
||||
int i;
|
||||
if (!lastpid || lastpid->tpid != tpid) {
|
||||
for (i=0; i<npids; i++)
|
||||
if (pids[i].tpid == tpid)
|
||||
break;
|
||||
if (i == npids)
|
||||
return NULL;
|
||||
lastpid = &pids[i];
|
||||
}
|
||||
return lastpid;
|
||||
}
|
||||
|
||||
volatile pid_t killpid;
|
||||
|
||||
static void delpid(int tpid)
|
||||
{
|
||||
pidpair *pp = findpid(tpid);
|
||||
if (pp) {
|
||||
pid_t kpid = pp->rpid;
|
||||
killpid = kpid;
|
||||
write(pp->fdout, "exit\n", sizeof("exit"));
|
||||
while (killpid == kpid)
|
||||
usleep(10000);
|
||||
}
|
||||
}
|
||||
|
||||
static void reaper(int sig)
|
||||
{
|
||||
int status, i;
|
||||
pid_t pid = waitpid(-1, &status, 0);
|
||||
if (pid > 0) {
|
||||
fprintf(stderr, "# %s %d\n", WIFEXITED(status) ? "exited" : "killed", pid);
|
||||
for (i=0; i<npids; i++)
|
||||
if (pids[i].rpid == pid)
|
||||
break;
|
||||
assert(i < npids);
|
||||
close(pids[i].fdout);
|
||||
close(pids[i].fdin);
|
||||
for (;i<npids-1; i++)
|
||||
pids[i] = pids[i+1];
|
||||
npids--;
|
||||
killpid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
signal(SIGCHLD, reaper);
|
||||
|
||||
while (fgets(inbuf, sizeof(inbuf), stdin)) {
|
||||
pidpair *pp;
|
||||
int tpid, len;
|
||||
char c, *ptr;
|
||||
lcount++;
|
||||
|
||||
if (inbuf[0] == '#' && !strncmp(inbuf+1, SCMP(" killed"))) {
|
||||
sscanf(inbuf+SOFF("killed"),"%d", &tpid);
|
||||
delpid(tpid);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inbuf[0] != '>')
|
||||
continue;
|
||||
ptr = inbuf+1;
|
||||
sscanf(ptr, "%d:%n", &tpid, &len);
|
||||
pp = findpid(tpid);
|
||||
if (!pp)
|
||||
pp = addpid(tpid); /* new process */
|
||||
|
||||
ptr = inbuf+len+1;
|
||||
len = strlen(ptr);
|
||||
write(pp->fdout, ptr, len); /* send command and wait for ack */
|
||||
read(pp->fdin, &c, 1);
|
||||
}
|
||||
while (npids)
|
||||
delpid(pids[0].tpid);
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/* mtest.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor, *cur2;
|
||||
MDB_cursor_op op;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[32] = "";
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
count = (rand()%384) + 64;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = rand()%1024;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_maxreaders(env, 1));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP /*|MDB_NOSYNC*/, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, NULL, 0, &dbi));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
for (i=0;i<count;i++) {
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
/* Set <data> in each iteration, since MDB_NOOVERWRITE may modify it */
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NOOVERWRITE))) {
|
||||
j++;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
}
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
j=0;
|
||||
key.mv_data = sval;
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(sval, "%03x ", values[i]);
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, NULL))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor last\n");
|
||||
E(mdb_cursor_get(cursor, &key, &data, MDB_LAST));
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor last/prev\n");
|
||||
E(mdb_cursor_get(cursor, &key, &data, MDB_LAST));
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
E(mdb_cursor_get(cursor, &key, &data, MDB_PREV));
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
printf("Deleting with cursor\n");
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cur2));
|
||||
for (i=0; i<50; i++) {
|
||||
if (RES(MDB_NOTFOUND, mdb_cursor_get(cur2, &key, &data, MDB_NEXT)))
|
||||
break;
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
E(mdb_del(txn, dbi, &key, NULL));
|
||||
}
|
||||
|
||||
printf("Restarting cursor in txn\n");
|
||||
for (op=MDB_FIRST, i=0; i<=32; op=MDB_NEXT, i++) {
|
||||
if (RES(MDB_NOTFOUND, mdb_cursor_get(cur2, &key, &data, op)))
|
||||
break;
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
mdb_cursor_close(cur2);
|
||||
E(mdb_txn_commit(txn));
|
||||
|
||||
printf("Restarting cursor outside txn\n");
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
for (op=MDB_FIRST, i=0; i<=32; op=MDB_NEXT, i++) {
|
||||
if (RES(MDB_NOTFOUND, mdb_cursor_get(cursor, &key, &data, op)))
|
||||
break;
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/* mtest2.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Just like mtest.c, but using a subDB instead of the main DB */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[32] = "";
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
count = (rand()%384) + 64;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = rand()%1024;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_maxreaders(env, 1));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id1", MDB_CREATE, &dbi));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
for (i=0;i<count;i++) {
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NOOVERWRITE)))
|
||||
j++;
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
j=0;
|
||||
key.mv_data = sval;
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(sval, "%03x ", values[i]);
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, NULL))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/* mtest3.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Tests for sorted duplicate DBs */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[32];
|
||||
char kval[sizeof(int)];
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
memset(sval, 0, sizeof(sval));
|
||||
|
||||
count = (rand()%384) + 64;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = rand()%1024;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id2", MDB_CREATE|MDB_DUPSORT, &dbi));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
for (i=0;i<count;i++) {
|
||||
if (!(i & 0x0f))
|
||||
sprintf(kval, "%03x", values[i]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA)))
|
||||
j++;
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
j=0;
|
||||
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(kval, "%03x", values[i & ~0x0f]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/* mtest4.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Tests for sorted duplicate DBs with fixed-size keys */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[8];
|
||||
char kval[sizeof(int)];
|
||||
|
||||
memset(sval, 0, sizeof(sval));
|
||||
|
||||
count = 510;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = i*5;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id4", MDB_CREATE|MDB_DUPSORT|MDB_DUPFIXED, &dbi));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
strcpy(kval, "001");
|
||||
for (i=0;i<count;i++) {
|
||||
sprintf(sval, "%07x", values[i]);
|
||||
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA)))
|
||||
j++;
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
/* there should be one full page of dups now.
|
||||
*/
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
/* test all 3 branches of split code:
|
||||
* 1: new key in lower half
|
||||
* 2: new key at split point
|
||||
* 3: new key in upper half
|
||||
*/
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
sprintf(sval, "%07x", values[3]+1);
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
(void)RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA));
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
sprintf(sval, "%07x", values[255]+1);
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
(void)RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA));
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
sprintf(sval, "%07x", values[500]+1);
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
(void)RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA));
|
||||
E(mdb_txn_commit(txn));
|
||||
|
||||
/* Try MDB_NEXT_MULTIPLE */
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT_MULTIPLE)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
j=0;
|
||||
|
||||
for (i= count - 1; i > -1; i-= (rand()%3)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(sval, "%07x", values[i]);
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/* mtest5.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Tests for sorted duplicate DBs using cursor_put */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[32];
|
||||
char kval[sizeof(int)];
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
memset(sval, 0, sizeof(sval));
|
||||
|
||||
count = (rand()%384) + 64;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = rand()%1024;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id2", MDB_CREATE|MDB_DUPSORT, &dbi));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
for (i=0;i<count;i++) {
|
||||
if (!(i & 0x0f))
|
||||
sprintf(kval, "%03x", values[i]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
if (RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NODUPDATA)))
|
||||
j++;
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
mdb_cursor_close(cursor);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
j=0;
|
||||
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(kval, "%03x", values[i & ~0x0f]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/* mtest6.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Tests for DB splits and merges */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
char dkbuf[1024];
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data, sdata;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
long kval;
|
||||
char *sval;
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id6", MDB_CREATE|MDB_INTEGERKEY, &dbi));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
E(mdb_stat(txn, dbi, &mst));
|
||||
|
||||
sval = calloc(1, mst.ms_psize / 4);
|
||||
key.mv_size = sizeof(long);
|
||||
key.mv_data = &kval;
|
||||
sdata.mv_size = mst.ms_psize / 4 - 30;
|
||||
sdata.mv_data = sval;
|
||||
|
||||
printf("Adding 12 values, should yield 3 splits\n");
|
||||
for (i=0;i<12;i++) {
|
||||
kval = i*5;
|
||||
sprintf(sval, "%08x", kval);
|
||||
data = sdata;
|
||||
(void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE));
|
||||
}
|
||||
printf("Adding 12 more values, should yield 3 splits\n");
|
||||
for (i=0;i<12;i++) {
|
||||
kval = i*5+4;
|
||||
sprintf(sval, "%08x", kval);
|
||||
data = sdata;
|
||||
(void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE));
|
||||
}
|
||||
printf("Adding 12 more values, should yield 3 splits\n");
|
||||
for (i=0;i<12;i++) {
|
||||
kval = i*5+1;
|
||||
sprintf(sval, "%08x", kval);
|
||||
data = sdata;
|
||||
(void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE));
|
||||
}
|
||||
E(mdb_cursor_get(cursor, &key, &data, MDB_FIRST));
|
||||
|
||||
do {
|
||||
printf("key: %p %s, data: %p %.*s\n",
|
||||
key.mv_data, mdb_dkey(&key, dkbuf),
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
} while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0);
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_commit(txn);
|
||||
|
||||
#if 0
|
||||
j=0;
|
||||
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(kval, "%03x", values[i & ~0x0f]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
#endif
|
||||
mdb_env_close(env);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/* sample-bdb.txt - BerkeleyDB toy/sample
|
||||
*
|
||||
* Do a line-by-line comparison of this and sample-mdb.txt
|
||||
*/
|
||||
/*
|
||||
* Copyright 2012-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <db.h>
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int rc;
|
||||
DB_ENV *env;
|
||||
DB *dbi;
|
||||
DBT key, data;
|
||||
DB_TXN *txn;
|
||||
DBC *cursor;
|
||||
char sval[32], kval[32];
|
||||
|
||||
/* Note: Most error checking omitted for simplicity */
|
||||
|
||||
#define FLAGS (DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_TXN|DB_INIT_MPOOL|DB_CREATE|DB_THREAD)
|
||||
rc = db_env_create(&env, 0);
|
||||
rc = env->open(env, "./testdb", FLAGS, 0664);
|
||||
rc = db_create(&dbi, env, 0);
|
||||
rc = env->txn_begin(env, NULL, &txn, 0);
|
||||
rc = dbi->open(dbi, txn, "test.bdb", NULL, DB_BTREE, DB_CREATE, 0664);
|
||||
|
||||
memset(&key, 0, sizeof(DBT));
|
||||
memset(&data, 0, sizeof(DBT));
|
||||
key.size = sizeof(int);
|
||||
key.data = sval;
|
||||
data.size = sizeof(sval);
|
||||
data.data = sval;
|
||||
|
||||
sprintf(sval, "%03x %d foo bar", 32, 3141592);
|
||||
rc = dbi->put(dbi, txn, &key, &data, 0);
|
||||
rc = txn->commit(txn, 0);
|
||||
if (rc) {
|
||||
fprintf(stderr, "txn->commit: (%d) %s\n", rc, db_strerror(rc));
|
||||
goto leave;
|
||||
}
|
||||
rc = env->txn_begin(env, NULL, &txn, 0);
|
||||
rc = dbi->cursor(dbi, txn, &cursor, 0);
|
||||
key.flags = DB_DBT_USERMEM;
|
||||
key.data = kval;
|
||||
key.ulen = sizeof(kval);
|
||||
data.flags = DB_DBT_USERMEM;
|
||||
data.data = sval;
|
||||
data.ulen = sizeof(sval);
|
||||
while ((rc = cursor->c_get(cursor, &key, &data, DB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.data, (int) key.size, (char *) key.data,
|
||||
data.data, (int) data.size, (char *) data.data);
|
||||
}
|
||||
rc = cursor->c_close(cursor);
|
||||
rc = txn->abort(txn);
|
||||
leave:
|
||||
rc = dbi->close(dbi, 0);
|
||||
rc = env->close(env, 0);
|
||||
return rc;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/* sample-mdb.txt - MDB toy/sample
|
||||
*
|
||||
* Do a line-by-line comparison of this and sample-bdb.txt
|
||||
*/
|
||||
/*
|
||||
* Copyright 2012-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_cursor *cursor;
|
||||
char sval[32];
|
||||
|
||||
/* Note: Most error checking omitted for simplicity */
|
||||
|
||||
rc = mdb_env_create(&env);
|
||||
rc = mdb_env_open(env, "./testdb", 0, 0664);
|
||||
rc = mdb_txn_begin(env, NULL, 0, &txn);
|
||||
rc = mdb_dbi_open(txn, NULL, 0, &dbi);
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = sval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
sprintf(sval, "%03x %d foo bar", 32, 3141592);
|
||||
rc = mdb_put(txn, dbi, &key, &data, 0);
|
||||
rc = mdb_txn_commit(txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_commit: (%d) %s\n", rc, mdb_strerror(rc));
|
||||
goto leave;
|
||||
}
|
||||
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
|
||||
rc = mdb_cursor_open(txn, dbi, &cursor);
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
leave:
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<tagfile>
|
||||
<compound kind="page">
|
||||
<name>mdb_copy_1</name>
|
||||
<title>mdb_copy - environment copy tool</title>
|
||||
<filename>mdb_copy.1</filename>
|
||||
</compound>
|
||||
<compound kind="page">
|
||||
<name>mdb_dump_1</name>
|
||||
<title>mdb_dump - environment export tool</title>
|
||||
<filename>mdb_dump.1</filename>
|
||||
</compound>
|
||||
<compound kind="page">
|
||||
<name>mdb_load_1</name>
|
||||
<title>mdb_load - environment import tool</title>
|
||||
<filename>mdb_load.1</filename>
|
||||
</compound>
|
||||
<compound kind="page">
|
||||
<name>mdb_stat_1</name>
|
||||
<title>mdb_stat - environment status tool</title>
|
||||
<filename>mdb_stat.1</filename>
|
||||
</compound>
|
||||
</tagfile>
|
||||
+8
-4
@@ -130,11 +130,11 @@ void queue_free(struct ll_queue* q) {
|
||||
// ВАЖНО: Не освобождаем элементы в очереди - они должны быть извлечены отдельно
|
||||
// Это упрощает архитектуру и предотвращает double-u_free
|
||||
|
||||
// Освободить список waiters (сбросить handle->internal)
|
||||
// Освободить список waiters (сбросить handle->internal, отменить отложенные вызовы)
|
||||
struct queue_waiter* w = q->waiter_head;
|
||||
while (w) {
|
||||
struct queue_waiter* next = w->next;
|
||||
if (w->handle) w->handle->internal = NULL;
|
||||
if (w->handle) { w->handle->internal = NULL; if (w->handle->call_soon_id) { uasync_call_soon_cancel(q->ua, w->handle->call_soon_id); w->handle->call_soon_id = NULL; } }
|
||||
u_free(w);
|
||||
w = next;
|
||||
}
|
||||
@@ -305,14 +305,17 @@ static uint32_t make_hash(const void* data, uint16_t len) {// алгоритм F
|
||||
|
||||
static void waiter_defer_cb(void* arg) {
|
||||
struct queue_waiter_handle* h = (struct queue_waiter_handle*)arg;
|
||||
h->defer_cb(h->defer_q, h->defer_arg);
|
||||
h->call_soon_id = NULL;
|
||||
if (h->defer_cb) h->defer_cb(h->defer_q, h->defer_arg);
|
||||
else DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "waiter_defer_cb: NULL callback (stale?) q=%p", (void*)h->defer_q);
|
||||
}
|
||||
|
||||
static inline void queue_waiter_call(struct ll_queue* q, queue_threshold_callback_fn cb, void* arg,
|
||||
struct queue_waiter_handle* h) {
|
||||
if (q->waiter_defer) {
|
||||
if (h->call_soon_id) { DEBUG_WARN(DEBUG_CATEGORY_LL_QUEUE, "queue_waiter_call: overwriting existing call_soon_id=%p", h->call_soon_id); uasync_call_soon_cancel(q->ua, h->call_soon_id); }
|
||||
h->defer_q = q; h->defer_cb = cb; h->defer_arg = arg;
|
||||
uasync_call_soon(q->ua, h, waiter_defer_cb);
|
||||
h->call_soon_id = uasync_call_soon(q->ua, h, waiter_defer_cb);
|
||||
} else {
|
||||
cb(q, arg);
|
||||
}
|
||||
@@ -692,6 +695,7 @@ int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h,
|
||||
|
||||
void queue_waiter_cancel(struct ll_queue* q, struct queue_waiter_handle* h) {
|
||||
if (!q || !h || !h->internal) return;
|
||||
if (h->call_soon_id) { uasync_call_soon_cancel(q->ua, h->call_soon_id); h->call_soon_id = NULL; }
|
||||
|
||||
struct queue_waiter* waiter = h->internal;
|
||||
struct queue_waiter* prev = NULL;
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
#ifndef LL_QUEUE_H
|
||||
#define LL_QUEUE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#include <stddef.h> // для size_t
|
||||
#include <stdint.h> // для uint64_t
|
||||
#include "memory_pool.h" // для struct memory_pool
|
||||
@@ -117,6 +122,7 @@ struct queue_waiter_handle {
|
||||
struct ll_queue* defer_q;
|
||||
queue_threshold_callback_fn defer_cb;
|
||||
void* defer_arg;
|
||||
void* call_soon_id; ///< хендл отложенного вызова (для отмены)
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -411,4 +417,8 @@ static inline size_t queue_total_bytes(struct ll_queue* q) {
|
||||
return q->total_bytes;
|
||||
}
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // LL_QUEUE_H
|
||||
@@ -2,6 +2,11 @@
|
||||
#ifndef MEMORY_POOL_H
|
||||
#define MEMORY_POOL_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#include <stddef.h>
|
||||
#include "debug_config.h"
|
||||
|
||||
@@ -37,4 +42,8 @@ size_t memory_pool_get_total_free_blocks(void);
|
||||
|
||||
int memory_pool_is_freed(struct memory_pool* pool, void* obj);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // MEMORY_POOL_H
|
||||
@@ -5,6 +5,11 @@
|
||||
#ifndef PLATFORM_COMPAT_H
|
||||
#define PLATFORM_COMPAT_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
@@ -30,6 +35,18 @@
|
||||
// POSIX functions missing on Windows
|
||||
#define strcasecmp _stricmp
|
||||
#define strncasecmp _strnicmp
|
||||
|
||||
// memmem — not available on Windows
|
||||
#define memmem compat_memmem
|
||||
static inline void* compat_memmem(const void* h, size_t hl, const void* n, size_t nl) {
|
||||
if (nl == 0) return (void*)h;
|
||||
if (hl < nl) return NULL;
|
||||
const unsigned char* hay = (const unsigned char*)h;
|
||||
const unsigned char* ndl = (const unsigned char*)n;
|
||||
for (size_t i = 0; i <= hl - nl; i++)
|
||||
if (hay[i] == ndl[0] && memcmp(hay + i, ndl, nl) == 0) return (void*)(hay + i);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// poll() and pollfd are already defined in winsock2.h on MSYS2 UCRT64
|
||||
// Only define if not already present
|
||||
@@ -174,4 +191,17 @@ int get_interface_ipv6_by_index(uint32_t netif_index, int temporary, uint8_t* ad
|
||||
// family: AF_INET or AF_INET6, returns ifindex (0 on failure)
|
||||
uint32_t get_default_route_netif_index(int family);
|
||||
|
||||
|
||||
// Cross-platform mkdir
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
#define utun_mkdir(path, mode) _mkdir(path)
|
||||
#else
|
||||
#include <sys/stat.h>
|
||||
#define utun_mkdir(path, mode) mkdir(path, mode)
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // PLATFORM_COMPAT_H
|
||||
@@ -32,6 +32,15 @@
|
||||
#ifndef _RADIX_H_
|
||||
#define _RADIX_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include "mem.h"
|
||||
|
||||
|
||||
@@ -76,6 +76,11 @@
|
||||
*/
|
||||
#ifndef SERIALIZE_H
|
||||
#define SERIALIZE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "../lib/mem.h"
|
||||
@@ -127,4 +132,8 @@ int serialize_decode(const uint8_t* in_buf, size_t in_len,
|
||||
|
||||
void serialize_free(const struct SerializeSchema* schema, void** structure);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // SERIALIZE_H
|
||||
@@ -9,6 +9,11 @@
|
||||
#ifndef SC_SHA256_H
|
||||
#define SC_SHA256_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/*************************** HEADER FILES ***************************/
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
@@ -29,4 +34,8 @@ void sc_sha256_init(SC_SHA256_CTX *ctx);
|
||||
void sc_sha256_update(SC_SHA256_CTX *ctx, const uint8_t data[], size_t len);
|
||||
void sc_sha256_final(SC_SHA256_CTX *ctx, uint8_t hash[]);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // SC_SHA256_H
|
||||
@@ -89,6 +89,7 @@ int socket_set_nonblocking(socket_t sock) {
|
||||
}
|
||||
|
||||
int socket_close_wrapper(socket_t sock) {
|
||||
if (sock == 0) return 0; // never close fd 0
|
||||
return close(sock);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
|
||||
#ifndef SOCKET_COMPAT_H
|
||||
#define SOCKET_COMPAT_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -73,4 +78,8 @@ const char* socket_strerror(int err);
|
||||
// взять порт из sockaddr_storage
|
||||
uint16_t ss_get_port(const struct sockaddr_storage *addr);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // SOCKET_COMPAT_H
|
||||
+34
-16
@@ -13,6 +13,9 @@
|
||||
#ifndef MSG_NOSIGNAL
|
||||
#define MSG_NOSIGNAL 0
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
#define SHUT_WR SD_SEND
|
||||
#endif
|
||||
|
||||
static void read_cb(socket_t sock, void* arg);
|
||||
static void write_cb(socket_t sock, void* arg);
|
||||
@@ -32,7 +35,7 @@ struct tcp_conn* tcp_conn_create(
|
||||
void (*on_error)(struct tcp_conn* tc, int err, void* arg),
|
||||
void* arg)
|
||||
{
|
||||
if (!ua || sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: invalid args ua=%p", ua); return NULL; }
|
||||
if (!ua || sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: invalid args ua=%p", (void*)ua); return NULL; }
|
||||
|
||||
struct tcp_conn* tc = u_calloc(1, sizeof(struct tcp_conn));
|
||||
if (!tc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: u_calloc failed"); return NULL; }
|
||||
@@ -104,8 +107,17 @@ struct tcp_conn* tcp_conn_create(
|
||||
return tc;
|
||||
}
|
||||
|
||||
static void tcp_conn_deferred_free(void* arg) {
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
if (tc->entry_pool) memory_pool_destroy(tc->entry_pool);
|
||||
if (tc->data_pool) memory_pool_destroy(tc->data_pool);
|
||||
u_free(tc);
|
||||
}
|
||||
|
||||
void tcp_conn_destroy(struct tcp_conn* tc) {
|
||||
if (!tc) return;
|
||||
if (tc->destroyed) return;
|
||||
tc->destroyed = 1;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_destroy: fd=%d connected=%d error=%d fin_remote=%d fin_local=%d closed=%d",
|
||||
(int)tc->sock, tc->connected, tc->error, tc->fin_remote, tc->fin_local, tc->closed);
|
||||
|
||||
@@ -127,14 +139,12 @@ void tcp_conn_destroy(struct tcp_conn* tc) {
|
||||
if (e->dgram && e->dgram != &tcp_fin_sentinel) memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e); queue_resume_callback(tc->write_queue);
|
||||
}
|
||||
queue_free(tc->read_queue);
|
||||
queue_free(tc->write_queue);
|
||||
queue_free(tc->read_queue); tc->read_queue = NULL;
|
||||
queue_free(tc->write_queue); tc->write_queue = NULL;
|
||||
|
||||
if (tc->write_buf) memory_pool_free(tc->data_pool, tc->write_buf);
|
||||
if (tc->sock != SOCKET_INVALID) socket_close_wrapper(tc->sock);
|
||||
memory_pool_destroy(tc->entry_pool);
|
||||
memory_pool_destroy(tc->data_pool);
|
||||
u_free(tc);
|
||||
if (tc->sock != SOCKET_INVALID) { socket_close_wrapper(tc->sock); tc->sock = SOCKET_INVALID; }
|
||||
uasync_call_soon(tc->ua, tc, tcp_conn_deferred_free);
|
||||
}
|
||||
|
||||
static void tcp_conn_handle_error(struct tcp_conn* tc, int err)
|
||||
@@ -157,13 +167,15 @@ static void tcp_conn_handle_error(struct tcp_conn* tc, int err)
|
||||
}
|
||||
}
|
||||
|
||||
if (tc->socket_id) {
|
||||
if (tc->sock != SOCKET_INVALID) {
|
||||
uasync_remove_socket_t(tc->ua, tc->sock);
|
||||
tc->socket_id = NULL;
|
||||
}
|
||||
if (tc->sock != SOCKET_INVALID) {
|
||||
#if HAS_EPOLL
|
||||
if (tc->ua && tc->ua->use_epoll && tc->ua->epoll_fd >= 0)
|
||||
epoll_ctl(tc->ua->epoll_fd, EPOLL_CTL_DEL, (int)tc->sock, NULL);
|
||||
#endif
|
||||
socket_close_wrapper(tc->sock);
|
||||
tc->sock = SOCKET_INVALID;
|
||||
tc->sock = SOCKET_INVALID; // ДО on_error: read/write в том же epoll event увидят INVALID
|
||||
}
|
||||
|
||||
queue_set_callback(tc->write_queue, NULL, NULL);
|
||||
@@ -183,7 +195,7 @@ static void tcp_conn_handle_error(struct tcp_conn* tc, int err)
|
||||
static void read_cb(socket_t sock, void* arg) {
|
||||
(void)sock;
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
if (!tc || tc->sock == SOCKET_INVALID) return;
|
||||
if (!tc || tc->sock == SOCKET_INVALID || !tc->read_queue) return;
|
||||
|
||||
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
|
||||
uint8_t* buf = memory_pool_alloc(tc->data_pool);
|
||||
@@ -200,8 +212,10 @@ static void read_cb(socket_t sock, void* arg) {
|
||||
if (n > 0) {
|
||||
e->dgram = buf;
|
||||
e->len = (uint16_t)n;
|
||||
queue_data_put(tc->read_queue, e);
|
||||
if (tc->read_queue->count >= tc->read_high_water && !tc->read_paused) {
|
||||
struct ll_queue* rq = tc->read_queue;
|
||||
queue_data_put(rq, e);
|
||||
// tc мог быть уничтожен через callback chain — read_queue обнулён
|
||||
if (rq && tc->read_queue && tc->read_queue->count >= tc->read_high_water && !tc->read_paused) {
|
||||
uasync_set_socket_read(tc->ua, tc->socket_id, 0);
|
||||
tc->read_paused = 1;
|
||||
queue_waiter_wait(tc->read_queue, &tc->read_waiter, resume_read_cb, tc);
|
||||
@@ -366,12 +380,12 @@ static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
|
||||
static void write_cb(socket_t sock, void* arg) {
|
||||
(void)sock;
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
if (!tc || tc->sock == SOCKET_INVALID) return;
|
||||
if (!tc || tc->sock == SOCKET_INVALID || !tc->write_queue) return;
|
||||
|
||||
if (!tc->connected) {
|
||||
int err = 0;
|
||||
socklen_t len = sizeof(err);
|
||||
if (getsockopt(tc->sock, SOL_SOCKET, SO_ERROR, &err, &len) == 0 && err == 0) {
|
||||
if (getsockopt(tc->sock, SOL_SOCKET, SO_ERROR, (char*)&err, &len) == 0 && err == 0) {
|
||||
tc->connected = 1;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: connect ok fd=%d", (int)tc->sock);
|
||||
} else {
|
||||
@@ -384,6 +398,10 @@ static void write_cb(socket_t sock, void* arg) {
|
||||
if (flush_write_buf(tc)) return;
|
||||
if (!tc->write_buf) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: write_cb flushed fd=%d wmon=%d (resume WQ cb)", (int)tc->sock, tc->write_monitor);
|
||||
if (tc->write_queue->waiter_defer) {
|
||||
uasync_set_socket_write(tc->ua, tc->socket_id, 0);
|
||||
tc->write_monitor = 0;
|
||||
}
|
||||
queue_resume_callback(tc->write_queue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
#ifndef TCP_IO_H
|
||||
#define TCP_IO_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#include "u_async.h"
|
||||
#include "socket_compat.h"
|
||||
#include "ll_queue.h"
|
||||
@@ -46,6 +51,7 @@ struct tcp_conn {
|
||||
uint8_t fin_remote; // FIN получен от удалённой стороны (recv == 0)
|
||||
uint8_t fin_local; // FIN отправлен удалённой стороне (shutdown SHUT_WR)
|
||||
uint8_t closed; // сокет полностью закрыт (close)
|
||||
uint8_t destroyed; // 1 = tcp_conn_destroy вызван, tc ожидает отложенного free
|
||||
|
||||
// Частичная отправка (из data_pool, не в очереди — досылается первой)
|
||||
uint8_t* write_buf;
|
||||
@@ -107,4 +113,8 @@ void tcp_conn_set_flushed(struct tcp_conn* tc, void (*on_flushed)(struct tcp_con
|
||||
// Принудительная остановка чтения: убирает EPOLLIN и отменяет read_waiter.
|
||||
void tcp_conn_pause_read(struct tcp_conn* tc);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -3,6 +3,11 @@
|
||||
#ifndef TIMEOUT_HEAP_H
|
||||
#define TIMEOUT_HEAP_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#include <stdint.h> // For uint64_t
|
||||
#include <stddef.h> // For size_t
|
||||
|
||||
@@ -94,4 +99,8 @@ size_t timeout_heap_get_freed_count(TimeoutHeap *h);
|
||||
|
||||
size_t timeout_heap_get_size(TimeoutHeap *h);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // TIMEOUT_HEAP_H
|
||||
+612
-603
File diff suppressed because it is too large.
Load diff
+79
-70
@@ -5,18 +5,23 @@
|
||||
#ifndef UASYNC_H
|
||||
#define UASYNC_H
|
||||
|
||||
#include "platform_compat.h"
|
||||
#include <stddef.h>
|
||||
#include <signal.h>
|
||||
#include "timeout_heap.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#include "platform_compat.h"
|
||||
#include <stddef.h>
|
||||
#include <signal.h>
|
||||
#include "timeout_heap.h"
|
||||
#include "socket_compat.h"
|
||||
|
||||
typedef void (*timeout_callback_t)(void* user_arg);// передаёт user_arg из uasync_set_timeout
|
||||
typedef void (*socket_callback_t)(int fd, void* user_arg);// передаёт user_arg из uasync_add_socket (for pipe/file)
|
||||
typedef void (*socket_t_callback_t)(socket_t sock, void* user_arg);// передаёт user_arg из uasync_add_socket_t (for sockets)
|
||||
// user_arg полезен если нужно передать управляющую структуру. Ее можно выделить в памяти и в ней хранить всё что надо. т.е. при set_timeout передаём и получаем ее в callback-е
|
||||
|
||||
|
||||
|
||||
|
||||
// Error type
|
||||
typedef int err_t;
|
||||
#define ERR_OK 0
|
||||
@@ -28,21 +33,21 @@ typedef int err_t;
|
||||
|
||||
typedef void (*uasync_post_callback_t)(void* user_arg);
|
||||
|
||||
#include "memory_pool.h"
|
||||
|
||||
struct timeout_node; // Forward declaration
|
||||
|
||||
#include "memory_pool.h"
|
||||
|
||||
struct timeout_node; // Forward declaration
|
||||
|
||||
struct posted_task {
|
||||
uasync_post_callback_t callback;
|
||||
void* arg;
|
||||
struct posted_task* next;
|
||||
};
|
||||
|
||||
// Uasync instance structure
|
||||
struct UASYNC {
|
||||
struct memory_pool* timeout_pool; // Pool for timeout_node allocation
|
||||
struct timeout_node* immediate_queue_head; // FIFO queue for immediate execution
|
||||
struct timeout_node* immediate_queue_tail;
|
||||
// Uasync instance structure
|
||||
struct UASYNC {
|
||||
struct memory_pool* timeout_pool; // Pool for timeout_node allocation
|
||||
struct timeout_node* immediate_queue_head; // FIFO queue for immediate execution
|
||||
struct timeout_node* immediate_queue_tail;
|
||||
TimeoutHeap* timeout_heap; // Heap for timeout management
|
||||
struct socket_array* sockets; // Array-based socket management
|
||||
// Debug counters for memory allocation tracking
|
||||
@@ -67,55 +72,55 @@ struct UASYNC {
|
||||
#ifdef _WIN32
|
||||
CRITICAL_SECTION posted_lock;
|
||||
#else
|
||||
pthread_mutex_t posted_lock;
|
||||
#endif
|
||||
volatile sig_atomic_t stop;
|
||||
pthread_mutex_t posted_lock;
|
||||
#endif
|
||||
volatile sig_atomic_t stop;
|
||||
};
|
||||
|
||||
// Type definitions
|
||||
typedef struct UASYNC uasync_t;
|
||||
typedef struct UASYNC UASYNC_t;
|
||||
|
||||
// Instance API - основной API для работы с uasync
|
||||
struct UASYNC* uasync_create(void);
|
||||
void uasync_destroy(struct UASYNC* ua, int close_fds);
|
||||
|
||||
// текущее время (timebase 0.1ms)
|
||||
uint64_t get_time_tb(void);
|
||||
// текущее время в микросекундах (для burst-измерений)
|
||||
uint64_t get_time_us(void);
|
||||
|
||||
// Timeouts, timebase = 0.1 mS
|
||||
// Как работать с таймаутами если таймаут может быть использован несколько раз:
|
||||
// 1. заводим дескриптор таймаута и обнуляем
|
||||
// 2. перед активацией таймаута проверяем дескриптор (t_id) на null
|
||||
// 3. при cancel или срабатывании дескриптор обнуляем
|
||||
// это обеспечит отсутствие утечек и задвоений таймаутов
|
||||
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* user_arg, timeout_callback_t callback, const char* name);
|
||||
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id);
|
||||
|
||||
// Immediate execution in next mainloop (FIFO order)
|
||||
void* uasync_call_soon(struct UASYNC* ua, void* user_arg, timeout_callback_t callback);
|
||||
err_t uasync_call_soon_cancel(struct UASYNC* ua, void* t_id);
|
||||
|
||||
|
||||
// Type definitions
|
||||
typedef struct UASYNC uasync_t;
|
||||
typedef struct UASYNC UASYNC_t;
|
||||
|
||||
// Instance API - основной API для работы с uasync
|
||||
struct UASYNC* uasync_create(void);
|
||||
void uasync_destroy(struct UASYNC* ua, int close_fds);
|
||||
|
||||
// текущее время (timebase 0.1ms)
|
||||
uint64_t get_time_tb(void);
|
||||
// текущее время в микросекундах (для burst-измерений)
|
||||
uint64_t get_time_us(void);
|
||||
|
||||
// Timeouts, timebase = 0.1 mS
|
||||
// Как работать с таймаутами если таймаут может быть использован несколько раз:
|
||||
// 1. заводим дескриптор таймаута и обнуляем
|
||||
// 2. перед активацией таймаута проверяем дескриптор (t_id) на null
|
||||
// 3. при cancel или срабатывании дескриптор обнуляем
|
||||
// это обеспечит отсутствие утечек и задвоений таймаутов
|
||||
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* user_arg, timeout_callback_t callback, const char* name);
|
||||
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id);
|
||||
|
||||
// Immediate execution in next mainloop (FIFO order)
|
||||
void* uasync_call_soon(struct UASYNC* ua, void* user_arg, timeout_callback_t callback);
|
||||
err_t uasync_call_soon_cancel(struct UASYNC* ua, void* t_id);
|
||||
|
||||
// Sockets - for regular file descriptors (pipe, file)
|
||||
void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_arg);
|
||||
// Sockets - for socket_t (cross-platform sockets)
|
||||
void* uasync_add_socket_t(struct UASYNC* ua, socket_t sock, socket_t_callback_t read_cbk, socket_t_callback_t write_cbk, socket_t_callback_t except_cbk, void* user_arg);
|
||||
err_t uasync_remove_socket(struct UASYNC* ua, void* s_id);
|
||||
err_t uasync_remove_socket_t(struct UASYNC* ua, socket_t sock);
|
||||
|
||||
// Динамическое управление мониторингом чтения/записи для сокета
|
||||
err_t uasync_set_socket_read(struct UASYNC* ua, void* s_id, int enable);
|
||||
err_t uasync_remove_socket(struct UASYNC* ua, void* s_id);
|
||||
err_t uasync_remove_socket_t(struct UASYNC* ua, socket_t sock);
|
||||
|
||||
// Динамическое управление мониторингом чтения/записи для сокета
|
||||
err_t uasync_set_socket_read(struct UASYNC* ua, void* s_id, int enable);
|
||||
err_t uasync_set_socket_write(struct UASYNC* ua, void* s_id, int enable);
|
||||
|
||||
// Single iteration of event loop with timeout (timebase units)
|
||||
void uasync_poll(struct UASYNC* ua, int timeout_tb);
|
||||
|
||||
// Mainloop (бесконечный цикл, __noreturn)
|
||||
void uasync_mainloop(struct UASYNC* ua);
|
||||
void uasync_stop(struct UASYNC* ua);
|
||||
|
||||
|
||||
// Single iteration of event loop with timeout (timebase units)
|
||||
void uasync_poll(struct UASYNC* ua, int timeout_tb);
|
||||
|
||||
// Mainloop (бесконечный цикл, __noreturn)
|
||||
void uasync_mainloop(struct UASYNC* ua);
|
||||
void uasync_stop(struct UASYNC* ua);
|
||||
|
||||
// Debug statistics
|
||||
void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free);
|
||||
|
||||
@@ -124,15 +129,19 @@ int uasync_lookup_socket(struct UASYNC* ua, int fd, void** socket_id);
|
||||
|
||||
// Print all resources (timers, sockets) for debugging
|
||||
void uasync_print_resources(struct UASYNC* ua, const char* prefix);
|
||||
|
||||
// Wakeup mechanism for interrupting poll
|
||||
int uasync_wakeup(struct UASYNC* ua);
|
||||
int uasync_get_wakeup_fd(struct UASYNC* ua); // returns write fd for wakeup pipe (for signal handlers)
|
||||
|
||||
// сообщить async (из другого thread) чтобы он вызвал callback с аргументом
|
||||
void uasync_post(struct UASYNC* ua, uasync_post_callback_t callback, void* user_arg);
|
||||
|
||||
// синхронизация памяти (для доступности из других потоков)
|
||||
void uasync_memsync(struct UASYNC* ua);
|
||||
|
||||
#endif // UASYNC_H
|
||||
|
||||
// Wakeup mechanism for interrupting poll
|
||||
int uasync_wakeup(struct UASYNC* ua);
|
||||
int uasync_get_wakeup_fd(struct UASYNC* ua); // returns write fd for wakeup pipe (for signal handlers)
|
||||
|
||||
// сообщить async (из другого thread) чтобы он вызвал callback с аргументом
|
||||
void uasync_post(struct UASYNC* ua, uasync_post_callback_t callback, void* user_arg);
|
||||
|
||||
// синхронизация памяти (для доступности из других потоков)
|
||||
void uasync_memsync(struct UASYNC* ua);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // UASYNC_H
|
||||
@@ -1,219 +0,0 @@
|
||||
Диаграмма вызовов функций при определении типа NAT (NAT detection)
|
||||
=====================================================================
|
||||
|
||||
=== ИНИЦИАЛИЗАЦИЯ BGP ===
|
||||
|
||||
route_bgp_init(instance)
|
||||
└── etcp_set_new_conn_cbk(instance, route_bgp_etcp_conn_cbk, NULL)
|
||||
└── При создании нового ETCP_CONN:
|
||||
route_bgp_etcp_conn_cbk(conn, arg)
|
||||
├── etcp_conn_set_up_cbk(conn, route_bgp_on_conn_up, bgp)
|
||||
└── etcp_conn_set_down_cbk(conn, route_bgp_on_conn_down, bgp)
|
||||
|
||||
|
||||
=== УСТАНОВКА СОЕДИНЕНИЯ (сервер получает INIT_REQUEST) ===
|
||||
|
||||
etcp_connections_read_callback_socket(sock, arg)
|
||||
└── Получает ETCP_INIT_REQUEST
|
||||
├── etcp_connection_create(instance, name) [если новый peer]
|
||||
│ └── Вызывает instance->etcp_new_conn_cbk [route_bgp_etcp_conn_cbk]
|
||||
├── etcp_link_new(etcp, e_sock, remote_addr, is_server=1)
|
||||
├── Отправляет ETCP_INIT_RESPONSE (с NAT info)
|
||||
├── link->initialized = 1
|
||||
└── etcp_conn_ready(link->etcp)
|
||||
└── conn->ready_cbk
|
||||
└── etcp_on_up(etcp)
|
||||
└── etcp->up_cbk = route_bgp_on_conn_up(etcp, bgp)
|
||||
|
||||
|
||||
=== УСТАНОВКА СОЕДИНЕНИЯ (клиент получает INIT_RESPONSE) ===
|
||||
|
||||
etcp_connections_read_callback_socket(sock, arg)
|
||||
└── Получает ETCP_INIT_RESPONSE
|
||||
├── Сохраняет NAT address: link->nat_ip, link->nat_port
|
||||
├── link->initialized = 1
|
||||
└── etcp_conn_ready(link->etcp)
|
||||
└── etcp_on_up(etcp)
|
||||
└── etcp->up_cbk = route_bgp_on_conn_up(etcp, bgp)
|
||||
|
||||
|
||||
=== ЗАПУСК ПРОВЕРКИ NAT (при поднятии соединения) ===
|
||||
|
||||
route_bgp_on_conn_up(etcp, arg)
|
||||
└── route_bgp_new_conn(conn)
|
||||
├── route_bgp_add_to_senders(bgp, conn)
|
||||
└── Сканирует ВСЕ линки ВСЕХ соединений:
|
||||
if (link->initialized && link->conn && link->nat_check_status < NAT_CHECK_IN_PROGRESS)
|
||||
└── route_bgp_start_link_nat_check(bgp, link)
|
||||
├── if (nat_check_status == IN_PROGRESS) return
|
||||
├── route_bgp_find_third_node(bgp, exclude=link->etcp)
|
||||
│ └── Ищет другой ETCP_CONN в senders_list
|
||||
├── Определяет target_ip/target_port:
|
||||
│ ├── Если link->nat_ip != 0: использует nat_ip/nat_port
|
||||
│ └── Иначе: использует link->remote_addr
|
||||
├── Проверяет is_local_subnet(target_ip) [если запрещено - skip]
|
||||
├── route_bgp_extract_nat_addr(conn, &nat_ip, &nat_port)
|
||||
│ └── Ищет первый линк с nat_ip != 0
|
||||
├── Создает nat_check_arg { link, nat_ip, nat_port }
|
||||
└── route_ping_send_req_addr(bgp, third_conn, target_ip, target_port,
|
||||
count=3, interval=500ms, timeout=1000ms,
|
||||
wait_timeout=5000ms,
|
||||
cb=nat_link_check_cb, arg=nat_check_arg,
|
||||
pubkey=peer_pubkey)
|
||||
|
||||
|
||||
=== ОТПРАВКА ЗАПРОСА НА ПИНГ (через third node) ===
|
||||
|
||||
route_ping_send_req_addr(bgp, to_conn, target_ip, target_port, ...)
|
||||
├── Создает BGP_PING_REQUEST { cmd, subcmd=PING_REQ, request_id, count, interval, timeout, target_ipv4, target_port, pubkey }
|
||||
├── etcp_send(to_conn, entry) [отправляет запрос third node]
|
||||
├── Создает route_ping_pending { request_id, callback=nat_link_check_cb, arg, timeout_timer }
|
||||
└── Добавляет в bgp->ping_pending
|
||||
|
||||
|
||||
=== ОБРАБОТКА PING_REQ (на third node) ===
|
||||
|
||||
route_bgp_receive_cbk(data, len, from_conn, bgp)
|
||||
└── ROUTE_SUBCMD_PING_REQ
|
||||
└── route_ping_handle_req(bgp, from_conn, data, len)
|
||||
├── Создает route_ping_series_ctx { reply_conn, request_id, target_addr, pubkey, local_sock, count_total }
|
||||
├── Находит первый IPv4 ETCP_SOCKET -> local_sock
|
||||
└── etcp_send_ping_to_socket(instance, local_sock, pubkey, &target_addr,
|
||||
timeout_ms, route_ping_single_cb, ctx, NULL, 0)
|
||||
└── Отправляет ETCP пинг на target_addr
|
||||
|
||||
|
||||
=== ОТВЕТ НА ПИНГ (целевой узел) ===
|
||||
|
||||
etcp_connections_read_callback_socket()
|
||||
└── Получает ETCP пинг -> отправляет pong
|
||||
|
||||
|
||||
=== ПОЛУЧЕНИЕ ОТВЕТА НА ПИНГ (third node) ===
|
||||
|
||||
route_ping_single_cb(success, rtt, arg, nonce, resp_data, resp_data_len)
|
||||
├── Обновляет статистику: count_sent++, count_ok++, sum_rtt += rtt
|
||||
├── Если count_sent < count_total:
|
||||
│ └── etcp_send_ping_to_socket(...) [следующий пинг]
|
||||
└── Иначе:
|
||||
└── route_ping_series_finish(ctx)
|
||||
├── Вычисляет avg_rtt
|
||||
└── Создает BGP_PING_RESPONSE { cmd, subcmd=PING_RESP, request_id, count_sent, count_ok, avg_rtt }
|
||||
└── etcp_send(reply_conn, entry) [отправляет результат инициатору]
|
||||
|
||||
|
||||
=== ПОЛУЧЕНИЕ PING_RESP (на инициаторе) ===
|
||||
|
||||
route_bgp_receive_cbk(data, len, from_conn, bgp)
|
||||
└── ROUTE_SUBCMD_PING_RESP
|
||||
└── route_ping_handle_resp(bgp, from_conn, data, len)
|
||||
├── Ищет route_ping_pending по request_id
|
||||
├── Отменяет timeout_timer
|
||||
└── Вызывает callback: nat_link_check_cb(success, avg_rtt, count_sent, count_ok, arg)
|
||||
|
||||
|
||||
=== CALLBACK ПРОВЕРКИ NAT ===
|
||||
|
||||
nat_link_check_cb(success, avg_rtt, count_sent, count_ok, arg)
|
||||
├── Определяет nat_type = success ? NAT_TYPE_OPEN : NAT_TYPE_RESTRICTED
|
||||
├── link->nat_type = nat_type
|
||||
├── link->nat_check_status = success ? NAT_CHECK_OPEN : NAT_CHECK_RESTRICTED
|
||||
└── route_bgp_send_nat_info(link->etcp, socket_id, nat_ip, nat_port, nat_type)
|
||||
├── Создает BGP_NAT_INFO { cmd, subcmd=NAT_INFO, socket_id, nat_ip[4], nat_port, nat_type }
|
||||
└── etcp_send(conn, entry) [отправляет результат клиенту]
|
||||
|
||||
|
||||
=== ПРИЕМ NAT_INFO (на клиенте) ===
|
||||
|
||||
route_bgp_receive_cbk(data, len, from_conn, bgp)
|
||||
└── ROUTE_SUBCMD_NAT_INFO
|
||||
└── route_bgp_handle_nat_info(bgp, from_conn, data, len)
|
||||
├── Ищет линки по remote_socket_id
|
||||
├── Устанавливает link->nat_type = info->nat_type
|
||||
└── Логирует результат NAT detection
|
||||
|
||||
|
||||
=== ЗАПРОС НА ПРОВЕРКУ NAT (NAT_CHECK_REQ - новый механизм) ===
|
||||
|
||||
route_bgp_send_nat_check_req(conn, socket_id)
|
||||
├── Создает BGP_NAT_CHECK_REQ { cmd, subcmd=NAT_CHECK_REQ, socket_id, interface_ip, interface_port }
|
||||
│ ├── Ищет линк с remote_socket_id == socket_id
|
||||
│ └── Берет interface_addr из линка -> interface_ip/interface_port
|
||||
└── etcp_send(conn, entry)
|
||||
|
||||
route_bgp_handle_nat_check_req(bgp, from_conn, data, len)
|
||||
├── Принимает BGP_NAT_CHECK_REQ
|
||||
├── Сохраняет interface_ip/interface_port в struct sockaddr_storage dummy
|
||||
├── Ищет линк с remote_socket_id == req->socket_id
|
||||
└── Если линк найден и не в процессе проверки:
|
||||
└── route_bgp_start_link_nat_check(bgp, target_link)
|
||||
[далее по стандартному flow]
|
||||
|
||||
|
||||
=== СВЯЗЬ СТРУКТУР ===
|
||||
|
||||
struct ETCP_SOCKET
|
||||
├── local_addr [адрес бинда из конфига]
|
||||
├── interface_addr [IP интерфейса + порт: интерфейс/default route/конфиг]
|
||||
└── ...
|
||||
|
||||
struct ETCP_LINK
|
||||
├── remote_addr [адрес удаленного узла]
|
||||
├── nat_ip [NAT IP, полученный из INIT_RESPONSE]
|
||||
├── nat_port [NAT port, полученный из INIT_RESPONSE]
|
||||
├── nat_type [OPEN/RESTRICTED, результат проверки]
|
||||
├── nat_check_status [NONE/WAITING/IN_PROGRESS/OPEN/RESTRICTED]
|
||||
├── initialized [0/1, флаг завершения handshake]
|
||||
└── ...
|
||||
|
||||
struct ETCP_CONN
|
||||
├── links [список ETCP_LINK]
|
||||
├── up_cbk [route_bgp_on_conn_up]
|
||||
├── down_cbk [route_bgp_on_conn_down]
|
||||
├── ready_cbk [вызывается при etcp_conn_ready]
|
||||
├── initialized [0/1, флаг готовности]
|
||||
├── links_up [0/1, есть ли активные линки]
|
||||
└── ...
|
||||
|
||||
|
||||
=== СТАТУСЫ NAT CHECK ===
|
||||
|
||||
NAT_CHECK_NONE = 0 [не проверялся]
|
||||
NAT_CHECK_WAITING = 1 [ожидает]
|
||||
NAT_CHECK_IN_PROGRESS = 2 [проверка запущена]
|
||||
NAT_CHECK_OPEN = 3 [результат: OPEN]
|
||||
NAT_CHECK_RESTRICTED = 4 [результат: RESTRICTED]
|
||||
|
||||
NAT_TYPE_UNKNOWN = 0
|
||||
NAT_TYPE_OPEN = 1
|
||||
NAT_TYPE_RESTRICTED = 2
|
||||
|
||||
NAT_VERIFIED_UNKNOWN = 4
|
||||
NAT_VERIFIED_OPEN = 5
|
||||
NAT_VERIFIED_RESTRICTED = 6
|
||||
NAT_VERIFIED_DIRECT = 7
|
||||
|
||||
|
||||
=== СООБЩЕНИЯ ПРОТОКОЛА ===
|
||||
|
||||
ETCP_INIT_REQUEST (0x02) [клиент -> сервер]
|
||||
ETCP_INIT_RESPONSE (0x03) [сервер -> клиент, включает NAT info]
|
||||
ETCP_INIT_REQUEST_NOINIT (0x04)
|
||||
ETCP_INIT_RESPONSE_NOINIT (0x05)
|
||||
ETCP_PING (0x06)
|
||||
ETCP_PONG (0x07)
|
||||
|
||||
ROUTE_SUBCMD_NODEINFO (0x04)
|
||||
ROUTE_SUBCMD_REQUEST_TABLE (0x05)
|
||||
ROUTE_SUBCMD_WITHDRAW (0x06)
|
||||
ROUTE_SUBCMD_PING_REQ (0x07)
|
||||
ROUTE_SUBCMD_PING_RESP (0x08)
|
||||
ROUTE_SUBCMD_NAT_INFO (0x09)
|
||||
ROUTE_SUBCMD_NAT_CHECK_REQ (0x0A)
|
||||
|
||||
|
||||
=== ПОЛНЫЙ FLOW (одна строка) ===
|
||||
|
||||
Handshake: etcp_connections_read_callback_socket -> INIT_REQUEST/RESPONSE -> link->initialized=1 -> etcp_conn_ready -> etcp_on_up -> route_bgp_on_conn_up -> route_bgp_new_conn -> route_bgp_start_link_nat_check -> route_ping_send_req_addr -> (через ETCP) -> route_ping_handle_req -> etcp_send_ping_to_socket -> (UDP) -> route_ping_single_cb -> route_ping_series_finish -> route_ping_handle_resp -> nat_link_check_cb -> route_bgp_send_nat_info -> route_bgp_handle_nat_info
|
||||
|
||||
NAT_CHECK_REQ flow: route_bgp_send_nat_check_req -> BGP_NAT_CHECK_REQ -> route_bgp_handle_nat_check_req -> route_bgp_start_link_nat_check -> [стандартный ping flow]
|
||||
@@ -0,0 +1,29 @@
|
||||
UTUN
|
||||
|
||||
Идея: объединить узлы в единую локальную сеть.
|
||||
Чтобы узлы сами находили оптимальные линки между собой, пробивали NAT где это можно, где нельзя - выбирали посредника с хорошей связью; чтобы подсто было добавлять новые узлы и адреса узлов сразу виделись во всей сети.
|
||||
|
||||
Похож на libp2p но трафик превращает в случайную последовательность кторую сложно классифицировать
|
||||
|
||||
Потенциальные варианты использования:
|
||||
- локальная сеть. хочу объединить офис, сотрудников (включая их работчие подсети), телефоны, офисы, сервер доступа итд в одно адресное пространство.
|
||||
- чат с файлообменником. узлы сети - это мемберы группы и одновременно сети.
|
||||
- CDN. узлы - потребители контента - это мемберы сети, которые распространяют трафик.
|
||||
|
||||
То есть задача utun - обеспечить связность группы узлов и адаптацию работы всей сети к подключению-отключении отдельных узлов.
|
||||
|
||||
Механизмы:
|
||||
1. Пробитие nat.
|
||||
2. автообновление списка узлов и их адресов для подключения
|
||||
3. шифрование трафика и маскировка handshake.
|
||||
handshake просиходит без сигнатур по которым можно опреедлить тип сервиса, весь трафик выглядлит как случайный набор байт случайного размера
|
||||
4. многоканальные подключения, агрегирование каналов, исключение мёртвых каналов
|
||||
подключиться к узлу можно через несколько линков. UTUN будет объединять пропускную способность каналов, наблюдая за состоянием каналов, не допуская перегрузки и обеспечивая быстрые переповторы через другие каналы при потерях или большой задержке.
|
||||
5. динамическая маршрутизация. Узлы могут назначать себе IP адреса и при подключении к сети анонсировать свои адреса/подсети.
|
||||
это максимально упрощает добавление нового узла в сеть.
|
||||
6. автоматический роутинг между узлами: узлы маршрутизируют трафик между собой.
|
||||
используя статические подключения, прописанные в конфиге.
|
||||
7. прямые подключения и поиск лучших подключений
|
||||
узлы могут подключаться друг к другу напрямую, либо если оба узла за строгим NAT - выбирать оптимального посредника.
|
||||
Узлы определяют свой тип подключения и NAT. Например у ростелеком - EIM NAT который можно использовать и для входящих подклюений.
|
||||
Можно использовать IPv6.
|
||||
@@ -0,0 +1,239 @@
|
||||
Вот подробное описание работы модуля динамической маршрутизации.
|
||||
|
||||
---
|
||||
|
||||
## Архитектура: три слоя
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ ЯДРО ОС (netlink / ip route) │ ← tun_route.c — только при старте/стопе
|
||||
│ НЕ динамически, только для configured subnets │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ ПЕРЕСЫЛКА ПАКЕТОВ │ ← routing.c
|
||||
│ route_pkt() → route_lookup() → etcp_route_send │ или queue_data_put в TUN
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ ТАБЛИЦА МАРШРУТОВ (ROUTE_TABLE) │ ← route_lib.c
|
||||
│ route_insert / route_delete / route_lookup │ LPM через бинарный поиск
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ BGP-ОБМЕН (ROUTE_BGP) │ ← route_bgp.c
|
||||
│ NODEINFO (анонс) / WITHDRAW (отзыв) / │ broadcast / loop prevention
|
||||
│ ping (NAT-детекция) │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Ключевые структуры данных
|
||||
|
||||
### `ROUTE_TABLE` (`route_lib.h:46`)
|
||||
```c
|
||||
struct ROUTE_TABLE {
|
||||
struct ROUTE_ENTRY *entries; // отсортированный плоский массив
|
||||
size_t count, capacity;
|
||||
};
|
||||
```
|
||||
Маршруты хранятся в **отсортированном плоском массиве** (не trie, не radix). Сортировка: `network ASC`, затем `prefix_length DESC`. Это позволяет LPM через бинарный поиск.
|
||||
|
||||
### `ROUTE_ENTRY` (`route_lib.h:34`)
|
||||
```c
|
||||
struct ROUTE_ENTRY {
|
||||
uint32_t network; // host byte order
|
||||
uint8_t prefix_length;
|
||||
struct NODEINFO_Q* v_node_info; // NULL = локальный маршрут
|
||||
};
|
||||
```
|
||||
Каждая запись ссылается на узел-владелец (`NODEINFO_Q`). Все маршруты одного узла удаляются за один проход по этому указателю.
|
||||
|
||||
### `NODEINFO` (`route_node.h:73`)
|
||||
Динамическая структура (packed) с полями фиксированной части + динамический хвост: имя, метаданные сокетов, IPv4/IPv6 адреса, подсети, транзитные узлы, hop_list. Передаётся по BGP как NODEINFO-пакет.
|
||||
|
||||
### `NODEINFO_Q` (`route_node.h:151`)
|
||||
Локальная обёртка вокруг `NODEINFO`:
|
||||
```c
|
||||
struct NODEINFO_Q {
|
||||
struct ll_entry ll; // linkage в очереди bgp->nodes (хэш-индекс по node_id)
|
||||
struct ll_queue* paths; // список NODEINFO_PATH — по каким соединениям доступен узел
|
||||
uint8_t last_ver; // последняя известная версия NODEINFO
|
||||
struct NODE_CONNECTIVITY connectivity; // локальное состояние проб (ping/RTT)
|
||||
struct NODEINFO node; // всегда в конце, динамический размер
|
||||
};
|
||||
```
|
||||
|
||||
### `ROUTE_BGP` (`route_bgp.h:90`)
|
||||
```c
|
||||
struct ROUTE_BGP {
|
||||
struct ll_queue* senders_list; // ROUTE_BGP_CONN_ITEM — все активные пиры для broadcast
|
||||
struct ll_queue* nodes; // хэш-индексированная очередь NODEINFO_Q
|
||||
struct NODEINFO_Q* local_node; // свой собственный узел
|
||||
// ... ping_pending, LMDB и т.д.
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Добавление маршрутов
|
||||
|
||||
Есть **три сценария** попадания маршрутов в таблицу:
|
||||
|
||||
### 1. Получение NODEINFO от пира (основной путь)
|
||||
|
||||
**Точка входа:** `route_bgp_receive_cbk()` (`route_bgp.c:162`) → `route_bgp_process_nodeinfo()` (`route_bgp.c:750`).
|
||||
|
||||
Алгоритм `route_bgp_process_nodeinfo`:
|
||||
|
||||
1. **Валидация:** проверка размера, `hop_count < MAX_HOPS(16)`, не свой ли node_id
|
||||
2. **Проверка версии:** `nodeinfo1->last_ver >= new_ver` — значит версия та же или новее → только обновляем paths (через какой conn доступен), маршруты не трогаем
|
||||
3. **Новая версия или новый узел:**
|
||||
- Выделяется/перевыделяется `NODEINFO_Q` (если изменился динамический размер)
|
||||
- Копируется NODEINFO + динамические данные
|
||||
- Источник (`from->peer_node_id`) добавляется в конец hop_list (инкремент `hop_count`)
|
||||
- Старый путь удаляется (`route_bgp_remove_path_by_hop`), новый добавляется (`route_bgp_add_path`)
|
||||
- **`route_insert(bgp->instance->rt, nodeinfo1)`** ← вот здесь маршруты попадают в таблицу
|
||||
- Сохраняется в LMDB
|
||||
4. **Broadcast:** рассылается всем пирам кроме тех, кто уже в hop_list (loop prevention)
|
||||
5. **Connectivity probe:** для новых или изменившихся узлов запускается зондирование связности
|
||||
|
||||
#### `route_insert()` (`route_lib.c:148`) — что происходит внутри:
|
||||
|
||||
```c
|
||||
bool route_insert(struct ROUTE_TABLE *table, struct NODEINFO_Q *node)
|
||||
```
|
||||
|
||||
1. Вызывает `get_node_routes(node, &subnets)` — получает указатель на IPv4-подсети внутри динамических данных NODEINFO (без копирования)
|
||||
2. Конвертирует адреса из `network byte order → host byte order` через `ntohl()`
|
||||
3. Проверяет **пересечения** с существующими маршрутами (точное совпадение network+prefix)
|
||||
4. При необходимости расширяет массив entries (`u_realloc`, ×2)
|
||||
5. Для каждой подсети:
|
||||
- Находит позицию вставки бинарным поиском (`binary_search_insert_pos`): по `network ASC`, затем `prefix_length DESC`
|
||||
- Сдвигает элементы вправо через `memmove()`
|
||||
- Записывает `entry->network`, `entry->prefix_length`, `entry->v_node_info = node`
|
||||
6. Инкрементирует `stats.local_routes` (если `hop_count==0`) или `stats.learned_routes`
|
||||
|
||||
**Важно:** при обновлении существующего узла (realloc) старые маршруты **сначала удаляются** (`route_delete`), потом вставляются новые. Это на случай если изменился состав подсетей.
|
||||
|
||||
### 2. Обновление своего local_node (`route_node.c`)
|
||||
|
||||
`route_bgp_update_my_nodeinfo()` вызывается при:
|
||||
- Инициализации BGP
|
||||
- Изменении конфигурации
|
||||
- NAT-детекции (изменился `nat_type` сокета)
|
||||
|
||||
Алгоритм:
|
||||
1. Считает сокеты, адреса, подсети из конфига
|
||||
2. Сравнивает с текущим `local_node` — если изменилось:
|
||||
- **`route_delete(rt, local_node)`** — удаляет старые локальные маршруты
|
||||
- Освобождает старый `local_node`, выделяет новый
|
||||
- Заполняет NODEINFO (node_id, hop_count=0, ключи, подсети)
|
||||
- Инкрементирует `ver`
|
||||
- **`route_insert(rt, local_node)`** — вставляет новые
|
||||
3. Если изменился состав адресов — помечает `dirty=1`, рассылает обновлённый NODEINFO
|
||||
|
||||
### 3. Получение полной таблицы (синхронизация при connect)
|
||||
|
||||
При установке соединения:
|
||||
1. `route_bgp_new_conn()` (`route_bgp.c:372`) добавляет conn в `senders_list` и отправляет `ROUTE_SUBCMD_REQUEST_TABLE`
|
||||
2. Пир отвечает `route_bgp_handle_request_table()` (`route_bgp.c:1015`):
|
||||
- Шлёт свой `local_node`
|
||||
- Шлёт все известные узлы из `bgp->nodes` (с loop prevention через hop_list)
|
||||
- Шлёт `ROUTE_SUBCMD_TABLE_COMPLETE`
|
||||
3. Каждый полученный NODEINFO обрабатывается через `route_bgp_process_nodeinfo` → `route_insert`
|
||||
|
||||
---
|
||||
|
||||
## Удаление маршрутов
|
||||
|
||||
Есть **два сценария**:
|
||||
|
||||
### 1. Получен WITHDRAW от пира
|
||||
|
||||
**Точка входа:** `route_bgp_process_withdraw()` (`route_bgp.c:890`).
|
||||
|
||||
Алгоритм:
|
||||
1. Извлекает `node_id` (кого удаляем) и `wd_source` (кто инициировал)
|
||||
2. Ищет `NODEINFO_Q` по `node_id`
|
||||
3. Вызывает `route_bgp_remove_path_by_hop(nq, wd_source)` — удаляет все пути, в hop_list которых есть `wd_source`
|
||||
4. Если **путей не осталось** (unreachable):
|
||||
- **`route_delete(bgp->instance->rt, nq)`** — удаляет все маршруты узла из таблицы
|
||||
- Отменяет connectivity probes
|
||||
- Удаляет paths, узел из `bgp->nodes`
|
||||
- Закрывает все соединения etcp_router для этого узла
|
||||
- **Broadcast WITHDRAW** дальше всем пирам (кроме отправителя)
|
||||
|
||||
#### `route_delete()` (`route_lib.c:221`) — что происходит внутри:
|
||||
|
||||
```c
|
||||
void route_delete(struct ROUTE_TABLE *table, struct NODEINFO_Q *node)
|
||||
```
|
||||
|
||||
Проходит по всему массиву entries, для каждой записи где `entry->v_node_info == node`:
|
||||
- Сдвигает оставшиеся элементы влево через `memmove()`
|
||||
- Декрементирует `count` и соответствующий счётчик статистики
|
||||
- **Не освобождает** `NODEINFO_Q` (этим занимается вызывающий код в `route_bgp.c`)
|
||||
|
||||
### 2. Соединение ETCP упало (conn DOWN)
|
||||
|
||||
**Точка входа:** `route_bgp_remove_conn()` (`route_bgp.c:417`).
|
||||
|
||||
Алгоритм:
|
||||
1. Отменяет pending NAT checks для линков этого соединения
|
||||
2. Итерирует **все узлы** в `bgp->nodes`:
|
||||
- Для каждого вызывает `route_bgp_remove_path(nq, conn)` — удаляет путь через этот conn
|
||||
- Если путь был последним (unreachable):
|
||||
- **`route_delete(rt, nq)`**
|
||||
- Удаляет узел из `bgp->nodes`, отменяет пробы
|
||||
- Закрывает etcp_router соединения
|
||||
- **Broadcast WITHDRAW** с `wd_source = peer_node_id` (или свой node_id если удаляемый узел и есть сам пир)
|
||||
3. Удаляет conn из `senders_list`
|
||||
|
||||
---
|
||||
|
||||
## Поиск маршрута и пересылка пакетов
|
||||
|
||||
### `route_lookup()` (`route_lib.c:248`) — LPM
|
||||
|
||||
Бинарный поиск по отсортированному массиву:
|
||||
- Сравнивает `(dest_ip & mask) == (network & mask)`
|
||||
- Запоминает лучший (наибольшая `prefix_length`)
|
||||
- При совпадении продолжает поиск вправо (там могут быть более специфичные /32 внутри /24)
|
||||
- Возвращает `ROUTE_ENTRY*` или NULL
|
||||
|
||||
### `route_pkt()` (`routing.c:53`) — единая точка пересылки
|
||||
|
||||
1. Извлекает IP-пакет (пропуская 1-байтовый cmd)
|
||||
2. Дропает мультикаст (224-239) и броадкаст (255.255.255.255)
|
||||
3. `route_lookup(instance->rt, dst_ip)` — ищет маршрут
|
||||
4. **Нет маршрута** → дроп, `dropped_packets++`
|
||||
5. **Маршрут удалённый** (`v_node_info != NULL && hop_count > 0`) → `etcp_route_send()` (через service router)
|
||||
6. **Маршрут локальный** (нет v_node_info или hop_count==0) → `queue_data_put(instance->tun->input_queue)` (доставка в TUN)
|
||||
|
||||
Два входа в `route_pkt`:
|
||||
- **Из TUN** (исходящий): `routing_pkt_from_tun_cb()` — callback на `tun->output_queue`
|
||||
- **Из ETCP** (входящий): `routing_pkt_from_etcp_cb()` — callback через `etcp_router_bind(ETCP_RT_ID_DATA=0)`
|
||||
|
||||
---
|
||||
|
||||
## Loop prevention
|
||||
|
||||
Distance-vector стиль: каждый NODEINFO-пакет содержит **hop_list** — массив node_id, через которые он прошёл. Перед broadcast проверяется, что целевой пир **не находится** в hop_list. Аналогично при WITHDRAW — `wd_source` ищется в hop_list каждого path.
|
||||
|
||||
---
|
||||
|
||||
## NAT-детекция (влияет на обновление local_node)
|
||||
|
||||
1. При новом соединении сканируются все линки, для каждого вызывается `route_bgp_start_link_nat_check()`
|
||||
2. Находится «третий узел» (другой подключённый пир, не private)
|
||||
3. Через третий узел отправляется ping-запрос на целевой IP/порт (`route_ping_send_req_addr`)
|
||||
4. Третий узел выполняет UDP-пинг, возвращает результат
|
||||
5. По результату устанавливается `NAT_TYPE_EIM` (reachable) или `NAT_TYPE_STRICT`
|
||||
6. Отправляется `NAT_INFO` обратно владельцу сокета
|
||||
7. Владелец обновляет `local_node` → `route_bgp_update_my_nodeinfo()` → удаление/вставка локальных маршрутов → broadcast обновлённого NODEINFO
|
||||
|
||||
---
|
||||
|
||||
## Итого: кто когда вызывает route_insert/route_delete
|
||||
|
||||
| Событие | Операция | Функция |
|
||||
|---|---|---|
|
||||
| Получен новый/обновлённый NODEINFO | `route_insert` | `route_bgp_process_nodeinfo` `:849` |
|
||||
| Обновление своего local_node | `route_delete` старого + `route_insert` нового | `route_bgp_update_my_nodeinfo` (`route_node.c`) |
|
||||
| Получен WITHDRAW (узел недоступен) | `route_delete` | `route_bgp_process_withdraw` `:904` |
|
||||
| Соединение упало (conn DOWN) | `route_delete` для каждого узла без путей | `route_bgp_remove_conn` `:468` |
|
||||
| Переаллокация NODEINFO_Q при обновлении | `route_delete` старого | `route_bgp_process_nodeinfo` `:812` |
|
||||
+71
-95
@@ -1,4 +1,5 @@
|
||||
bin_PROGRAMS = utun
|
||||
noinst_LIBRARIES = libutun.a
|
||||
|
||||
# Sources that are always compiled
|
||||
utun_CORE_SOURCES = \
|
||||
@@ -8,10 +9,13 @@ utun_CORE_SOURCES = \
|
||||
config_updater.c \
|
||||
route_lib.c \
|
||||
route6_lib.c \
|
||||
route_bgp.c \
|
||||
topo_group.c \
|
||||
route_ping.c \
|
||||
route_node.c \
|
||||
topo_node.c \
|
||||
topo_node_lmdb.c \
|
||||
route_connectivity.c \
|
||||
conn_mgr.c \
|
||||
db_sync.c \
|
||||
routing.c \
|
||||
tun_if.c \
|
||||
tun_route.c \
|
||||
@@ -26,13 +30,16 @@ utun_CORE_SOURCES = \
|
||||
etcp_dump.c \
|
||||
secure_channel.c \
|
||||
crc32.c \
|
||||
stcp_link.c \
|
||||
stcp.c \
|
||||
stcp_server.c \
|
||||
stcp_client.c \
|
||||
pkt_normalizer.c \
|
||||
packet_dump.c \
|
||||
etcp_api.c \
|
||||
etcp_connect.c \
|
||||
control_server.c \
|
||||
msg_transport.c \
|
||||
firewall.c \
|
||||
eim_nat.c \
|
||||
nat_transport.c \
|
||||
@@ -41,118 +48,87 @@ utun_CORE_SOURCES = \
|
||||
etcp_router.c \
|
||||
proxy/tcp_proxy_server.c \
|
||||
proxy/udp_proxy.c \
|
||||
proxy/socks_proxy.c \
|
||||
proxy/icmp_proxy.c \
|
||||
lwip_tcp/lwip_pbuf.c \
|
||||
lwip_tcp/lwip_tcp.c \
|
||||
lwip_tcp/lwip_tcp_in.c \
|
||||
lwip_tcp/lwip_tcp_out.c
|
||||
|
||||
# libutun: all core sources except main()
|
||||
libutun_a_SOURCES = \
|
||||
utun_instance.c \
|
||||
config_parser.c \
|
||||
config_updater.c \
|
||||
route_lib.c \
|
||||
route6_lib.c \
|
||||
topo_group.c \
|
||||
route_ping.c \
|
||||
topo_node.c \
|
||||
topo_node_lmdb.c \
|
||||
route_connectivity.c \
|
||||
conn_mgr.c \
|
||||
db_sync.c \
|
||||
routing.c \
|
||||
tun_if.c \
|
||||
tun_route.c \
|
||||
tun_linux.c \
|
||||
tun_freebsd.c \
|
||||
tun_windows.c \
|
||||
etcp.c \
|
||||
etcp_connections.c \
|
||||
etcp_bbr.c \
|
||||
etcp_loadbalancer.c \
|
||||
etcp_debug.c \
|
||||
etcp_dump.c \
|
||||
secure_channel.c \
|
||||
crc32.c \
|
||||
stcp_link.c \
|
||||
stcp.c \
|
||||
stcp_server.c \
|
||||
stcp_client.c \
|
||||
pkt_normalizer.c \
|
||||
packet_dump.c \
|
||||
etcp_api.c \
|
||||
etcp_connect.c \
|
||||
control_server.c \
|
||||
msg_transport.c \
|
||||
firewall.c \
|
||||
eim_nat.c \
|
||||
nat_transport.c \
|
||||
dummynet.c \
|
||||
proxy/tcp_proxy_client.c \
|
||||
etcp_router.c \
|
||||
proxy/tcp_proxy_server.c \
|
||||
proxy/udp_proxy.c \
|
||||
proxy/socks_proxy.c \
|
||||
proxy/icmp_proxy.c \
|
||||
lwip_tcp/lwip_pbuf.c \
|
||||
lwip_tcp/lwip_tcp.c \
|
||||
lwip_tcp/lwip_tcp_in.c \
|
||||
lwip_tcp/lwip_tcp_out.c
|
||||
libutun_a_CFLAGS = $(utun_CFLAGS)
|
||||
|
||||
# Platform-specific TUN libs (Windows only)
|
||||
utun_TUN_LIBS = @TUN_LIBS@
|
||||
|
||||
# TinyCrypt sources (only used without OpenSSL)
|
||||
utun_TINYCRYPT_SOURCES = \
|
||||
$(top_srcdir)/tinycrypt/lib/source/aes_encrypt.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/aes_decrypt.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/cbc_mode.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/ccm_mode.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/cmac_mode.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/ctr_mode.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/ecc.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/ecc_dh.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/ecc_dsa.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/ecc_platform_specific.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/hmac.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/sha256.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/utils.c
|
||||
|
||||
# Combine sources based on OpenSSL usage
|
||||
if USE_OPENSSL
|
||||
utun_SOURCES = $(utun_CORE_SOURCES) $(utun_TUN_SOURCES)
|
||||
else
|
||||
utun_SOURCES = $(utun_CORE_SOURCES) $(utun_TINYCRYPT_SOURCES) $(utun_TUN_SOURCES)
|
||||
endif
|
||||
utun_SOURCES = utun.c
|
||||
|
||||
# Include paths
|
||||
utun_CORE_CFLAGS = \
|
||||
utun_CFLAGS = \
|
||||
-I$(top_srcdir)/lib \
|
||||
-I$(top_srcdir)/src/uip \
|
||||
-I$(top_srcdir)/tinycrypt/lib/include \
|
||||
-I$(top_srcdir)/tinycrypt/lib/source \
|
||||
-g \
|
||||
$(DEBUG_FLAGS)
|
||||
|
||||
utun_CFLAGS = $(utun_CORE_CFLAGS)
|
||||
|
||||
# Libraries
|
||||
utun_CORE_LDADD = \
|
||||
utun_LDADD = \
|
||||
libutun.a \
|
||||
$(top_builddir)/lib/libuasync.a \
|
||||
-lpthread \
|
||||
-lm
|
||||
|
||||
if USE_OPENSSL
|
||||
utun_LDADD = $(utun_CORE_LDADD) -lcrypto $(utun_TUN_LIBS)
|
||||
else
|
||||
utun_LDADD = $(utun_CORE_LDADD) $(utun_TUN_LIBS)
|
||||
endif
|
||||
|
||||
# TinyCrypt object files (for tests that need them)
|
||||
TINYCRYPT_SRCDIR = $(top_srcdir)/tinycrypt/lib/source
|
||||
|
||||
TINYCRYPT_OBJS = \
|
||||
utun-aes_encrypt.o \
|
||||
utun-aes_decrypt.o \
|
||||
utun-ccm_mode.o \
|
||||
utun-cmac_mode.o \
|
||||
utun-ctr_mode.o \
|
||||
utun-ecc.o \
|
||||
utun-ecc_dh.o \
|
||||
utun-ecc_dsa.o \
|
||||
utun-sha256.o \
|
||||
utun-ecc_platform_specific.o \
|
||||
utun-utils.o
|
||||
|
||||
# Rules to build TinyCrypt objects (for tests)
|
||||
TINYCRYPT_CFLAGS = -g -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
|
||||
utun-aes_encrypt.o: $(TINYCRYPT_SRCDIR)/aes_encrypt.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
utun-aes_decrypt.o: $(TINYCRYPT_SRCDIR)/aes_decrypt.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
utun-ccm_mode.o: $(TINYCRYPT_SRCDIR)/ccm_mode.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
utun-cmac_mode.o: $(TINYCRYPT_SRCDIR)/cmac_mode.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
utun-ctr_mode.o: $(TINYCRYPT_SRCDIR)/ctr_mode.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
utun-ecc.o: $(TINYCRYPT_SRCDIR)/ecc.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
utun-ecc_dh.o: $(TINYCRYPT_SRCDIR)/ecc_dh.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
utun-ecc_dsa.o: $(TINYCRYPT_SRCDIR)/ecc_dsa.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
utun-sha256.o: $(TINYCRYPT_SRCDIR)/sha256.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
# Force rebuild of ecc_platform_specific.o to pick up platform changes
|
||||
utun-ecc_platform_specific.o: $(TINYCRYPT_SRCDIR)/ecc_platform_specific.c FORCE
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
FORCE:
|
||||
|
||||
utun-utils.o: $(TINYCRYPT_SRCDIR)/utils.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
# Build TinyCrypt objects for tests (convenience target)
|
||||
tinycrypt-objects: $(TINYCRYPT_OBJS)
|
||||
-lm \
|
||||
-lcrypto \
|
||||
$(utun_TUN_LIBS)
|
||||
|
||||
# Copy binary to project root after building
|
||||
all-local: copy-to-root
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
Архитектура таблицы c быстрой репликацией между несколькими узлами:
|
||||
|
||||
1. В конец таблицы каждый узел может самостоятельно добавлять данные (со своим timestamp, желательно время правильное)
|
||||
2. узлы между собой синхронизируются, распространяя обновления по соседям
|
||||
|
||||
3. TTL: изменение имеет время жизни. узел удаляет собственные записи которые не были отправлены никому в течении суток
|
||||
|
||||
4. Формат записи:
|
||||
|
||||
ID (64bit monotonic autoincrement)
|
||||
chain_hash (256bit) - цепочка хешей. считается так: chain_hash[index+1]=hash(chain_hash[index],ID[index],timestamp[index],datahash[index]). если хеш совпадает - это признак того что все строки выше синхронизированы.
|
||||
timestamp (64bit) - время создания записи. строки должны сортироваться по возрастанию concat(datahash(младшая часть числа,64 bit)+timestamp(старшая часть))
|
||||
datahash (64bit) - хеш данных этой строки
|
||||
data (varchar) - данные в формате json
|
||||
|
||||
алгоритм репликации (2 узла):
|
||||
узел A запрашивает синхронизацию и передает свой last id
|
||||
test_id= min(mast id, peer last id)
|
||||
узел B передает: test_id, chain_hash(test id), hash(test id-1), hash(test id-2), hash(test id-4), hash(test id-8) итд 16,32,..., до первого элемента включитально (лимитируем вылетевший индекс первым элементом)
|
||||
узел A - сравнивает, находит проверяемый диапазон id, отправляет другому узлу 16 (можно больше) хешей (линейно разбив проверяемый диапазон на более короткие поддиапазоны). таким образом узлы уточняют первый ид который не совпал.
|
||||
Когда первая различающияся запись найдена, узел отправляет хеш этой и n (например 32) последующих записей (если записей много). если записей мало (<4) то узел отправляет сразу содержимое записей.
|
||||
(надо додумать алгоритм, чтобы оптимизировать количество итераций - лучше передать больше данных за раз чем много итераций с ожиданием ответной стороны)
|
||||
@@ -0,0 +1,9 @@
|
||||
Задача сделать механизм маршрутизации между узлами для групповых чатов.
|
||||
|
||||
Узлы для всех чатов находятся в одной таблице - это nodeinfo (информация о подключении)
|
||||
В другой таблице есть список узлов конкретного чата.
|
||||
|
||||
надо сделать возможность хранить одновременно несколько групп узлов. в группу узлов добавить тип - utun или чатовая.
|
||||
если utun - в группе используется роутинг (ipv4/ipv6).
|
||||
если чатовая - то только связность между узлами.
|
||||
|
||||
+141
-14
@@ -46,7 +46,9 @@ typedef enum {
|
||||
SECTION_ALLOWED_KEYS,
|
||||
SECTION_NAT,
|
||||
SECTION_TCP_PROXY_CLIENT,
|
||||
SECTION_TCP_PROXY_SERVER
|
||||
SECTION_TCP_PROXY_SERVER,
|
||||
SECTION_MSG_TRANSPORT,
|
||||
SECTION_NETWORK
|
||||
} section_type_t;
|
||||
|
||||
static char* trim(char *str) {
|
||||
@@ -356,6 +358,10 @@ static int parse_global(const char *key, const char *value, struct global_config
|
||||
global->my_node_id = strtoull(value, NULL, 16);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "tun_enabled") == 0) {
|
||||
global->tun_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "tun_ifname") == 0) {
|
||||
snprintf(global->tun_ifname, sizeof(global->tun_ifname), "%s", value);
|
||||
return 0;
|
||||
@@ -376,6 +382,10 @@ static int parse_global(const char *key, const char *value, struct global_config
|
||||
global->keepalive_interval = atoi(value);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "keepalive_adaptive") == 0) {
|
||||
global->keepalive_adaptive = atoi(value);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "bbr_max_cwnd") == 0) {
|
||||
global->bbr_max_cwnd = atoi(value);
|
||||
return 0;
|
||||
@@ -386,6 +396,17 @@ static int parse_global(const char *key, const char *value, struct global_config
|
||||
if (strcmp(key, "log_file") == 0) {
|
||||
return assign_string(global->log_file, sizeof(global->log_file), value);
|
||||
}
|
||||
if (strcmp(key, "db_path") == 0) {
|
||||
return assign_string(global->db_path, sizeof(global->db_path), value);
|
||||
}
|
||||
if (strcmp(key, "db_sync_enabled") == 0) {
|
||||
global->db_sync_enabled = atoi(value);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "db_sync_ttl") == 0) {
|
||||
global->db_sync_ttl = (uint32_t)atoi(value);
|
||||
return 0;
|
||||
}
|
||||
/* debug_categories key is deprecated - use [debug] section instead */
|
||||
if (strcmp(key, "enable_timestamp") == 0) {
|
||||
global->enable_timestamp = atoi(value);
|
||||
@@ -407,7 +428,7 @@ static int parse_global(const char *key, const char *value, struct global_config
|
||||
global->tun_test_mode = atoi(value);
|
||||
return 0;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown global option '%s'. Valid: my_node_name, my_private_key, my_public_key, my_node_id, tun_ifname, tun_ip, mtu, keepalive_timeout, keepalive_interval, bbr_max_cwnd, debug_level, log_file, enable_timestamp, enable_function_names, enable_file_lines, enable_colors, tun_test_mode", filename, line_num, key);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown global option '%s'. Valid: my_node_name, my_private_key, my_public_key, my_node_id, tun_ifname, tun_ip, mtu, keepalive_timeout, keepalive_interval, keepalive_adaptive, bbr_max_cwnd, debug_level, log_file, db_path, enable_timestamp, enable_function_names, enable_file_lines, enable_colors, tun_test_mode", filename, line_num, key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -436,6 +457,17 @@ static int parse_control(const char *key, const char *value, struct global_confi
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int parse_msg_transport(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
|
||||
if (strcmp(key, "port") == 0) {
|
||||
char port_str[16];
|
||||
snprintf(port_str, sizeof(port_str), "%s", value);
|
||||
parse_sockaddr("127.0.0.1", port_str, &global->msg_transport_sock);
|
||||
return 0;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown msg_transport option '%s'. Valid: port", filename, line_num, key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int parse_tcp_proxy_client(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
|
||||
if (strcmp(key, "enabled") == 0) {
|
||||
global->tcp_proxy_client_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
|
||||
@@ -457,6 +489,22 @@ static int parse_tcp_proxy_client(const char *key, const char *value, struct glo
|
||||
global->tcp_proxy_client_via_node_id = strtoull(value, NULL, 16);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "socks_enabled") == 0) {
|
||||
global->tcp_proxy_client_socks_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "socks_addr") == 0) {
|
||||
strncpy(global->tcp_proxy_client_socks_addr, value, sizeof(global->tcp_proxy_client_socks_addr) - 1);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "http_proxy_enabled") == 0) {
|
||||
global->tcp_proxy_client_http_proxy_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "http_proxy_addr") == 0) {
|
||||
strncpy(global->tcp_proxy_client_http_proxy_addr, value, sizeof(global->tcp_proxy_client_http_proxy_addr) - 1);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "forward") == 0) {
|
||||
if (global->tcp_proxy_client_mapping_count >= MAX_TCP_PROXY_CLIENT_MAPPINGS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Too many tcp_proxy_client forward rules (max %d)", MAX_TCP_PROXY_CLIENT_MAPPINGS);
|
||||
@@ -493,7 +541,7 @@ static int parse_tcp_proxy_client(const char *key, const char *value, struct glo
|
||||
global->tcp_proxy_client_mapping_count++;
|
||||
return 0;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown tcp_proxy_client option '%s'. Valid: enabled, tun_name, tun_ip, mtu, via_node, forward", filename, line_num, key);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown tcp_proxy_client option '%s'. Valid: enabled, tun_name, tun_ip, mtu, via_node, forward, socks_enabled, socks_addr, http_proxy_enabled, http_proxy_addr", filename, line_num, key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -611,6 +659,8 @@ static int parse_server(const char *key, const char *value, struct CFG_SERVER *s
|
||||
srv->type = CFG_SERVER_TYPE_NAT;
|
||||
} else if (strcmp(value, "private") == 0) {
|
||||
srv->type = CFG_SERVER_TYPE_PRIVATE;
|
||||
} else if (strcmp(value, "local") == 0) {
|
||||
srv->type = CFG_SERVER_TYPE_LOCAL;
|
||||
} else {
|
||||
srv->type = CFG_SERVER_TYPE_UNKNOWN;
|
||||
}
|
||||
@@ -624,7 +674,12 @@ static int parse_server(const char *key, const char *value, struct CFG_SERVER *s
|
||||
srv->only_local = atoi(value) ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown server option '%s'. Valid: addr, so_mark, fib, netif, type, mtu, only_local", filename, line_num, key);
|
||||
if (strcmp(key, "transport") == 0) {
|
||||
if (strcmp(value, "tcp") == 0) srv->transport = 1;
|
||||
else srv->transport = 0; // default udp
|
||||
return 0;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown server option '%s'. Valid: addr, so_mark, fib, netif, type, mtu, only_local, transport", filename, line_num, key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -670,6 +725,21 @@ static int parse_client(const char *key, const char *value, struct CFG_CLIENT *c
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int parse_network(const char *key, const char *value, struct CFG_NETWORK *net, const char *filename, int line_num) {
|
||||
if (strcmp(key, "id") == 0) {
|
||||
net->id = strtoull(value, NULL, 16);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "pubkey") == 0) {
|
||||
return assign_string(net->pubkey_hex, sizeof(net->pubkey_hex), value);
|
||||
}
|
||||
if (strcmp(key, "signing_key") == 0) {
|
||||
return assign_string(net->signing_key_hex, sizeof(net->signing_key_hex), value);
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown network option '%s'. Valid: id, pubkey, signing_key", filename, line_num, key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static section_type_t parse_section_header(const char *line, char *name, size_t name_len) {
|
||||
if (line[0] != '[') return SECTION_UNKNOWN;
|
||||
|
||||
@@ -692,6 +762,7 @@ static section_type_t parse_section_header(const char *line, char *name, size_t
|
||||
if (strcasecmp(section, "nat") == 0) return SECTION_NAT;
|
||||
if (strcasecmp(section, "tcp_proxy_client") == 0) return SECTION_TCP_PROXY_CLIENT;
|
||||
if (strcasecmp(section, "tcp_proxy_server") == 0) return SECTION_TCP_PROXY_SERVER;
|
||||
if (strcasecmp(section, "msg_transport") == 0) return SECTION_MSG_TRANSPORT;
|
||||
|
||||
char *colon = strchr(section, ':');
|
||||
if (!colon) return SECTION_UNKNOWN;
|
||||
@@ -705,14 +776,15 @@ static section_type_t parse_section_header(const char *line, char *name, size_t
|
||||
|
||||
if (strcasecmp(type, "server") == 0) return SECTION_SERVER;
|
||||
if (strcasecmp(type, "client") == 0) return SECTION_CLIENT;
|
||||
|
||||
if (strcasecmp(type, "network") == 0) return SECTION_NETWORK;
|
||||
|
||||
return SECTION_UNKNOWN;
|
||||
}
|
||||
|
||||
static struct utun_config* parse_config_internal(FILE *fp, const char *filename) {
|
||||
struct utun_config *cfg = u_calloc(1, sizeof(struct utun_config));
|
||||
if (!cfg) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "parse_config_internal: failed to allocate memory for config structure");
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "failed to allocate memory for config structure");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -720,6 +792,7 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
cfg->global.name[0] = '\0';
|
||||
cfg->global.keepalive_timeout = 2000; // Default 2 seconds
|
||||
cfg->global.keepalive_interval = 200; // Default 0.2 s
|
||||
cfg->global.keepalive_adaptive = 1; // Default: adaptive enabled
|
||||
cfg->global.bbr_max_cwnd = 1048576; // Default 1MB (was INFLIGHT_LIM_MAX)
|
||||
cfg->global.tcp_recv_buf = 0; // 0 = OS default
|
||||
cfg->global.firewall_rules = NULL;
|
||||
@@ -727,10 +800,13 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
cfg->global.firewall_bypass_all = 0;
|
||||
cfg->global.control_allows = NULL;
|
||||
cfg->global.control_allow_count = 0;
|
||||
cfg->global.db_sync_enabled = 0;
|
||||
cfg->global.db_sync_ttl = 86400;
|
||||
|
||||
section_type_t cur_section = SECTION_UNKNOWN;
|
||||
struct CFG_SERVER *cur_server = NULL;
|
||||
struct CFG_CLIENT *cur_client = NULL;
|
||||
struct CFG_NETWORK *cur_network = NULL;
|
||||
char line[MAX_LINE_LEN];
|
||||
int line_num = 0;
|
||||
|
||||
@@ -754,6 +830,11 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
cfg->clients = cur_client;
|
||||
cur_client = NULL;
|
||||
}
|
||||
if (cur_section == SECTION_NETWORK && cur_network) {
|
||||
cur_network->next = cfg->networks;
|
||||
cfg->networks = cur_network;
|
||||
cur_network = NULL;
|
||||
}
|
||||
|
||||
char name[MAX_CONN_NAME_LEN];
|
||||
cur_section = parse_section_header(trimmed, name, sizeof(name));
|
||||
@@ -761,7 +842,7 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
if (cur_section == SECTION_SERVER) {
|
||||
cur_server = u_calloc(1, sizeof(struct CFG_SERVER));
|
||||
if (!cur_server) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "parse_config_internal: failed to allocate memory for server %s", name);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "failed to allocate memory for server %s", name);
|
||||
goto error;
|
||||
}
|
||||
strcpy(cur_server->name, name);
|
||||
@@ -769,10 +850,17 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
} else if (cur_section == SECTION_CLIENT) {
|
||||
cur_client = u_calloc(1, sizeof(struct CFG_CLIENT));
|
||||
if (!cur_client) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "parse_config_internal: failed to allocate memory for client %s", name);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "failed to allocate memory for client %s", name);
|
||||
goto error;
|
||||
}
|
||||
strcpy(cur_client->name, name);
|
||||
} else if (cur_section == SECTION_NETWORK) {
|
||||
cur_network = u_calloc(1, sizeof(struct CFG_NETWORK));
|
||||
if (!cur_network) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "failed to allocate memory for network %s", name);
|
||||
goto error;
|
||||
}
|
||||
strcpy(cur_network->name, name);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -874,6 +962,16 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
cfg->global.tcp_recv_buf = atoi(value);
|
||||
}
|
||||
break;
|
||||
case SECTION_MSG_TRANSPORT:
|
||||
if (parse_msg_transport(key, value, &cfg->global, filename, line_num) < 0) {
|
||||
// parse_msg_transport already printed the error
|
||||
}
|
||||
break;
|
||||
case SECTION_NETWORK:
|
||||
if (cur_network && parse_network(key, value, cur_network, filename, line_num) < 0) {
|
||||
// parse_network already printed the error
|
||||
}
|
||||
break;
|
||||
default:
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Key outside section: %s", filename, line_num, key);
|
||||
break;
|
||||
@@ -889,11 +987,23 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
cur_client->next = cfg->clients;
|
||||
cfg->clients = cur_client;
|
||||
}
|
||||
if (cur_section == SECTION_NETWORK && cur_network) {
|
||||
cur_network->next = cfg->networks;
|
||||
cfg->networks = cur_network;
|
||||
}
|
||||
|
||||
// Set default TUN interface name if not provided
|
||||
if (cfg->global.tun_ifname[0] == '\0') {
|
||||
snprintf(cfg->global.tun_ifname, sizeof(cfg->global.tun_ifname), "tun0");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Auto-generated TUN interface name: %s", cfg->global.tun_ifname);
|
||||
// Post-parse defaults: tun_enabled=1 if not explicitly disabled
|
||||
if (!cfg->global.tun_enabled && cfg->global.tun_ifname[0] == '\0' && cfg->global.tun_ip.family == AF_UNSPEC) {
|
||||
// No TUN config at all — treat as explicitly off (for non-root SOCKS/HTTP-only mode)
|
||||
} else if (!cfg->global.tun_enabled) {
|
||||
// TUN was explicitly disabled via tun_enabled=no
|
||||
} else {
|
||||
// TUN enabled (default) — set default ifname if not provided
|
||||
cfg->global.tun_enabled = 1;
|
||||
if (cfg->global.tun_ifname[0] == '\0') {
|
||||
snprintf(cfg->global.tun_ifname, sizeof(cfg->global.tun_ifname), "tun0");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Auto-generated TUN interface name: %s", cfg->global.tun_ifname);
|
||||
}
|
||||
}
|
||||
|
||||
return cfg;
|
||||
@@ -904,6 +1014,7 @@ error:
|
||||
free_cfg_client_links(cur_client->links);
|
||||
u_free(cur_client);
|
||||
}
|
||||
if (cur_network) u_free(cur_network);
|
||||
free_config(cfg);
|
||||
return NULL;
|
||||
}
|
||||
@@ -942,6 +1053,14 @@ void free_config(struct utun_config *config) {
|
||||
client = next;
|
||||
}
|
||||
|
||||
// Free networks
|
||||
struct CFG_NETWORK *network = config->networks;
|
||||
while (network) {
|
||||
struct CFG_NETWORK *next = network->next;
|
||||
u_free(network);
|
||||
network = next;
|
||||
}
|
||||
|
||||
// Free route entries
|
||||
free_route_entries(config->route_subnets);
|
||||
free_route_entries(config->my_subnets);
|
||||
@@ -1036,8 +1155,16 @@ void print_config(const struct utun_config *cfg) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " allow=%s", ip_str);
|
||||
} else {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " allow=%s:%d", ip_str, r->port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Networks:");
|
||||
struct CFG_NETWORK *net = cfg->networks;
|
||||
while (net) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " [network:%s] id=%012llx pubkey=%.64s signing_key=%.64s",
|
||||
net->name, (unsigned long long)net->id, net->pubkey_hex, net->signing_key_hex);
|
||||
net = net->next;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " no rules");
|
||||
}
|
||||
|
||||
+24
-3
@@ -24,9 +24,10 @@ struct IP {
|
||||
};
|
||||
|
||||
#define CFG_SERVER_TYPE_UNKNOWN 0
|
||||
#define CFG_SERVER_TYPE_PUBLIC 1
|
||||
#define CFG_SERVER_TYPE_NAT 2
|
||||
#define CFG_SERVER_TYPE_PUBLIC 1
|
||||
#define CFG_SERVER_TYPE_NAT 2
|
||||
#define CFG_SERVER_TYPE_PRIVATE 3
|
||||
#define CFG_SERVER_TYPE_LOCAL 4
|
||||
|
||||
#define CFG_IPV6_MODE_NONE 0 // normal explicit address
|
||||
#define CFG_IPV6_MODE_TEMPORARY 1 // temporary IPv6 from interface
|
||||
@@ -40,6 +41,7 @@ struct CFG_SERVER {
|
||||
int so_mark;
|
||||
int fib; // FreeBSD FIB (routing table), -1 = don't set
|
||||
uint8_t type; // public/nat/private
|
||||
uint8_t transport; // 0=udp (default), 1=tcp
|
||||
uint8_t ipv6_mode; // CFG_IPV6_MODE_*
|
||||
int mtu;
|
||||
uint8_t only_local; // 1 = only local connections, no forwarding
|
||||
@@ -60,6 +62,14 @@ struct CFG_CLIENT {
|
||||
struct CFG_CLIENT *next; // Next client in linked list
|
||||
};
|
||||
|
||||
struct CFG_NETWORK {
|
||||
struct CFG_NETWORK* next;
|
||||
char name[64];
|
||||
uint64_t id; // 56-bit network ID
|
||||
char pubkey_hex[SC_PUBKEY_SIZE * 2 + 1]; // 64 hex chars
|
||||
char signing_key_hex[SC_PRIVKEY_SIZE * 2 + 1]; // 64 hex chars
|
||||
};
|
||||
|
||||
struct CFG_ROUTE_ENTRY {
|
||||
struct CFG_ROUTE_ENTRY* next;
|
||||
struct IP ip;
|
||||
@@ -78,7 +88,7 @@ struct CFG_CONTROL_ALLOW {
|
||||
};
|
||||
|
||||
struct CFG_ALLOWED_KEY {
|
||||
uint8_t key_bin[SC_PUBKEY_SIZE]; // public key in binary (64 bytes)
|
||||
uint8_t key_bin[SC_PUBKEY_SIZE]; // public key in binary (32 bytes, X25519)
|
||||
struct CFG_ALLOWED_KEY *next;
|
||||
};
|
||||
|
||||
@@ -94,14 +104,19 @@ struct global_config {
|
||||
char my_private_key_hex[MAX_KEY_LEN];
|
||||
char my_public_key_hex[MAX_KEY_LEN];
|
||||
uint64_t my_node_id;
|
||||
int tun_enabled; // 1 = create TUN (default), 0 = skip TUN (run without root)
|
||||
char tun_ifname[16]; // TUN interface name (e.g., "tun12")
|
||||
struct IP tun_ip;
|
||||
int mtu;
|
||||
struct sockaddr_storage control_sock;
|
||||
char control_ip[MAX_ADDR_LEN]; // Control server IP address
|
||||
struct sockaddr_storage msg_transport_sock;
|
||||
|
||||
// Debug and logging configuration
|
||||
char log_file[256]; // Path to log file (empty = stdout)
|
||||
char db_path[256]; // Path to LMDB nodeinfo database (empty = disabled)
|
||||
int db_sync_enabled; // 1 = enable distributed DB sync (default: 0)
|
||||
uint32_t db_sync_ttl; // TTL for unsent records in seconds (default: 86400)
|
||||
char debug_level[16]; // debug level: error, warn, info, debug, trace
|
||||
int enable_timestamp; // enable timestamps in logs
|
||||
int enable_function_names; // enable function names in logs
|
||||
@@ -118,6 +133,7 @@ struct global_config {
|
||||
int tun_test_mode; // test mode: 1 = don't open real TUN, queues only
|
||||
int keepalive_timeout; // keepalive timeout in ms (default: 2000)
|
||||
int keepalive_interval; // keepalive interval in ms (default: 200)
|
||||
int keepalive_adaptive; // 1 = adaptive period (default), 0 = fixed interval
|
||||
int bbr_max_cwnd; // BBR cwnd cap in bytes (default: 100000)
|
||||
|
||||
// Firewall configuration
|
||||
@@ -158,6 +174,10 @@ struct global_config {
|
||||
uint64_t tcp_proxy_client_via_node_id; // через этот узел проксируются все forward-правила
|
||||
struct tcp_proxy_client_mapping_config tcp_proxy_client_mappings[MAX_TCP_PROXY_CLIENT_MAPPINGS];
|
||||
int tcp_proxy_client_mapping_count;
|
||||
int tcp_proxy_client_socks_enabled;
|
||||
char tcp_proxy_client_socks_addr[64]; // e.g. "127.0.0.1:1080"
|
||||
int tcp_proxy_client_http_proxy_enabled;
|
||||
char tcp_proxy_client_http_proxy_addr[64]; // e.g. "127.0.0.1:8080"
|
||||
|
||||
// TCP proxy server (exit node) configuration ([tcp_proxy_server] section)
|
||||
int tcp_proxy_server_enabled;
|
||||
@@ -170,6 +190,7 @@ struct utun_config {
|
||||
struct CFG_CLIENT* clients;
|
||||
struct CFG_ROUTE_ENTRY* route_subnets;
|
||||
struct CFG_ROUTE_ENTRY* my_subnets;
|
||||
struct CFG_NETWORK* networks;
|
||||
};
|
||||
|
||||
struct utun_config* parse_config(const char *filename);
|
||||
|
||||
+41
-26
@@ -15,7 +15,7 @@
|
||||
#include "../lib/mem.h"
|
||||
|
||||
#define PRIV_HEXKEY_LEN 65 // 32 bytes * 2 hex chars + null
|
||||
#define PUB_HEXKEY_LEN 129 // 64 bytes * 2 hex chars + null
|
||||
#define PUB_HEXKEY_LEN 65 // 32 bytes * 2 hex chars + null
|
||||
#define HEXNODEID_LEN 17 // 8 bytes * 2 hex chars + null
|
||||
#define MAX_LINE_LEN 1024
|
||||
|
||||
@@ -41,8 +41,8 @@ static int is_valid_priv_key(const char *key) {
|
||||
}
|
||||
|
||||
static int is_valid_pub_key(const char *key) {
|
||||
if (!key || strlen(key) != 128) return 0;
|
||||
for (int i = 0; i < 128; i++) {
|
||||
if (!key || strlen(key) != 64) return 0;
|
||||
for (int i = 0; i < 64; i++) {
|
||||
if (!isxdigit((unsigned char)key[i])) return 0;
|
||||
}
|
||||
return 1;
|
||||
@@ -151,21 +151,25 @@ static int insert_or_replace_option(char **buf, size_t *buf_len, size_t *buf_cap
|
||||
char *opt_pos = find_option(*buf, *buf_len, option);
|
||||
|
||||
if (opt_pos) {
|
||||
// Find option line end
|
||||
char *line_end = strchr(opt_pos, '\n');
|
||||
// Determine if old line has trailing \n
|
||||
char *nl = strchr(opt_pos, '\n');
|
||||
int has_trailing_nl = (nl != NULL);
|
||||
char *line_end = nl ? nl : *buf + *buf_len;
|
||||
|
||||
if (!line_end) line_end = *buf + *buf_len;
|
||||
|
||||
// Prepare new line
|
||||
char new_line[MAX_LINE_LEN];
|
||||
int new_len = snprintf(new_line, sizeof(new_line), "%s=%s\n", option, value);
|
||||
|
||||
int new_len;
|
||||
if (has_trailing_nl) {
|
||||
new_len = snprintf(new_line, sizeof(new_line), "%s=%s\n", option, value);
|
||||
} else {
|
||||
new_len = snprintf(new_line, sizeof(new_line), "%s=%s", option, value);
|
||||
}
|
||||
if (new_len <= 0 || new_len >= (int)sizeof(new_line)) return -1;
|
||||
|
||||
|
||||
// Calculate length difference
|
||||
size_t old_line_len = line_end - opt_pos + 1;
|
||||
size_t old_line_len = has_trailing_nl ? (line_end - opt_pos + 1) : (line_end - opt_pos);
|
||||
long len_diff = new_len - old_line_len;
|
||||
|
||||
|
||||
// Ensure buffer capacity
|
||||
if (*buf_len + len_diff + 1 > *buf_capacity) {
|
||||
*buf_capacity = *buf_len + len_diff + 1024;
|
||||
@@ -177,11 +181,11 @@ static int insert_or_replace_option(char **buf, size_t *buf_len, size_t *buf_cap
|
||||
opt_pos = find_option(*buf, *buf_len, option);
|
||||
|
||||
if (!opt_pos) return -1;
|
||||
line_end = strchr(opt_pos, '\n');
|
||||
|
||||
if (!line_end) line_end = *buf + *buf_len;
|
||||
nl = strchr(opt_pos, '\n');
|
||||
has_trailing_nl = (nl != NULL);
|
||||
line_end = nl ? nl : *buf + *buf_len;
|
||||
}
|
||||
|
||||
|
||||
// Move content and insert new line
|
||||
memmove(opt_pos + new_len, line_end, *buf_len - (line_end - *buf) + 1);
|
||||
|
||||
@@ -194,24 +198,31 @@ static int insert_or_replace_option(char **buf, size_t *buf_len, size_t *buf_cap
|
||||
char *section_end = find_section_global(*buf, *buf_len);
|
||||
|
||||
if (!section_end) return -1;
|
||||
|
||||
|
||||
// Find end of global section (next [ or end of buffer)
|
||||
char *insert_pos = strchr(section_end, '[');
|
||||
int need_leading_nl = 0;
|
||||
|
||||
if (!insert_pos) insert_pos = *buf + *buf_len;
|
||||
else {
|
||||
if (!insert_pos) {
|
||||
insert_pos = *buf + *buf_len;
|
||||
if (*buf_len > 0 && (*buf)[*buf_len - 1] != '\n')
|
||||
need_leading_nl = 1;
|
||||
} else {
|
||||
// Move to beginning of next line
|
||||
char *prev_nl = insert_pos;
|
||||
while (prev_nl > *buf && *prev_nl != '\n') prev_nl--;
|
||||
if (*prev_nl == '\n') insert_pos = prev_nl + 1;
|
||||
}
|
||||
|
||||
|
||||
// Prepare new line
|
||||
char new_line[MAX_LINE_LEN];
|
||||
int new_len = snprintf(new_line, sizeof(new_line), "%s=%s\n", option, value);
|
||||
|
||||
int new_len;
|
||||
if (need_leading_nl)
|
||||
new_len = snprintf(new_line, sizeof(new_line), "\n%s=%s\n", option, value);
|
||||
else
|
||||
new_len = snprintf(new_line, sizeof(new_line), "%s=%s\n", option, value);
|
||||
if (new_len <= 0 || new_len >= (int)sizeof(new_line)) return -1;
|
||||
|
||||
|
||||
// Ensure buffer capacity
|
||||
if (*buf_len + new_len + 1 > *buf_capacity) {
|
||||
*buf_capacity = *buf_len + new_len + 1024;
|
||||
@@ -224,15 +235,19 @@ static int insert_or_replace_option(char **buf, size_t *buf_len, size_t *buf_cap
|
||||
|
||||
if (!section_end) return -1;
|
||||
insert_pos = strchr(section_end, '[');
|
||||
need_leading_nl = 0;
|
||||
|
||||
if (!insert_pos) insert_pos = *buf + *buf_len;
|
||||
else {
|
||||
if (!insert_pos) {
|
||||
insert_pos = *buf + *buf_len;
|
||||
if (*buf_len > 0 && (*buf)[*buf_len - 1] != '\n')
|
||||
need_leading_nl = 1;
|
||||
} else {
|
||||
char *prev_nl = insert_pos;
|
||||
while (prev_nl > *buf && *prev_nl != '\n') prev_nl--;
|
||||
if (*prev_nl == '\n') insert_pos = prev_nl + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Move content and insert new line
|
||||
memmove(insert_pos + new_len, insert_pos, *buf_len - (insert_pos - *buf) + 1);
|
||||
|
||||
|
||||
+1010
File diff suppressed because it is too large.
Load diff
+448
@@ -0,0 +1,448 @@
|
||||
#ifndef CONN_MGR_H
|
||||
#define CONN_MGR_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "etcp_api.h"
|
||||
#include "etcp_router.h"
|
||||
#include "topo_node.h"
|
||||
|
||||
struct UTUN_INSTANCE;
|
||||
struct ETCP_CONN;
|
||||
struct TOPO_NODEQ;
|
||||
struct TOPO_GROUP;
|
||||
|
||||
/**
|
||||
* @file conn_mgr.h
|
||||
* @brief Менеджер установки соединений. Трёхфазная машина подключения к нодам
|
||||
* с выбором оптимального пути: прямой → обратный → через посредников.
|
||||
*
|
||||
* Основные возможности:
|
||||
* - Автоматический выбор лучшего способа подключения к целевой ноде
|
||||
* - Фаза 1 (DIRECT): попытка прямого INIT-рукопожатия на все известные IPv4-адреса цели.
|
||||
* Итерация по парам (наш сокет, целевой адрес) с проверкой NAT-совместимости.
|
||||
* Таймаут: CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS
|
||||
* - Фаза 2 (REVERSE): если у нас есть прямой IP, а у цели нет — отправляем DIRECT_REQ
|
||||
* с нашими адресами через BGP-маршрут. Цель сама инициирует INIT в нашу сторону.
|
||||
* Таймаут: CONN_MGR_CONNECT_REVERSE_TIMEOUT_MS
|
||||
* - Фаза 3 (INDIRECT): если DIRECT и REVERSE не сработали — обмениваемся через BGP
|
||||
* списками лучших промежуточных узлов-кандидатов с RTT, выбираем общих с минимальным
|
||||
* суммарным RTT. Трафик идёт через etcp_router (транзитная ретрансляция).
|
||||
* Таймаут: CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS
|
||||
* - Фоновое сканирование локальной сети (broadcast/multicast UDP-ping)
|
||||
* - Фоновое поддержание списка лучших кандидатов-посредников (периодический ping,
|
||||
* удаление stale-кандидатов)
|
||||
* - Фоновый обход всех известных BGP-нод для поддержания RTT-замеров
|
||||
* - Управление idle-таймаутом с автоматическим разрывом неактивных соединений
|
||||
*
|
||||
* @note Правила использования:
|
||||
* 1. conn_mgr_init() вызывается один раз при старте экземпляра utun
|
||||
* 2. conn_mgr_connect_node() — асинхронный вызов, результат приходит в callback
|
||||
* 3. Повторный вызов conn_mgr_connect_node для уже подключённой ноды вернёт
|
||||
* CONN_MGR_ERR_ALREADY_CONNECTED; для ноды в состоянии CONNECTING добавит
|
||||
* дополнительный callback в список
|
||||
* 4. conn_mgr_disconnect_node() отправляет DISCONNECT-пакет целевой ноде и
|
||||
* сбрасывает conn_mgr_type в NODEINFO_Q
|
||||
* 5. Для alien-нод (подключившихся к нам самостоятельно) используется
|
||||
* conn_mgr_add_alien_node() вместо conn_mgr_connect_node()
|
||||
* 6. conn_mgr_update_best_candidates() вызывается извне (обычно из route_connectivity)
|
||||
* для обновления RTT кандидатов
|
||||
*
|
||||
* @note Внутренние таймеры (всё в timebase-единицах, 1 tb = 0.1 мс):
|
||||
* - bg_ping_timer: фоновый ping нод (CONN_MGR_BG_PING_INTERVAL_TB)
|
||||
* - candidate_ping_timer: обновление RTT кандидатов (CONN_MGR_CANDIDATE_PING_TB)
|
||||
* - idle_timer: проверка неактивности подключённых нод (CONN_MGR_IDLE_CHECK_INTERVAL_TB)
|
||||
*/
|
||||
|
||||
/* ==================== Константы и лимиты ==================== */
|
||||
|
||||
#define CONN_MGR_MAX_CANDIDATES 3 ///< Макс. число лучших кандидатов-посредников
|
||||
#define CONN_MGR_CANDIDATE_CACHE_MS 20000 ///< Время жизни кандидата в кеше (мс) — не используется напрямую в .h
|
||||
#define CONN_MGR_CANDIDATE_STALE_TB 300000 ///< Порог устаревания кандидата (tb), после которого удаляется из списка
|
||||
#define CONN_MGR_CANDIDATE_PING_TB 20000 ///< Интервал фонового ping кандидатов (tb, ~2 сек)
|
||||
#define CONN_MGR_BG_PING_INTERVAL_TB 1000 ///< Интервал фонового ping BGP-нод (tb, ~100 мс)
|
||||
#define CONN_MGR_BG_PING_CYCLE_MIN_TB 100000 ///< Минимальная длительность полного цикла обхода нод (tb, ~10 сек)
|
||||
#define CONN_MGR_IDLE_CHECK_INTERVAL_TB 10000 ///< Интервал проверки idle-таймаута (tb, ~1 сек)
|
||||
#define CONN_MGR_LOCAL_SCAN_ATTEMPTS 3 ///< Число попыток локального сканирования
|
||||
#define CONN_MGR_LOCAL_SCAN_TIMEOUT_MS 100 ///< Таймаут локального сканирования (мс)
|
||||
#define CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS 5000 ///< Таймаут фазы DIRECT (мс)
|
||||
#define CONN_MGR_CONNECT_REVERSE_TIMEOUT_MS 15000 ///< Таймаут фазы REVERSE (мс)
|
||||
#define CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS 15000 ///< Таймаут фазы INDIRECT (мс)
|
||||
|
||||
/* ==================== Коды возврата ==================== */
|
||||
|
||||
#define CONN_MGR_OK 0 ///< Успех
|
||||
#define CONN_MGR_ERR_NOT_FOUND -1 ///< Нода не найдена в BGP
|
||||
#define CONN_MGR_ERR_NO_ADDRESSES -2 ///< Нет известных адресов для подключения
|
||||
#define CONN_MGR_ERR_TIMEOUT -3 ///< Таймаут соединения
|
||||
#define CONN_MGR_ERR_UNREACHABLE -4 ///< Нода недостижима (все фазы провалились)
|
||||
#define CONN_MGR_ERR_REFUSED -5 ///< Соединение отклонено (зарезервировано)
|
||||
#define CONN_MGR_ERR_ALREADY_CONNECTED -6 ///< Уже подключены
|
||||
#define CONN_MGR_ERR_INTERNAL -7 ///< Внутренняя ошибка
|
||||
|
||||
/* ==================== Состояния ==================== */
|
||||
|
||||
/**
|
||||
* @enum CONN_MGR_STATE
|
||||
* @brief Состояние подключения к ноде.
|
||||
*/
|
||||
enum CONN_MGR_STATE {
|
||||
CONN_MGR_STATE_DISCONNECTED = 0, ///< Не подключены
|
||||
CONN_MGR_STATE_CONNECTING = 1, ///< В процессе установки соединения
|
||||
CONN_MGR_STATE_CONNECTED = 2, ///< Подключены
|
||||
};
|
||||
|
||||
/**
|
||||
* @enum CM_TRY_STATE
|
||||
* @brief Состояние отдельной попытки (локальное сканирование / основное подключение).
|
||||
*/
|
||||
enum CM_TRY_STATE {
|
||||
CM_TRY_NONE = 0, ///< Попытка не начиналась
|
||||
CM_TRY_PENDING = 1, ///< Попытка в процессе
|
||||
CM_TRY_OK = 2, ///< Попытка успешна
|
||||
CM_TRY_FAILED = 3, ///< Попытка провалилась
|
||||
};
|
||||
|
||||
/* ==================== Протокол CONN_MGR (ETCP_RT_ID_CONN_MGR = 0x11) ==================== */
|
||||
|
||||
/**
|
||||
* @defgroup conn_mgr_subcmd Подкоманды протокола CONN_MGR
|
||||
* @{
|
||||
*/
|
||||
#define CONN_MGR_SUBCMD_DIRECT_REQ 0x01 ///< "Я за NAT, подключись ко мне" — передача наших адресов цели
|
||||
#define CONN_MGR_SUBCMD_DIRECT_RESP 0x02 ///< Ответ на DIRECT_REQ (зарезервирован, не реализован)
|
||||
#define CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ 0x03 ///< Запрос обмена кандидатами-посредниками
|
||||
#define CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP 0x04 ///< Ответ с нашими кандидатами + замеры RTT до кандидатов инициатора
|
||||
#define CONN_MGR_SUBCMD_INTERM_SELECTED 0x05 ///< Выбранные посредники (финальный шаг INDIRECT-фазы)
|
||||
#define CONN_MGR_SUBCMD_DISCONNECT 0x06 ///< Уведомление о разрыве соединения
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @struct CONN_MGR_CANDIDATE
|
||||
* @brief Запись о кандидате-посреднике: идентификатор ноды + RTT до неё.
|
||||
*
|
||||
* Используется в списке best_candidates и во всех пакетах обмена кандидатами.
|
||||
*/
|
||||
struct CONN_MGR_CANDIDATE {
|
||||
uint64_t node_id; ///< Идентификатор ноды-кандидата
|
||||
uint16_t rtt; ///< RTT до кандидата (мс)
|
||||
} __attribute__((packed));
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
/**
|
||||
* @struct CONN_MGR_DIRECT_REQ
|
||||
* @brief Пакет фазы REVERSE: "подключись ко мне".
|
||||
*
|
||||
* Отправляется когда мы имеем прямой IP, но цель за NAT.
|
||||
* Содержит наши адреса, по которым цель должна инициировать INIT.
|
||||
*/
|
||||
struct CONN_MGR_DIRECT_REQ {
|
||||
uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR
|
||||
uint8_t subcmd; ///< CONN_MGR_SUBCMD_DIRECT_REQ
|
||||
uint32_t request_id; ///< Идентификатор запроса для сопоставления ответа
|
||||
uint8_t addr_count; ///< Количество адресов, следующих за заголовком
|
||||
/* далее: addr_count записей по 8 байт каждая (type:1, ip:4, port:2, sock_id:1) */
|
||||
};
|
||||
|
||||
/**
|
||||
* @struct CONN_MGR_DIRECT_RESP
|
||||
* @brief Ответ на DIRECT_REQ (зарезервирован, не отправляется).
|
||||
*/
|
||||
struct CONN_MGR_DIRECT_RESP {
|
||||
uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR
|
||||
uint8_t subcmd; ///< CONN_MGR_SUBCMD_DIRECT_RESP
|
||||
uint32_t request_id; ///< Идентификатор запроса
|
||||
uint8_t accepted; ///< 1 — принято, 0 — отклонено
|
||||
};
|
||||
|
||||
/**
|
||||
* @struct CONN_MGR_INTERM_EXCHANGE_REQ
|
||||
* @brief Запрос обмена списками кандидатов-посредников (фаза INDIRECT, инициатор).
|
||||
*
|
||||
* Инициатор отправляет свой топ-4 кандидатов. Цель должна ответить
|
||||
* CONN_MGR_INTERM_EXCHANGE_RESP со своими кандидатами и замерами RTT до наших.
|
||||
*/
|
||||
struct CONN_MGR_INTERM_EXCHANGE_REQ {
|
||||
uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR
|
||||
uint8_t subcmd; ///< CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ
|
||||
uint32_t request_id; ///< Идентификатор запроса
|
||||
uint8_t candidate_count; ///< Количество кандидатов в массиве (≤4)
|
||||
struct CONN_MGR_CANDIDATE candidates[4]; ///< Наши лучшие кандидаты с RTT
|
||||
};
|
||||
|
||||
/**
|
||||
* @struct CONN_MGR_INTERM_EXCHANGE_RESP
|
||||
* @brief Ответ на INTERM_EXCHANGE_REQ (фаза INDIRECT, цель).
|
||||
*
|
||||
* Содержит два списка:
|
||||
* - my_candidates: лучшие кандидаты цели с RTT до них (измеренным целью)
|
||||
* - your_candidates: кандидаты инициатора, но с RTT, измеренным со стороны цели
|
||||
*/
|
||||
struct CONN_MGR_INTERM_EXCHANGE_RESP {
|
||||
uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR
|
||||
uint8_t subcmd; ///< CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP
|
||||
uint32_t request_id; ///< Идентификатор запроса
|
||||
uint8_t my_count; ///< Количество наших кандидатов (≤4)
|
||||
uint8_t your_count; ///< Количество кандидатов инициатора (≤4)
|
||||
struct CONN_MGR_CANDIDATE my_candidates[4]; ///< Наши кандидаты + наш RTT до них
|
||||
struct CONN_MGR_CANDIDATE your_candidates[4]; ///< Кандидаты инициатора + наш RTT до них
|
||||
};
|
||||
|
||||
/**
|
||||
* @struct CONN_MGR_INTERM_SELECTED
|
||||
* @brief Финальный выбор посредников (фаза INDIRECT).
|
||||
*
|
||||
* Инициатор вычисляет лучших общих посредников по сумме RTT
|
||||
* и отправляет выбранный список цели.
|
||||
*/
|
||||
struct CONN_MGR_INTERM_SELECTED {
|
||||
uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR
|
||||
uint8_t subcmd; ///< CONN_MGR_SUBCMD_INTERM_SELECTED
|
||||
uint32_t request_id; ///< Идентификатор запроса (всегда 0 для извещения)
|
||||
uint8_t count; ///< Количество выбранных посредников (≤3)
|
||||
struct CONN_MGR_CANDIDATE selected[3]; ///< Выбранные посредники (node_id + RTT от нас до них)
|
||||
};
|
||||
|
||||
/**
|
||||
* @struct CONN_MGR_DISCONNECT
|
||||
* @brief Уведомление о разрыве соединения.
|
||||
*/
|
||||
struct CONN_MGR_DISCONNECT {
|
||||
uint8_t cmd; ///< ETCP_RT_ID_CONN_MGR
|
||||
uint8_t subcmd; ///< CONN_MGR_SUBCMD_DISCONNECT
|
||||
uint64_t node_id; ///< Идентификатор ноды, с которой разрываем соединение
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
/* ==================== Размеры пакетов протокола ==================== */
|
||||
|
||||
#define CONN_MGR_DIRECT_REQ_HDR_SIZE sizeof(struct CONN_MGR_DIRECT_REQ) ///< Размер заголовка DIRECT_REQ (без addr_count адресов)
|
||||
#define CONN_MGR_DIRECT_RESP_HDR_SIZE sizeof(struct CONN_MGR_DIRECT_RESP) ///< Размер DIRECT_RESP
|
||||
#define CONN_MGR_INTERM_EXCHANGE_REQ_SIZE sizeof(struct CONN_MGR_INTERM_EXCHANGE_REQ) ///< Размер INTERM_EXCHANGE_REQ
|
||||
#define CONN_MGR_INTERM_EXCHANGE_RESP_SIZE sizeof(struct CONN_MGR_INTERM_EXCHANGE_RESP) ///< Размер INTERM_EXCHANGE_RESP (макс.)
|
||||
#define CONN_MGR_INTERM_SELECTED_SIZE sizeof(struct CONN_MGR_INTERM_SELECTED) ///< Размер INTERM_SELECTED
|
||||
#define CONN_MGR_DISCONNECT_SIZE sizeof(struct CONN_MGR_DISCONNECT) ///< Размер DISCONNECT
|
||||
|
||||
/* ==================== Callback-типы и внутренние структуры ==================== */
|
||||
|
||||
/**
|
||||
* @typedef conn_mgr_connect_callback_t
|
||||
* @brief Callback результата подключения.
|
||||
* @param result код возврата (CONN_MGR_OK или CONN_MGR_ERR_*)
|
||||
* @param node_id идентификатор ноды
|
||||
* @param arg пользовательский аргумент
|
||||
*/
|
||||
typedef void (*conn_mgr_connect_callback_t)(int result, uint64_t node_id, void* arg);
|
||||
|
||||
/**
|
||||
* @struct cm_cb_node
|
||||
* @brief Узел связного списка callback'ов для одной ноды.
|
||||
*
|
||||
* Позволяет регистрировать несколько callback'ов на одно подключение.
|
||||
* Новые callback'и добавляются в начало списка.
|
||||
*/
|
||||
struct cm_cb_node {
|
||||
conn_mgr_connect_callback_t cb; ///< Функция-callback
|
||||
void* arg; ///< Пользовательский аргумент
|
||||
struct cm_cb_node* next; ///< Следующий callback в списке
|
||||
};
|
||||
|
||||
/**
|
||||
* @struct CONN_MGR_ENTRY
|
||||
* @brief Запись о подключении к одной ноде.
|
||||
*
|
||||
* Хранит состояние трёхфазной машины, список выбранных посредников,
|
||||
* idle-таймер и список ожидающих callback'ов.
|
||||
*/
|
||||
struct CONN_MGR_ENTRY {
|
||||
uint64_t node_id; ///< Идентификатор целевой ноды
|
||||
uint8_t state; ///< Текущее состояние (enum CONN_MGR_STATE)
|
||||
uint8_t conn_type; ///< Тип соединения (CONN_TYPE_DIRECT/REVERSE/INDIRECT)
|
||||
uint8_t alien; ///< 1 — alien-нода (сама подключилась к нам), 0 — мы инициаторы
|
||||
uint32_t idle_timeout_ms; ///< Таймаут неактивности (мс), 0 — без таймаута
|
||||
uint64_t last_traffic_tb; ///< Время последнего трафика (tb) для idle-проверки
|
||||
void* idle_timer; ///< Хендл таймера idle-проверки
|
||||
|
||||
struct cm_cb_node* cb_list; ///< Связный список callback'ов результата
|
||||
|
||||
uint64_t intermediaries[CONN_MGR_MAX_INTERMEDIARIES]; ///< Выбранные посредники (node_id)
|
||||
uint8_t intermediariy_count; ///< Количество выбранных посредников
|
||||
uint8_t rr_idx; ///< Round-robin индекс для выбора посредника при отправке
|
||||
|
||||
uint8_t local_scan_state; ///< Состояние локального сканирования (enum CM_TRY_STATE)
|
||||
uint8_t main_connect_state; ///< Состояние основного подключения (enum CM_TRY_STATE)
|
||||
struct {
|
||||
uint8_t phase; ///< Текущая фаза: 1=DIRECT, 2=REVERSE, 3=INDIRECT
|
||||
void* timer; ///< Хендл таймера текущей фазы
|
||||
uint32_t request_id; ///< request_id для INDIRECT-обмена
|
||||
void* conn_ctx; ///< Контекст ETCP-соединения (для REVERSE)
|
||||
} main;
|
||||
|
||||
struct CONN_MGR* mgr; ///< Обратная ссылка на менеджер
|
||||
};
|
||||
|
||||
/**
|
||||
* @struct CONN_MGR
|
||||
* @brief Корневая структура менеджера соединений.
|
||||
*
|
||||
* Создаётся один раз в conn_mgr_init(), хранится в UTUN_INSTANCE.
|
||||
* Содержит динамический массив entries, список лучших кандидатов,
|
||||
* таймеры фонового ping и список ожидающих обменов (reverse/exchange pending).
|
||||
*/
|
||||
struct CONN_MGR {
|
||||
struct UTUN_INSTANCE* instance; ///< Родительский экземпляр utun
|
||||
|
||||
struct CONN_MGR_ENTRY* entries; ///< Динамический массив записей о подключениях
|
||||
size_t entry_count; ///< Текущее количество записей
|
||||
size_t entry_capacity; ///< Выделенная ёмкость массива entries
|
||||
|
||||
void* bg_ping_timer; ///< Хендл таймера фонового ping BGP-нод
|
||||
size_t bg_ping_cursor; ///< Курсор текущей ноды в цикле обхода
|
||||
uint64_t bg_ping_cycle_start_tb; ///< Время старта текущего цикла обхода (tb)
|
||||
|
||||
void* candidate_ping_timer; ///< Хендл таймера обновления RTT кандидатов
|
||||
|
||||
struct CONN_MGR_CANDIDATE best_candidates[CONN_MGR_MAX_CANDIDATES]; ///< Топ кандидатов-посредников, отсортированных по RTT
|
||||
uint8_t best_candidate_count; ///< Количество актуальных кандидатов (≤CONN_MGR_MAX_CANDIDATES)
|
||||
|
||||
uint32_t next_request_id; ///< Счётчик request_id для INTERM_EXCHANGE
|
||||
uint8_t initialized; ///< Флаг инициализации
|
||||
|
||||
struct cm_reverse_pending* reverse_pending; ///< Связный список ожидающих REVERSE-подключений
|
||||
struct cm_exchange_pending* exchange_pending; ///< Связный список ожидающих INDIRECT-обменов
|
||||
};
|
||||
|
||||
/* ==================== Жизненный цикл ==================== */
|
||||
|
||||
/**
|
||||
* @brief Инициализирует менеджер соединений.
|
||||
* @param instance родительский экземпляр utun
|
||||
* @return указатель на CONN_MGR или NULL при ошибке
|
||||
*
|
||||
* Регистрирует обработчик в etcp_router для ETCP_RT_ID_CONN_MGR,
|
||||
* запускает фоновые таймеры (bg_ping, candidate_ping).
|
||||
*/
|
||||
struct CONN_MGR* conn_mgr_init(struct UTUN_INSTANCE* instance);
|
||||
|
||||
/**
|
||||
* @brief Уничтожает менеджер соединений.
|
||||
* @param mgr менеджер
|
||||
*
|
||||
* Освобождает все записи, таймеры, pending-структуры.
|
||||
* Вызывает cm_clear_nodeinfo для каждой записи.
|
||||
*/
|
||||
void conn_mgr_destroy(struct CONN_MGR* mgr);
|
||||
|
||||
/* ==================== Подключение / отключение ==================== */
|
||||
|
||||
/**
|
||||
* @brief Запускает асинхронное подключение к ноде.
|
||||
* @param mgr менеджер
|
||||
* @param node_id идентификатор целевой ноды
|
||||
* @param idle_timeout_ms таймаут неактивности (мс), 0 = без таймаута
|
||||
* @param cb callback результата (CONN_MGR_OK, CONN_MGR_ERR_TIMEOUT, CONN_MGR_ERR_UNREACHABLE и т.д.)
|
||||
* @param cb_arg пользовательский аргумент для callback
|
||||
* @return CONN_MGR_OK если процесс запущен, CONN_MGR_ERR_* при ошибке
|
||||
*
|
||||
* @note Если нода уже подключена — сразу вызывает callback с CONN_MGR_OK и возвращает CONN_MGR_OK.
|
||||
* @note Если нода в процессе подключения — добавляет callback в список ожидания.
|
||||
* @note Автоматически определяет наличие BGP-соединения (уже существующий ETCP-линк).
|
||||
* @note Для alien-нод используйте conn_mgr_add_alien_node().
|
||||
*/
|
||||
int conn_mgr_connect_node(struct CONN_MGR* mgr, uint64_t node_id,
|
||||
uint32_t idle_timeout_ms,
|
||||
conn_mgr_connect_callback_t cb, void* cb_arg);
|
||||
|
||||
/**
|
||||
* @brief Разрывает подключение к ноде.
|
||||
* @param mgr менеджер
|
||||
* @param node_id идентификатор ноды
|
||||
* @return CONN_MGR_OK или CONN_MGR_ERR_NOT_FOUND
|
||||
*
|
||||
* Отправляет DISCONNECT-пакет целевой ноде, сбрасывает conn_mgr_type в NODEINFO_Q,
|
||||
* уничтожает запись entry.
|
||||
*/
|
||||
int conn_mgr_disconnect_node(struct CONN_MGR* mgr, uint64_t node_id);
|
||||
|
||||
/* ==================== Управление idle-таймаутом ==================== */
|
||||
|
||||
/**
|
||||
* @brief Устанавливает idle-таймаут для подключённой ноды.
|
||||
* @param mgr менеджер
|
||||
* @param node_id идентификатор ноды
|
||||
* @param timeout_ms таймаут неактивности (мс), 0 = отключить проверку
|
||||
* @return CONN_MGR_OK или CONN_MGR_ERR_NOT_FOUND / CONN_MGR_ERR_INTERNAL
|
||||
*
|
||||
* Если таймаут > 0 и нода в состоянии CONNECTED — запускает периодический таймер.
|
||||
* При отсутствии трафика в течение timeout_ms соединение автоматически разрывается.
|
||||
*/
|
||||
int conn_mgr_set_idle_timeout(struct CONN_MGR* mgr, uint64_t node_id, uint32_t timeout_ms);
|
||||
|
||||
/**
|
||||
* @brief Получает текущий статус подключения к ноде.
|
||||
* @param mgr менеджер
|
||||
* @param node_id идентификатор ноды
|
||||
* @param out_state [out] состояние (enum CONN_MGR_STATE)
|
||||
* @param out_conn_type [out] тип соединения (CONN_TYPE_DIRECT/REVERSE/INDIRECT)
|
||||
* @return CONN_MGR_OK
|
||||
*/
|
||||
int conn_mgr_get_status(struct CONN_MGR* mgr, uint64_t node_id,
|
||||
uint8_t* out_state, uint8_t* out_conn_type);
|
||||
|
||||
/* ==================== Alien-ноды ==================== */
|
||||
|
||||
/**
|
||||
* @brief Регистрирует alien-ноду (подключившуюся к нам самостоятельно).
|
||||
* @param mgr менеджер
|
||||
* @param nodeinfo_data сырые данные NODEINFO (BGP-пакет)
|
||||
* @param len длина данных
|
||||
* @return CONN_MGR_OK или CONN_MGR_ERR_INTERNAL
|
||||
*
|
||||
* Alien-нода — это нода, которая сама инициировала INIT-подключение к нам.
|
||||
* Для неё не запускаются фазы подключения, сразу устанавливается CONN_MGR_STATE_CONNECTED.
|
||||
* Создаёт entry с флагом alien=1.
|
||||
*/
|
||||
int conn_mgr_add_alien_node(struct CONN_MGR* mgr, const uint8_t* nodeinfo_data, size_t len);
|
||||
|
||||
/**
|
||||
* @brief Отправляет данные подключённой ноде через etcp_router.
|
||||
* @param mgr менеджер
|
||||
* @param node_id идентификатор целевой ноды
|
||||
* @param entry ll_entry с данными для отправки (освобождается после отправки)
|
||||
* @return CONN_MGR_OK или CONN_MGR_ERR_NOT_FOUND
|
||||
*
|
||||
* Для INDIRECT-соединений пакет автоматически маршрутизируется через
|
||||
* выбранного посредника (round-robin по intermediaries[]).
|
||||
* Обновляет last_traffic_tb для idle-проверки.
|
||||
*/
|
||||
int conn_mgr_send(struct CONN_MGR* mgr, uint64_t node_id, struct ll_entry* entry);
|
||||
|
||||
/* ==================== Управление кандидатами ==================== */
|
||||
|
||||
/**
|
||||
* @brief Обновляет RTT кандидата в списке лучших посредников.
|
||||
* @param mgr менеджер
|
||||
* @param node_id идентификатор ноды-кандидата
|
||||
* @param rtt RTT до кандидата (мс)
|
||||
*
|
||||
* Если кандидат уже в списке — обновляет RTT. Если нет — вставляет с сортировкой
|
||||
* по возрастанию RTT. Если список заполнен и новый кандидат имеет худший RTT —
|
||||
* игнорируется. Список всегда отсортирован: best_candidates[0] — наименьший RTT.
|
||||
*
|
||||
* @note Обычно вызывается из route_connectivity после успешного probe.
|
||||
*/
|
||||
void conn_mgr_update_best_candidates(struct CONN_MGR* mgr, uint64_t node_id, uint16_t rtt);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
+277
-53
@@ -10,7 +10,9 @@
|
||||
#include "etcp_connections.h"
|
||||
#include "tun_if.h"
|
||||
#include "route_lib.h"
|
||||
#include "route_bgp.h"
|
||||
#include "topo_group.h"
|
||||
#include "etcp_router.h"
|
||||
#include "topo_node.h"
|
||||
#include "pkt_normalizer.h"
|
||||
#include "../tools/etcpmon/etcpmon_protocol.h"
|
||||
#include "../lib/u_async.h"
|
||||
@@ -89,6 +91,7 @@ static void client_write_callback(socket_t fd, void* arg);
|
||||
static void client_except_callback(socket_t fd, void* arg);
|
||||
static void handle_client_data(struct control_server* server, struct control_client* client);
|
||||
static void close_client(struct control_server* server, struct control_client* client);
|
||||
static void control_client_idle_cb(void* arg);
|
||||
|
||||
static void send_conn_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
|
||||
static void send_socket_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
|
||||
@@ -96,8 +99,16 @@ static void send_metrics(struct control_server* server, struct control_client* c
|
||||
static void send_debug_config(struct control_server* server, struct control_client* client, uint8_t seq_id);
|
||||
static void send_error(struct control_client* client, uint8_t error_code, const char* msg, uint8_t seq_id);
|
||||
|
||||
static void control_server_send_node_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
|
||||
static void send_single_node_info(struct control_server* server, struct control_client* client, struct TOPO_NODEQ* nq);
|
||||
static int control_client_send(struct control_server* server, struct control_client* client, uint8_t* dgram, uint16_t len);
|
||||
|
||||
static struct ETCP_CONN* find_connection_by_peer_id(struct UTUN_INSTANCE* instance, uint64_t peer_id);
|
||||
|
||||
/* ETCP ID/subcmd constants for BGP_NODEINFO_PACKET (from route_bgp.h) */
|
||||
#define CTRL_BGP_CMD 0x01
|
||||
#define CTRL_BGP_SUBCMD 0x04
|
||||
|
||||
/* ============================================================================
|
||||
* Server Initialization
|
||||
* ============================================================================ */
|
||||
@@ -400,13 +411,27 @@ static void accept_callback(socket_t fd, void* arg) {
|
||||
|
||||
client->fd = client_fd;
|
||||
client->server = server; /* Store back pointer to server */
|
||||
// client->connected = 1;
|
||||
client->selected_peer_id = 0;
|
||||
|
||||
|
||||
client->output_queue = queue_new(server->ua, 0, 0, 0, "ctrl_out");
|
||||
if (!client->output_queue) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to create output queue for client");
|
||||
u_free(client);
|
||||
#ifdef _WIN32
|
||||
closesocket(client_fd);
|
||||
#else
|
||||
close(client_fd);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
client->subscribed_nodes = 0;
|
||||
client->write_registered = 0;
|
||||
client->idle_timer = uasync_set_timeout(server->ua, 300000, client, control_client_idle_cb, "ctrl_idle");
|
||||
|
||||
/* Register with uasync */
|
||||
client->socket_id = uasync_add_socket_t(server->ua, client_fd,
|
||||
client_read_callback,
|
||||
NULL, /* write callback - not needed for now */
|
||||
client_write_callback,
|
||||
client_except_callback,
|
||||
client);
|
||||
|
||||
@@ -454,6 +479,12 @@ static void client_read_callback(socket_t fd, void* arg) {
|
||||
struct control_client* client = (struct control_client*)arg;
|
||||
struct control_server* server = client->server;
|
||||
|
||||
if (client->recv_len >= ETCPMON_MAX_MSG_SIZE) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Control recv buffer overflow (pre-check)");
|
||||
close_client(server, client);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Read available data */
|
||||
uint8_t* buf = client->recv_buffer + client->recv_len;
|
||||
size_t buf_space = ETCPMON_MAX_MSG_SIZE - client->recv_len;
|
||||
@@ -544,9 +575,45 @@ static void client_read_callback(socket_t fd, void* arg) {
|
||||
}
|
||||
|
||||
static void client_write_callback(socket_t fd, void* arg) {
|
||||
/* Not used for now - writes are immediate */
|
||||
struct control_client* client = (struct control_client*)arg;
|
||||
struct control_server* server = client->server;
|
||||
struct ll_queue* q = client->output_queue;
|
||||
|
||||
while (q->head) {
|
||||
struct ll_entry* entry = q->head;
|
||||
|
||||
#ifdef _WIN32
|
||||
int sent = send(fd, (const char*)entry->dgram, entry->len, 0);
|
||||
if (sent == SOCKET_ERROR) {
|
||||
int err = WSAGetLastError();
|
||||
if (err != WSAEWOULDBLOCK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Client send error: %d, closing", err);
|
||||
close_client(server, client);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
#else
|
||||
ssize_t sent = send(fd, entry->dgram, entry->len, 0);
|
||||
if (sent < 0) {
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Client send error: %s, closing", strerror(errno));
|
||||
close_client(server, client);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if ((size_t)sent < entry->len) { entry->dgram += sent; entry->len -= (uint16_t)sent; q->total_bytes -= (size_t)sent; return; }
|
||||
|
||||
struct ll_entry* e = queue_data_get(q);
|
||||
queue_dgram_free(e); queue_entry_free(e);
|
||||
}
|
||||
|
||||
uasync_set_socket_write(server->ua, client->socket_id, 0);
|
||||
client->write_registered = 0;
|
||||
if (q->count > 128) DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "ctrl_out: large output queue count=%d fd=%d", q->count, (int)client->fd);
|
||||
(void)fd;
|
||||
(void)arg;
|
||||
}
|
||||
|
||||
static void client_except_callback(socket_t fd, void* arg) {
|
||||
@@ -560,10 +627,35 @@ static void client_except_callback(socket_t fd, void* arg) {
|
||||
(void)fd;
|
||||
}
|
||||
|
||||
static int control_client_send(struct control_server* server, struct control_client* client, uint8_t* dgram, uint16_t len) {
|
||||
(void)server;
|
||||
if (!client || !client->output_queue || !dgram || len == 0) return -1;
|
||||
struct ll_entry* entry = queue_entry_new(0);
|
||||
if (!entry) { DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate output queue entry"); return -1; }
|
||||
entry->dgram = dgram;
|
||||
entry->len = len;
|
||||
entry->int_len = len;
|
||||
queue_data_put(client->output_queue, entry);
|
||||
if (!client->write_registered) {
|
||||
uasync_set_socket_write(client->server->ua, client->socket_id, 1);
|
||||
client->write_registered = 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void control_client_idle_cb(void* arg) {
|
||||
struct control_client* client = (struct control_client*)arg;
|
||||
if (!client || !client->server) return;
|
||||
client->idle_timer = NULL;
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Control client idle timeout fd=%d, closing", client->fd);
|
||||
close_client(client->server, client);
|
||||
}
|
||||
|
||||
static void close_client(struct control_server* server, struct control_client* client) {
|
||||
if (!server || !client) return;
|
||||
|
||||
if (client->idle_timer) { uasync_cancel_timeout(server->ua, client->idle_timer); client->idle_timer = NULL; }
|
||||
|
||||
/* Log disconnection */
|
||||
if (server->log_file) {
|
||||
fprintf(server->log_file, "%llu: [LOG] Client connection closed\n",
|
||||
@@ -583,7 +675,9 @@ static void close_client(struct control_server* server, struct control_client* c
|
||||
close(client->fd);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
if (client->output_queue) { queue_free(client->output_queue); client->output_queue = NULL; }
|
||||
|
||||
/* Remove from list */
|
||||
struct control_client** curr = &server->clients;
|
||||
while (*curr) {
|
||||
@@ -614,7 +708,7 @@ static void handle_client_data(struct control_server* server, struct control_cli
|
||||
close_client(server, client);
|
||||
return;
|
||||
}
|
||||
if (hdr->type < ETCPMON_CMD_LIST_CONN || hdr->type > ETCPMON_CMD_SET_DEBUG_CONFIG) {
|
||||
if (hdr->type < ETCPMON_CMD_LIST_CONN || hdr->type > ETCPMON_CMD_SUBSCRIBE_NODES) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Invalid command type from client: 0x%02X", hdr->type);
|
||||
close_client(server, client);
|
||||
return;
|
||||
@@ -690,9 +784,30 @@ static void handle_client_data(struct control_server* server, struct control_cli
|
||||
struct etcpmon_cmd_action* cmd = (struct etcpmon_cmd_action*)payload;
|
||||
if (strncmp(cmd->action, "nat", 3) == 0) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Action 'nat' received, requesting NAT check for all links");
|
||||
route_bgp_request_nat_check_all(server->instance->bgp);
|
||||
topo_group_request_nat_check_all(topo_groups_get_default(server->instance->topo_groups));
|
||||
} else if (strncmp(cmd->action, "nodes", 5) == 0) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Action 'nodes' received, dumping all BGP nodes");
|
||||
uint16_t text_max = (ETCPMON_MAX_MSG_SIZE > 1024) ? ETCPMON_MAX_MSG_SIZE - 512 : 1024;
|
||||
char* text = (char*)u_malloc(text_max);
|
||||
if (text) {
|
||||
int text_len = topo_node_format_all(topo_groups_get_default(server->instance->topo_groups), text, text_max);
|
||||
if (text_len < 0) text_len = 0;
|
||||
text[text_len] = '\0';
|
||||
uint16_t rsp_size = ETCPMON_ACTION_RESULT_SIZE(text_len);
|
||||
uint8_t* buffer = (uint8_t*)u_malloc(rsp_size);
|
||||
if (buffer) {
|
||||
struct etcpmon_msg_header* rsp_hdr = (struct etcpmon_msg_header*)buffer;
|
||||
etcpmon_build_header(rsp_hdr, sizeof(struct etcpmon_rsp_action_result) + text_len + 1,
|
||||
ETCPMON_RSP_ACTION_RESULT, req_seq);
|
||||
struct etcpmon_rsp_action_result* rsp = (struct etcpmon_rsp_action_result*)(buffer + sizeof(*rsp_hdr));
|
||||
rsp->code = 0;
|
||||
memcpy(buffer + sizeof(*rsp_hdr) + sizeof(*rsp), text, text_len + 1);
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
}
|
||||
u_free(text);
|
||||
}
|
||||
} else {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Unknown action received: '%s'", cmd->action);
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Unknown action received: '%.32s'", cmd->action);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -721,6 +836,17 @@ static void handle_client_data(struct control_server* server, struct control_cli
|
||||
}
|
||||
break;
|
||||
|
||||
case ETCPMON_CMD_SUBSCRIBE_NODES:
|
||||
client->subscribed_nodes = 1;
|
||||
if (server->log_file) {
|
||||
fprintf(server->log_file, "%llu: [LOG] Client subscribed to node updates\n",
|
||||
(unsigned long long)get_timestamp_ms());
|
||||
fflush(server->log_file);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Client subscribed to node updates");
|
||||
control_server_send_node_list(server, client, req_seq);
|
||||
break;
|
||||
|
||||
case ETCPMON_CMD_DISCONNECT:
|
||||
if (server->log_file) {
|
||||
fprintf(server->log_file, "%llu: [LOG] Client requested disconnect\n",
|
||||
@@ -736,6 +862,10 @@ static void handle_client_data(struct control_server* server, struct control_cli
|
||||
return;
|
||||
}
|
||||
|
||||
/* Reset idle timer on each valid command */
|
||||
if (client->idle_timer) { uasync_cancel_timeout(server->ua, client->idle_timer); }
|
||||
client->idle_timer = uasync_set_timeout(server->ua, 300000, client, control_client_idle_cb, "ctrl_idle");
|
||||
|
||||
/* Remove processed message from buffer */
|
||||
uint16_t msg_size = hdr->size;
|
||||
if (client->recv_len > msg_size) {
|
||||
@@ -795,13 +925,7 @@ static void send_conn_list(struct control_server* server, struct control_client*
|
||||
fflush(server->log_file);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
send(client->fd, (const char*)buffer, rsp_size, 0);
|
||||
#else
|
||||
send(client->fd, buffer, rsp_size, 0);
|
||||
#endif
|
||||
|
||||
u_free(buffer);
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
}
|
||||
|
||||
static void send_socket_list(struct control_server* server, struct control_client* client, uint8_t seq_id) {
|
||||
@@ -819,8 +943,7 @@ static void send_socket_list(struct control_server* server, struct control_clien
|
||||
struct etcpmon_rsp_socket_list* rsp = (struct etcpmon_rsp_socket_list*)(buffer + sizeof(*hdr));
|
||||
rsp->count = 0;
|
||||
|
||||
send(client->fd, buffer, rsp_size, 0);
|
||||
u_free(buffer);
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -868,6 +991,7 @@ static void send_socket_list(struct control_server* server, struct control_clien
|
||||
info[i].ip[sizeof(info[i].ip) - 1] = '\0';
|
||||
info[i].port = 0;
|
||||
}
|
||||
info[i].config_type = sock->type;
|
||||
info[i].nat_type = sock->nat_type;
|
||||
|
||||
/* NAT address from socket (set when INIT_RESPONSE received) */
|
||||
@@ -882,24 +1006,21 @@ static void send_socket_list(struct control_server* server, struct control_clien
|
||||
inet_ntop(AF_INET6, &sin6->sin6_addr, info[i].nat_ip, sizeof(info[i].nat_ip));
|
||||
info[i].nat_port = ntohs(sin6->sin6_port);
|
||||
}
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NAT, "socket_list to gui: sock=%s config=%d nat_type=%d nat_ip=%s nat_port=%u",
|
||||
sock->name, sock->type, sock->nat_type,
|
||||
info[i].nat_ip[0] ? info[i].nat_ip : "none", info[i].nat_port);
|
||||
|
||||
sock = sock->next;
|
||||
}
|
||||
|
||||
if (server->log_file) {
|
||||
log_hex_data(server->log_file, "TX", buffer, rsp_size);
|
||||
fprintf(server->log_file, "%llu: [LOG] Sent RSP_SOCKET_LIST count=%d\n",
|
||||
(unsigned long long)get_timestamp_ms(), count);
|
||||
(unsigned long long)get_timestamp_ms(), count);
|
||||
fflush(server->log_file);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
send(client->fd, (const char*)buffer, rsp_size, 0);
|
||||
#else
|
||||
send(client->fd, buffer, rsp_size, 0);
|
||||
#endif
|
||||
|
||||
u_free(buffer);
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
}
|
||||
|
||||
static void send_metrics(struct control_server* server, struct control_client* client, uint8_t seq_id) {
|
||||
@@ -1067,17 +1188,56 @@ static void send_metrics(struct control_server* server, struct control_client* c
|
||||
}
|
||||
|
||||
/* BGP routing */
|
||||
if (instance->bgp) {
|
||||
rsp->tun.rt_bgp_senders = (uint32_t)queue_entry_count(instance->bgp->senders_list);
|
||||
rsp->tun.rt_bgp_nodes = (uint32_t)queue_entry_count(instance->bgp->nodes);
|
||||
{ struct TOPO_GROUP* g = topo_groups_get_default(instance->topo_groups);
|
||||
if (g) {
|
||||
rsp->tun.rt_bgp_senders = (uint32_t)queue_entry_count(g->senders_list);
|
||||
rsp->tun.rt_bgp_nodes = (uint32_t)queue_entry_count(g->nodes);
|
||||
} else {
|
||||
rsp->tun.rt_bgp_senders = 0;
|
||||
rsp->tun.rt_bgp_nodes = 0;
|
||||
}
|
||||
} }
|
||||
} else {
|
||||
memset(&rsp->tun, 0, sizeof(rsp->tun));
|
||||
}
|
||||
|
||||
/* Fill router congestion metrics — aggregate over all router_conns */
|
||||
memset(&rsp->router, 0, sizeof(rsp->router));
|
||||
if (instance->router_conns) {
|
||||
uint32_t rtt_cnt = 0;
|
||||
struct ll_entry* re = instance->router_conns->head;
|
||||
while (re) {
|
||||
struct ETCP_ROUTER_CONN* rc = (struct ETCP_ROUTER_CONN*)re;
|
||||
rsp->router.total_inflight += (uint32_t)(rc->tx_seq - rc->tx_acked);
|
||||
if (rc->send_q) rsp->router.total_send_q += (uint32_t)queue_entry_count(rc->send_q);
|
||||
if (rc->recv_q) rsp->router.total_recv_q += (uint32_t)queue_entry_count(rc->recv_q);
|
||||
rsp->router.pkts_sent += rc->c_pkts_sent;
|
||||
rsp->router.pkts_send_err += rc->c_pkts_send_err;
|
||||
rsp->router.pkts_rcvd += rc->c_pkts_rcvd;
|
||||
rsp->router.ack_sent += rc->c_ack_sent;
|
||||
rsp->router.ack_recv += rc->c_ack_recv;
|
||||
rsp->router.dup_dropped += rc->c_dup_dropped;
|
||||
rsp->router.oob_dropped += rc->c_oob_dropped;
|
||||
rsp->router.stale_ack += rc->c_stale_ack;
|
||||
rsp->router.sign_fail += rc->c_sign_fail;
|
||||
if (rc->rtt > 0) {
|
||||
if (rc->rtt > rsp->router.rtt_last) rsp->router.rtt_last = rc->rtt;
|
||||
rsp->router.rtt_avg += rc->rtt;
|
||||
rtt_cnt++;
|
||||
}
|
||||
{
|
||||
uint16_t j = (uint16_t)(rc->rtt_jitter / 32768);
|
||||
if (j > rsp->router.jitter) rsp->router.jitter = j;
|
||||
}
|
||||
if (rc->minrtt_window_count > 0) {
|
||||
if (rsp->router.minrtt == 0 || rc->minrtt < rsp->router.minrtt)
|
||||
rsp->router.minrtt = rc->minrtt;
|
||||
}
|
||||
re = re->next;
|
||||
}
|
||||
if (rtt_cnt > 0) rsp->router.rtt_avg /= rtt_cnt;
|
||||
rsp->router.total_conns = (uint32_t)queue_entry_count(instance->router_conns);
|
||||
}
|
||||
|
||||
/* Fill link metrics */
|
||||
struct etcpmon_link_metrics* link_info = (struct etcpmon_link_metrics*)(buffer + sizeof(*hdr) + sizeof(*rsp));
|
||||
link = conn->links;
|
||||
@@ -1136,13 +1296,7 @@ static void send_metrics(struct control_server* server, struct control_client* c
|
||||
fflush(server->log_file);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
send(client->fd, (const char*)buffer, rsp_size, 0);
|
||||
#else
|
||||
send(client->fd, buffer, rsp_size, 0);
|
||||
#endif
|
||||
|
||||
u_free(buffer);
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
}
|
||||
|
||||
static void send_error(struct control_client* client, uint8_t error_code, const char* msg, uint8_t seq_id) {
|
||||
@@ -1170,13 +1324,7 @@ static void send_error(struct control_client* client, uint8_t error_code, const
|
||||
fflush(server->log_file);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
send(client->fd, (const char*)buffer, rsp_size, 0);
|
||||
#else
|
||||
send(client->fd, buffer, rsp_size, 0);
|
||||
#endif
|
||||
|
||||
u_free(buffer);
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
}
|
||||
|
||||
static void send_debug_config(struct control_server* server, struct control_client* client, uint8_t seq_id) {
|
||||
@@ -1215,13 +1363,7 @@ static void send_debug_config(struct control_server* server, struct control_clie
|
||||
fflush(server->log_file);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
send(client->fd, (const char*)buffer, rsp_size, 0);
|
||||
#else
|
||||
send(client->fd, buffer, rsp_size, 0);
|
||||
#endif
|
||||
|
||||
u_free(buffer);
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
@@ -1240,6 +1382,88 @@ static struct ETCP_CONN* find_connection_by_peer_id(struct UTUN_INSTANCE* instan
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* Node Info Helpers (send_single_node_info, send_node_list, notify_change)
|
||||
* ============================================================================ */
|
||||
|
||||
static void send_single_node_info(struct control_server* server, struct control_client* client, struct TOPO_NODEQ* nq) {
|
||||
if (!server || !nq || !nq->node) return;
|
||||
uint8_t buf[8192];
|
||||
int ser_len = topo_node_serialize(topo_groups_get_default(server->instance->topo_groups), nq, buf + 2, sizeof(buf) - 2);
|
||||
if (ser_len < 0) { DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to serialize node info"); return; }
|
||||
buf[0] = CTRL_BGP_CMD; buf[1] = CTRL_BGP_SUBCMD;
|
||||
size_t data_size = 2 + (size_t)ser_len;
|
||||
|
||||
size_t np_size = sizeof(struct etcpmon_msg_header) + data_size;
|
||||
uint8_t* buffer = (uint8_t*)u_malloc(np_size);
|
||||
if (!buffer) { DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate node info buffer"); return; }
|
||||
|
||||
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
|
||||
etcpmon_build_header(hdr, (uint16_t)data_size, ETCPMON_RSP_NODE_INFO, 0);
|
||||
memcpy(buffer + sizeof(*hdr), buf, data_size);
|
||||
|
||||
control_client_send(server, client, buffer, (uint16_t)np_size);
|
||||
}
|
||||
|
||||
static void control_server_send_node_list(struct control_server* server, struct control_client* client, uint8_t seq_id) {
|
||||
struct TOPO_GROUP* group = topo_groups_get_default(server->instance->topo_groups);
|
||||
if (!group) { send_error(client, ETCPMON_ERR_SERVER_BUSY, "BGP not available", seq_id); return; }
|
||||
|
||||
/* Send local node first */
|
||||
if (group->local_node) send_single_node_info(server, client, group->local_node);
|
||||
|
||||
/* Send all known remote nodes */
|
||||
struct ll_entry* e = group->nodes ? group->nodes->head : NULL;
|
||||
while (e) {
|
||||
struct TOPO_NODEQ* nq = (struct TOPO_NODEQ*)e;
|
||||
send_single_node_info(server, client, nq);
|
||||
e = e->next;
|
||||
}
|
||||
|
||||
/* Send end-of-list marker */
|
||||
uint8_t* buffer = (uint8_t*)u_malloc(sizeof(struct etcpmon_msg_header));
|
||||
if (!buffer) return;
|
||||
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
|
||||
etcpmon_build_header(hdr, 0, ETCPMON_RSP_NODE_INFO_END, seq_id);
|
||||
control_client_send(server, client, buffer, sizeof(struct etcpmon_msg_header));
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Sent node list to client (seq=%d)", seq_id);
|
||||
}
|
||||
|
||||
void control_server_notify_node_change(struct control_server* server, struct TOPO_NODEQ* node) {
|
||||
if (!server || !node) return;
|
||||
struct control_client* client = server->clients;
|
||||
while (client) {
|
||||
if (client->subscribed_nodes) send_single_node_info(server, client, node);
|
||||
client = client->next;
|
||||
}
|
||||
}
|
||||
|
||||
void control_server_notify_node_removed(struct control_server* server, uint64_t node_id) {
|
||||
if (!server) return;
|
||||
|
||||
uint8_t* buffer = (uint8_t*)u_malloc(sizeof(struct etcpmon_msg_header) + sizeof(uint64_t));
|
||||
if (!buffer) return;
|
||||
|
||||
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
|
||||
etcpmon_build_header(hdr, sizeof(uint64_t), ETCPMON_RSP_NODE_REMOVED, 0);
|
||||
*(uint64_t*)(buffer + sizeof(*hdr)) = node_id;
|
||||
|
||||
struct control_client* client = server->clients;
|
||||
while (client) {
|
||||
if (client->subscribed_nodes) {
|
||||
/* Every client needs its own copy since ownership transfers to the queue */
|
||||
uint8_t* copy = (uint8_t*)u_malloc(sizeof(struct etcpmon_msg_header) + sizeof(uint64_t));
|
||||
if (copy) {
|
||||
memcpy(copy, buffer, sizeof(struct etcpmon_msg_header) + sizeof(uint64_t));
|
||||
control_client_send(server, client, copy, sizeof(struct etcpmon_msg_header) + sizeof(uint64_t));
|
||||
}
|
||||
}
|
||||
client = client->next;
|
||||
}
|
||||
u_free(buffer);
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* Public API Implementation
|
||||
* ============================================================================ */
|
||||
|
||||
+10
-3
@@ -19,6 +19,8 @@ extern "C" {
|
||||
/* Forward declarations */
|
||||
struct UTUN_INSTANCE;
|
||||
struct UASYNC;
|
||||
struct TOPO_NODEQ;
|
||||
struct ll_queue;
|
||||
|
||||
/* Forward declaration */
|
||||
struct control_server;
|
||||
@@ -36,9 +38,11 @@ struct control_client {
|
||||
|
||||
/* Selected connection */
|
||||
uint64_t selected_peer_id; /* 0 = none selected */
|
||||
|
||||
/* Client state */
|
||||
// uint8_t connected;
|
||||
|
||||
struct ll_queue* output_queue; /* Output queue for async sends */
|
||||
void* idle_timer; /* Client idle timeout */
|
||||
uint8_t subscribed_nodes; /* 1 = subscribed to node updates */
|
||||
uint8_t write_registered; /* 1 = write events registered in uasync */
|
||||
};
|
||||
|
||||
/* Control server state */
|
||||
@@ -113,6 +117,9 @@ void control_server_process_updates(struct control_server* server);
|
||||
*/
|
||||
uint32_t control_server_get_client_count(const struct control_server* server);
|
||||
|
||||
void control_server_notify_node_change(struct control_server* server, struct TOPO_NODEQ* node);
|
||||
void control_server_notify_node_removed(struct control_server* server, uint64_t node_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
#ifndef CRC32_H
|
||||
#define CRC32_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
@@ -16,4 +21,8 @@ uint32_t crc32_calc_ex(const uint8_t *data, size_t len, uint32_t initial_crc);
|
||||
// Update CRC32 incrementally
|
||||
uint32_t crc32_update(uint32_t crc, const uint8_t *data, size_t len);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // CRC32_H
|
||||
+940
@@ -0,0 +1,940 @@
|
||||
// db_sync.c — Distributed content-addressed table with LMDB + peer sync via etcp_router
|
||||
|
||||
#include "db_sync.h"
|
||||
#include "etcp_api.h"
|
||||
#include "etcp.h"
|
||||
#include "etcp_router.h"
|
||||
#include "utun_instance.h"
|
||||
#include "topo_group.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/sha256.h"
|
||||
#include "../lib/liblmdb/lmdb.h"
|
||||
#include "../lib/platform_compat.h"
|
||||
#include <string.h>
|
||||
|
||||
#define DEBUG_CATEGORY_DB_SYNC DEBUG_CATEGORY_DEBUG
|
||||
|
||||
// ---- LMDB key/value layout ----
|
||||
// Key: 16 bytes = timestamp:8 BE || datahash:8 BE (sorted by timestamp first)
|
||||
// Value: id:8 || chain_hash:32 || creator:8 || flags:1 || data_len:4 || data:variable
|
||||
#define DB_VAL_OFF_ID 0
|
||||
#define DB_VAL_OFF_CHAIN_HASH 8
|
||||
#define DB_VAL_OFF_CREATOR 40
|
||||
#define DB_VAL_OFF_FLAGS 48
|
||||
#define DB_VAL_OFF_DATA_LEN 49
|
||||
#define DB_VAL_OFF_DATA 53
|
||||
#define DB_VAL_HDR_SIZE 53
|
||||
|
||||
// ---- Module state ----
|
||||
struct DB_SYNC {
|
||||
struct UTUN_INSTANCE* inst;
|
||||
MDB_env* env;
|
||||
MDB_dbi dbi_records;
|
||||
MDB_dbi dbi_meta;
|
||||
uint64_t next_id;
|
||||
uint64_t last_connected_tb; // timebase when any peer was last connected (for TTL)
|
||||
uint64_t last_timestamp_us; // гарантия монотонности timestamp
|
||||
|
||||
struct DB_SYNC_PEER {
|
||||
uint64_t node_id;
|
||||
uint8_t synced; // 0=not, 1=in_progress, 2=synced
|
||||
}* peers;
|
||||
int peer_count;
|
||||
int peer_capacity;
|
||||
|
||||
void* ttl_timer;
|
||||
void* peer_check_timer;
|
||||
uint8_t enabled;
|
||||
};
|
||||
|
||||
|
||||
// ---- Forward declarations ----
|
||||
static void db_sync_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry);
|
||||
static void db_sync_on_new_conn(struct ETCP_CONN* conn, void* arg);
|
||||
static void db_sync_on_conn_up(struct ETCP_CONN* conn, void* arg);
|
||||
static void db_sync_on_conn_down(struct ETCP_CONN* conn, void* arg);
|
||||
static void db_sync_peer_check_cb(void* arg);
|
||||
static void db_sync_ttl_cleanup_cb(void* arg);
|
||||
static int db_sync_send(struct DB_SYNC* db, uint64_t dst_node_id, const uint8_t* payload, size_t len);
|
||||
static void db_sync_initiate_sync(struct DB_SYNC* db, uint64_t peer_node_id);
|
||||
|
||||
// ---- LMDB helpers ----
|
||||
static int db_lmdb_open(struct DB_SYNC* db, const char* path) {
|
||||
int rc = mdb_env_create(&db->env);
|
||||
if (rc) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: mdb_env_create failed: %s", mdb_strerror(rc)); return -1; }
|
||||
rc = mdb_env_set_mapsize(db->env, DB_SYNC_DEFAULT_MAPSIZE);
|
||||
if (rc) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: mdb_env_set_mapsize failed: %s", mdb_strerror(rc)); mdb_env_close(db->env); db->env=NULL; return -1; }
|
||||
rc = mdb_env_set_maxdbs(db->env, 3);
|
||||
if (rc) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: mdb_env_set_maxdbs failed: %s", mdb_strerror(rc)); mdb_env_close(db->env); db->env=NULL; return -1; }
|
||||
rc = mdb_env_open(db->env, path, 0, 0644);
|
||||
if (rc) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: mdb_env_open(%s) failed: %s", path, mdb_strerror(rc)); mdb_env_close(db->env); db->env=NULL; return -1; }
|
||||
|
||||
MDB_txn* txn;
|
||||
rc = mdb_txn_begin(db->env, NULL, 0, &txn);
|
||||
if (rc) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: txn_begin failed: %s", mdb_strerror(rc)); mdb_env_close(db->env); db->env=NULL; return -1; }
|
||||
rc = mdb_dbi_open(txn, "records", MDB_CREATE, &db->dbi_records);
|
||||
if (rc) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: dbi_open(records) failed: %s", mdb_strerror(rc)); mdb_txn_abort(txn); mdb_env_close(db->env); db->env=NULL; return -1; }
|
||||
rc = mdb_dbi_open(txn, "meta", MDB_CREATE, &db->dbi_meta);
|
||||
if (rc) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: dbi_open(meta) failed: %s", mdb_strerror(rc)); mdb_txn_abort(txn); mdb_env_close(db->env); db->env=NULL; return -1; }
|
||||
rc = mdb_txn_commit(txn);
|
||||
if (rc) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: txn_commit failed: %s", mdb_strerror(rc)); mdb_env_close(db->env); db->env=NULL; return -1; }
|
||||
|
||||
// Read next_id from meta
|
||||
MDB_txn* rt;
|
||||
rc = mdb_txn_begin(db->env, NULL, MDB_RDONLY, &rt);
|
||||
if (rc == MDB_SUCCESS) {
|
||||
MDB_val key, data; key.mv_size = 7; key.mv_data = (char*)"next_id";
|
||||
rc = mdb_get(rt, db->dbi_meta, &key, &data);
|
||||
if (rc == MDB_SUCCESS && data.mv_size >= 8) db->next_id = *(uint64_t*)data.mv_data;
|
||||
else db->next_id = 1;
|
||||
mdb_txn_abort(rt);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: LMDB opened at %s, next_id=%llu", path, (unsigned long long)db->next_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void db_lmdb_close(struct DB_SYNC* db) {
|
||||
if (!db->env) return;
|
||||
mdb_env_close(db->env); db->env = NULL;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: LMDB closed");
|
||||
}
|
||||
|
||||
// Build 16-byte key: timestamp:8 BE || datahash:8 BE
|
||||
static void db_key_build(uint8_t key[16], uint64_t timestamp, uint64_t datahash) {
|
||||
uint64_t ts_be = htobe64(timestamp), dh_be = htobe64(datahash);
|
||||
memcpy(key, &ts_be, 8); memcpy(key + 8, &dh_be, 8);
|
||||
}
|
||||
|
||||
// Get chain_hash at a given sorted position (0-based) via cursor. Returns 0 on success.
|
||||
static int db_chain_hash_at(struct DB_SYNC* db, uint32_t pos, uint8_t chain_hash_out[32]) {
|
||||
MDB_txn* txn; int rc = mdb_txn_begin(db->env, NULL, MDB_RDONLY, &txn);
|
||||
if (rc) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: chain_hash_at txn_begin failed: %s", mdb_strerror(rc)); return -1; }
|
||||
MDB_cursor* cursor; rc = mdb_cursor_open(txn, db->dbi_records, &cursor);
|
||||
if (rc) { mdb_txn_abort(txn); return -1; }
|
||||
MDB_val key, val;
|
||||
rc = mdb_cursor_get(cursor, &key, &val, MDB_FIRST);
|
||||
for (uint32_t i = 0; rc == 0 && i < pos; i++) rc = mdb_cursor_get(cursor, &key, &val, MDB_NEXT);
|
||||
if (rc == 0 && val.mv_size >= DB_VAL_HDR_SIZE) memcpy(chain_hash_out, (uint8_t*)val.mv_data + DB_VAL_OFF_CHAIN_HASH, 32);
|
||||
else { mdb_cursor_close(cursor); mdb_txn_abort(txn); return -1; }
|
||||
mdb_cursor_close(cursor); mdb_txn_abort(txn);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get count of records
|
||||
static uint32_t db_count(struct DB_SYNC* db) {
|
||||
MDB_txn* txn; int rc = mdb_txn_begin(db->env, NULL, MDB_RDONLY, &txn);
|
||||
if (rc) return 0;
|
||||
MDB_stat stat; rc = mdb_stat(txn, db->dbi_records, &stat);
|
||||
mdb_txn_abort(txn);
|
||||
return rc == 0 ? (uint32_t)stat.ms_entries : 0;
|
||||
}
|
||||
|
||||
// Get datahash at given sorted position
|
||||
static int db_datahash_at(struct DB_SYNC* db, uint32_t pos, uint64_t* datahash) {
|
||||
MDB_txn* txn; int rc = mdb_txn_begin(db->env, NULL, MDB_RDONLY, &txn);
|
||||
if (rc) return -1;
|
||||
MDB_cursor* cursor; rc = mdb_cursor_open(txn, db->dbi_records, &cursor);
|
||||
if (rc) { mdb_txn_abort(txn); return -1; }
|
||||
MDB_val key, val; rc = mdb_cursor_get(cursor, &key, &val, MDB_FIRST);
|
||||
for (uint32_t i=0; rc==0 && i<pos; i++) rc = mdb_cursor_get(cursor, &key, &val, MDB_NEXT);
|
||||
if (rc==0) {
|
||||
*datahash = be64toh(*(uint64_t*)((uint8_t*)key.mv_data+8));
|
||||
mdb_cursor_close(cursor); mdb_txn_abort(txn); return 0;
|
||||
}
|
||||
mdb_cursor_close(cursor); mdb_txn_abort(txn); return -1;
|
||||
}
|
||||
|
||||
// ---- SHA256 helpers ----
|
||||
static void db_sha256(const uint8_t* data, size_t len, uint8_t hash[32]) {
|
||||
SC_SHA256_CTX ctx; sc_sha256_init(&ctx); sc_sha256_update(&ctx, data, len); sc_sha256_final(&ctx, hash);
|
||||
}
|
||||
|
||||
// Compute datahash: first 64 bits of SHA256(json_data)
|
||||
static uint64_t db_datahash(const uint8_t* data, size_t len) {
|
||||
uint8_t hash[32]; db_sha256(data, len, hash);
|
||||
uint64_t dh; memcpy(&dh, hash, 8); return dh;
|
||||
}
|
||||
|
||||
// Compute chain_hash for a record
|
||||
static void db_chain_hash_compute(const uint8_t prev_chain_hash[32], uint64_t id, uint64_t timestamp, uint64_t datahash, uint8_t out[32]) {
|
||||
uint8_t buf[32+8+8+8]; // prev_chain_hash:32 + id:8 + timestamp:8 + datahash:8
|
||||
memcpy(buf, prev_chain_hash, 32);
|
||||
memcpy(buf+32, &id, 8); memcpy(buf+40, ×tamp, 8); memcpy(buf+48, &datahash, 8);
|
||||
db_sha256(buf, 56, out);
|
||||
}
|
||||
|
||||
// Get previous record's chain_hash (record before the given key in sorted order)
|
||||
static int db_prev_chain_hash(struct DB_SYNC* db, const uint8_t key[16], uint8_t chain_hash[32]) {
|
||||
MDB_txn* txn; int rc = mdb_txn_begin(db->env, NULL, MDB_RDONLY, &txn);
|
||||
if (rc) return -1;
|
||||
MDB_cursor* cursor; rc = mdb_cursor_open(txn, db->dbi_records, &cursor);
|
||||
if (rc) { mdb_txn_abort(txn); return -1; }
|
||||
MDB_val search_key, val; search_key.mv_size = 16; search_key.mv_data = (void*)key;
|
||||
rc = mdb_cursor_get(cursor, &search_key, &val, MDB_SET_RANGE);
|
||||
if (rc == 0) {
|
||||
rc = mdb_cursor_get(cursor, &search_key, &val, MDB_PREV);
|
||||
if (rc == 0 && val.mv_size >= DB_VAL_HDR_SIZE) {
|
||||
memcpy(chain_hash, (uint8_t*)val.mv_data + DB_VAL_OFF_CHAIN_HASH, 32);
|
||||
mdb_cursor_close(cursor); mdb_txn_abort(txn); return 0;
|
||||
}
|
||||
}
|
||||
// No previous record → use zero hash
|
||||
memset(chain_hash, 0, 32);
|
||||
mdb_cursor_close(cursor); mdb_txn_abort(txn);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- Insert record ----
|
||||
static int db_record_insert(struct DB_SYNC* db, uint64_t id, uint64_t timestamp, uint64_t datahash, const char* json, size_t json_len) {
|
||||
uint8_t key[16]; db_key_build(key, timestamp, datahash);
|
||||
|
||||
// Check duplicate
|
||||
MDB_txn* txn; int rc = mdb_txn_begin(db->env, NULL, 0, &txn);
|
||||
if (rc) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: insert txn_begin failed: %s", mdb_strerror(rc)); return -1; }
|
||||
MDB_val dup_key, dup_val; dup_key.mv_size = 16; dup_key.mv_data = key;
|
||||
rc = mdb_get(txn, db->dbi_records, &dup_key, &dup_val);
|
||||
if (rc == MDB_SUCCESS) { mdb_txn_abort(txn); return 1; } // already exists
|
||||
|
||||
// Compute chain_hash
|
||||
uint8_t prev_ch[32]; db_prev_chain_hash(db, key, prev_ch);
|
||||
uint8_t chain_hash[32]; db_chain_hash_compute(prev_ch, id, timestamp, datahash, chain_hash);
|
||||
|
||||
// Build value
|
||||
size_t val_size = DB_VAL_HDR_SIZE + json_len;
|
||||
uint8_t* val_buf = u_malloc(val_size);
|
||||
if (!val_buf) { mdb_txn_abort(txn); return -1; }
|
||||
uint64_t creator = db->inst->node_id;
|
||||
memcpy(val_buf + DB_VAL_OFF_ID, &id, 8);
|
||||
memcpy(val_buf + DB_VAL_OFF_CHAIN_HASH, chain_hash, 32);
|
||||
memcpy(val_buf + DB_VAL_OFF_CREATOR, &creator, 8);
|
||||
val_buf[DB_VAL_OFF_FLAGS] = 0;
|
||||
*(uint32_t*)(val_buf + DB_VAL_OFF_DATA_LEN) = (uint32_t)json_len;
|
||||
if (json_len > 0) memcpy(val_buf + DB_VAL_OFF_DATA, json, json_len);
|
||||
|
||||
MDB_val val; val.mv_size = val_size; val.mv_data = val_buf;
|
||||
MDB_val k; k.mv_size = 16; k.mv_data = key;
|
||||
rc = mdb_put(txn, db->dbi_records, &k, &val, 0);
|
||||
u_free(val_buf);
|
||||
|
||||
if (rc) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: mdb_put failed: %s", mdb_strerror(rc)); mdb_txn_abort(txn); return -1; }
|
||||
|
||||
// Update chain_hash for subsequent records (out-of-order insert case)
|
||||
// Check if there are records AFTER this key
|
||||
MDB_cursor* cursor; rc = mdb_cursor_open(txn, db->dbi_records, &cursor);
|
||||
if (rc == 0) {
|
||||
MDB_val ckey, cval; ckey.mv_size = 16; ckey.mv_data = key;
|
||||
rc = mdb_cursor_get(cursor, &ckey, &cval, MDB_SET);
|
||||
if (rc == 0) {
|
||||
uint8_t running_ch[32]; memcpy(running_ch, chain_hash, 32);
|
||||
uint64_t running_id = id;
|
||||
uint64_t running_ts = timestamp;
|
||||
uint64_t running_dh = datahash;
|
||||
// Move to next
|
||||
while (mdb_cursor_get(cursor, &ckey, &cval, MDB_NEXT) == 0) {
|
||||
if (cval.mv_size < DB_VAL_HDR_SIZE) continue;
|
||||
uint64_t n_id = *(uint64_t*)((uint8_t*)cval.mv_data + DB_VAL_OFF_ID);
|
||||
uint64_t n_ts = be64toh(*(uint64_t*)ckey.mv_data);
|
||||
uint64_t n_dh = be64toh(*(uint64_t*)((uint8_t*)ckey.mv_data + 8));
|
||||
uint8_t new_ch[32]; db_chain_hash_compute(running_ch, n_id, n_ts, n_dh, new_ch);
|
||||
memcpy((uint8_t*)cval.mv_data + DB_VAL_OFF_CHAIN_HASH, new_ch, 32);
|
||||
mdb_cursor_put(cursor, &ckey, &cval, MDB_CURRENT);
|
||||
memcpy(running_ch, new_ch, 32);
|
||||
}
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
}
|
||||
|
||||
// Update next_id in meta
|
||||
MDB_val mk, md; mk.mv_size = 7; mk.mv_data = (char*)"next_id"; md.mv_size = 8; md.mv_data = &id; id++;
|
||||
mdb_put(txn, db->dbi_meta, &mk, &md, 0);
|
||||
db->next_id = id;
|
||||
|
||||
rc = mdb_txn_commit(txn);
|
||||
if (rc) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: insert commit failed: %s", mdb_strerror(rc)); return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- Send ----
|
||||
static int db_sync_send(struct DB_SYNC* db, uint64_t dst_node_id, const uint8_t* payload, size_t len) {
|
||||
struct ll_entry* entry = queue_entry_new(0);
|
||||
if (!entry) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "queue_entry_new failed"); return -1; }
|
||||
uint8_t* buf = u_malloc(len + 1);
|
||||
if (!buf) { queue_entry_free(entry); return -1; }
|
||||
buf[0] = ETCP_RT_ID_DB_SYNC; memcpy(buf + 1, payload, len);
|
||||
entry->dgram = buf; entry->len = len + 1;
|
||||
int ret = etcp_route_send(db->inst, dst_node_id, entry, 0);
|
||||
// etcp_route_send always takes ownership and frees entry in all code paths
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ---- Peer management ----
|
||||
static struct DB_SYNC_PEER* db_peer_find(struct DB_SYNC* db, uint64_t node_id) {
|
||||
for (int i=0; i<db->peer_count; i++) if (db->peers[i].node_id == node_id) return &db->peers[i];
|
||||
return NULL;
|
||||
}
|
||||
static struct DB_SYNC_PEER* db_peer_add(struct DB_SYNC* db, uint64_t node_id) {
|
||||
struct DB_SYNC_PEER* p = db_peer_find(db, node_id);
|
||||
if (p) return p;
|
||||
if (db->peer_count >= db->peer_capacity) {
|
||||
int nc = db->peer_capacity ? db->peer_capacity * 2 : 8;
|
||||
struct DB_SYNC_PEER* np = u_realloc(db->peers, nc * sizeof(struct DB_SYNC_PEER));
|
||||
if (!np) return NULL;
|
||||
db->peers = np; db->peer_capacity = nc;
|
||||
}
|
||||
p = &db->peers[db->peer_count++];
|
||||
p->node_id = node_id; p->synced = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
// ---- Sync protocol: INIT_SYNC (A→B) ----
|
||||
static void db_handle_init_sync(struct DB_SYNC* db, uint64_t src_node_id, const uint8_t* payload, size_t len) {
|
||||
if (len < 36) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: INIT_SYNC too short %zu from %016llx", len, (unsigned long long)src_node_id); return; }
|
||||
uint32_t peer_count = *(uint32_t*)payload;
|
||||
const uint8_t* peer_last_ch = payload + 4;
|
||||
|
||||
uint32_t my_count = db_count(db);
|
||||
uint32_t test_pos = (peer_count < my_count ? peer_count : my_count);
|
||||
if (test_pos > 0) test_pos--;
|
||||
|
||||
// Send INIT_RESP
|
||||
uint8_t resp[4096];
|
||||
uint32_t resp_off = 0;
|
||||
resp[resp_off++] = DB_MSG_INIT_RESP;
|
||||
memcpy(resp+resp_off, &test_pos, 4); resp_off += 4;
|
||||
|
||||
uint8_t my_ch[32];
|
||||
if (my_count == 0) {
|
||||
memset(my_ch, 0, 32);
|
||||
} else if (db_chain_hash_at(db, test_pos, my_ch) != 0) {
|
||||
memset(my_ch, 0, 32);
|
||||
}
|
||||
memcpy(resp+resp_off, my_ch, 32); resp_off += 32;
|
||||
|
||||
// Always include sparse_count at position 37 (after test_pos + chain_hash)
|
||||
// For complete case: sparse_count=0, no sparse entries follow
|
||||
// For sparse case: sparse entries start at position 38
|
||||
|
||||
// Check if chain_hash matches
|
||||
if (memcmp(my_ch, peer_last_ch, 32) == 0) {
|
||||
resp[resp_off++] = 0; // sparse_count = 0
|
||||
db_sync_send(db, src_node_id, resp, resp_off);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: INIT_RESP complete to %016llx test_pos=%u", (unsigned long long)src_node_id, test_pos);
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip sparse_count slot for now, write it at the end
|
||||
uint32_t sparse_count_pos = resp_off;
|
||||
resp_off++; // placeholder for sparse_count
|
||||
|
||||
// Build sparse hashes: chain_hash at positions test_pos-1, -2, -4, -8, -16, ...
|
||||
int sparse_count = 0;
|
||||
for (uint32_t k = 0; k < 16; k++) {
|
||||
uint32_t step = (uint32_t)(1u << k); // 1, 2, 4, 8, ...
|
||||
if (test_pos < step) break;
|
||||
uint32_t pos = test_pos - step;
|
||||
if (db_chain_hash_at(db, pos, my_ch) != 0) break;
|
||||
if (resp_off + 36 > sizeof(resp)) break;
|
||||
memcpy(resp+resp_off, &pos, 4); resp_off += 4;
|
||||
memcpy(resp+resp_off, my_ch, 32); resp_off += 32;
|
||||
sparse_count++;
|
||||
}
|
||||
resp[sparse_count_pos] = (uint8_t)sparse_count;
|
||||
|
||||
db_sync_send(db, src_node_id, resp, resp_off);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: INIT_RESP to %016llx test_pos=%u sparse=%d", (unsigned long long)src_node_id, test_pos, sparse_count);
|
||||
}
|
||||
|
||||
// ---- Sync protocol: INIT_RESP (B→A) ----
|
||||
static void db_handle_init_resp(struct DB_SYNC* db, uint64_t src_node_id, const uint8_t* payload, size_t len) {
|
||||
if (len < 37) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: INIT_RESP too short %zu from %016llx", len, (unsigned long long)src_node_id); return; }
|
||||
uint32_t test_pos = *(uint32_t*)payload;
|
||||
const uint8_t* peer_ch = payload + 4;
|
||||
uint8_t sparse_count = payload[36];
|
||||
|
||||
uint8_t my_ch[32];
|
||||
if (db_chain_hash_at(db, test_pos, my_ch) != 0) { memset(my_ch, 0, 32); }
|
||||
|
||||
// Check if complete
|
||||
if (sparse_count == 0 && memcmp(my_ch, peer_ch, 32) == 0) {
|
||||
struct DB_SYNC_PEER* p = db_peer_find(db, src_node_id);
|
||||
if (p) p->synced = 2;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: sync complete with %016llx", (unsigned long long)src_node_id);
|
||||
return;
|
||||
}
|
||||
|
||||
// If peer has empty table (chain_hash is all zeros), send everything
|
||||
{
|
||||
uint8_t zero[32]; memset(zero, 0, 32);
|
||||
if (memcmp(peer_ch, zero, 32) == 0 && sparse_count == 0) {
|
||||
uint32_t my_count = db_count(db);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: peer %016llx has empty table, sending all %u records", (unsigned long long)src_node_id, my_count);
|
||||
// Send all records in batches
|
||||
uint32_t sent = 0;
|
||||
while (sent < my_count) {
|
||||
uint32_t batch = my_count - sent;
|
||||
if (batch > DB_SEND_DATA_MAX) batch = DB_SEND_DATA_MAX;
|
||||
uint8_t sdbuf[8192]; uint32_t off=0;
|
||||
sdbuf[off++] = DB_MSG_SEND_DATA; memcpy(sdbuf+off, &sent, 4); off+=4;
|
||||
uint16_t* rcp = (uint16_t*)(sdbuf+off); off+=2; uint16_t rc=0;
|
||||
MDB_txn* txn; int rrc = mdb_txn_begin(db->env, NULL, MDB_RDONLY, &txn);
|
||||
if (rrc==0) { MDB_cursor* cursor; rrc=mdb_cursor_open(txn, db->dbi_records, &cursor);
|
||||
if (rrc==0) { MDB_val key,val; rrc=mdb_cursor_get(cursor, &key, &val, MDB_FIRST);
|
||||
uint32_t cur=0; while (rrc==0 && cur < sent) { rrc=mdb_cursor_get(cursor,&key,&val,MDB_NEXT); cur++; }
|
||||
while (rrc==0 && rc < batch && off+val.mv_size+16 < 8000) {
|
||||
uint64_t ts = be64toh(*(uint64_t*)key.mv_data); uint64_t dh = be64toh(*(uint64_t*)((uint8_t*)key.mv_data+8));
|
||||
uint64_t rid = *(uint64_t*)((uint8_t*)val.mv_data+DB_VAL_OFF_ID); uint32_t dlen = *(uint32_t*)((uint8_t*)val.mv_data+DB_VAL_OFF_DATA_LEN);
|
||||
memcpy(sdbuf+off, &rid,8); off+=8; memcpy(sdbuf+off, &ts,8); off+=8; memcpy(sdbuf+off, &dh,8); off+=8;
|
||||
memcpy(sdbuf+off, &dlen,4); off+=4; if (dlen>0) { memcpy(sdbuf+off, (uint8_t*)val.mv_data+DB_VAL_OFF_DATA, dlen); off+=dlen; }
|
||||
rc++; cur++; rrc=mdb_cursor_get(cursor, &key, &val, MDB_NEXT); }
|
||||
mdb_cursor_close(cursor); } mdb_txn_abort(txn); }
|
||||
*rcp = rc; sent += rc;
|
||||
db_sync_send(db, src_node_id, sdbuf, off);
|
||||
if (rc == 0) break; // no more records
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (memcmp(my_ch, peer_ch, 32) == 0) {
|
||||
struct DB_SYNC_PEER* p = db_peer_find(db, src_node_id);
|
||||
if (p) p->synced = 2;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: chain_hash match with %016llx at pos %u — synced", (unsigned long long)src_node_id, test_pos);
|
||||
// Send our extra records if any (beyond test_pos)
|
||||
uint32_t my_count = db_count(db);
|
||||
if (my_count > test_pos + 1) {
|
||||
// We have more records — send them
|
||||
uint32_t from = test_pos + 1;
|
||||
uint32_t remaining = my_count - from;
|
||||
uint32_t batch = remaining < DB_SEND_DATA_MAX ? remaining : DB_SEND_DATA_MAX;
|
||||
// Continue in SEND_DATA handler...
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Find divergence range using sparse hashes
|
||||
uint32_t div_start = 0, div_end = test_pos;
|
||||
const uint8_t* sparse = payload + 37;
|
||||
for (int i = 0; i < sparse_count && sparse + 36 <= payload + len; i++) {
|
||||
uint32_t pos = *(uint32_t*)sparse;
|
||||
const uint8_t* peer_sparse_ch = sparse + 4;
|
||||
uint8_t my_sparse_ch[32];
|
||||
if (db_chain_hash_at(db, pos, my_sparse_ch) == 0) {
|
||||
if (memcmp(my_sparse_ch, peer_sparse_ch, 32) == 0) { if (pos + 1 > div_start) div_start = pos + 1; }
|
||||
else { if (pos < div_end) div_end = pos; }
|
||||
}
|
||||
sparse += 36;
|
||||
}
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: divergence with %016llx range [%u, %u]", (unsigned long long)src_node_id, div_start, div_end);
|
||||
|
||||
if (div_end - div_start <= 1) {
|
||||
// Divergence is small — request data directly via REFINE round
|
||||
uint8_t ref[512]; uint32_t ref_off = 0;
|
||||
ref[ref_off++] = DB_MSG_REFINE;
|
||||
memcpy(ref+ref_off, &div_start, 4); ref_off += 4;
|
||||
memcpy(ref+ref_off, &div_end, 4); ref_off += 4;
|
||||
uint8_t hcount = 0; ref[ref_off++] = hcount; // no hashes, just requesting data
|
||||
db_sync_send(db, src_node_id, ref, ref_off);
|
||||
} else {
|
||||
// Send REFINE with our hashes
|
||||
uint8_t ref[512]; uint32_t ref_off = 0;
|
||||
ref[ref_off++] = DB_MSG_REFINE;
|
||||
memcpy(ref+ref_off, &div_start, 4); ref_off += 4;
|
||||
memcpy(ref+ref_off, &div_end, 4); ref_off += 4;
|
||||
uint32_t range = div_end - div_start;
|
||||
uint8_t count = range < DB_REFINE_HASHES ? (uint8_t)range : DB_REFINE_HASHES;
|
||||
ref[ref_off++] = count;
|
||||
for (uint8_t i = 0; i < count; i++) {
|
||||
uint32_t pos = div_start + (range * i / count);
|
||||
uint64_t dh;
|
||||
if (db_datahash_at(db, pos, &dh) == 0) {
|
||||
memcpy(ref+ref_off, &pos, 4); ref_off += 4;
|
||||
memcpy(ref+ref_off, &dh, 8); ref_off += 8;
|
||||
}
|
||||
}
|
||||
db_sync_send(db, src_node_id, ref, ref_off);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Sync protocol: REFINE ----
|
||||
static void db_handle_refine(struct DB_SYNC* db, uint64_t src_node_id, const uint8_t* payload, size_t len) {
|
||||
if (len < 9) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "db_sync: REFINE too short %zu", len); return; }
|
||||
uint32_t from = *(uint32_t*)payload;
|
||||
uint32_t to = *(uint32_t*)(payload+4);
|
||||
uint8_t hcount = payload[8];
|
||||
|
||||
if (hcount == 0) {
|
||||
// Peer requests data in range [from, to]
|
||||
uint32_t my_count = db_count(db);
|
||||
uint32_t send_from = from;
|
||||
uint32_t send_count = (to - from + 1) < DB_SEND_DATA_MAX ? (to - from + 1) : DB_SEND_DATA_MAX;
|
||||
if (send_from >= my_count) return; // nothing to send
|
||||
|
||||
// Build SEND_DATA
|
||||
uint8_t* buf = u_malloc(8192);
|
||||
if (!buf) return;
|
||||
uint32_t off = 0; buf[off++] = DB_MSG_SEND_DATA;
|
||||
memcpy(buf+off, &send_from, 4); off += 4;
|
||||
uint16_t rec_count = 0;
|
||||
uint16_t* cnt_ptr = (uint16_t*)(buf+off); off += 2;
|
||||
|
||||
MDB_txn* txn; int rc = mdb_txn_begin(db->env, NULL, MDB_RDONLY, &txn);
|
||||
if (rc == 0) {
|
||||
MDB_cursor* cursor; rc = mdb_cursor_open(txn, db->dbi_records, &cursor);
|
||||
if (rc == 0) {
|
||||
MDB_val key, val; rc = mdb_cursor_get(cursor, &key, &val, MDB_FIRST);
|
||||
uint32_t cur = 0;
|
||||
while (rc == 0 && cur < send_from) { rc = mdb_cursor_get(cursor, &key, &val, MDB_NEXT); cur++; }
|
||||
while (rc == 0 && rec_count < send_count && off + val.mv_size + 16 < 8000) {
|
||||
uint64_t ts = be64toh(*(uint64_t*)key.mv_data);
|
||||
uint64_t dh = be64toh(*(uint64_t*)((uint8_t*)key.mv_data + 8));
|
||||
uint64_t rid = *(uint64_t*)((uint8_t*)val.mv_data + DB_VAL_OFF_ID);
|
||||
uint32_t dlen = *(uint32_t*)((uint8_t*)val.mv_data + DB_VAL_OFF_DATA_LEN);
|
||||
const uint8_t* dptr = (uint8_t*)val.mv_data + DB_VAL_OFF_DATA;
|
||||
memcpy(buf+off, &rid, 8); off += 8;
|
||||
memcpy(buf+off, &ts, 8); off += 8;
|
||||
memcpy(buf+off, &dh, 8); off += 8;
|
||||
memcpy(buf+off, &dlen, 4); off += 4;
|
||||
if (dlen > 0) { memcpy(buf+off, dptr, dlen); off += dlen; }
|
||||
rec_count++;
|
||||
rc = mdb_cursor_get(cursor, &key, &val, MDB_NEXT);
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
}
|
||||
mdb_txn_abort(txn);
|
||||
}
|
||||
*cnt_ptr = rec_count;
|
||||
db_sync_send(db, src_node_id, buf, off);
|
||||
u_free(buf);
|
||||
return;
|
||||
}
|
||||
|
||||
// Peer sent hashes at positions — find mismatch
|
||||
uint32_t first_mismatch = to + 1;
|
||||
const uint8_t* hp = payload + 9;
|
||||
for (uint8_t i = 0; i < hcount && hp + 12 <= payload + len; i++) {
|
||||
uint32_t pos = *(uint32_t*)hp;
|
||||
uint64_t peer_dh = *(uint64_t*)(hp + 4);
|
||||
uint64_t my_dh;
|
||||
if (db_datahash_at(db, pos, &my_dh) == 0 && my_dh == peer_dh) {
|
||||
if (pos >= from) { if (pos + 1 < first_mismatch) first_mismatch = pos + 1; }
|
||||
} else {
|
||||
if (pos < first_mismatch) first_mismatch = pos;
|
||||
}
|
||||
hp += 12;
|
||||
}
|
||||
|
||||
// Send SEND_DATA from first_mismatch
|
||||
uint32_t my_count = db_count(db);
|
||||
uint32_t send_count = 4;
|
||||
if (first_mismatch > to || first_mismatch >= my_count) { send_count = 0; }
|
||||
|
||||
uint8_t sdbuf[8192]; uint32_t off = 0;
|
||||
sdbuf[off++] = DB_MSG_SEND_DATA;
|
||||
memcpy(sdbuf+off, &first_mismatch, 4); off += 4;
|
||||
uint16_t rc_count = 0;
|
||||
uint16_t* rcp = (uint16_t*)(sdbuf+off); off += 2;
|
||||
|
||||
if (send_count > 0) {
|
||||
MDB_txn* txn; int rrc = mdb_txn_begin(db->env, NULL, MDB_RDONLY, &txn);
|
||||
if (rrc == 0) {
|
||||
MDB_cursor* cursor; rrc = mdb_cursor_open(txn, db->dbi_records, &cursor);
|
||||
if (rrc == 0) {
|
||||
MDB_val key, val; rrc = mdb_cursor_get(cursor, &key, &val, MDB_FIRST);
|
||||
uint32_t cur = 0;
|
||||
while (rrc == 0 && cur < first_mismatch) { rrc = mdb_cursor_get(cursor, &key, &val, MDB_NEXT); cur++; }
|
||||
while (rrc == 0 && rc_count < send_count && off + val.mv_size + 16 < 8000) {
|
||||
uint64_t ts = be64toh(*(uint64_t*)key.mv_data);
|
||||
uint64_t dh = be64toh(*(uint64_t*)((uint8_t*)key.mv_data + 8));
|
||||
uint64_t rid = *(uint64_t*)((uint8_t*)val.mv_data + DB_VAL_OFF_ID);
|
||||
uint32_t dlen = *(uint32_t*)((uint8_t*)val.mv_data + DB_VAL_OFF_DATA_LEN);
|
||||
const uint8_t* dptr = (uint8_t*)val.mv_data + DB_VAL_OFF_DATA;
|
||||
memcpy(sdbuf+off, &rid, 8); off += 8;
|
||||
memcpy(sdbuf+off, &ts, 8); off += 8;
|
||||
memcpy(sdbuf+off, &dh, 8); off += 8;
|
||||
memcpy(sdbuf+off, &dlen, 4); off += 4;
|
||||
if (dlen > 0) { memcpy(sdbuf+off, dptr, dlen); off += dlen; }
|
||||
rc_count++; rrc = mdb_cursor_get(cursor, &key, &val, MDB_NEXT);
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
}
|
||||
mdb_txn_abort(txn);
|
||||
}
|
||||
}
|
||||
*rcp = rc_count;
|
||||
db_sync_send(db, src_node_id, sdbuf, off);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: SEND_DATA to %016llx from=%u count=%u", (unsigned long long)src_node_id, first_mismatch, rc_count);
|
||||
}
|
||||
|
||||
// ---- Sync protocol: SEND_DATA ----
|
||||
static void db_handle_send_data(struct DB_SYNC* db, uint64_t src_node_id, const uint8_t* payload, size_t len) {
|
||||
if (len < 6) return;
|
||||
uint32_t from = *(uint32_t*)payload;
|
||||
uint16_t count = *(uint16_t*)(payload+4);
|
||||
const uint8_t* ptr = payload + 6;
|
||||
|
||||
for (uint16_t i = 0; i < count; i++) {
|
||||
if (ptr + 28 > payload + len) break;
|
||||
uint64_t rid = *(uint64_t*)ptr; ptr += 8;
|
||||
uint64_t ts = *(uint64_t*)ptr; ptr += 8;
|
||||
uint64_t dh = *(uint64_t*)ptr; ptr += 8;
|
||||
uint32_t dlen = *(uint32_t*)ptr; ptr += 4;
|
||||
if (ptr + dlen > payload + len) break;
|
||||
db_record_insert(db, rid, ts, dh, (const char*)ptr, dlen);
|
||||
ptr += dlen;
|
||||
}
|
||||
|
||||
// Send our extra records too (bidirectional)
|
||||
uint32_t my_count = db_count(db);
|
||||
struct DB_SYNC_PEER* p = db_peer_find(db, src_node_id);
|
||||
uint32_t peer_known = from + count;
|
||||
if (my_count > peer_known && p && p->synced == 1) {
|
||||
// Send our records beyond what peer knows
|
||||
uint32_t send_count = my_count - peer_known;
|
||||
if (send_count > DB_SEND_DATA_MAX) send_count = DB_SEND_DATA_MAX;
|
||||
uint8_t sdbuf[8192]; uint32_t off=0;
|
||||
sdbuf[off++] = DB_MSG_SEND_DATA; memcpy(sdbuf+off, &peer_known, 4); off+=4;
|
||||
uint16_t* rcp = (uint16_t*)(sdbuf+off); off+=2;
|
||||
uint16_t rc=0;
|
||||
MDB_txn* txn; int rrc = mdb_txn_begin(db->env, NULL, MDB_RDONLY, &txn);
|
||||
if (rrc==0) {
|
||||
MDB_cursor* cursor; rrc = mdb_cursor_open(txn, db->dbi_records, &cursor);
|
||||
if (rrc==0) {
|
||||
MDB_val key,val; rrc = mdb_cursor_get(cursor, &key, &val, MDB_FIRST);
|
||||
uint32_t cur=0;
|
||||
while (rrc==0 && cur < peer_known) { rrc=mdb_cursor_get(cursor,&key,&val,MDB_NEXT); cur++; }
|
||||
while (rrc==0 && rc<send_count && off+val.mv_size+16 < 8000) {
|
||||
uint64_t rts = be64toh(*(uint64_t*)key.mv_data);
|
||||
uint64_t rdh = be64toh(*(uint64_t*)((uint8_t*)key.mv_data+8));
|
||||
uint64_t rrid = *(uint64_t*)((uint8_t*)val.mv_data+DB_VAL_OFF_ID);
|
||||
uint32_t rdlen = *(uint32_t*)((uint8_t*)val.mv_data+DB_VAL_OFF_DATA_LEN);
|
||||
const uint8_t* rdptr = (uint8_t*)val.mv_data+DB_VAL_OFF_DATA;
|
||||
memcpy(sdbuf+off, &rrid,8); off+=8; memcpy(sdbuf+off, &rts,8); off+=8;
|
||||
memcpy(sdbuf+off, &rdh,8); off+=8; memcpy(sdbuf+off, &rdlen,4); off+=4;
|
||||
if (rdlen>0) { memcpy(sdbuf+off, rdptr, rdlen); off+=rdlen; }
|
||||
rc++; rrc=mdb_cursor_get(cursor, &key, &val, MDB_NEXT);
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
}
|
||||
mdb_txn_abort(txn);
|
||||
}
|
||||
*rcp = rc;
|
||||
db_sync_send(db, src_node_id, sdbuf, off);
|
||||
}
|
||||
|
||||
// Verify sync complete
|
||||
uint32_t new_count = db_count(db);
|
||||
uint8_t my_last_ch[32];
|
||||
if (new_count > 0 && db_chain_hash_at(db, new_count - 1, my_last_ch) == 0) {
|
||||
uint8_t sd[37]; sd[0] = DB_MSG_SYNC_DONE;
|
||||
memcpy(sd+1, &new_count, 4); memcpy(sd+5, my_last_ch, 32);
|
||||
db_sync_send(db, src_node_id, sd, 37);
|
||||
}
|
||||
if (p) p->synced = 2;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: received %u records from %016llx", count, (unsigned long long)src_node_id);
|
||||
}
|
||||
|
||||
// ---- Sync protocol: SYNC_DONE ----
|
||||
static void db_handle_sync_done(struct DB_SYNC* db, uint64_t src_node_id, const uint8_t* payload, size_t len) {
|
||||
if (len < 36) return;
|
||||
uint32_t peer_count = *(uint32_t*)payload;
|
||||
const uint8_t* peer_ch = payload + 4;
|
||||
uint32_t my_count = db_count(db);
|
||||
uint8_t my_ch[32];
|
||||
if (my_count > 0) db_chain_hash_at(db, my_count - 1, my_ch);
|
||||
else memset(my_ch, 0, 32);
|
||||
if (my_count != peer_count || memcmp(my_ch, peer_ch, 32) != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DB_SYNC, "db_sync: SYNC_DONE mismatch with %016llx my=%u peer=%u — re-syncing",
|
||||
(unsigned long long)src_node_id, my_count, peer_count);
|
||||
db_sync_initiate_sync(db, src_node_id);
|
||||
return;
|
||||
}
|
||||
struct DB_SYNC_PEER* p = db_peer_find(db, src_node_id);
|
||||
if (p) p->synced = 2;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: sync confirmed with %016llx count=%u", (unsigned long long)src_node_id, my_count);
|
||||
}
|
||||
|
||||
// ---- Sync protocol: ACK_PUSH ----
|
||||
static void db_handle_ack_push(struct DB_SYNC* db, uint64_t src_node_id, const uint8_t* payload, size_t len) {
|
||||
(void)src_node_id;
|
||||
if (len < 16) return;
|
||||
uint64_t dh = *(uint64_t*)payload;
|
||||
uint64_t ts = *(uint64_t*)(payload + 8);
|
||||
uint8_t key[16]; db_key_build(key, ts, dh);
|
||||
MDB_txn* txn; int rc = mdb_txn_begin(db->env, NULL, 0, &txn);
|
||||
if (rc) return;
|
||||
MDB_val k, val; k.mv_size = 16; k.mv_data = key;
|
||||
rc = mdb_get(txn, db->dbi_records, &k, &val);
|
||||
if (rc == 0 && val.mv_size >= DB_VAL_HDR_SIZE) {
|
||||
uint64_t creator = *(uint64_t*)((uint8_t*)val.mv_data + DB_VAL_OFF_CREATOR);
|
||||
uint8_t flags = *(uint8_t*)((uint8_t*)val.mv_data + DB_VAL_OFF_FLAGS);
|
||||
if (creator == db->inst->node_id && !(flags & DB_REC_FLAG_WAS_SENT)) {
|
||||
flags |= DB_REC_FLAG_WAS_SENT;
|
||||
*(uint8_t*)((uint8_t*)val.mv_data + DB_VAL_OFF_FLAGS) = flags;
|
||||
mdb_put(txn, db->dbi_records, &k, &val, 0);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: ACK_PUSH mark sent dh=%016llx ts=%llu from %016llx", (unsigned long long)dh, (unsigned long long)ts, (unsigned long long)src_node_id);
|
||||
}
|
||||
}
|
||||
mdb_txn_commit(txn);
|
||||
}
|
||||
|
||||
// ---- Sync protocol: PUSH ----
|
||||
static void db_handle_push(struct DB_SYNC* db, uint64_t src_node_id, const uint8_t* payload, size_t len) {
|
||||
if (len < 28) return;
|
||||
const uint8_t* ptr = payload;
|
||||
uint64_t rid = *(uint64_t*)ptr; ptr += 8;
|
||||
uint64_t ts = *(uint64_t*)ptr; ptr += 8;
|
||||
uint64_t dh = *(uint64_t*)ptr; ptr += 8;
|
||||
uint32_t dlen = *(uint32_t*)ptr; ptr += 4;
|
||||
if (ptr + dlen > payload + len) return;
|
||||
int ret = db_record_insert(db, rid, ts, dh, (const char*)ptr, dlen);
|
||||
if (ret == 0) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: PUSH inserted from %016llx id=%llu dh=%016llx", (unsigned long long)src_node_id, (unsigned long long)rid, (unsigned long long)dh);
|
||||
uint8_t ack[17]; ack[0] = DB_MSG_ACK_PUSH; memcpy(ack+1, &dh, 8); memcpy(ack+9, &ts, 8);
|
||||
db_sync_send(db, src_node_id, ack, 17);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Main receive callback ----
|
||||
static void db_sync_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
if (!entry || entry->len < 2) { if (entry) { queue_dgram_free(entry); queue_entry_free(entry); } return; }
|
||||
struct DB_SYNC* db = conn ? conn->instance->db_sync : NULL;
|
||||
if (!db || !db->enabled) { queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
|
||||
uint64_t src_node_id = conn ? conn->peer_node_id : 0;
|
||||
|
||||
uint8_t type = entry->dgram[1];
|
||||
const uint8_t* payload = entry->dgram + 2;
|
||||
size_t plen = entry->len - 2;
|
||||
|
||||
switch (type) {
|
||||
case DB_MSG_INIT_SYNC: db_handle_init_sync(db, src_node_id, payload, plen); break;
|
||||
case DB_MSG_INIT_RESP: db_handle_init_resp(db, src_node_id, payload, plen); break;
|
||||
case DB_MSG_REFINE: db_handle_refine(db, src_node_id, payload, plen); break;
|
||||
case DB_MSG_SEND_DATA: db_handle_send_data(db, src_node_id, payload, plen); break;
|
||||
case DB_MSG_PUSH: db_handle_push(db, src_node_id, payload, plen); break;
|
||||
case DB_MSG_ACK_PUSH: db_handle_ack_push(db, src_node_id, payload, plen); break;
|
||||
case DB_MSG_SYNC_DONE: db_handle_sync_done(db, src_node_id, payload, plen); break;
|
||||
default: DEBUG_WARN(DEBUG_CATEGORY_DB_SYNC, "db_sync: unknown msg type 0x%02x from %016llx", type, (unsigned long long)src_node_id); break;
|
||||
}
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
}
|
||||
|
||||
// ---- Connection callbacks ----
|
||||
static void db_sync_on_new_conn(struct ETCP_CONN* conn, void* arg) {
|
||||
(void)arg;
|
||||
if (!conn || !conn->instance || !conn->instance->db_sync) return;
|
||||
etcp_conn_add_up_cbk(conn, db_sync_on_conn_up, NULL);
|
||||
etcp_conn_add_down_cbk(conn, db_sync_on_conn_down, NULL);
|
||||
}
|
||||
|
||||
static void db_sync_on_conn_up(struct ETCP_CONN* conn, void* arg) {
|
||||
(void)arg;
|
||||
if (!conn || !conn->instance || !conn->instance->db_sync) return;
|
||||
struct DB_SYNC* db = conn->instance->db_sync;
|
||||
uint64_t peer_id = conn->peer_node_id;
|
||||
if (peer_id == 0 || peer_id == db->inst->node_id) return;
|
||||
db->last_connected_tb = get_time_tb();
|
||||
struct DB_SYNC_PEER* p = db_peer_add(db, peer_id);
|
||||
if (p && p->synced == 0) {
|
||||
p->synced = 1;
|
||||
db_sync_initiate_sync(db, peer_id);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: peer up %016llx", (unsigned long long)peer_id);
|
||||
}
|
||||
|
||||
static void db_sync_on_conn_down(struct ETCP_CONN* conn, void* arg) {
|
||||
(void)arg;
|
||||
if (!conn || !conn->instance || !conn->instance->db_sync) return;
|
||||
struct DB_SYNC* db = conn->instance->db_sync;
|
||||
uint64_t peer_id = conn->peer_node_id;
|
||||
if (peer_id == 0) return;
|
||||
struct DB_SYNC_PEER* p = db_peer_find(db, peer_id);
|
||||
if (p) p->synced = 0;
|
||||
int any_up = 0;
|
||||
for (int i = 0; i < db->peer_count; i++) { if (db->peers[i].synced >= 1) { any_up = 1; break; } }
|
||||
if (!any_up) db->last_connected_tb = 0;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: peer down %016llx", (unsigned long long)peer_id);
|
||||
}
|
||||
|
||||
// ---- Initiate sync ----
|
||||
static void db_sync_initiate_sync(struct DB_SYNC* db, uint64_t peer_node_id) {
|
||||
uint32_t my_count = db_count(db);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: initiate_sync to %016llx my_count=%u", (unsigned long long)peer_node_id, my_count);
|
||||
uint8_t msg[37]; msg[0] = DB_MSG_INIT_SYNC;
|
||||
memcpy(msg+1, &my_count, 4);
|
||||
if (my_count > 0) {
|
||||
if (db_chain_hash_at(db, my_count-1, msg+5) != 0) memset(msg+5, 0, 32);
|
||||
} else {
|
||||
memset(msg+5, 0, 32);
|
||||
}
|
||||
db_sync_send(db, peer_node_id, msg, 37);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: INIT_SYNC → %016llx my_count=%u", (unsigned long long)peer_node_id, my_count);
|
||||
}
|
||||
|
||||
// ---- Periodic timers ----
|
||||
static void db_sync_peer_check_cb(void* arg) {
|
||||
struct DB_SYNC* db = (struct DB_SYNC*)arg;
|
||||
if (!db || !db->enabled) return;
|
||||
struct TOPO_GROUP* group = topo_groups_get_default(db->inst->topo_groups);
|
||||
if (!group) { db->peer_check_timer = uasync_set_timeout(db->inst->ua, DB_SYNC_PEER_CHECK_INTERVAL * 10000u, db, db_sync_peer_check_cb, "db_sync_peer"); return; }
|
||||
|
||||
// Check all known peers with active connections
|
||||
struct ll_entry* e = group->senders_list->head;
|
||||
while (e) {
|
||||
struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)e->data;
|
||||
if (item->conn && item->conn->peer_node_id != 0 && item->conn->links_up) {
|
||||
uint64_t pid = item->conn->peer_node_id;
|
||||
if (pid != db->inst->node_id) {
|
||||
struct DB_SYNC_PEER* p = db_peer_add(db, pid);
|
||||
if (p && p->synced == 0) { p->synced = 1; db_sync_initiate_sync(db, pid); }
|
||||
}
|
||||
}
|
||||
e = e->next;
|
||||
}
|
||||
db->peer_check_timer = uasync_set_timeout(db->inst->ua, DB_SYNC_PEER_CHECK_INTERVAL * 10000u, db, db_sync_peer_check_cb, "db_sync_peer");
|
||||
}
|
||||
|
||||
static void db_sync_ttl_cleanup_cb(void* arg) {
|
||||
struct DB_SYNC* db = (struct DB_SYNC*)arg;
|
||||
if (!db || !db->enabled || !db->env) { if (db) db->ttl_timer = uasync_set_timeout(db->inst->ua, DB_SYNC_TTL_INTERVAL * 10000u, db, db_sync_ttl_cleanup_cb, "db_sync_ttl"); return; }
|
||||
uint64_t my_id = db->inst->node_id;
|
||||
uint64_t now_us = get_time_us();
|
||||
uint64_t ttl_us = (uint64_t)(db->inst->config->global.db_sync_ttl) * 1000000uLL;
|
||||
uint64_t cutoff = now_us - ttl_us;
|
||||
|
||||
MDB_txn* txn; int rc = mdb_txn_begin(db->env, NULL, 0, &txn);
|
||||
if (rc != 0) { db->ttl_timer = uasync_set_timeout(db->inst->ua, DB_SYNC_TTL_INTERVAL * 10000u, db, db_sync_ttl_cleanup_cb, "db_sync_ttl"); return; }
|
||||
|
||||
MDB_cursor* cursor; rc = mdb_cursor_open(txn, db->dbi_records, &cursor);
|
||||
if (rc == 0) {
|
||||
MDB_val key, val; rc = mdb_cursor_get(cursor, &key, &val, MDB_FIRST);
|
||||
while (rc == 0) {
|
||||
if (val.mv_size >= DB_VAL_HDR_SIZE) {
|
||||
uint64_t creator = *(uint64_t*)((uint8_t*)val.mv_data + DB_VAL_OFF_CREATOR);
|
||||
uint8_t flags = *(uint8_t*)((uint8_t*)val.mv_data + DB_VAL_OFF_FLAGS);
|
||||
uint64_t ts = be64toh(*(uint64_t*)key.mv_data);
|
||||
if (creator == my_id && !(flags & DB_REC_FLAG_WAS_SENT) && ts < cutoff) {
|
||||
mdb_cursor_del(cursor, 0);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: TTL deleted unsent record ts=%llu dh=%016llx", (unsigned long long)ts, (unsigned long long)be64toh(*(uint64_t*)((uint8_t*)key.mv_data+8)));
|
||||
}
|
||||
}
|
||||
rc = mdb_cursor_get(cursor, &key, &val, MDB_NEXT);
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
}
|
||||
mdb_txn_commit(txn);
|
||||
db->ttl_timer = uasync_set_timeout(db->inst->ua, DB_SYNC_TTL_INTERVAL * 10000u, db, db_sync_ttl_cleanup_cb, "db_sync_ttl");
|
||||
}
|
||||
|
||||
// ---- Public API ----
|
||||
int db_sync_init(struct UTUN_INSTANCE* inst) {
|
||||
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "NULL instance"); return -1; }
|
||||
struct DB_SYNC* db = u_calloc(1, sizeof(struct DB_SYNC));
|
||||
if (!db) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "u_calloc failed"); return -1; }
|
||||
db->inst = inst; db->last_connected_tb = 0; db->last_timestamp_us = 0;
|
||||
if (!inst->config->global.db_sync_enabled) {
|
||||
db->enabled = 0; inst->db_sync = db;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: disabled by config");
|
||||
return 0;
|
||||
}
|
||||
db->enabled = 1;
|
||||
inst->db_sync = db;
|
||||
|
||||
// Open LMDB
|
||||
const char* db_path = inst->config->global.db_path;
|
||||
char sync_path[512];
|
||||
if (db_path[0]) {
|
||||
snprintf(sync_path, sizeof(sync_path), "%s/sync", db_path);
|
||||
} else {
|
||||
snprintf(sync_path, sizeof(sync_path), "/tmp/utun_db_sync");
|
||||
}
|
||||
if (db_lmdb_open(db, sync_path) != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DB_SYNC, "LMDB open failed, sync disabled");
|
||||
db->enabled = 0;
|
||||
}
|
||||
|
||||
// Register in etcp_router
|
||||
etcp_router_bind(inst, ETCP_RT_ID_DB_SYNC, db_sync_recv_cb);
|
||||
|
||||
// Subscribe to ETCP connection events
|
||||
etcp_add_new_conn_cbk(inst, db_sync_on_new_conn, NULL);
|
||||
|
||||
// For existing connections: add up/down callbacks
|
||||
{
|
||||
struct ETCP_CONN* conn = inst->connections;
|
||||
while (conn) {
|
||||
etcp_conn_add_up_cbk(conn, db_sync_on_conn_up, NULL);
|
||||
etcp_conn_add_down_cbk(conn, db_sync_on_conn_down, NULL);
|
||||
conn = conn->next;
|
||||
}
|
||||
}
|
||||
|
||||
// Start periodic timers
|
||||
db->peer_check_timer = uasync_set_timeout(inst->ua, DB_SYNC_PEER_CHECK_INTERVAL * 10000u, db, db_sync_peer_check_cb, "db_sync_peer");
|
||||
db->ttl_timer = uasync_set_timeout(inst->ua, DB_SYNC_TTL_INTERVAL * 10000u, db, db_sync_ttl_cleanup_cb, "db_sync_ttl");
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: initialized (enabled=%d)", db->enabled);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void db_sync_destroy(struct UTUN_INSTANCE* inst) {
|
||||
if (!inst || !inst->db_sync) return;
|
||||
struct DB_SYNC* db = inst->db_sync;
|
||||
inst->db_sync = NULL;
|
||||
|
||||
etcp_router_unbind(inst, ETCP_RT_ID_DB_SYNC);
|
||||
|
||||
if (db->peer_check_timer) { uasync_cancel_timeout(inst->ua, db->peer_check_timer); db->peer_check_timer = NULL; }
|
||||
if (db->ttl_timer) { uasync_cancel_timeout(inst->ua, db->ttl_timer); db->ttl_timer = NULL; }
|
||||
|
||||
// Remove up/down callbacks from existing connections
|
||||
{ struct ETCP_CONN* conn = inst->connections; while (conn) { etcp_conn_remove_up_cbk(conn, db_sync_on_conn_up, NULL); etcp_conn_remove_down_cbk(conn, db_sync_on_conn_down, NULL); conn = conn->next; } }
|
||||
|
||||
db_lmdb_close(db);
|
||||
if (db->peers) u_free(db->peers);
|
||||
u_free(db);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: destroyed");
|
||||
}
|
||||
|
||||
int db_sync_insert_len(struct UTUN_INSTANCE* inst, const char* json_data, size_t len) {
|
||||
if (!inst || !inst->db_sync || !json_data || len == 0) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "invalid args"); return -1; }
|
||||
struct DB_SYNC* db = inst->db_sync;
|
||||
if (!db->enabled) return -1;
|
||||
|
||||
// Compute datahash and timestamp
|
||||
uint64_t datahash = db_datahash((const uint8_t*)json_data, len);
|
||||
uint64_t now_us = get_time_us();
|
||||
// Guarantee monotonicity
|
||||
if (now_us <= db->last_timestamp_us) now_us = db->last_timestamp_us + 1;
|
||||
db->last_timestamp_us = now_us;
|
||||
uint64_t timestamp = now_us;
|
||||
uint64_t id = db->next_id;
|
||||
|
||||
int ret = db_record_insert(db, id, timestamp, datahash, json_data, len);
|
||||
if (ret != 0) return ret; // 1 = duplicate, -1 = error
|
||||
|
||||
// Broadcast PUSH to all synced peers
|
||||
uint8_t pbuf[2048];
|
||||
uint32_t off = 0; pbuf[off++] = DB_MSG_PUSH;
|
||||
memcpy(pbuf+off, &id, 8); off += 8;
|
||||
memcpy(pbuf+off, ×tamp, 8); off += 8;
|
||||
memcpy(pbuf+off, &datahash, 8); off += 8;
|
||||
memcpy(pbuf+off, &len, 4); off += 4;
|
||||
if (len > 0 && off + len <= sizeof(pbuf)) { memcpy(pbuf+off, json_data, len); off += len; }
|
||||
for (int i = 0; i < db->peer_count; i++) if (db->peers[i].synced >= 1 && db->peers[i].node_id != inst->node_id) db_sync_send(db, db->peers[i].node_id, pbuf, off);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DB_SYNC, "db_sync: insert id=%llu dh=%016llx ts=%llu len=%zu", (unsigned long long)id, (unsigned long long)datahash, (unsigned long long)timestamp, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int db_sync_insert(struct UTUN_INSTANCE* inst, const char* json_data) {
|
||||
if (!json_data) return -1;
|
||||
return db_sync_insert_len(inst, json_data, strlen(json_data));
|
||||
}
|
||||
|
||||
uint32_t db_sync_count(struct UTUN_INSTANCE* inst) {
|
||||
if (!inst || !inst->db_sync) return 0;
|
||||
return db_count(inst->db_sync);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// db_sync.h — Distributed content-addressed table with LMDB storage and peer sync via etcp_router
|
||||
#ifndef DB_SYNC_H
|
||||
#define DB_SYNC_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
struct UTUN_INSTANCE;
|
||||
|
||||
// etcp_router service ID
|
||||
#define ETCP_RT_ID_DB_SYNC 0x20
|
||||
|
||||
// Message types
|
||||
#define DB_MSG_INIT_SYNC 0x01
|
||||
#define DB_MSG_INIT_RESP 0x02
|
||||
#define DB_MSG_REFINE 0x03
|
||||
#define DB_MSG_SEND_DATA 0x04
|
||||
#define DB_MSG_PUSH 0x05
|
||||
#define DB_MSG_ACK_PUSH 0x06
|
||||
#define DB_MSG_SYNC_DONE 0x07
|
||||
|
||||
// Defaults
|
||||
#define DB_SYNC_DEFAULT_TTL 86400
|
||||
#define DB_SYNC_DEFAULT_MAPSIZE (100UL * 1024 * 1024)
|
||||
#define DB_SYNC_PEER_CHECK_INTERVAL 5
|
||||
#define DB_SYNC_TTL_INTERVAL 3600
|
||||
|
||||
// Record flags
|
||||
#define DB_REC_FLAG_WAS_SENT 0x01
|
||||
|
||||
// Sync protocol constants
|
||||
#define DB_REFINE_HASHES 16
|
||||
#define DB_SEND_DATA_MAX 32
|
||||
|
||||
int db_sync_init(struct UTUN_INSTANCE* inst);
|
||||
void db_sync_destroy(struct UTUN_INSTANCE* inst);
|
||||
int db_sync_insert(struct UTUN_INSTANCE* inst, const char* json_data);
|
||||
int db_sync_insert_len(struct UTUN_INSTANCE* inst, const char* json_data, size_t len);
|
||||
uint32_t db_sync_count(struct UTUN_INSTANCE* inst);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // DB_SYNC_H
|
||||
Binary file not shown.
@@ -577,6 +577,11 @@ struct dummynet_filter {
|
||||
uint32_t jitter_ms;
|
||||
struct ETCP_LINK* link;
|
||||
struct dummynet_stats stats;
|
||||
struct {
|
||||
uint32_t ip;
|
||||
uint16_t port;
|
||||
} block_addrs[4];
|
||||
uint8_t block_count;
|
||||
};
|
||||
|
||||
static ssize_t dummynet_filter_send_hook(socket_t fd, const void* buf, size_t len,
|
||||
@@ -585,6 +590,16 @@ static ssize_t dummynet_filter_send_hook(socket_t fd, const void* buf, size_t le
|
||||
struct dummynet_filter* df = (struct dummynet_filter*)ctx;
|
||||
(void)link;
|
||||
df->stats.recv++;
|
||||
if (addr && addr->sa_family == AF_INET && df->block_count > 0) {
|
||||
const struct sockaddr_in* sin = (const struct sockaddr_in*)addr;
|
||||
for (uint8_t i = 0; i < df->block_count; i++) {
|
||||
if (sin->sin_addr.s_addr == df->block_addrs[i].ip &&
|
||||
sin->sin_port == df->block_addrs[i].port) {
|
||||
df->stats.lost++;
|
||||
return (ssize_t)len;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (df->loss_permille > 0) {
|
||||
uint32_t roll = (uint32_t)rand() % 1000;
|
||||
if (roll < df->loss_permille) {
|
||||
@@ -637,6 +652,26 @@ void dummynet_filter_destroy(struct dummynet_filter* df) {
|
||||
u_free(df);
|
||||
}
|
||||
|
||||
void dummynet_filter_block_addr(struct dummynet_filter* df, uint32_t ip, uint16_t port) {
|
||||
if (!df || df->block_count >= 4) return;
|
||||
for (uint8_t i = 0; i < df->block_count; i++)
|
||||
if (df->block_addrs[i].ip == ip && df->block_addrs[i].port == port) return;
|
||||
df->block_addrs[df->block_count].ip = ip;
|
||||
df->block_addrs[df->block_count].port = port;
|
||||
df->block_count++;
|
||||
}
|
||||
|
||||
void dummynet_filter_unblock_addr(struct dummynet_filter* df, uint32_t ip, uint16_t port) {
|
||||
if (!df) return;
|
||||
for (uint8_t i = 0; i < df->block_count; i++) {
|
||||
if (df->block_addrs[i].ip == ip && df->block_addrs[i].port == port) {
|
||||
df->block_count--;
|
||||
if (i < df->block_count) memmove(&df->block_addrs[i], &df->block_addrs[i+1], (df->block_count - i) * sizeof(df->block_addrs[0]));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const struct dummynet_stats* dummynet_filter_get_stats(struct dummynet_filter* df) {
|
||||
return df ? &df->stats : NULL;
|
||||
}
|
||||
@@ -21,6 +21,11 @@
|
||||
#ifndef DUMMYNET_H
|
||||
#define DUMMYNET_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
@@ -176,5 +181,11 @@ void dummynet_filter_attach(struct dummynet_filter* df, struct ETCP_LINK* link);
|
||||
void dummynet_filter_detach(struct dummynet_filter* df);
|
||||
void dummynet_filter_destroy(struct dummynet_filter* df);
|
||||
const struct dummynet_stats* dummynet_filter_get_stats(struct dummynet_filter* df);
|
||||
void dummynet_filter_block_addr(struct dummynet_filter* df, uint32_t ip, uint16_t port);
|
||||
void dummynet_filter_unblock_addr(struct dummynet_filter* df, uint32_t ip, uint16_t port);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // DUMMYNET_H
|
||||
@@ -1,6 +1,11 @@
|
||||
#ifndef EIM_NAT_H
|
||||
#define EIM_NAT_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
@@ -51,4 +56,8 @@ int eim_nat_add_forward(struct eim_nat_ctx* ctx, uint8_t proto,
|
||||
uint32_t internal_ip_host, uint16_t internal_port_net,
|
||||
uint16_t external_port);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
+287
-33
@@ -3,8 +3,9 @@
|
||||
#include "etcp.h"
|
||||
#include "etcp_debug.h"
|
||||
#include "etcp_loadbalancer.h"
|
||||
#include "etcp_router.h"
|
||||
#include "routing.h"
|
||||
#include "route_bgp.h"
|
||||
#include "topo_group.h"
|
||||
#include "route_ping.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
@@ -15,6 +16,7 @@
|
||||
#include <sys/time.h>
|
||||
#include <math.h> // For bandwidth calcs
|
||||
#include <limits.h> // For UINT16_MAX
|
||||
#include <time.h> // For strftime in metrics snapshot
|
||||
#include "../lib/mem.h"
|
||||
#include "../lib/memory_pool.h"
|
||||
|
||||
@@ -70,7 +72,7 @@ static int inflight_seq_cmp(const void* a, const void* b) {
|
||||
|
||||
static void feed_dgram_to_asm(struct ETCP_CONN* etcp, struct PKTNORM* pn,
|
||||
uint8_t* dgram, uint16_t dgram_len,
|
||||
uint8_t** asm_buf, uint16_t* asm_len, uint16_t* asm_cap,
|
||||
uint8_t** asm_buf, uint16_t* asm_len, uint32_t* asm_cap,
|
||||
uint32_t* returned) {
|
||||
uint32_t need = (uint32_t)*asm_len + dgram_len;
|
||||
if (need >= ASM_BUF_MAX_SIZE) {
|
||||
@@ -78,9 +80,10 @@ static void feed_dgram_to_asm(struct ETCP_CONN* etcp, struct PKTNORM* pn,
|
||||
return;
|
||||
}
|
||||
if (need > *asm_cap) {
|
||||
uint16_t new_cap = *asm_cap ? *asm_cap : 256;
|
||||
uint32_t new_cap = *asm_cap ? *asm_cap : 256;
|
||||
while (new_cap < need) new_cap *= 2;
|
||||
uint8_t* new_buf = u_realloc(*asm_buf, new_cap);
|
||||
if (new_cap > 0xFFFF) new_cap = 0xFFFF;
|
||||
uint8_t* new_buf = u_realloc(*asm_buf, (uint16_t)new_cap);
|
||||
if (!new_buf) return;
|
||||
*asm_buf = new_buf;
|
||||
*asm_cap = new_cap;
|
||||
@@ -93,7 +96,7 @@ static void feed_dgram_to_asm(struct ETCP_CONN* etcp, struct PKTNORM* pn,
|
||||
if (pkt_len == 0 || pkt_len > 16384 || *asm_len < 2 + pkt_len) break;
|
||||
|
||||
const uint8_t* pkt_data = *asm_buf + 2;
|
||||
if (pkt_data[0] != ETCP_ID_ROUTE_ENTRY) {
|
||||
if (pkt_data[0] != ETCP_ID_TOPO_ENTRY) {
|
||||
struct ll_entry* e = ll_alloc_lldgram(pkt_len);
|
||||
if (e) {
|
||||
memcpy(e->dgram, pkt_data, pkt_len);
|
||||
@@ -138,7 +141,7 @@ static void etcp_return_inflight_to_normalizer(struct ETCP_CONN* etcp) {
|
||||
|
||||
uint8_t* asm_buf = NULL;
|
||||
uint16_t asm_len = 0;
|
||||
uint16_t asm_cap = 0;
|
||||
uint32_t asm_cap = 0;
|
||||
uint32_t returned_packets = 0;
|
||||
|
||||
for (int i = 0; i < inflight_count; i++) {
|
||||
@@ -193,7 +196,7 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
|
||||
|
||||
struct ETCP_CONN* etcp = u_calloc(1, sizeof(struct ETCP_CONN));
|
||||
if (!etcp) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "etcp_connection_create: creating connection failed for instance %p", instance);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "creating connection failed for instance %p", instance);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -201,6 +204,7 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
|
||||
etcp->input_queue = queue_new(instance->ua, 0, 0, 0, "ETCP input"); // No hash for input_queue
|
||||
queue_set_threshold(etcp->input_queue, 0, 0); // Backpressure: ждать полного освобождения
|
||||
etcp->output_queue = queue_new(instance->ua, 0, 0, 0, "ETCP output"); // No hash for output_queue
|
||||
etcp->transit_queues = queue_new(instance->ua, TRANSIT_QUEUE_HASH, 0, 16, "transit_q_reg"); // Hash for transit queues
|
||||
etcp->input_send_q = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, 0, 4, "input_send_q"); // Hash for send_q
|
||||
etcp->input_wait_ack = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, 0, 4, "input_wait_ack"); // Hash for wait_ack
|
||||
etcp->recv_q = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, 0, 4, "recv_q"); // Hash for recv_q
|
||||
@@ -216,9 +220,10 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
|
||||
// Initialize log_name with local node_id (peer will be updated later when known)
|
||||
snprintf(etcp->log_name, sizeof(etcp->log_name), "%04X->???? [%s]", (uint16_t)instance->node_id, etcp->name);
|
||||
|
||||
if (!etcp->input_queue || !etcp->output_queue || !etcp->input_send_q || !etcp->recv_q || !etcp->ack_q ||
|
||||
if (!etcp->input_queue || !etcp->output_queue || !etcp->transit_queues ||
|
||||
!etcp->input_send_q || !etcp->recv_q || !etcp->ack_q ||
|
||||
!etcp->input_wait_ack || !etcp->inflight_pool || !etcp->io_pool) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "etcp_connection_create: error - closing - input:%p output:%p send:%p wait:%x pool:%p",
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "error - closing - input:%p output:%p send:%p wait:%x pool:%p",
|
||||
etcp->input_queue, etcp->output_queue, etcp->input_send_q, etcp->input_wait_ack, etcp->inflight_pool);
|
||||
etcp_connection_close(etcp);
|
||||
return NULL;
|
||||
@@ -239,6 +244,7 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
|
||||
etcp_connection_close(etcp);
|
||||
return NULL;
|
||||
}
|
||||
etcp->send_input_q = etcp->normalizer->input;
|
||||
|
||||
// Set input queue callback
|
||||
queue_set_callback(etcp->input_queue, input_queue_cb, etcp);
|
||||
@@ -252,8 +258,9 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
|
||||
etcp->log_name, etcp, etcp->mtu, etcp->next_tx_id);
|
||||
|
||||
// Вызываем callback для нового соединения если установлен
|
||||
if (instance && instance->etcp_new_conn_cbk) {
|
||||
instance->etcp_new_conn_cbk(etcp, instance->etcp_new_conn_arg);
|
||||
if (instance) {
|
||||
struct etcp_cbk_entry* cbe = instance->new_conn_cbks;
|
||||
while (cbe) { cbe->fn(etcp, cbe->arg); cbe = cbe->next; }
|
||||
}
|
||||
|
||||
return etcp;
|
||||
@@ -261,14 +268,15 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
|
||||
|
||||
|
||||
static void etcp_on_up(struct ETCP_CONN* etcp) {
|
||||
// if (!etcp->initialized) return;
|
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "[%s] initialized=%d", etcp->log_name, etcp->initialized);
|
||||
if (etcp->up_cbk) etcp->up_cbk(etcp, etcp->up_arg);
|
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "[%s] UP links_up=%d initialized=%d", etcp->log_name, etcp->links_up, etcp->initialized);
|
||||
struct etcp_cbk_entry* cbe = etcp->up_cbks;
|
||||
while (cbe) { cbe->fn(etcp, cbe->arg); cbe = cbe->next; }
|
||||
}
|
||||
|
||||
static void etcp_on_down(struct ETCP_CONN* etcp) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "[%s]", etcp->log_name);
|
||||
if (etcp->down_cbk) etcp->down_cbk(etcp, etcp->down_arg);
|
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "[%s] DOWN links_up=%d", etcp->log_name, etcp->links_up);
|
||||
struct etcp_cbk_entry* cbe = etcp->down_cbks;
|
||||
while (cbe) { cbe->fn(etcp, cbe->arg); cbe = cbe->next; }
|
||||
}
|
||||
|
||||
|
||||
@@ -291,12 +299,16 @@ void etcp_connection_close(struct ETCP_CONN* etcp) {
|
||||
uasync_cancel_timeout(etcp->instance->ua, etcp->ack_resp_timer);
|
||||
etcp->ack_resp_timer = NULL;
|
||||
}
|
||||
etcp_metrics_stop_timer(etcp);
|
||||
|
||||
routing_del_conn(etcp);
|
||||
|
||||
// NOTE: route_bgp_remove_conn is already called via down_cbk in etcp_on_down() above
|
||||
// NOTE: topo_bgp_remove_conn is already called via down_cbk in etcp_on_down() above
|
||||
// DO NOT call it again here to avoid double-processing and ref_count corruption
|
||||
|
||||
// Очистить транзитные очереди (до pn_deinit/stcp_link_close — waiter'ы на normalizer->input/tx_queue)
|
||||
etcp_router_transit_queues_destroy(etcp);
|
||||
|
||||
// Deinitialize packet normalizer (this will call routing_del_conn)
|
||||
if (etcp->normalizer) {
|
||||
pn_deinit((struct PKTNORM*)etcp->normalizer);
|
||||
@@ -324,6 +336,11 @@ void etcp_connection_close(struct ETCP_CONN* etcp) {
|
||||
drain_and_free_queue(&etcp->recv_q);
|
||||
drain_and_free_queue(&etcp->ack_q);
|
||||
|
||||
// Free callback chains
|
||||
{ struct etcp_cbk_entry* cbe = etcp->ready_cbks; while (cbe) { struct etcp_cbk_entry* n = cbe->next; u_free(cbe); cbe = n; } etcp->ready_cbks = NULL; }
|
||||
{ struct etcp_cbk_entry* cbe = etcp->up_cbks; while (cbe) { struct etcp_cbk_entry* n = cbe->next; u_free(cbe); cbe = n; } etcp->up_cbks = NULL; }
|
||||
{ struct etcp_cbk_entry* cbe = etcp->down_cbks; while (cbe) { struct etcp_cbk_entry* n = cbe->next; u_free(cbe); cbe = n; } etcp->down_cbks = NULL; }
|
||||
|
||||
// Free memory pools after all elements are returned
|
||||
if (etcp->inflight_pool) {
|
||||
memory_pool_destroy(etcp->inflight_pool);
|
||||
@@ -337,6 +354,14 @@ void etcp_connection_close(struct ETCP_CONN* etcp) {
|
||||
|
||||
u_free(etcp->name);
|
||||
|
||||
{ struct UTUN_INSTANCE* inst = etcp->instance;
|
||||
if (inst && inst->connections) {
|
||||
struct ETCP_CONN** pp = &inst->connections;
|
||||
while (*pp && *pp != etcp) pp = &(*pp)->next;
|
||||
if (*pp) { *pp = etcp->next; if (inst->connections_count) inst->connections_count--; }
|
||||
}
|
||||
}
|
||||
|
||||
// Clear next pointer to prevent dangling references
|
||||
etcp->next = NULL;
|
||||
|
||||
@@ -374,7 +399,10 @@ void etcp_conn_reset(struct ETCP_CONN* etcp) {
|
||||
etcp->last_rr_link = NULL;
|
||||
|
||||
// Return unconfirmed inflight data to normalizer input (до нормалайзера)
|
||||
if (etcp->normalizer) etcp_return_inflight_to_normalizer(etcp);
|
||||
if (etcp->normalizer) {
|
||||
etcp->normalizer->input->callback_suspended = 1;
|
||||
etcp_return_inflight_to_normalizer(etcp);
|
||||
}
|
||||
|
||||
// Clear queues (keep queue structures)
|
||||
clear_queue(etcp->input_queue);
|
||||
@@ -453,6 +481,11 @@ void etcp_conn_reset(struct ETCP_CONN* etcp) {
|
||||
}
|
||||
}
|
||||
|
||||
clear_queue(etcp->input_send_q);
|
||||
clear_queue(etcp->input_wait_ack);
|
||||
queue_resume_callback(etcp->input_send_q);
|
||||
queue_resume_callback(etcp->input_wait_ack);
|
||||
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "end");
|
||||
}
|
||||
|
||||
@@ -473,8 +506,8 @@ void etcp_conn_reinit(struct ETCP_CONN* etcp) {// Если сбой в обме
|
||||
etcp->initialized = 0;// еще раз придёт conn_ready_callback
|
||||
|
||||
// Отменяем висящие NAT-ping'и для этого соединения
|
||||
if (etcp->instance && etcp->instance->bgp) {
|
||||
route_ping_cancel_for_conn(etcp->instance->bgp, etcp);
|
||||
if (etcp->instance && etcp->instance->topo_groups) {
|
||||
route_ping_cancel_for_conn(topo_groups_get_default(etcp->instance->topo_groups), etcp);
|
||||
}
|
||||
|
||||
// Сбрасываем NAT-статус у всех линков этого соединения
|
||||
@@ -502,8 +535,10 @@ void etcp_conn_ready(struct ETCP_CONN* conn) {
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] Connection ready", conn->log_name);
|
||||
|
||||
etcp_metrics_start_timer(conn);
|
||||
|
||||
// Вызываем callback если установлен
|
||||
if (conn->ready_cbk) conn->ready_cbk(conn, conn->ready_arg);
|
||||
{ struct etcp_cbk_entry* cbe = conn->ready_cbks; while (cbe) { cbe->fn(conn, cbe->arg); cbe = cbe->next; } }
|
||||
if (conn->links_up) etcp_on_up(conn);
|
||||
}
|
||||
|
||||
@@ -717,7 +752,7 @@ static void input_queue_cb(struct ll_queue* q, void* arg) {
|
||||
}
|
||||
etcp->unacked_bytes += len;
|
||||
|
||||
// DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "input_queue_cb: successfully moved from input_queue to input_send_q");
|
||||
// DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "successfully moved from input_queue to input_send_q");
|
||||
|
||||
// etcp_conn_process_send_queue(etcp);// сразу обработаем этот пакет
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] nextloop, input_queue size=%d ", etcp->log_name, q->count);
|
||||
@@ -757,6 +792,7 @@ static void ack_timeout_check(struct ETCP_CONN* etcp) {
|
||||
|
||||
// Update stats
|
||||
etcp->retransmissions_count++;
|
||||
etcp_metrics_add_loss(etcp, 1);
|
||||
}
|
||||
else {// не надо до конца сканировать - они уже сортированы по таймстемпу т.к. очередь fifo, а timestamp = время добавления в очередь = время отправки
|
||||
// shedule timer
|
||||
@@ -864,7 +900,7 @@ static void etcp_conn_process_send_queue(struct ETCP_CONN* etcp) {// вызыв
|
||||
char l_status[256]={0};
|
||||
struct ETCP_LINK* link = etcp->links;
|
||||
while (link) {
|
||||
snprintf (l_status+strlen(l_status), 256-strlen(l_status), "L%d%d%s,%s ", link->recv_keepalive, link->remote_keepalive, (link->shaper_timer)?"wait":"rdy", link->send_blocked_inflight?"inf_block":"rdy");
|
||||
snprintf (l_status+strlen(l_status), 256-strlen(l_status), "L%d%d%s ", link->recv_keepalive, link->remote_keepalive, (link->shaper_timer)?"wait":"rdy");
|
||||
link = link->next;
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] TX state: %d (link not ready, skip send) %s", etcp->log_name, etcp->tx_state, l_status);
|
||||
@@ -934,8 +970,6 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
|
||||
bbr_note_loss(inf_pkt->last_link->bbr);
|
||||
inf_pkt->last_link->inflight_bytes -= inf_pkt->ll.len;
|
||||
inf_pkt->last_link->inflight_packets--;
|
||||
if (inf_pkt->last_link->send_blocked_inflight && inf_pkt->last_link->inflight_bytes < inf_pkt->last_link->inflight_lim_bytes)
|
||||
loadbalancer_link_ready(inf_pkt->last_link);
|
||||
}
|
||||
|
||||
// Always add to the CURRENT link (first send or retransmission)
|
||||
@@ -955,6 +989,7 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
|
||||
inf_pkt->state=INFLIGHT_STATE_WAIT_ACK;
|
||||
|
||||
queue_data_put_with_index(etcp->input_wait_ack, &inf_pkt->ll);// move dgram to wait_ack queue
|
||||
etcp_metrics_add_sent(etcp, inf_pkt->ll.len);
|
||||
}
|
||||
size_t ack_q_size = queue_entry_count(etcp->ack_q);
|
||||
|
||||
@@ -1095,7 +1130,7 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
|
||||
}
|
||||
}
|
||||
// фрейм data (0) обязательно в конец
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] TX DATA: seq=%u len=%u retry=%d; Qlen: in=%d snd=%d wait=%d", etcp->log_name, inf_pkt->seq, inf_pkt->ll.len, inf_pkt->send_count, etcp->input_queue->count, etcp->input_send_q->count, etcp->input_wait_ack->count);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] TX DATA: seq=%u len=%u retry=%d; Qlen: in=%d snd=%d wait=%d", etcp->log_name, inf_pkt->seq, inf_pkt->ll.len, inf_pkt->send_count, etcp->input_queue->count, etcp->input_send_q->count, etcp->input_wait_ack->count);
|
||||
|
||||
dgram->data[ptr++]=0;// payload
|
||||
dgram->data[ptr++]=inf_pkt->seq;
|
||||
@@ -1175,8 +1210,9 @@ void etcp_output_try_assembly(struct ETCP_CONN* etcp) {
|
||||
// Add to output_queue using the same ETCP_FRAGMENT structure
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] moving packet id=%u to output_queue (qlen=%d)", etcp->log_name,
|
||||
next_expected_id, etcp->output_queue->count);
|
||||
uint16_t pkt_len = rx_pkt->ll.len;
|
||||
if (queue_data_put(etcp->output_queue, (struct ll_entry*)rx_pkt) == 0) {
|
||||
delivered_bytes += rx_pkt->ll.len;
|
||||
delivered_bytes += pkt_len;
|
||||
delivered_count++;
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to add packet id=%u to output_queue", etcp->log_name,
|
||||
@@ -1220,7 +1256,7 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d
|
||||
|
||||
if (!acked_pkt) {
|
||||
// Packet might be already acknowledged or not found
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_ack_recv: packet seq=%u not found in wait_ack queue", seq);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "packet seq=%u not found in wait_ack queue", seq);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1299,7 +1335,7 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "");
|
||||
if (!pkt) return;
|
||||
if (memory_pool_is_freed(pkt->link->etcp->instance->pkt_pool, pkt)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_conn_input: pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt);
|
||||
volatile int _halt = 1; while (_halt) {}
|
||||
}
|
||||
if (!pkt->data_len) {
|
||||
@@ -1352,6 +1388,7 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
uint16_t ret_ts=data[1] | (data[2]<<8);// cur_ts=ret_ts = RTT
|
||||
uint16_t new_rtt=cur_ts-ret_ts;
|
||||
pkt->link->rtt_last=new_rtt;
|
||||
etcp_metrics_add_rtt(etcp, new_rtt);
|
||||
|
||||
int recv_dt_tx1=data[3] | (data[4]<<8);// localtime удаленной стороны момента принятия пакета - timestamp этого пакета (на стороне отправителя, т.е. у нас)
|
||||
|
||||
@@ -1397,7 +1434,7 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
// формируем ACK
|
||||
uint32_t seq=data[1] | (data[2]<<8) | (data[3]<<16) | (data[4]<<24);
|
||||
if (etcp->got_initial_pkt == 0) {
|
||||
if (seq==1) etcp->got_initial_pkt = 1;
|
||||
if (seq==1) { etcp->got_initial_pkt = 1; DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] Initial packet seq=1 received", etcp->log_name); }
|
||||
else {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] Waiting for initial packet but recv seq=%d, ignoring", etcp->log_name, seq);
|
||||
len=0;
|
||||
@@ -1471,6 +1508,7 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
memcpy(payload_data, data + 5, pkt_len);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX seq=%u need=%u asm_len=%d", etcp->log_name, seq, etcp->last_delivered_id+1, etcp->recv_q->count);
|
||||
queue_data_put_with_index(etcp->recv_q, (struct ll_entry*)rx_pkt);
|
||||
etcp_metrics_add_rcvd(etcp, pkt_len);
|
||||
if ((int32_t)(seq - etcp->last_delivered_id) == 1) etcp_output_try_assembly(etcp);// пробуем собрать выходную очередь из фрагментов
|
||||
} else {
|
||||
etcp->rx_dup_count++;
|
||||
@@ -1496,7 +1534,10 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
link->burst_recv_received = 0;
|
||||
}
|
||||
if (link->burst_recv_valid) {
|
||||
if (seq != link->burst_recv_next_seq) link->burst_recv_valid = 0;
|
||||
if (seq != link->burst_recv_next_seq) {
|
||||
if (seq > link->burst_recv_next_seq) etcp_metrics_add_loss(etcp, seq - link->burst_recv_next_seq);
|
||||
link->burst_recv_valid = 0;
|
||||
}
|
||||
if (seq < 16) {
|
||||
link->burst_recv_times[seq] = get_time_us();
|
||||
link->burst_recv_next_seq = seq + 1;
|
||||
@@ -1563,8 +1604,46 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
break;
|
||||
}
|
||||
|
||||
case ETCP_SECTION_METRICS: {
|
||||
if (len < 2 + SC_SIGN_SIZE) { len = 0; break; }
|
||||
uint16_t csv_len = data[1] | (data[2] << 8);
|
||||
if ((uint32_t)(2 + SC_SIGN_SIZE + csv_len) > len) { len = 0; break; }
|
||||
uint8_t* sig = data + 2;
|
||||
uint8_t* csv = data + 2 + SC_SIGN_SIZE;
|
||||
uint8_t* peer_pubkey = pkt->link->remote_ed25519_pubkey;
|
||||
struct sc_stream_sign_state verify_state;
|
||||
int sig_ok = (sc_stream_sign_verify_init(&verify_state, peer_pubkey) == SC_OK
|
||||
&& sc_stream_sign_update(&verify_state, csv, csv_len) == SC_OK
|
||||
&& sc_stream_sign_verify(&verify_state, sig, SC_SIGN_SIZE) == SC_OK);
|
||||
if (!sig_ok && verify_state.initialized) /* update failed, then verify didn't run */
|
||||
sc_stream_sign_cleanup(&verify_state);
|
||||
if (sig_ok) {
|
||||
const char* stats_dir = etcp->instance->stats_dir;
|
||||
if (stats_dir[0] && etcp->peer_node_id != 0) {
|
||||
char path[768];
|
||||
snprintf(path, sizeof(path), "%s/from_%016llx.dat", stats_dir, (unsigned long long)etcp->peer_node_id);
|
||||
FILE* f = fopen(path, "a");
|
||||
if (f) {
|
||||
fprintf(f, "%.*s,", csv_len, csv);
|
||||
for (int i = 0; i < SC_SIGN_SIZE; i++) fprintf(f, "%02x", sig[i]);
|
||||
fprintf(f, "\n");
|
||||
fclose(f);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] Received metrics from peer (%u bytes)", etcp->log_name, csv_len);
|
||||
if (etcp->instance->api_bindings.on_metrics_rcvd)
|
||||
etcp->instance->api_bindings.on_metrics_rcvd(etcp->instance->api_bindings.metrics_user_ptr, etcp, csv, csv_len, sig);
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] Cannot open from_metrics file: %s", etcp->log_name, path);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "[%s] Metrics signature verification FAILED", etcp->log_name);
|
||||
}
|
||||
data += 2 + SC_SIGN_SIZE + csv_len; len -= 2 + SC_SIGN_SIZE + csv_len;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_conn_input: unknown section type=0x%02x", type);
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "unknown section type=0x%02x", type);
|
||||
len=0;
|
||||
break;
|
||||
}
|
||||
@@ -1572,7 +1651,7 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
}
|
||||
|
||||
if (memory_pool_is_freed(pkt->link->etcp->instance->pkt_pool, pkt)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_conn_input: pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt);
|
||||
volatile int _halt = 1; while (_halt) {}
|
||||
}
|
||||
|
||||
@@ -1597,3 +1676,178 @@ void etcp_update_mtu(struct ETCP_CONN* etcp) {
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================== Метрики: гистограммы RTT/потерь + тотальные счётчики
|
||||
|
||||
static const uint16_t etcp_metrics_rtt_bounds[] = { 10, 20, 30, 50, 80, 130, 210, 340, 550, 900, 1500 };
|
||||
#define ETCP_METRICS_RTT_BOUNDS_COUNT (sizeof(etcp_metrics_rtt_bounds) / sizeof(etcp_metrics_rtt_bounds[0]))
|
||||
|
||||
void etcp_metrics_init(struct etcp_metrics* m) {
|
||||
if (!m) return;
|
||||
memset(m, 0, sizeof(*m));
|
||||
}
|
||||
|
||||
void etcp_metrics_add_rtt(struct ETCP_CONN* etcp, uint16_t rtt_tb) {
|
||||
if (!etcp) return;
|
||||
struct etcp_metrics* m = &etcp->metrics;
|
||||
int i;
|
||||
for (i = 0; i < (int)ETCP_METRICS_RTT_BOUNDS_COUNT; i++) {
|
||||
if (rtt_tb < etcp_metrics_rtt_bounds[i]) break;
|
||||
}
|
||||
if (i >= ETCP_METRICS_RTT_BUCKETS) i = ETCP_METRICS_RTT_BUCKETS - 1;
|
||||
m->rtt_hist[i]++;
|
||||
m->work_samples++;
|
||||
}
|
||||
|
||||
void etcp_metrics_add_sent(struct ETCP_CONN* etcp, uint32_t len) {
|
||||
if (!etcp) return;
|
||||
struct etcp_metrics* m = &etcp->metrics;
|
||||
m->work_sent++;
|
||||
m->work_bytes_sent += len;
|
||||
m->total_sent++;
|
||||
m->total_bytes_sent += len;
|
||||
}
|
||||
|
||||
void etcp_metrics_add_rcvd(struct ETCP_CONN* etcp, uint32_t len) {
|
||||
if (!etcp) return;
|
||||
struct etcp_metrics* m = &etcp->metrics;
|
||||
m->work_rcvd++;
|
||||
m->work_bytes_rcvd += len;
|
||||
m->total_rcvd++;
|
||||
m->total_bytes_rcvd += len;
|
||||
}
|
||||
|
||||
void etcp_metrics_add_loss(struct ETCP_CONN* etcp, uint32_t count) {
|
||||
if (!etcp) return;
|
||||
struct etcp_metrics* m = &etcp->metrics;
|
||||
m->work_lost += count;
|
||||
m->total_lost += count;
|
||||
}
|
||||
|
||||
static void etcp_metrics_do_snapshot(struct ETCP_CONN* etcp) {
|
||||
if (!etcp || !etcp->instance) return;
|
||||
struct etcp_metrics* m = &etcp->metrics;
|
||||
|
||||
// Копируем working → final
|
||||
memcpy(m->rtt_hist_final, m->rtt_hist, sizeof(m->rtt_hist_final));
|
||||
memcpy(m->loss_hist_final, m->loss_hist, sizeof(m->loss_hist_final));
|
||||
m->samples_final = m->work_samples;
|
||||
m->sent_final = m->work_sent;
|
||||
m->rcvd_final = m->work_rcvd;
|
||||
m->bytes_sent_final = m->work_bytes_sent;
|
||||
m->bytes_rcvd_final = m->work_bytes_rcvd;
|
||||
m->lost_final = m->work_lost;
|
||||
|
||||
// Рассчитываем loss-гистограмму
|
||||
uint32_t total = m->work_sent + m->work_lost;
|
||||
if (total > 0) {
|
||||
uint32_t loss_pct = (m->work_lost * 100 + total / 2) / total;
|
||||
int bucket = (int)loss_pct;
|
||||
if (bucket >= ETCP_METRICS_LOSS_BUCKETS) bucket = ETCP_METRICS_LOSS_BUCKETS - 1;
|
||||
m->loss_hist[bucket]++;
|
||||
}
|
||||
|
||||
// Обнуляем рабочую копию
|
||||
memset(m->rtt_hist, 0, sizeof(m->rtt_hist));
|
||||
memset(m->loss_hist, 0, sizeof(m->loss_hist));
|
||||
m->work_samples = 0;
|
||||
m->work_sent = 0; m->work_rcvd = 0;
|
||||
m->work_bytes_sent = 0; m->work_bytes_rcvd = 0;
|
||||
m->work_lost = 0;
|
||||
|
||||
m->last_snapshot_tb = get_time_tb();
|
||||
|
||||
// Строим CSV строку
|
||||
char csv_buf[4096];
|
||||
int pos = 0;
|
||||
time_t now = time(NULL);
|
||||
struct tm* tm = localtime(&now);
|
||||
char date_str[16], time_str[16];
|
||||
if (tm) { strftime(date_str, sizeof(date_str), "%Y-%m-%d", tm); strftime(time_str, sizeof(time_str), "%H:%M:%S", tm); }
|
||||
else { strcpy(date_str, "----"); strcpy(time_str, "----"); }
|
||||
pos += snprintf(csv_buf + pos, sizeof(csv_buf) - pos, "%s,%s", date_str, time_str);
|
||||
for (int i = 0; i < ETCP_METRICS_RTT_BUCKETS; i++) pos += snprintf(csv_buf + pos, sizeof(csv_buf) - pos, ",%u", m->rtt_hist_final[i]);
|
||||
for (int i = 0; i < ETCP_METRICS_LOSS_BUCKETS; i++) pos += snprintf(csv_buf + pos, sizeof(csv_buf) - pos, ",%u", m->loss_hist_final[i]);
|
||||
pos += snprintf(csv_buf + pos, sizeof(csv_buf) - pos, ",%u,%u,%u,%u,%u,%u", m->samples_final, m->sent_final, m->rcvd_final, m->bytes_sent_final, m->bytes_rcvd_final, m->lost_final);
|
||||
pos += snprintf(csv_buf + pos, sizeof(csv_buf) - pos, ",%llu,%llu,%llu,%llu,%llu", (unsigned long long)m->total_sent, (unsigned long long)m->total_rcvd, (unsigned long long)m->total_bytes_sent, (unsigned long long)m->total_bytes_rcvd, (unsigned long long)m->total_lost);
|
||||
uint32_t csv_len = (uint32_t)pos;
|
||||
|
||||
// Запись локального файла
|
||||
const char* stats_dir = etcp->instance->stats_dir;
|
||||
if (stats_dir[0] && etcp->peer_node_id != 0) {
|
||||
char path[768];
|
||||
snprintf(path, sizeof(path), "%s/%016llx.dat", stats_dir, (unsigned long long)etcp->peer_node_id);
|
||||
FILE* f = fopen(path, "a");
|
||||
if (f) { fprintf(f, "%s\n", csv_buf); fclose(f); }
|
||||
else DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] Cannot open metrics file: %s", etcp->log_name, path);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] Metrics snapshot: samples=%u sent=%u rcvd=%u lost=%u", etcp->log_name, m->samples_final, m->sent_final, m->rcvd_final, m->lost_final);
|
||||
|
||||
// Подпись Ed25519 и отправка пиру
|
||||
if (etcp->crypto_ctx.initialized && etcp->initialized) {
|
||||
struct sc_stream_sign_state sign_state;
|
||||
if (sc_stream_sign_init(&etcp->crypto_ctx, &sign_state) == SC_OK) {
|
||||
uint8_t sig[SC_SIGN_SIZE];
|
||||
size_t sig_len = sizeof(sig);
|
||||
if (sc_stream_sign_update(&sign_state, (uint8_t*)csv_buf, csv_len) == SC_OK
|
||||
&& sc_stream_sign_final(&sign_state, sig, &sig_len) == SC_OK) {
|
||||
// Построить и отправить дграмму с секцией METRICS
|
||||
struct ETCP_LINK* link = etcp_loadbalancer_select_link(etcp);
|
||||
if (link) {
|
||||
struct ETCP_DGRAM* dgram = memory_pool_alloc(etcp->instance->pkt_pool);
|
||||
if (dgram) {
|
||||
dgram->link = link;
|
||||
dgram->noencrypt_len = 0;
|
||||
dgram->timestamp = get_current_timestamp();
|
||||
int ptr = 0;
|
||||
// Минимальная ACK-секция
|
||||
dgram->data[ptr++] = 1; dgram->data[ptr++] = 0;
|
||||
dgram->data[ptr++] = etcp->last_delivered_id; dgram->data[ptr++] = etcp->last_delivered_id>>8;
|
||||
dgram->data[ptr++] = etcp->last_delivered_id>>16; dgram->data[ptr++] = etcp->last_delivered_id>>24;
|
||||
dgram->data[ptr++] = etcp->rx_dup_count; dgram->data[ptr++] = etcp->rx_dup_count>>8;
|
||||
// METRICS секция
|
||||
dgram->data[ptr++] = ETCP_SECTION_METRICS;
|
||||
uint16_t csv16 = (uint16_t)csv_len;
|
||||
dgram->data[ptr++] = csv16 & 0xFF; dgram->data[ptr++] = (csv16 >> 8) & 0xFF;
|
||||
memcpy(dgram->data + ptr, sig, SC_SIGN_SIZE); ptr += SC_SIGN_SIZE;
|
||||
memcpy(dgram->data + ptr, csv_buf, csv_len); ptr += csv_len;
|
||||
dgram->data_len = ptr;
|
||||
etcp_encrypt_send(dgram);
|
||||
memory_pool_free(etcp->instance->pkt_pool, dgram);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] Metrics sent to peer (%u bytes)", etcp->log_name, csv_len);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sc_stream_sign_cleanup(&sign_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void metrics_snapshot_timer_cb(void* arg) {
|
||||
struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg;
|
||||
if (!etcp) return;
|
||||
etcp->metrics.timer = NULL;
|
||||
|
||||
if (etcp->metrics.work_sent + etcp->metrics.work_rcvd >= ETCP_METRICS_MIN_PACKETS)
|
||||
etcp_metrics_do_snapshot(etcp);
|
||||
|
||||
// Перезапускаем таймер
|
||||
etcp->metrics.timer = uasync_set_timeout(etcp->instance->ua, ETCP_METRICS_INTERVAL_TB, etcp, metrics_snapshot_timer_cb, "etcp_metrics");
|
||||
}
|
||||
|
||||
void etcp_metrics_start_timer(struct ETCP_CONN* etcp) {
|
||||
if (!etcp || etcp->metrics.timer) return;
|
||||
etcp_metrics_init(&etcp->metrics);
|
||||
etcp->metrics.timer = uasync_set_timeout(etcp->instance->ua, ETCP_METRICS_INTERVAL_TB, etcp, metrics_snapshot_timer_cb, "etcp_metrics");
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] Metrics timer started (interval=%ums)", etcp->log_name, ETCP_METRICS_INTERVAL_TB / 10);
|
||||
}
|
||||
|
||||
void etcp_metrics_stop_timer(struct ETCP_CONN* etcp) {
|
||||
if (!etcp) return;
|
||||
if (etcp->metrics.timer && etcp->instance) {
|
||||
uasync_cancel_timeout(etcp->instance->ua, etcp->metrics.timer);
|
||||
etcp->metrics.timer = NULL;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] Metrics timer stopped", etcp->log_name);
|
||||
}
|
||||
}
|
||||
|
||||
+63
-9
@@ -13,6 +13,7 @@ extern "C" {
|
||||
|
||||
#include "pkt_normalizer.h"
|
||||
|
||||
struct stcp_link; // forward declaration
|
||||
// In struct ETCP_CONN, add:
|
||||
//struct pn_pair* normalizer;
|
||||
|
||||
@@ -20,6 +21,11 @@ extern "C" {
|
||||
struct UTUN_INSTANCE;
|
||||
struct ETCP_CONN;
|
||||
typedef void (*etcp_on_conn_ready)(struct ETCP_CONN* conn, void* arg);
|
||||
struct etcp_cbk_entry {
|
||||
etcp_on_conn_ready fn;
|
||||
void* arg;
|
||||
struct etcp_cbk_entry* next;
|
||||
};
|
||||
struct UASYNC;
|
||||
|
||||
uint16_t get_current_timestamp(void);
|
||||
@@ -31,6 +37,7 @@ uint16_t get_current_timestamp(void);
|
||||
#define ETCP_SECTION_MEAS_TS 0x07 // Measurement timestamp for bandwidth (burst packet)
|
||||
#define ETCP_SECTION_MEAS_RESP 0x08 // Measurement response (burst result)
|
||||
#define ETCP_SECTION_FILLER 0x09 // Filler/dummy data (discarded by receiver)
|
||||
#define ETCP_SECTION_METRICS 0x0A // Metrics CSV exchange (ed25519 signed)
|
||||
|
||||
// Burst measurement constants
|
||||
#define BURST_PACKET_COUNT 12 // число пакетов в burst
|
||||
@@ -54,6 +61,41 @@ uint16_t get_current_timestamp(void);
|
||||
|
||||
// MEAS_RESP valid
|
||||
#define MEAS_RESP_VALID 1
|
||||
// Метрики: гистограммы RTT и потерь с 10-мин снимками
|
||||
#define ETCP_METRICS_RTT_BUCKETS 12 // 0-1,1-2,2-3,3-5,5-8,8-13,13-21,21-34,34-55,55-90,90-150,150+ ms
|
||||
#define ETCP_METRICS_LOSS_BUCKETS 11 // 0%,1%,...,10%+
|
||||
#define ETCP_METRICS_INTERVAL_TB 6000000 // 10 мин в 0.1ms
|
||||
#define ETCP_METRICS_MIN_PACKETS 200 // мин пакетов для обновления гистограмм
|
||||
|
||||
struct etcp_metrics {
|
||||
uint32_t rtt_hist[ETCP_METRICS_RTT_BUCKETS]; // рабочая RTT-гистограмма
|
||||
uint32_t loss_hist[ETCP_METRICS_LOSS_BUCKETS]; // рабочая loss-гистограмма
|
||||
uint32_t work_samples; // RTT замеров в окне
|
||||
uint32_t work_sent; // пакетов отправлено в окне
|
||||
uint32_t work_rcvd; // пакетов принято в окне
|
||||
uint32_t work_bytes_sent; // байт отправлено в окне
|
||||
uint32_t work_bytes_rcvd; // байт принято в окне
|
||||
uint32_t work_lost; // потеряно в окне
|
||||
|
||||
uint32_t rtt_hist_final[ETCP_METRICS_RTT_BUCKETS]; // финальная RTT-гистограмма
|
||||
uint32_t loss_hist_final[ETCP_METRICS_LOSS_BUCKETS]; // финальная loss-гистограмма
|
||||
uint32_t samples_final;
|
||||
uint32_t sent_final;
|
||||
uint32_t rcvd_final;
|
||||
uint32_t bytes_sent_final;
|
||||
uint32_t bytes_rcvd_final;
|
||||
uint32_t lost_final;
|
||||
|
||||
uint64_t total_sent;
|
||||
uint64_t total_rcvd;
|
||||
uint64_t total_bytes_sent;
|
||||
uint64_t total_bytes_rcvd;
|
||||
uint64_t total_lost;
|
||||
|
||||
void* timer;
|
||||
uint64_t last_snapshot_tb;
|
||||
};
|
||||
|
||||
#define INFLIGHT_STATE_WAIT_ACK 0
|
||||
#define INFLIGHT_STATE_WAIT_SEND 1
|
||||
#define INFLIGHT_INITIAL_HASH_SIZE 1024
|
||||
@@ -104,6 +146,7 @@ struct ETCP_CONN {
|
||||
// Links (channels) - linked list
|
||||
struct ETCP_LINK* links;
|
||||
struct ETCP_LINK* last_rr_link; // последний линк, выбранный round-robin
|
||||
struct stcp_link *transport_link; // non-NULL for TCP connections
|
||||
|
||||
// Crypto and state
|
||||
struct secure_channel crypto_ctx;
|
||||
@@ -111,6 +154,7 @@ struct ETCP_CONN {
|
||||
|
||||
// Peer info
|
||||
uint64_t peer_node_id; // Peer node ID
|
||||
uint8_t peer_ed25519_pubkey[SC_PUBKEY_SIZE]; // Ed25519 pubkey пира (из INIT)
|
||||
|
||||
// ============ Processing incoming data to be sent by ETCP
|
||||
struct ll_queue* input_queue; // Incoming packets to send (rx_pool -> ETCP_FRAGMENT)
|
||||
@@ -128,10 +172,12 @@ struct ETCP_CONN {
|
||||
void (*link_ready_for_send_fn)(struct ETCP_CONN*);// функцию которую должен вызвать драйвер линка при готовности линка принимать данные
|
||||
|
||||
struct ll_queue* output_queue; // Assembled outgoing packets (storage: ETCP_FRAGMENT / rx_pool)
|
||||
struct ll_queue* transit_queues; // hash по (src_node_id:8, dst_node_id:8) — транзитные очереди
|
||||
struct ll_queue* send_input_q; // единая входная очередь отправки (normalizer->input или tx_queue)
|
||||
|
||||
|
||||
// IDs and state
|
||||
uint32_t next_tx_id; // Next TX ID
|
||||
uint32_t next_tx_id; // ID для добавления в очередь отправки (с этим id будет добавлен следующий пакет)
|
||||
uint32_t last_rx_id; // Last received ID
|
||||
uint32_t last_delivered_id; // Last delivered to output_queue
|
||||
|
||||
@@ -173,20 +219,18 @@ struct ETCP_CONN {
|
||||
// uint32_t total_packets_sent; // Total packets sent counter - Not used
|
||||
|
||||
// Flags
|
||||
uint8_t routing_exchange_active; // 0 - не активен, 1 - надо инициировать обмен маршрутами (клиент), 2 - обмен маршрутами активен
|
||||
uint8_t routing_exchange_active; // 0-не активен, 1-надо инициировать (клиент), 2-обмен идёт, 3-завершён, 4-пропущен (нет BGP)
|
||||
uint8_t got_initial_pkt; //
|
||||
uint8_t initialized; // 0 - только созданный ETCP, 1 - хотя бы один линк проинициалзирован (обмен ключами произведен)
|
||||
uint32_t session_id; // случайный ID сессии (генерируется клиентом) для защиты от ложного reinit
|
||||
uint8_t tx_state; // 0 - n/a, 1 - data_wait (queues empty), 2 - link_wait (link busy)
|
||||
uint8_t links_up; // 0 - канал не готов для передачи, 1 - канал готов для передачи (хотя бы один линк не down)
|
||||
|
||||
// Callback for ready notification
|
||||
etcp_on_conn_ready ready_cbk; // callback при готовности соединения
|
||||
void* ready_arg; // аргумент для ready_cbk
|
||||
etcp_on_conn_ready up_cbk; // callback при готовности соединения
|
||||
void* up_arg; // аргумент для ready_cbk
|
||||
etcp_on_conn_ready down_cbk; // callback при готовности соединения
|
||||
void* down_arg; // аргумент для ready_cbk
|
||||
// Callback chains for ready/up/down notifications
|
||||
struct etcp_cbk_entry* ready_cbks; // цепочка callback'ов при готовности соединения
|
||||
struct etcp_cbk_entry* up_cbks; // цепочка callback'ов при поднятии канала
|
||||
struct etcp_cbk_entry* down_cbks; // цепочка callback'ов при падении канала
|
||||
void (*bgp_ready_cbk)(struct ETCP_CONN* conn); // вызывается когда BGP готов (завершён или пропущен)
|
||||
|
||||
uint32_t cnt_ack_hit_inf; // счетчик удлений из inflight
|
||||
uint32_t cnt_ack_hit_sndq; // счетчик удалений inflight пакетов из sndq
|
||||
@@ -196,6 +240,8 @@ struct ETCP_CONN {
|
||||
uint32_t tx_dup_count; // счетчик дубликатов удаленной стороны (из ACK)
|
||||
uint32_t debug[8]; // 8 значений для дебага (live watch)
|
||||
|
||||
struct etcp_metrics metrics; // гистограммы RTT/потерь, тотальные счётчики
|
||||
|
||||
// Logging identifier (format: "XXXX→XXXX [name]" - last 4 digits of local and peer node_id + optional name)
|
||||
char log_name[256];
|
||||
char* name; // Connection name from config (e.g., "client_test1"), or NULL/empty
|
||||
@@ -243,6 +289,14 @@ void etcp_on_link_down(struct ETCP_CONN* etcp);
|
||||
|
||||
void etcp_update_mtu(struct ETCP_CONN* etcp);
|
||||
|
||||
void etcp_metrics_init(struct etcp_metrics* m);
|
||||
void etcp_metrics_add_rtt(struct ETCP_CONN* etcp, uint16_t rtt_tb);
|
||||
void etcp_metrics_add_sent(struct ETCP_CONN* etcp, uint32_t len);
|
||||
void etcp_metrics_add_rcvd(struct ETCP_CONN* etcp, uint32_t len);
|
||||
void etcp_metrics_add_loss(struct ETCP_CONN* etcp, uint32_t count);
|
||||
void etcp_metrics_start_timer(struct ETCP_CONN* etcp);
|
||||
void etcp_metrics_stop_timer(struct ETCP_CONN* etcp);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
+76
-162
@@ -1,197 +1,111 @@
|
||||
/**
|
||||
* @file etcp_api.c
|
||||
* @brief Реализация API для приёма-передачи пакетов через ETCP (per-instance bindings)
|
||||
* @brief ETCP API — send/bind/recv dispatch
|
||||
*/
|
||||
|
||||
#include "etcp_api.h"
|
||||
#include "etcp.h"
|
||||
#include "pkt_normalizer.h"
|
||||
#include "utun_instance.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
#include <string.h>
|
||||
#include "route_lib.h"
|
||||
#include "route_bgp.h"
|
||||
|
||||
#define DEBUG_CATEGORY_ETCP_API 1
|
||||
#define DEBUG_CATEGORY_ETCP_API DEBUG_CATEGORY_ETCP
|
||||
|
||||
#define ETCP_MAX_BINDINGS 256 /**< Максимальное количество bindings (по одному на каждый ID 0-255) */
|
||||
|
||||
// после создания подключения надо выждать conn_ready. только после этого можно передавать сообщения. Иначе сообщения могут потеряться.
|
||||
// conn_ready может вызваться несколько раз (после каждого переподключения - например когда удаленный узел перегрузился или возникла неустранимая ошибка).
|
||||
void etcp_conn_set_ready_cbk(struct ETCP_CONN* etcp, etcp_cbk_fn callback_fn, void* arg) {
|
||||
if (!etcp) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "NULL conn"); return; }
|
||||
etcp->ready_cbk = callback_fn;
|
||||
etcp->ready_arg = arg;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "conn=%s cbk=%p arg=%p", etcp->log_name, (void*)callback_fn, arg);
|
||||
void etcp_conn_set_ready_cbk(struct ETCP_CONN* e, etcp_cbk_fn fn, void* arg) {
|
||||
if (!e) return;
|
||||
struct etcp_cbk_entry* entry = e->ready_cbks;
|
||||
while (entry) { struct etcp_cbk_entry* next = entry->next; u_free(entry); entry = next; }
|
||||
e->ready_cbks = NULL;
|
||||
if (fn) etcp_conn_add_ready_cbk(e, fn, arg);
|
||||
}
|
||||
void etcp_conn_set_up_cbk(struct ETCP_CONN* e, etcp_cbk_fn fn, void* arg) {
|
||||
if (!e) return;
|
||||
struct etcp_cbk_entry* entry = e->up_cbks;
|
||||
while (entry) { struct etcp_cbk_entry* next = entry->next; u_free(entry); entry = next; }
|
||||
e->up_cbks = NULL;
|
||||
if (fn) etcp_conn_add_up_cbk(e, fn, arg);
|
||||
}
|
||||
void etcp_conn_set_down_cbk(struct ETCP_CONN* e, etcp_cbk_fn fn, void* arg) {
|
||||
if (!e) return;
|
||||
struct etcp_cbk_entry* entry = e->down_cbks;
|
||||
while (entry) { struct etcp_cbk_entry* next = entry->next; u_free(entry); entry = next; }
|
||||
e->down_cbks = NULL;
|
||||
if (fn) etcp_conn_add_down_cbk(e, fn, arg);
|
||||
}
|
||||
void etcp_set_new_conn_cbk(struct UTUN_INSTANCE* inst, etcp_cbk_fn fn, void* arg) {
|
||||
if (!inst) return;
|
||||
struct etcp_cbk_entry* entry = inst->new_conn_cbks;
|
||||
while (entry) { struct etcp_cbk_entry* next = entry->next; u_free(entry); entry = next; }
|
||||
inst->new_conn_cbks = NULL;
|
||||
if (fn) etcp_add_new_conn_cbk(inst, fn, arg);
|
||||
}
|
||||
|
||||
void etcp_conn_set_up_cbk(struct ETCP_CONN* etcp, etcp_cbk_fn callback_fn, void* arg) {
|
||||
if (!etcp) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "NULL conn"); return; }
|
||||
etcp->up_cbk = callback_fn;
|
||||
etcp->up_arg = arg;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "conn=%s cbk=%p arg=%p", etcp->log_name, (void*)callback_fn, arg);
|
||||
static void etcp_cbk_add_to_chain(struct etcp_cbk_entry** head, etcp_cbk_fn fn, void* arg) {
|
||||
if (!head || !fn) return;
|
||||
struct etcp_cbk_entry* e = u_malloc(sizeof(struct etcp_cbk_entry));
|
||||
if (!e) return;
|
||||
e->fn = fn; e->arg = arg; e->next = *head;
|
||||
*head = e;
|
||||
}
|
||||
|
||||
void etcp_conn_set_down_cbk(struct ETCP_CONN* etcp, etcp_cbk_fn callback_fn, void* arg) {
|
||||
if (!etcp) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "NULL conn"); return; }
|
||||
etcp->down_cbk = callback_fn;
|
||||
etcp->down_arg = arg;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "conn=%s cbk=%p arg=%p", etcp->log_name, (void*)callback_fn, arg);
|
||||
}
|
||||
|
||||
// Установить callback при создании нового ETCP соединения
|
||||
void etcp_set_new_conn_cbk(struct UTUN_INSTANCE* instance, etcp_cbk_fn callback_fn, void* arg) {
|
||||
if (!instance) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_set_new_conn_cbk: NULL instance");
|
||||
return;
|
||||
static void etcp_cbk_remove_from_chain(struct etcp_cbk_entry** head, etcp_cbk_fn fn, void* arg) {
|
||||
if (!head || !fn) return;
|
||||
struct etcp_cbk_entry** p = head;
|
||||
while (*p) {
|
||||
if ((*p)->fn == fn && (*p)->arg == arg) {
|
||||
struct etcp_cbk_entry* rm = *p;
|
||||
*p = rm->next; u_free(rm); return;
|
||||
}
|
||||
p = &(*p)->next;
|
||||
}
|
||||
instance->etcp_new_conn_cbk = callback_fn;
|
||||
instance->etcp_new_conn_arg = arg;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "etcp_set_new_conn_cbk: instance=%p cbk=%p arg=%p",
|
||||
(void*)instance, (void*)callback_fn, arg);
|
||||
}
|
||||
|
||||
void etcp_conn_add_ready_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg) { if (conn) etcp_cbk_add_to_chain(&conn->ready_cbks, fn, arg); }
|
||||
void etcp_conn_remove_ready_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg) { if (conn) etcp_cbk_remove_from_chain(&conn->ready_cbks, fn, arg); }
|
||||
void etcp_conn_add_up_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg) { if (conn) etcp_cbk_add_to_chain(&conn->up_cbks, fn, arg); }
|
||||
void etcp_conn_remove_up_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg) { if (conn) etcp_cbk_remove_from_chain(&conn->up_cbks, fn, arg); }
|
||||
void etcp_conn_add_down_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg) { if (conn) etcp_cbk_add_to_chain(&conn->down_cbks, fn, arg); }
|
||||
void etcp_conn_remove_down_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg) { if (conn) etcp_cbk_remove_from_chain(&conn->down_cbks, fn, arg); }
|
||||
void etcp_add_new_conn_cbk(struct UTUN_INSTANCE* inst, etcp_cbk_fn fn, void* arg) { if (inst) etcp_cbk_add_to_chain(&inst->new_conn_cbks, fn, arg); }
|
||||
void etcp_remove_new_conn_cbk(struct UTUN_INSTANCE* inst, etcp_cbk_fn fn, void* arg) { if (inst) etcp_cbk_remove_from_chain(&inst->new_conn_cbks, fn, arg); }
|
||||
|
||||
void etcp_set_routing_exchange_state(struct ETCP_CONN* conn, uint8_t new_state) {
|
||||
if (!conn) return;
|
||||
conn->routing_exchange_active = new_state;
|
||||
if (new_state >= 3 && conn->bgp_ready_cbk)
|
||||
conn->bgp_ready_cbk(conn);
|
||||
}
|
||||
|
||||
int etcp_bind(struct UTUN_INSTANCE* inst, uint8_t id, etcp_recv_fn callback) {
|
||||
if (!inst) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_bind: NULL instance");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((unsigned)id >= ETCP_MAX_BINDINGS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Invalid ID %u (max=%d)", id, ETCP_MAX_BINDINGS);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!callback) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_bind: NULL callback for id=%u", id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (inst->api_bindings.callbacks[id] != NULL) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Overwriting existing callback for id=%u", id);
|
||||
}
|
||||
if (!inst || !callback || (unsigned)id >= ETCP_MAX_BINDINGS) return -1;
|
||||
inst->api_bindings.callbacks[id] = callback;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Bound id=%u → callback=%p", id, (void*)callback);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int etcp_unbind(struct UTUN_INSTANCE* inst, uint8_t id) {
|
||||
if (!inst) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: NULL instance");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((unsigned)id >= ETCP_MAX_BINDINGS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: Invalid ID %u", id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (inst->api_bindings.callbacks[id] == NULL) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: ID %u not bound", id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!inst || (unsigned)id >= ETCP_MAX_BINDINGS) return -1;
|
||||
inst->api_bindings.callbacks[id] = NULL;
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: Unbound ID %u for instance %p", id, (void*)inst);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int etcp_send(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "etcp_send enter conn=%p entry=%p", conn, entry);
|
||||
if (!conn) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: NULL connection");
|
||||
return -1;
|
||||
}
|
||||
if (!entry) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: NULL entry");
|
||||
return -1;
|
||||
}
|
||||
if (!conn->normalizer) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: Connection has no normalizer");
|
||||
return -1;
|
||||
}
|
||||
if (conn->initialized==0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: Connection is not initialized");
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct PKTNORM* pn = conn->normalizer;
|
||||
if (!pn || !pn->input) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: pn=%p or input null", pn);
|
||||
return -1;
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_send: queued to [%s]", conn->log_name);
|
||||
int result = queue_data_put(pn->input, entry);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_NORMALIZER, "etcp_send after put result=%d", result);
|
||||
if (result != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_send: queue_data_put failed");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
if (!conn || !entry || !conn->send_input_q) return -1;
|
||||
return queue_data_put(conn->send_input_q, entry);
|
||||
}
|
||||
|
||||
void etcp_int_recv(struct ll_queue* queue, void* arg) {
|
||||
struct ETCP_CONN* conn = (struct ETCP_CONN*)arg;
|
||||
|
||||
if (!queue || !conn) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Invalid arguments (queue=%p, conn=%p)",
|
||||
(void*)queue, (void*)conn);
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем элемент из очереди
|
||||
struct ll_entry* entry = queue_data_get(queue);
|
||||
if (!entry) {
|
||||
// Очередь пуста - это нормально, просто возобновляем коллбэк
|
||||
queue_resume_callback(queue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем что есть данные
|
||||
if (!entry->dgram || entry->len == 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Empty packet received");
|
||||
queue_entry_free(entry);
|
||||
queue_dgram_free(entry);
|
||||
queue_resume_callback(queue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем ID пакета (первый байт кодограммы)
|
||||
uint8_t id = entry->dgram[0];
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Received packet with ID %u from conn [%s] %p",
|
||||
id, conn->log_name, (void*)conn);
|
||||
|
||||
// Получаем instance из соединения
|
||||
if (!queue || !conn) return;
|
||||
struct ll_entry* e = queue_data_get(queue);
|
||||
if (!e) { queue_resume_callback(queue); return; }
|
||||
if (!e->dgram || e->len == 0) { queue_dgram_free(e); queue_entry_free(e); queue_resume_callback(queue); return; }
|
||||
uint8_t id = e->dgram[0];
|
||||
struct UTUN_INSTANCE* inst = conn->instance;
|
||||
if (!inst) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Connection has no instance");
|
||||
queue_entry_free(entry);
|
||||
queue_dgram_free(entry);
|
||||
queue_resume_callback(queue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ищем binding для этого ID в instance
|
||||
if (inst->api_bindings.callbacks[id] != NULL) {
|
||||
// Вызываем коллбэк
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Delivering packet ID %u to callback %p",
|
||||
id, (void*)inst->api_bindings.callbacks[id]);
|
||||
inst->api_bindings.callbacks[id](conn, entry);
|
||||
} else {
|
||||
// Нет binding для этого ID - по умолчанию считаем что это данные (ID=0)
|
||||
if (inst->api_bindings.callbacks[0] != NULL) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_recv: No binding for ID %u, routing to default (ID=0)", id);
|
||||
inst->api_bindings.callbacks[0](conn, entry);
|
||||
} else {
|
||||
// Нет default binding - освобождаем пакет
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_recv: No binding for ID %u and no default handler, dropping packet", id);
|
||||
queue_entry_free(entry);
|
||||
queue_dgram_free(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Возобновляем коллбэк для получения следующего пакета
|
||||
if (!inst) { queue_dgram_free(e); queue_entry_free(e); queue_resume_callback(queue); return; }
|
||||
if (inst->api_bindings.callbacks[id])
|
||||
inst->api_bindings.callbacks[id](conn, e);
|
||||
else if (inst->api_bindings.callbacks[0])
|
||||
inst->api_bindings.callbacks[0](conn, e);
|
||||
else { queue_dgram_free(e); queue_entry_free(e); }
|
||||
queue_resume_callback(queue);
|
||||
}
|
||||
+60
-8
@@ -6,6 +6,8 @@
|
||||
* - etcp_send() - отправить пакет в очередь normalizer
|
||||
* - etcp_bind() - подписаться на пакеты с определенным ID
|
||||
* - etcp_int_recv() - коллбэк для сбора пакетов из всех подключений
|
||||
* !!! для обычной отправки-приёма между узлами используем более универсальный модушь etcp_router.
|
||||
* это в первую очередь - апи для использования etcp-router-ом
|
||||
*
|
||||
* Формат кодограмм: <cmd 1 byte> <data ... n bytes>
|
||||
* cmd = 0 - пакет для передачи адресату
|
||||
@@ -15,25 +17,47 @@
|
||||
#ifndef ETCP_API_H
|
||||
#define ETCP_API_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include "../lib/ll_queue.h"
|
||||
|
||||
#define ETCP_MAX_BINDINGS 256
|
||||
|
||||
// ETCP packet IDs
|
||||
// === Raw ETCP packet IDs (etcp_bind/etcp_send) ===
|
||||
#define ETCP_ID_DATA 0x00 // Пакет для передачи адресату
|
||||
#define ETCP_ID_ROUTE_ENTRY 0x01 // Элемент роутинг-таблицы
|
||||
#define ETCP_ID_NAT 0x02 // NAT трафик между узлами
|
||||
#define ETCP_ID_SVC_ROUTE 0x03 // Маршрутизируемые сервисные пакеты (etcp_router)
|
||||
#define ETCP_ID_TCP_PROXY 0x04 // TCP proxy exit node (server)
|
||||
#define ETCP_ID_UDP_PROXY 0x05 // UDP datagram прокси (client ↔ exit)
|
||||
#define ETCP_ID_ICMP_PROXY 0x06 // ICMP echo прокси (ping через exit)
|
||||
#define ETCP_ID_TCP_PROXY_CLIENT 0x07 // TCP proxy client (клиент)
|
||||
#define ETCP_ID_ROUTE_ENTRY 0x01 // Элемент роутинг-таблицы (BGP)
|
||||
#define ETCP_ID_SVC_ROUTE 0x03 // Транспорт роутера (etcp_router)
|
||||
|
||||
// === Router-сервисы (etcp_router_bind/etcp_route_send) ===
|
||||
#define ETCP_RT_ID_DATA 0x00 // routing.c — маршрутизация данных
|
||||
#define ETCP_RT_ID_NAT 0x02 // NAT трафик между узлами
|
||||
#define ETCP_RT_ID_SVC_ROUTE 0x03 // etcp_router — транспорт роутера
|
||||
#define ETCP_RT_ID_TCP_PROXY 0x04 // TCP proxy (клиент ↔ exit)
|
||||
#define ETCP_RT_ID_UDP_PROXY 0x05 // UDP datagram прокси (client ↔ exit)
|
||||
#define ETCP_RT_ID_ICMP_PROXY 0x06 // ICMP echo прокси (ping через exit)
|
||||
#define ETCP_RT_ID_MSG_TRANSPORT 0x10 // msg_transport — локальный IPC транспорт сообщений
|
||||
#define ETCP_RT_ID_CONN_MGR 0x11 // Connection Manager — management connections
|
||||
|
||||
// Forward declarations
|
||||
struct ETCP_CONN;
|
||||
struct UTUN_INSTANCE;
|
||||
|
||||
/**
|
||||
* @brief Тип коллбэка для приёма метрик от пира
|
||||
* @param user_ptr пользовательский указатель
|
||||
* @param conn ETCP соединение от которого получены метрики
|
||||
* @param csv CSV-строка метрик
|
||||
* @param csv_len длина строки
|
||||
* @param sig Ed25519 подпись (64 байта) или NULL если ключ неизвестен
|
||||
*/
|
||||
typedef void (*etcp_metrics_fn)(void* user_ptr, struct ETCP_CONN* conn,
|
||||
const uint8_t* csv, uint32_t csv_len,
|
||||
const uint8_t* sig);
|
||||
|
||||
/**
|
||||
* @brief Тип коллбэка для приёма пакетов
|
||||
* @param conn ETCP соединение от которого получен пакет
|
||||
@@ -47,8 +71,19 @@ typedef void (*etcp_recv_fn)(struct ETCP_CONN* conn, struct ll_entry* entry);
|
||||
// Forward declaration
|
||||
struct ETCP_CONN;
|
||||
struct UTUN_INSTANCE;
|
||||
struct TOPO_NODEQ;
|
||||
typedef void (*etcp_cbk_fn)(struct ETCP_CONN* conn, void* arg);
|
||||
|
||||
// ---- Background connection initialization ----
|
||||
#define ETCP_CONNECT_EARLY 1
|
||||
#define ETCP_CONNECT_LATE 2
|
||||
#define ETCP_CONNECT_BGP_READY 4 // BGP-синхронизация завершена
|
||||
|
||||
typedef void (*etcp_connect_callback_t)(void* arg, struct ETCP_CONN* conn, int type);
|
||||
|
||||
int etcp_connect(struct UTUN_INSTANCE* instance, struct TOPO_NODEQ* node,
|
||||
etcp_connect_callback_t cb, void* arg, uint8_t flags);
|
||||
|
||||
/**
|
||||
* @brief Установить callback при создании нового ETCP соединения
|
||||
*
|
||||
@@ -63,6 +98,8 @@ void etcp_set_new_conn_cbk(struct UTUN_INSTANCE* instance, etcp_cbk_fn callback_
|
||||
*/
|
||||
struct ETCP_BINDINGS {
|
||||
etcp_recv_fn callbacks[ETCP_MAX_BINDINGS]; /**< Массив callbacks, NULL = не установлен */
|
||||
etcp_metrics_fn on_metrics_rcvd; /**< Callback приёма метрик от пира */
|
||||
void* metrics_user_ptr; /**< Аргумент для on_metrics_rcvd */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -114,6 +151,17 @@ void etcp_conn_set_ready_cbk(struct ETCP_CONN* conn, etcp_cbk_fn callback_fn, vo
|
||||
void etcp_conn_set_up_cbk (struct ETCP_CONN* conn, etcp_cbk_fn callback_fn, void* arg);
|
||||
void etcp_conn_set_down_cbk (struct ETCP_CONN* conn, etcp_cbk_fn callback_fn, void* arg);
|
||||
|
||||
void etcp_conn_add_ready_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg);
|
||||
void etcp_conn_remove_ready_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg);
|
||||
void etcp_conn_add_up_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg);
|
||||
void etcp_conn_remove_up_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg);
|
||||
void etcp_conn_add_down_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg);
|
||||
void etcp_conn_remove_down_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg);
|
||||
void etcp_add_new_conn_cbk(struct UTUN_INSTANCE* inst, etcp_cbk_fn fn, void* arg);
|
||||
void etcp_remove_new_conn_cbk(struct UTUN_INSTANCE* inst, etcp_cbk_fn fn, void* arg);
|
||||
|
||||
void etcp_set_routing_exchange_state(struct ETCP_CONN* conn, uint8_t new_state);
|
||||
|
||||
/**
|
||||
* @brief Внутренняя функция etcp: Коллбэк для очередей output ll_queue normalizer
|
||||
*
|
||||
@@ -128,4 +176,8 @@ void etcp_conn_set_down_cbk (struct ETCP_CONN* conn, etcp_cbk_fn callback_fn, vo
|
||||
*/
|
||||
void etcp_int_recv(struct ll_queue* queue, void* arg);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // ETCP_API_H
|
||||
@@ -1,6 +1,10 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define BBR_SCALE 8
|
||||
#define BBR_UNIT (1 << BBR_SCALE)
|
||||
#define BW_SCALE 24
|
||||
@@ -110,3 +114,7 @@ void bbr_main(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t mss, uint32_t inflight_bytes, int is_cwnd_limited);
|
||||
void bbr_note_loss(struct bbr* bbr);
|
||||
void bbr_tx_start(struct bbr* bbr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,283 @@
|
||||
#include "etcp_api.h"
|
||||
#include "etcp.h"
|
||||
#include "utun_instance.h"
|
||||
#include "topo_node.h"
|
||||
#include "stcp_link.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/socket_compat.h"
|
||||
#include <string.h>
|
||||
|
||||
#define DEBUG_CATEGORY_ETCP_CONNECT DEBUG_CATEGORY_ETCP
|
||||
|
||||
struct etcp_connect_cb_node {
|
||||
etcp_connect_callback_t cb;
|
||||
void* arg;
|
||||
uint8_t flags;
|
||||
struct etcp_connect_cb_node* next;
|
||||
};
|
||||
|
||||
struct ETCP_CONNECT {
|
||||
struct ETCP_CONNECT* next;
|
||||
struct UTUN_INSTANCE* instance;
|
||||
uint64_t node_id;
|
||||
struct ETCP_CONN* conn;
|
||||
struct etcp_connect_cb_node* cb_list;
|
||||
void* initial_timer;
|
||||
void* settle_timer;
|
||||
struct stcp_link* tcp_link;
|
||||
uint16_t min_rtt;
|
||||
uint8_t early_delivered : 1;
|
||||
uint8_t done : 1;
|
||||
uint8_t tcp_ready : 1;
|
||||
};
|
||||
static void tcp_link_ready_cb(struct stcp_link* link, void* arg);
|
||||
static void tcp_link_close_cb(struct stcp_link* link, int err, void* arg);
|
||||
static void connect_ready_cb(struct ETCP_CONN* conn, void* arg);
|
||||
|
||||
static struct ETCP_CONNECT* connect_find(struct UTUN_INSTANCE* inst, uint64_t node_id) {
|
||||
struct ETCP_CONNECT* ctx = inst->pending_connects;
|
||||
while (ctx) { if (ctx->node_id == node_id) return ctx; ctx = ctx->next; }
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void connect_deliver(struct ETCP_CONNECT* ctx, int type) {
|
||||
struct etcp_connect_cb_node** pp = &ctx->cb_list;
|
||||
while (*pp) {
|
||||
struct etcp_connect_cb_node* node = *pp;
|
||||
if (type == 0 || (node->flags & (uint8_t)type)) {
|
||||
*pp = node->next;
|
||||
node->cb(node->arg, (type == 0) ? NULL : ctx->conn, type);
|
||||
u_free(node);
|
||||
} else {
|
||||
pp = &node->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void connect_cancel(struct ETCP_CONNECT* ctx) {
|
||||
if (!ctx) return;
|
||||
struct UTUN_INSTANCE* inst = ctx->instance;
|
||||
if (inst) {
|
||||
struct ETCP_CONNECT** pp = &inst->pending_connects;
|
||||
while (*pp && *pp != ctx) pp = &(*pp)->next;
|
||||
if (*pp) *pp = ctx->next;
|
||||
if (ctx->initial_timer) { uasync_cancel_timeout(inst->ua, ctx->initial_timer); ctx->initial_timer = NULL; }
|
||||
if (ctx->settle_timer) { uasync_cancel_timeout(inst->ua, ctx->settle_timer); ctx->settle_timer = NULL; }
|
||||
}
|
||||
u_free(ctx);
|
||||
}
|
||||
|
||||
static void connect_create_links_v4(struct ETCP_CONNECT* ctx, struct TOPO_NODEQ* node) {
|
||||
if (!node || !node->node) return;
|
||||
for (const struct TOPO_ADDR4* a = node->node->v4_addrs; a; a = a->next) {
|
||||
if (a->port == 0) continue;
|
||||
{ int zero = 1; for (int j = 0; j < 4; j++) if (a->addr[j] != 0) { zero = 0; break; } if (zero) continue; }
|
||||
struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET;
|
||||
memcpy(&sin.sin_addr.s_addr, a->addr, 4); sin.sin_port = htons(a->port);
|
||||
struct sockaddr_storage sa; memcpy(&sa, &sin, sizeof(sin));
|
||||
if (!(a->protocol & TOPO_PROTO_TCP)) {
|
||||
struct ETCP_SOCKET* s = ctx->instance->etcp_sockets;
|
||||
while (s) { if (s->local_addr.ss_family == AF_INET) etcp_link_new(ctx->conn, s, &sa, 0); s = s->next; }
|
||||
}
|
||||
if ((a->protocol & TOPO_PROTO_TCP) && !ctx->tcp_link) {
|
||||
struct stcp_link_config tcp_cfg = {.ua = ctx->instance->ua, .my_keys = &ctx->instance->my_keys, .inst = ctx->instance,
|
||||
.peer_pubkey = ctx->conn->crypto_ctx.peer_public_key, .peer_pubkey_mode = 0,
|
||||
.remote_addr = &sa, .remote_port = a->port};
|
||||
ctx->tcp_link = stcp_link_connect(&tcp_cfg);
|
||||
if (ctx->tcp_link) { ctx->conn->transport_link = (void*)ctx->tcp_link; stcp_link_set_on_ready(ctx->tcp_link, tcp_link_ready_cb, ctx); stcp_link_set_on_close(ctx->tcp_link, tcp_link_close_cb, ctx); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void connect_create_links_v6(struct ETCP_CONNECT* ctx, struct TOPO_NODEQ* node) {
|
||||
if (!node || !node->node) return;
|
||||
for (const struct TOPO_ADDR6* a = node->node->v6_addrs; a; a = a->next) {
|
||||
if (a->port == 0) continue;
|
||||
int zero = 1; for (int j = 0; j < 16; j++) if (a->addr[j] != 0) { zero = 0; break; } if (zero) continue;
|
||||
struct sockaddr_in6 sin6; memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6;
|
||||
memcpy(&sin6.sin6_addr, a->addr, 16); sin6.sin6_port = htons(a->port);
|
||||
struct sockaddr_storage sa; memcpy(&sa, &sin6, sizeof(sin6));
|
||||
if (!(a->protocol & TOPO_PROTO_TCP)) {
|
||||
struct ETCP_SOCKET* s = ctx->instance->etcp_sockets;
|
||||
while (s) { if (s->local_addr.ss_family == AF_INET6) etcp_link_new(ctx->conn, s, &sa, 0); s = s->next; }
|
||||
}
|
||||
if ((a->protocol & TOPO_PROTO_TCP) && !ctx->tcp_link) {
|
||||
struct stcp_link_config tcp_cfg = {.ua = ctx->instance->ua, .my_keys = &ctx->instance->my_keys, .inst = ctx->instance,
|
||||
.peer_pubkey = ctx->conn->crypto_ctx.peer_public_key, .peer_pubkey_mode = 0,
|
||||
.remote_addr = &sa, .remote_port = a->port};
|
||||
ctx->tcp_link = stcp_link_connect(&tcp_cfg);
|
||||
if (ctx->tcp_link) {
|
||||
ctx->conn->transport_link = (void*)ctx->tcp_link;
|
||||
stcp_link_set_on_ready(ctx->tcp_link, tcp_link_ready_cb, ctx);
|
||||
stcp_link_set_on_close(ctx->tcp_link, tcp_link_close_cb, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void tcp_link_ready_cb(struct stcp_link* link, void* arg) {
|
||||
struct ETCP_CONNECT* ctx = (struct ETCP_CONNECT*)arg;
|
||||
if (!ctx || ctx->done || ctx->tcp_ready) return;
|
||||
ctx->tcp_ready = 1;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] TCP link ready for node 0x%016llx", (unsigned long long)ctx->node_id);
|
||||
connect_ready_cb(ctx->conn, ctx);
|
||||
}
|
||||
|
||||
static void tcp_link_close_cb(struct stcp_link* link, int err, void* arg) {
|
||||
struct ETCP_CONNECT* ctx = (struct ETCP_CONNECT*)arg;
|
||||
if (!ctx || ctx->done) return;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] TCP link closed node 0x%016llx err=%d",
|
||||
(unsigned long long)ctx->node_id, err);
|
||||
ctx->tcp_link = NULL;
|
||||
}
|
||||
|
||||
static void connect_bgp_ready_cb(struct ETCP_CONN* conn) {
|
||||
struct ETCP_CONNECT* ctx = connect_find(conn->instance, conn->peer_node_id);
|
||||
if (!ctx || ctx->done) return;
|
||||
connect_deliver(ctx, ETCP_CONNECT_BGP_READY);
|
||||
}
|
||||
|
||||
static void connect_initial_timeout_cb(void* arg) {
|
||||
struct ETCP_CONNECT* ctx = (struct ETCP_CONNECT*)arg;
|
||||
if (!ctx || ctx->done) return;
|
||||
ctx->done = 1;
|
||||
ctx->initial_timer = NULL;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] timeout for node 0x%016llx", (unsigned long long)ctx->node_id);
|
||||
struct ETCP_CONN* conn = ctx->conn; ctx->conn = NULL;
|
||||
if (ctx->tcp_link) { stcp_link_close(ctx->tcp_link); ctx->tcp_link = NULL; }
|
||||
connect_deliver(ctx, 0);
|
||||
if (conn) etcp_connection_close(conn);
|
||||
connect_cancel(ctx);
|
||||
}
|
||||
|
||||
static void connect_settle_timeout_cb(void* arg) {
|
||||
struct ETCP_CONNECT* ctx = (struct ETCP_CONNECT*)arg;
|
||||
if (!ctx || ctx->done) return;
|
||||
ctx->done = 1;
|
||||
ctx->settle_timer = NULL;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] settle for node 0x%016llx, min_rtt=%u",
|
||||
(unsigned long long)ctx->node_id, ctx->min_rtt);
|
||||
struct ETCP_LINK* link = ctx->conn->links;
|
||||
while (link) {
|
||||
struct ETCP_LINK* next = link->next;
|
||||
if (!link->initialized) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] removing failed link=%p for node 0x%016llx",
|
||||
(void*)link, (unsigned long long)ctx->node_id);
|
||||
etcp_link_close(link);
|
||||
}
|
||||
link = next;
|
||||
}
|
||||
if (ctx->tcp_link && !ctx->tcp_ready) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] removing failed TCP link for node 0x%016llx",
|
||||
(unsigned long long)ctx->node_id);
|
||||
stcp_link_close(ctx->tcp_link); ctx->tcp_link = NULL;
|
||||
}
|
||||
etcp_conn_remove_ready_cbk(ctx->conn, connect_ready_cb, ctx);
|
||||
connect_deliver(ctx, ETCP_CONNECT_LATE);
|
||||
connect_cancel(ctx);
|
||||
}
|
||||
|
||||
static void connect_ready_cb(struct ETCP_CONN* conn, void* arg) {
|
||||
struct ETCP_CONNECT* ctx = (struct ETCP_CONNECT*)arg;
|
||||
if (!ctx || ctx->done) return;
|
||||
struct ETCP_LINK* link = conn->links;
|
||||
while (link) {
|
||||
if (link->initialized && link->rtt_last && link->rtt_last < ctx->min_rtt)
|
||||
ctx->min_rtt = link->rtt_last;
|
||||
link = link->next;
|
||||
}
|
||||
if (ctx->early_delivered) return;
|
||||
ctx->early_delivered = 1;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] early ready for node 0x%016llx, min_rtt=%u",
|
||||
(unsigned long long)ctx->node_id, ctx->min_rtt);
|
||||
connect_deliver(ctx, ETCP_CONNECT_EARLY);
|
||||
if (ctx->initial_timer) { uasync_cancel_timeout(ctx->instance->ua, ctx->initial_timer); ctx->initial_timer = NULL; }
|
||||
uint32_t settle_tb;
|
||||
if (ctx->min_rtt == 0xFFFF) settle_tb = 20000;
|
||||
else {
|
||||
settle_tb = (uint32_t)ctx->min_rtt * 8;
|
||||
if (settle_tb < 5000) settle_tb = 5000;
|
||||
if (settle_tb > 20000) settle_tb = 20000;
|
||||
}
|
||||
ctx->settle_timer = uasync_set_timeout(ctx->instance->ua, (int)settle_tb, ctx,
|
||||
connect_settle_timeout_cb, "etcp_connect_settle");
|
||||
}
|
||||
|
||||
int etcp_connect(struct UTUN_INSTANCE* inst, struct TOPO_NODEQ* node,
|
||||
etcp_connect_callback_t cb, void* arg, uint8_t flags) {
|
||||
if (!inst || !node || !cb) return -1;
|
||||
uint64_t node_id = node->node->node_id;
|
||||
|
||||
struct ETCP_CONN* existing = inst->connections;
|
||||
while (existing) {
|
||||
if (existing->peer_node_id == node_id) {
|
||||
struct ETCP_LINK* l = existing->links;
|
||||
while (l) { if (l->link_state == 3) break; l = l->next; }
|
||||
if (l) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] node 0x%016llx already connected, delivering immediately",
|
||||
(unsigned long long)node_id);
|
||||
if (flags & ETCP_CONNECT_EARLY) cb(arg, existing, ETCP_CONNECT_EARLY);
|
||||
if (flags & ETCP_CONNECT_LATE) cb(arg, existing, ETCP_CONNECT_LATE);
|
||||
if ((flags & ETCP_CONNECT_BGP_READY) && existing->routing_exchange_active >= 3)
|
||||
cb(arg, existing, ETCP_CONNECT_BGP_READY);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
existing = existing->next;
|
||||
}
|
||||
|
||||
struct ETCP_CONNECT* ctx = connect_find(inst, node_id);
|
||||
if (ctx) {
|
||||
struct etcp_connect_cb_node* cn = u_calloc(1, sizeof(struct etcp_connect_cb_node));
|
||||
if (!cn) return -1;
|
||||
cn->cb = cb; cn->arg = arg; cn->flags = flags; cn->next = ctx->cb_list; ctx->cb_list = cn;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] node 0x%016llx already connecting, added cb",
|
||||
(unsigned long long)node_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct ETCP_CONN* conn = etcp_connection_create(inst, NULL);
|
||||
if (!conn) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] failed to create ETCP_CONN for node 0x%016llx",
|
||||
(unsigned long long)node_id); cb(arg, NULL, 0); return -1; }
|
||||
conn->next = inst->connections; inst->connections = conn; inst->connections_count++;
|
||||
if (sc_init_ctx(&conn->crypto_ctx, &inst->my_keys) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] sc_init_ctx failed for node 0x%016llx", (unsigned long long)node_id);
|
||||
etcp_connection_close(conn); cb(arg, NULL, 0); return -1;
|
||||
}
|
||||
if (sc_set_peer_public_key(&conn->crypto_ctx, node->node->public_key, 0) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] sc_set_peer_public_key failed for node 0x%016llx", (unsigned long long)node_id);
|
||||
etcp_connection_close(conn); cb(arg, NULL, 0); return -1;
|
||||
}
|
||||
|
||||
ctx = u_calloc(1, sizeof(struct ETCP_CONNECT));
|
||||
if (!ctx) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] alloc failed");
|
||||
etcp_connection_close(conn); cb(arg, NULL, 0); return -1; }
|
||||
ctx->instance = inst; ctx->node_id = node_id; ctx->conn = conn;
|
||||
ctx->min_rtt = 0xFFFF;
|
||||
{
|
||||
struct etcp_connect_cb_node* cn = u_calloc(1, sizeof(struct etcp_connect_cb_node));
|
||||
if (!cn) { u_free(ctx); etcp_connection_close(conn); cb(arg, NULL, 0); return -1; }
|
||||
cn->cb = cb; cn->arg = arg; cn->flags = flags; ctx->cb_list = cn;
|
||||
}
|
||||
|
||||
etcp_conn_add_ready_cbk(conn, connect_ready_cb, ctx);
|
||||
conn->bgp_ready_cbk = connect_bgp_ready_cb;
|
||||
|
||||
connect_create_links_v4(ctx, node);
|
||||
connect_create_links_v6(ctx, node);
|
||||
|
||||
if (!conn->links && !conn->transport_link) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] no links created for node 0x%016llx",
|
||||
(unsigned long long)node_id);
|
||||
if (ctx->tcp_link) { stcp_link_close(ctx->tcp_link); ctx->tcp_link = NULL; }
|
||||
connect_deliver(ctx, 0);
|
||||
etcp_connection_close(conn); u_free(ctx); return -1;
|
||||
}
|
||||
|
||||
ctx->initial_timer = uasync_set_timeout(inst->ua, (int)inst->etcp_connect_timeout_tb, ctx, connect_initial_timeout_cb, "etcp_connect_init");
|
||||
ctx->next = inst->pending_connects; inst->pending_connects = ctx;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] started for node 0x%016llx", (unsigned long long)node_id);
|
||||
return 0;
|
||||
}
|
||||
+677
-551
File diff suppressed because it is too large.
Load diff
+51
-3
@@ -1,6 +1,11 @@
|
||||
#ifndef ETCP_CONNECTIONS_H
|
||||
#define ETCP_CONNECTIONS_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
// подмодуль ETCP который обслуживает сокеты ETCP для приёма-передачи пакетов и одно ETCP подключение через несколько каналов связи (failover)
|
||||
|
||||
#include "secure_channel.h"
|
||||
@@ -54,6 +59,46 @@ struct ETCP_DGRAM {// пакет (незашифрованный)
|
||||
/* Размер базового ACK: type(1) + count(1) + last_delivered_id(4) + rx_dup_count(2) */
|
||||
#define ETCP_ACK_BASE_SIZE (2 + 4 + 2)
|
||||
|
||||
// --- INIT packet wire formats (inside encrypted payload, before padding) ---
|
||||
|
||||
// INIT REQUEST: client → server
|
||||
struct ETCP_INIT_REQUEST_PKT {
|
||||
uint8_t code; // 0: ETCP_INIT_REQUEST (0x02) или ETCP_INIT_REQUEST_NOINIT (0x04)
|
||||
uint8_t node_id[8]; // 1: sender node_id (big-endian)
|
||||
uint8_t session_id[4]; // 9: session (big-endian)
|
||||
uint8_t mtu[2]; // 13: client MTU (big-endian)
|
||||
uint8_t keepalive[2]; // 15: keepalive interval (big-endian)
|
||||
uint8_t recovery[2]; // 17: recovery interval/100 (big-endian)
|
||||
uint8_t link_id; // 19: client's local link id
|
||||
uint8_t socket_id; // 20: client's socket id
|
||||
uint8_t only_local; // 21: client only_local flag
|
||||
uint8_t type; // 22: client socket type (CFG_SERVER_TYPE_*)
|
||||
// V2 fields (NAT_DIRECT detection):
|
||||
uint8_t src_ipv4[4]; // 23: client interface_addr IPv4 (big-endian, 0 if N/A)
|
||||
uint8_t src_port[2]; // 27: client interface_addr port (big-endian)
|
||||
// V3 fields:
|
||||
uint8_t ed25519_pubkey[SC_PUBKEY_SIZE]; // 29: client Ed25519 pubkey (32 bytes)
|
||||
} __attribute__((packed));
|
||||
#define ETCP_INIT_REQ_SIZE sizeof(struct ETCP_INIT_REQUEST_PKT)
|
||||
|
||||
// INIT RESPONSE: server → client
|
||||
struct ETCP_INIT_RESPONSE_PKT {
|
||||
uint8_t code; // 0: ETCP_INIT_RESPONSE (0x03) или ETCP_INIT_RESPONSE_NOINIT (0x05)
|
||||
uint8_t node_id[8]; // 1: server node_id (big-endian)
|
||||
uint8_t session_id[4]; // 9: session (big-endian)
|
||||
uint8_t mtu[2]; // 13: server MTU (big-endian)
|
||||
uint8_t link_id; // 15: server's local link id
|
||||
uint8_t remote_socket_id; // 16: echo of client's socket_id
|
||||
uint8_t only_local; // 17: server only_local flag
|
||||
uint8_t type; // 18: server socket type (CFG_SERVER_TYPE_*)
|
||||
// V2 fields:
|
||||
uint8_t peer_ipv4[4]; // 19: client's external NAT address (big-endian)
|
||||
uint8_t peer_port[2]; // 23: client's external NAT port (big-endian)
|
||||
// V3 fields:
|
||||
uint8_t ed25519_pubkey[SC_PUBKEY_SIZE]; // 25: server Ed25519 pubkey (32 bytes)
|
||||
} __attribute__((packed));
|
||||
#define ETCP_INIT_RESP_SIZE sizeof(struct ETCP_INIT_RESPONSE_PKT)
|
||||
|
||||
typedef void (*etcp_ping_callback_t)(int success, uint16_t rtt, void* arg, uint64_t nonce,
|
||||
const uint8_t* resp_data, size_t resp_data_len);
|
||||
struct PING_CONTEXT {
|
||||
@@ -106,6 +151,7 @@ struct ETCP_SOCKET {
|
||||
#define NAT_TYPE_UNKNOWN 0
|
||||
#define NAT_TYPE_EIM 1 // Endpoint-Independent Mapping
|
||||
#define NAT_TYPE_STRICT 2 // Address/Restricted or Symmetric
|
||||
#define NAT_TYPE_DIRECT 3 // real public IP, no NAT (detected during INIT handshake)
|
||||
|
||||
// Verified NAT types (published in nodeinfo after server-side NAT detection)
|
||||
// Values above CFG_SERVER_TYPE_PRIVATE to avoid collision with config types
|
||||
@@ -147,6 +193,7 @@ struct ETCP_LINK {
|
||||
uint8_t remote_only_local; // only_local flag from peer
|
||||
uint8_t remote_type; // CFG_SERVER_TYPE_* (type of peer's socket)
|
||||
uint8_t nat_type; // NAT_TYPE_* (detected for this client link)
|
||||
uint8_t remote_ed25519_pubkey[SC_PUBKEY_SIZE]; // Ed25519 pubkey пира (из INIT handshake)
|
||||
uint8_t recv_keepalive; // 1 - up, 0 - down (принимаются ли пакеты)
|
||||
uint8_t remote_keepalive; // 1 - up, 0 - down (удаленная сторона сообщает - принимаются ли у нее пакеты)
|
||||
uint8_t link_status; // 1 - up, 0 - down (итоговый статус - если есть проблемы на любой стороне - линк down)
|
||||
@@ -171,9 +218,6 @@ struct ETCP_LINK {
|
||||
uint32_t inflight_packets;
|
||||
uint32_t inflight_lim_bytes;
|
||||
|
||||
/* Биты блокировки отправки по причине (для корректного resume) */
|
||||
unsigned send_blocked_inflight : 1;
|
||||
|
||||
// statistics
|
||||
size_t encrypt_errors;
|
||||
size_t decrypt_errors;
|
||||
@@ -297,4 +341,8 @@ int etcp_send_ping_to_socket(struct UTUN_INSTANCE* instance, struct ETCP_SOCKET*
|
||||
|
||||
void etcp_connections_read_callback_socket(socket_t sock, void* arg);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // ETCP_CONNECTIONS_H
|
||||
@@ -1,6 +1,11 @@
|
||||
#ifndef ETCP_DEBUG_H
|
||||
#define ETCP_DEBUG_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#include "etcp_connections.h"
|
||||
|
||||
typedef struct { char a[16]; } IP_STR;
|
||||
@@ -9,4 +14,8 @@ IP_STR ip_to_string(uint32_t ip);
|
||||
|
||||
void etcp_dump_pkt_sections(struct ETCP_DGRAM* pkt, struct ETCP_LINK* link, int is_send);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
+1
-2
@@ -161,9 +161,8 @@ void etcp_dump_conn_state(struct ETCP_CONN* conn) {
|
||||
link->keepalive_sent_count > 0 ? "has_sent" : "idle");
|
||||
|
||||
/* inflight (BBR) */
|
||||
DUMP_LINE("BBR: bytes=%u pkts=%u lim=%u blocked=%d mode=%d cycle=%d pacing=%u",
|
||||
DUMP_LINE("BBR: bytes=%u pkts=%u lim=%u mode=%d cycle=%d pacing=%u",
|
||||
link->inflight_bytes, link->inflight_packets, link->inflight_lim_bytes,
|
||||
link->send_blocked_inflight,
|
||||
link->bbr ? link->bbr->mode : -1,
|
||||
link->bbr ? link->bbr->cycle_idx : -1,
|
||||
link->bbr_pacing_rate);
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
#ifndef ETCP_DUMP_H
|
||||
#define ETCP_DUMP_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#include "etcp.h"
|
||||
#include "utun_instance.h"
|
||||
|
||||
void etcp_dump_conn_state(struct ETCP_CONN* conn);
|
||||
void etcp_dump_all_conns(struct UTUN_INSTANCE* instance);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
+6
-18
@@ -57,8 +57,8 @@ struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp) {
|
||||
|
||||
if (available_count == 0) {
|
||||
if (etcp->links)
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] no suitable link found: inf:%s, tmr:%s",
|
||||
etcp->log_name, etcp->links->send_blocked_inflight?"wait":"rdy", etcp->links->shaper_timer?"wait":"rdy");
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] no suitable link found: tmr:%s",
|
||||
etcp->log_name, etcp->links->shaper_timer?"wait":"rdy");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -181,20 +181,8 @@ void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram) {
|
||||
int loadbalancer_link_can_send(struct ETCP_LINK* link) {
|
||||
if (!link) return 0;
|
||||
if (link->burst_active) return 1; // burst bypasses all limits
|
||||
int can = 1;
|
||||
if (link->inflight_bytes >= link->inflight_lim_bytes) {
|
||||
link->send_blocked_inflight = 1;
|
||||
can = 0;
|
||||
} else {
|
||||
link->send_blocked_inflight = 0;
|
||||
}
|
||||
if (link->shaper_timer) {
|
||||
// link->send_blocked_bandwidth = 1;
|
||||
can = 0;
|
||||
} else {
|
||||
// link->send_blocked_bandwidth = 0;
|
||||
}
|
||||
return can;
|
||||
if (link->shaper_timer) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void loadbalancer_link_ready(struct ETCP_LINK* link) {
|
||||
@@ -202,8 +190,8 @@ void loadbalancer_link_ready(struct ETCP_LINK* link) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "invalid link (%p)", link);
|
||||
return;
|
||||
}
|
||||
if (loadbalancer_link_can_send(link) == 0) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "link still blocked");
|
||||
if (link->shaper_timer) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "link still blocked by shaper");
|
||||
return;
|
||||
}
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "link=%p now ready, notifying ETCP_CONN", link);
|
||||
|
||||
+881
-228
File diff suppressed because it is too large.
Load diff
+110
-6
@@ -9,34 +9,73 @@
|
||||
#ifndef ETCP_ROUTER_H
|
||||
#define ETCP_ROUTER_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include "etcp_api.h"
|
||||
|
||||
#define TRANSIT_QUEUE_HASH 256
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct SVC_ROUTE_HDR {
|
||||
uint8_t cmd; // ETCP_ID_SVC_ROUTE (0x03)
|
||||
uint8_t cmd; // ETCP_ID_SVC_ROUTE (0x03) / ETCP_RT_ID_SVC_ROUTE
|
||||
uint64_t dst_node_id;
|
||||
uint64_t src_node_id;
|
||||
uint32_t seq; // data: tx_seq; ACK: rx_seq (ожидаемый seq)
|
||||
uint8_t svc_id; // идентификатор сервиса
|
||||
uint8_t flags; // bit7=START, bit6=RST, bit5-4=sess_id
|
||||
uint16_t timestamp; // часы отправителя (0.1ms), для RTT; ACK: эхо последнего data-пакета
|
||||
};
|
||||
#pragma pack(pop)
|
||||
#define SVC_ROUTE_HDR_SIZE sizeof(struct SVC_ROUTE_HDR) // 23
|
||||
#define SVC_ROUTE_HDR_SIZE sizeof(struct SVC_ROUTE_HDR) // 25
|
||||
#define SVC_ROUTE_MAX_BINDINGS 256
|
||||
|
||||
// Биты в flags
|
||||
#define ROUTER_FLAG_START 0x80
|
||||
#define ROUTER_FLAG_RST 0x40
|
||||
#define ROUTER_FLAG_SIGNED 0x08 // пакет содержит Ed25519 подпись (64 байта после payload)
|
||||
#define ROUTER_FLAG_CLOSE 0x02 // нормальное закрытие conn
|
||||
#define ROUTER_SESS_ID_SHIFT 4
|
||||
#define ROUTER_SESS_ID_MASK 0x30
|
||||
|
||||
// Очередь транзитных пакетов для пары (src_node_id, dst_node_id)
|
||||
// Создаётся когда не получается отправить пакет в ETCP напрямую,
|
||||
// удаляется когда очередь становится пустой.
|
||||
// ll.data[0..15] = src:8 + dst:8 — совпадает с полями ниже по памяти
|
||||
struct TRANSIT_QUEUE {
|
||||
struct ll_entry ll; // data[0..15] = src_node_id:8 + dst_node_id:8 (16-byte hash index)
|
||||
uint64_t src_node_id; // = data[0..7]
|
||||
uint64_t dst_node_id; // = data[8..15]
|
||||
struct ll_queue* q; // FIFO транзитных пакетов (ll_entry с dgram), начиная с data[16]
|
||||
struct queue_waiter_handle waiter; // backpressure waiter на bp-очередь (normalizer->input или tx_queue)
|
||||
struct ETCP_CONN* conn; // next_hop (для etcp_send в drain_cb)
|
||||
};
|
||||
|
||||
// Состояние одного логического подключения (remote_node_id + svc_id)
|
||||
struct ETCP_ROUTER_CONN {
|
||||
struct ll_entry ll; // data[0..7]=remote_node_id, data[8]=svc_id — хеш-индекс
|
||||
uint64_t remote_node_id; // = data[0..7]
|
||||
uint8_t svc_id; // = data[8]
|
||||
uint16_t last_dgram_ts; // последний timestamp датаграммы (rx или tx, timebase 0.1ms)
|
||||
uint16_t rtt; // измеренный RTT (0.1ms)
|
||||
int32_t rtt_jitter; // EMA jitter, fixed-point *65536 (как в ETCP)
|
||||
uint16_t max_inflight; // настраиваемый рабочий максимум (default=ROUTER_MAX_INFLIGHT)
|
||||
uint16_t inflight_limit; // текущий эффективный лимит (=max_inflight или MINRTT_PROBE_MAX_INFLIGHT)
|
||||
uint16_t minrtt; // скользящее среднее minRTT (0.1ms), default=100 (10ms)
|
||||
uint16_t minrtt_window[10]; // окно 10 последних замеров minRTT
|
||||
uint64_t minrtt_window_tb[10];// таймстемпы замеров (timebase 0.1ms)
|
||||
uint8_t minrtt_window_idx; // cyclic index 0..9
|
||||
uint8_t minrtt_window_count; // активных записей в окне (0..10)
|
||||
uint8_t minrtt_probe; // режим измерения (inflight capped at 4)
|
||||
uint64_t last_inflight_loaded_tb; // когда inflight >= threshold
|
||||
uint64_t last_inflight_unloaded_tb; // когда inflight < threshold
|
||||
uint8_t inflight_was_loaded; // 1=канал загружен
|
||||
uint16_t last_recv_pkt_ts; // timestamp пира из последнего data-пакета
|
||||
uint64_t last_recv_pkt_local_tb; // наше время приёма последнего data-пакета (0.1ms timebase)
|
||||
uint8_t last_recv_updated; // флаг: есть данные для эха в ACK
|
||||
|
||||
uint32_t tx_seq; // следующий seq для отправки
|
||||
uint32_t rx_seq; // ожидаемый seq для сборки (next expected)
|
||||
@@ -52,9 +91,36 @@ struct ETCP_ROUTER_CONN {
|
||||
struct ll_queue* send_q; // очередь ожидающих отправки (inflight полон)
|
||||
void* send_resume_timer; // таймер возобновления отправки
|
||||
uint8_t send_blocked; // 1 = inflight полон, ждём ack/таймер
|
||||
uint8_t no_route; // 1 = нет BGP-маршрута, передача приостановлена
|
||||
void* no_route_timer; // таймер 20ms проверки появления маршрута
|
||||
uint8_t no_ack_count; // счётчик последовательных ретрансмиссий без ACK
|
||||
uint8_t closed; // 1 = в процессе закрытия, таймеры игнорируют
|
||||
void* close_callback; // пользовательский коллбэк завершения закрытия
|
||||
void* close_callback_arg; // аргумент для close_callback
|
||||
uint8_t sess_id; // наш session id (0-3)
|
||||
uint8_t peer_sess_id; // последний sess_id от peer'а
|
||||
uint8_t start_sent; // 0 = нужно отправить START в первом data
|
||||
uint8_t peer_sync_done; // 0 = rx_seq ещё не синхронизирован с удалённым seq (авто-создание/рестарт)
|
||||
|
||||
// Ретрансмиты: inflight очередь
|
||||
struct ll_queue* inflight_q; // хеш по seq (4 байта), непрерывный блок без SACK
|
||||
void* retrans_timer; // таймер проверки застоя ACK
|
||||
uint64_t last_ack_changed_tb; // когда tx_acked последний раз менялся (0.1ms)
|
||||
|
||||
// Очередь приёма входящего трафика (между сетью и recv_q)
|
||||
struct ll_queue* incoming_q; // FIFO очередь входящих пакетов
|
||||
uint8_t incoming_data_ready; // 1 = данные в incoming_q и callback был вызван
|
||||
|
||||
uint32_t c_pkts_sent; // успешные отправки данных
|
||||
uint32_t c_pkts_send_err; // ошибки отправки
|
||||
uint32_t c_pkts_rcvd; // получено и доставлено данных
|
||||
uint32_t c_ack_sent; // отправлено ACK
|
||||
uint32_t c_ack_recv; // получено ACK (последовательных)
|
||||
uint32_t c_retrans_done; // число выполненых ретрансмитов
|
||||
uint32_t c_dup_dropped; // дропнуто дубликатов seq
|
||||
uint32_t c_oob_dropped; // дропнуто out-of-bounds seq
|
||||
uint32_t c_stale_ack; // устаревших ACK
|
||||
uint32_t c_sign_fail; // signature verification failures
|
||||
};
|
||||
|
||||
#define ROUTER_CONN_HASH_SIZE 256
|
||||
@@ -63,11 +129,30 @@ struct ETCP_ROUTER_CONN {
|
||||
#define ROUTER_ACK_INTERVAL_TB 100 // интервал ACK: 10ms в timebase (0.1ms)
|
||||
#define ROUTER_ACK_IDLE_TB 5000 // idle таймаут: 500ms
|
||||
#define ROUTER_SEND_RESUME_TB 500 // retry интервал send_q: 50ms
|
||||
#define ROUTER_MAX_SEND_Q_PACKETS 4 // порог backpressure на send_q
|
||||
#define ROUTER_MAX_SEND_Q_PACKETS 64 // порог backpressure на send_q
|
||||
|
||||
// Флаги в старших битах seq (взаимоисключающие)
|
||||
#define ROUTER_SEQ_CLOSE_FLAG 0x80000000 // нормальное закрытие conn
|
||||
#define ROUTER_SEQ_RST_FLAG 0x40000000 // conn не найден (reset)
|
||||
// Ретрансмиты
|
||||
#define ROUTER_RETRANS_TIMEOUT_TB 3000 // 300ms в timebase (0.1ms)
|
||||
#define ROUTER_INFLIGHT_HASH_SIZE 1024
|
||||
#define ROUTER_NO_ROUTE_RETRY_TB 200 // 20ms проверка появления маршрута
|
||||
#define ROUTER_NO_ACK_MAX_RETRANS 17 // 17 × 300ms ≈ 5s таймаут без ACK
|
||||
|
||||
// minRTT measurement
|
||||
#define MINRTT_UNLOADED_MIN_TB 500 // 50ms минимальное время разгрузки канала
|
||||
#define MINRTT_PROBE_TIMEOUT_TB 100000 // 10 сек без свежих замеров → probe
|
||||
#define MINRTT_PROBE_MAX_INFLIGHT 4 // лимит inflight в режиме probe
|
||||
#define MINRTT_WINDOW_SIZE 10
|
||||
#define MINRTT_DEFAULT_TB 100 // default minRTT = 10ms (заменяется первым RTT)
|
||||
|
||||
// Inflight запись — копия отправленного пакета для возможного ретрансмита
|
||||
struct ROUTER_INFLIGHT {
|
||||
struct ll_entry ll; // индекс по seq (4 байта, offset 0)
|
||||
uint32_t seq;
|
||||
uint64_t last_sent_tb; // время последней отправки (0.1ms)
|
||||
uint8_t send_count; // число переотправок
|
||||
uint8_t* payload; // копия данных
|
||||
size_t payload_len;
|
||||
};
|
||||
|
||||
// Bindings для сервисов внутри etcp_router (аналогично ETCP_BINDINGS)
|
||||
struct ETCP_ROUTER_BINDINGS {
|
||||
@@ -96,9 +181,21 @@ struct ETCP_ROUTER_CONN* etcp_router_conn_get(struct UTUN_INSTANCE* inst,
|
||||
int etcp_router_conn_send(struct ETCP_ROUTER_CONN* rconn,
|
||||
const uint8_t* data, size_t len);
|
||||
|
||||
// Отправить данные с Ed25519 подписью (аналог etcp_router_conn_send с флагом ROUTER_FLAG_SIGNED)
|
||||
int etcp_router_conn_send_signed(struct ETCP_ROUTER_CONN* rconn,
|
||||
const uint8_t* data, size_t len);
|
||||
|
||||
// Закрыть seq-подключение
|
||||
void etcp_router_conn_close(struct ETCP_ROUTER_CONN* rconn);
|
||||
|
||||
// Установить рабочий max_inflight, пересчитывает inflight_limit и state machine
|
||||
void router_set_max_inflight(struct ETCP_ROUTER_CONN* rconn, uint16_t new_max);
|
||||
|
||||
// Асинхронное закрытие: коллбэк вызывается после освобождения памяти rconn
|
||||
void etcp_router_conn_close_async(struct ETCP_ROUTER_CONN* rconn,
|
||||
void (*on_close_done)(void* arg),
|
||||
void* close_arg);
|
||||
|
||||
// Закрыть все router_conn для указанного remote_node_id (peer умер)
|
||||
void etcp_router_conn_close_all_for_node(struct UTUN_INSTANCE* inst, uint64_t remote_node_id);
|
||||
|
||||
@@ -124,4 +221,11 @@ void etcp_router_cancel_send_ready(struct UTUN_INSTANCE* inst, uint64_t node_id,
|
||||
// Очищает send_q/recv_q, уведомляет сервис через cb(NULL, entry) с remote_node_id.
|
||||
void etcp_router_conn_restart(struct UTUN_INSTANCE* inst, uint64_t remote_node_id, uint8_t svc_id);
|
||||
|
||||
// Удалить все транзитные очереди соединения (вызывается при закрытии ETCP_CONN)
|
||||
void etcp_router_transit_queues_destroy(struct ETCP_CONN* conn);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // ETCP_ROUTER_H
|
||||
+1
-1
@@ -32,7 +32,7 @@ int fw_load_rules(struct firewall_ctx *ctx, const struct global_config *cfg) {
|
||||
if (cfg->firewall_rule_count > 0) {
|
||||
ctx->rules = u_calloc(cfg->firewall_rule_count, sizeof(struct CFG_FIREWALL_RULE));
|
||||
if (!ctx->rules) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "fw_load_rules: failed to allocate rules");
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "failed to allocate rules");
|
||||
return -1;
|
||||
}
|
||||
memcpy(ctx->rules, cfg->firewall_rules,
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
#ifndef LWIP_PBUF_H
|
||||
#define LWIP_PBUF_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
@@ -38,4 +43,8 @@ int pbuf_take(struct pbuf *p, const void *data, uint16_t len);
|
||||
int pbuf_take_at(struct pbuf *p, const void *data, uint16_t len, uint16_t offset);
|
||||
void* pbuf_get_contiguous(const struct pbuf *p, void *buf, size_t len, size_t offset);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -458,7 +458,7 @@ struct tcp_pcb *tcp_listen(struct tcp_pcb *pcb)
|
||||
lpcb = (struct tcp_pcb_listen *)memory_pool_alloc(pcb->ctx->pcb_listen_pool);
|
||||
if (lpcb == NULL) {
|
||||
pcb->ctx->stats.pcb_listen_fails++;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_listen: out of memory");
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "out of memory");
|
||||
return NULL;
|
||||
}
|
||||
memset(lpcb, 0, sizeof(*lpcb));
|
||||
@@ -1097,7 +1097,7 @@ struct tcp_pcb *tcp_alloc(struct lwip_tcp_ctx *ctx, uint8_t prio)
|
||||
pcb = (struct tcp_pcb *)memory_pool_alloc(ctx->pcb_pool);
|
||||
if (pcb == NULL) {
|
||||
ctx->stats.pcb_fails++;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_alloc: out of memory after all retries");
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "out of memory after all retries");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1116,7 +1116,7 @@ struct tcp_pcb *tcp_alloc(struct lwip_tcp_ctx *ctx, uint8_t prio)
|
||||
pcb->sv = (int16_t)(TCP_RTO_MIN_MS / TCP_SLOW_INTERVAL);
|
||||
pcb->rtime = -1;
|
||||
pcb->cwnd = 1;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "tcp_alloc: cwnd=%u snd_buf=%u mss=%u snd_wnd=%u", pcb->cwnd, pcb->snd_buf, pcb->mss, pcb->snd_wnd);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "cwnd=%u snd_buf=%u mss=%u snd_wnd=%u", pcb->cwnd, pcb->snd_buf, pcb->mss, pcb->snd_wnd);
|
||||
pcb->tmr = ctx->ticks;
|
||||
pcb->last_timer = ctx->slowtmr_ctr;
|
||||
pcb->ssthresh = TCP_SND_BUF;
|
||||
@@ -1167,7 +1167,7 @@ void tcp_accept(struct tcp_pcb *pcb, tcp_accept_fn accept)
|
||||
void tcp_poll(struct tcp_pcb *pcb, tcp_poll_fn poll, uint8_t interval)
|
||||
{
|
||||
if (!pcb || pcb->state == LISTEN) return;
|
||||
pcb->poll = poll;
|
||||
pcb->pollfn = poll;
|
||||
pcb->pollinterval = interval;
|
||||
}
|
||||
|
||||
|
||||
+10
-1
@@ -2,6 +2,11 @@
|
||||
#ifndef LWIP_TCP_H
|
||||
#define LWIP_TCP_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "lwip_pbuf.h"
|
||||
@@ -164,7 +169,7 @@ struct tcp_pcb {
|
||||
tcp_sent_fn sent;
|
||||
tcp_recv_fn recv;
|
||||
tcp_connected_fn connected;
|
||||
tcp_poll_fn poll;
|
||||
tcp_poll_fn pollfn;
|
||||
tcp_err_fn errf;
|
||||
uint32_t keep_idle;
|
||||
uint8_t persist_cnt;
|
||||
@@ -270,4 +275,8 @@ void tcp_poll (struct tcp_pcb *pcb, tcp_poll_fn poll, uint8_t interval);
|
||||
#define tcp_clear_flags(pcb, clr_flags) ((pcb)->flags &= (tcpflags_t)~(clr_flags))
|
||||
#define tcp_is_flag_set(pcb, flag) (((pcb)->flags & (flag)) != 0)
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -67,7 +67,7 @@ static struct tcp_seg *tcp_seg_copy_local(struct tcp_pcb *pcb, struct tcp_seg *s
|
||||
struct tcp_seg *cseg;
|
||||
if (!seg || !pcb || !pcb->ctx) return NULL;
|
||||
cseg = (struct tcp_seg *)memory_pool_alloc(pcb->ctx->seg_pool);
|
||||
if (!cseg) { pcb->ctx->stats.seg_fails++; DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_seg_copy_local: no memory"); return NULL; }
|
||||
if (!cseg) { pcb->ctx->stats.seg_fails++; DEBUG_ERROR(DEBUG_CATEGORY_ALL, "no memory"); return NULL; }
|
||||
memcpy(cseg, seg, sizeof(struct tcp_seg));
|
||||
pbuf_ref(cseg->p);
|
||||
return cseg;
|
||||
@@ -139,14 +139,14 @@ void lwip_tcp_input(struct lwip_tcp_ctx *ctx, struct pbuf *p,
|
||||
uint16_t wire_dst_port = tcphdr->dest;
|
||||
|
||||
if (p->len < TCP_HLEN) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_input: short packet (%u bytes) discarded", p->tot_len);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "short packet (%u bytes) discarded", p->tot_len);
|
||||
pbuf_free(p);
|
||||
return;
|
||||
}
|
||||
|
||||
hdrlen_bytes = TCPH_HDRLEN_BYTES(tcphdr);
|
||||
if ((hdrlen_bytes < TCP_HLEN) || (hdrlen_bytes > p->tot_len)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_input: invalid header length (%u)", (unsigned)hdrlen_bytes);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "invalid header length (%u)", (unsigned)hdrlen_bytes);
|
||||
pbuf_free(p);
|
||||
return;
|
||||
}
|
||||
@@ -163,7 +163,7 @@ void lwip_tcp_input(struct lwip_tcp_ctx *ctx, struct pbuf *p,
|
||||
opt2len = (uint16_t)(tcphdr_optlen - tcphdr_opt1len);
|
||||
pbuf_header(p, -(int16_t)tcphdr_opt1len);
|
||||
if (opt2len > p->next->len) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_input: options overflow second pbuf");
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "options overflow second pbuf");
|
||||
pbuf_free(p);
|
||||
return;
|
||||
}
|
||||
@@ -184,7 +184,7 @@ void lwip_tcp_input(struct lwip_tcp_ctx *ctx, struct pbuf *p,
|
||||
if (flags & (TCP_FIN | TCP_SYN)) {
|
||||
tcplen++;
|
||||
if (tcplen < p->tot_len) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_input: length overflow");
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "length overflow");
|
||||
pbuf_free(p);
|
||||
return;
|
||||
}
|
||||
@@ -314,7 +314,7 @@ void lwip_tcp_input(struct lwip_tcp_ctx *ctx, struct pbuf *p,
|
||||
if (err == LERR_ABRT) goto aborted;
|
||||
if (err != LERR_OK) {
|
||||
pcb->refused_data = recv_data;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ALL, "tcp_input: keep incoming packet, pcb is full");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ALL, "keep incoming packet, pcb is full");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,7 +386,7 @@ static void tcp_listen_input(struct tcp_pcb_listen *pcb)
|
||||
(unsigned)tcphdr->src, (unsigned)tcphdr->dest);
|
||||
npcb = tcp_alloc(pcb->ctx, pcb->prio);
|
||||
if (npcb == NULL) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_listen_input: could not allocate PCB");
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "could not allocate PCB");
|
||||
if (pcb->accept) pcb->accept(pcb->callback_arg, NULL, LERR_MEM);
|
||||
return;
|
||||
}
|
||||
@@ -1043,7 +1043,7 @@ static void tcp_parseopt(struct tcp_pcb *pcb)
|
||||
case TCP_OPT_MSS:
|
||||
if (tcp_get_next_optbyte() != TCP_OPT_MSS_LEN ||
|
||||
(tcp_optidx - 2 + TCP_OPT_MSS_LEN) > tcphdr_optlen) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_parseopt: bad MSS option length");
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "bad MSS option length");
|
||||
return;
|
||||
}
|
||||
mss = (uint16_t)(tcp_get_next_optbyte() << 8);
|
||||
@@ -1053,7 +1053,7 @@ static void tcp_parseopt(struct tcp_pcb *pcb)
|
||||
default:
|
||||
data = tcp_get_next_optbyte();
|
||||
if (data < 2) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "tcp_parseopt: bad option length");
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "bad option length");
|
||||
return;
|
||||
}
|
||||
tcp_optidx += data - 2;
|
||||
|
||||
Loaded 100 of 627 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user