Compare commits
234
Commits
congestion
...
chatgui
| 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 | ||
|
|
f913bdf351 | ||
|
|
0dd942be63 | ||
|
|
474bb8bd6b | ||
|
|
3419ff3a85 | ||
|
|
6b2b0f1360 | ||
|
|
20e81e493d | ||
|
|
23732039de | ||
|
|
f883c8da75 | ||
|
|
4211e32f51 | ||
|
|
7c840c8cfd | ||
|
|
1a1e305b6b | ||
|
|
974c6770e0 | ||
|
|
075c4f4583 | ||
|
|
9e133699bf | ||
|
|
81cad40e3b | ||
|
|
baca018387 | ||
|
|
48a4f182aa | ||
|
|
7c456a1652 | ||
|
|
a0ea19acb8 | ||
|
|
1f34629753 | ||
|
|
9a3eb1ce80 | ||
|
|
5982b011b4 | ||
|
|
e55c4ec7ce | ||
|
|
dd1b9f2680 | ||
|
|
52f32da3f1 | ||
|
|
defb70f63a | ||
|
|
40c7a5b487 | ||
|
|
e81299a727 | ||
|
|
3217f744f9 | ||
|
|
24a5de9398 | ||
|
|
a09c647dfd | ||
|
|
11002bcb57 | ||
|
|
7a35f4c386 | ||
|
|
483bf440fb | ||
|
|
6f1916b6b2 | ||
|
|
01dbd76834 | ||
|
|
6ee030ca7c | ||
|
|
3ae4988353 | ||
|
|
57281a975e | ||
|
|
b953a6daf0 | ||
|
|
612b6bf1bf | ||
|
|
58e45e9232 | ||
|
|
f8111f7f03 | ||
|
|
859290c202 | ||
|
|
cfd885de7c | ||
|
|
8d0c40195b | ||
|
|
e765d62e43 | ||
|
|
c56ec0b0ed | ||
|
|
4dfc0ce947 | ||
|
|
b8da0db1ab | ||
|
|
30acde7b18 | ||
|
|
c0b6e5769e | ||
|
|
3c036ecc4c | ||
|
|
cb764a1143 | ||
|
|
0c8de3db84 | ||
|
|
4ba790b921 | ||
|
|
e0378ebf1d | ||
|
|
d784b2bfbe | ||
|
|
0449a5fd9d | ||
|
|
d0029178e7 | ||
|
|
ca7f6c0d01 | ||
|
|
609be50b8f | ||
|
|
e4e5ed71f8 | ||
|
|
4716641dff | ||
|
|
bbf39c091d | ||
|
|
a0da29f1eb | ||
|
|
dc2b394e7a | ||
|
|
04f6b3e040 | ||
|
|
63f019530b | ||
|
|
3533ec3122 | ||
|
|
ac3fe79db8 | ||
|
|
e7eb24537d | ||
|
|
51d5790938 | ||
|
|
2a259191b9 | ||
|
|
331d43c6f7 | ||
|
|
7f5325ac24 | ||
|
|
3a2b427287 | ||
|
|
43baeb2306 | ||
|
|
fceec1aea6 | ||
|
|
c5c4967b69 | ||
|
|
4556f327c2 | ||
|
|
226a5744b5 | ||
|
|
1f4fe37fe5 | ||
|
|
e7a9163011 | ||
|
|
b43de4c1f0 | ||
|
|
60800a7f35 | ||
|
|
f43631a532 | ||
|
|
6d661360b1 | ||
|
|
b2e72db31b | ||
|
|
c5b4ec7a05 | ||
|
|
0adff0253d | ||
|
|
4f0baa37db | ||
|
|
6c79939db3 |
No files matched your search
+8
-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
|
||||
@@ -108,3 +113,5 @@ cl
|
||||
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,13 +335,19 @@ 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
|
||||
|
||||
## Работа с очередями. Важно!
|
||||
С очередью следуею работать придерживаясь следующих правил:
|
||||
- при сетевом обмене не забиваем очередь. всегда используем Пороговое ожидание (backpressure).
|
||||
- для работы с очередью полностью загрузи ll_queue.h и пойми как работает.
|
||||
|
||||
### Запись в очередь
|
||||
- Очереди забивать нельзя. Добавляй следующий элемент только когда очередь стала пустой.
|
||||
- Используй `queue_wait_threshold` для ожидания освобождения очереди до заданного порога.
|
||||
- Порог задаётся через `queue_set_threshold(q, max_packets, max_bytes)`.
|
||||
- Используй `queue_waiter_wait` для ожидания освобождения очереди до заданного порога.
|
||||
|
||||
### Чтение из очереди
|
||||
- Используй `queue_set_callback`: при вызове callback обработай один или несколько элементов, потом вызови `queue_resume_callback`.
|
||||
@@ -314,6 +370,41 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
|
||||
|
||||
## Bugs Debugging Protocol
|
||||
Всегда когда начинаешь диагностику ознакомься со скиллом (используй 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 для редактирования исходников - запрещено
|
||||
@@ -331,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`
|
||||
@@ -347,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`
|
||||
@@ -356,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 и собственным публичным ключом). Передаётся в конце пакета
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
# ETCP Router Architecture
|
||||
|
||||
## Структуры данных
|
||||
|
||||
```
|
||||
ETCP_ROUTER_CONN (один на пару remote_node_id + svc_id)
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ ll_entry (хеш-индекс: remote_node_id[8] + svc_id[1]) │
|
||||
│ remote_node_id: u64 │
|
||||
│ svc_id: u8 │
|
||||
│ last_dgram_ts: u16 (timestamp последней датаграммы) │
|
||||
│ │
|
||||
│ tx_seq: u32 следующий seq для отправки │
|
||||
│ rx_seq: u32 ожидаемый seq для сборки │
|
||||
│ tx_acked: u32 сколько наших пакетов подтвердил remote │
|
||||
│ last_sent_ack_seq: u32 последний отправленный ACK │
|
||||
│ │
|
||||
│ recv_q: ll_queue* очередь reorder (хеш по seq[4]) │
|
||||
│ send_q: ll_queue* очередь при переполнении inflight │
|
||||
│ │
|
||||
│ ack_timer: void* периодический 100ms │
|
||||
│ idle_ack_timer: void* idle таймаут 500ms │
|
||||
│ send_resume_timer: void* retry send_q 50ms │
|
||||
│ send_blocked: u8 inflight полон │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
|
||||
SVC_ROUTE_HDR (22 байта)
|
||||
┌──────┬────────────────┬────────────────┬───────┬────────┬──────────┐
|
||||
│ cmd │ dst_node_id │ src_node_id │ seq │ svc_id │ payload │
|
||||
│ u8 │ u64 │ u64 │ u32 │ u8 │ ... │
|
||||
└──────┴────────────────┴────────────────┴───────┴────────┴──────────┘
|
||||
|
||||
seq в data-пакетах = tx_seq (счетчик отправленных)
|
||||
seq в ACK-пакетах = rx_seq (ожидаемый следующий)
|
||||
CLOSE-флаг = 0x80000000 в seq, RST-флаг = 0x40000000
|
||||
```
|
||||
|
||||
## Send Path (отправка данных)
|
||||
|
||||
```
|
||||
etcp_route_send(inst, dst, entry)
|
||||
etcp_router_conn_send(rconn, data, len)
|
||||
│
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ rconn = find/get │ поиск по (dst, svc_id)
|
||||
│ авто-создание если │ или etcp_router_conn_get()
|
||||
│ ещё нет │
|
||||
└────────┬────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────┐
|
||||
│ inflight < ROUTER_MAX(256)? │
|
||||
│ tx_seq - tx_acked < 256 ? │
|
||||
└──────┬───────────┬───────────┘
|
||||
│ YES │ NO (inflight полон)
|
||||
▼ ▼
|
||||
┌──────────────┐ ┌─────────────────────┐
|
||||
│router_send_one│ │ router_enqueue_send │ → send_q (FIFO)
|
||||
│ (прямая) │ │ (в очередь) │ send_blocked = 1
|
||||
└──────┬────────┘ └─────────┬───────────┘ send_resume_timer (50ms)
|
||||
│ │
|
||||
▼ │
|
||||
┌────────────────────┐ │
|
||||
│ tx_seq++ │ │
|
||||
│ hdr.dst = dst │ │
|
||||
│ hdr.src = node_id │ │
|
||||
│ hdr.seq = tx_seq │ │
|
||||
│ hdr.svc_id = svc │ │
|
||||
│ │ │
|
||||
│ etcp_send(conn, e) │ │
|
||||
└────────────────────┘ │
|
||||
│
|
||||
┌────────────────────────────┘
|
||||
│ (при получении ACK от remote)
|
||||
▼
|
||||
┌────────────────────┐
|
||||
│ router_drain_send_q│ вызывается из ACK handler
|
||||
│ │ или по send_resume_timer
|
||||
│ while inflight < 256│
|
||||
│ e = send_q.pop() │
|
||||
│ router_send_one(e)│
|
||||
│ │
|
||||
│ если send_q пуст: │
|
||||
│ send_blocked = 0 │
|
||||
└────────────────────┘
|
||||
```
|
||||
|
||||
## Receive Path (приём данных)
|
||||
|
||||
```
|
||||
etcp_router_recv_cb(conn, entry)
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ dst == node_id? │
|
||||
└──┬───────────────┬───┘
|
||||
│ YES │ NO (транзит)
|
||||
▼ ▼
|
||||
┌────────────────────┐ ┌──────────────────────────┐
|
||||
│ Мы — целевая нода │ │ Транзит: найти next hop │
|
||||
└────────┬───────────┘ │ route_bgp_find_conn_for_ │
|
||||
│ │ node(bgp, hdr.dst) │
|
||||
▼ │ etcp_send(next, entry) │
|
||||
┌────────────────────┐ └──────────────────────────┘
|
||||
│ pl_len == 0? │
|
||||
└──┬───────────┬─────┘
|
||||
│ YES │ NO (данные)
|
||||
▼ ▼
|
||||
┌─────────┐ ┌─────────────────────────┐
|
||||
│ CLOSE/ │ │ rconn = find/get │
|
||||
│ RST? │ │ (авто-создание если нет) │
|
||||
└──┬──┬───┘ └───────────┬─────────────┘
|
||||
│ │ │
|
||||
│ │ NO (ACK) ▼
|
||||
│ ▼ ┌───────────────────────────────────────┐
|
||||
│ ┌──────────┐│ Проверка границ seq: │
|
||||
│ │ tx_acked ││ d = seq - rx_seq │
|
||||
│ │ = hdr.seq││ |d| < ROUTER_MAX_INFLIGHT? │
|
||||
│ │ (только ││ │
|
||||
│ │ вперёд) ││ Дубликат? │
|
||||
│ │ drain_q ││ rx_seq - seq > 0 ИЛИ │
|
||||
│ └──────────┘│ seq уже в recv_q? │
|
||||
│ └──────┬─────────────┬──────────────────┘
|
||||
│ │ OK │ out-of-bounds / dup
|
||||
│ ▼ ▼
|
||||
│ ┌──────────────┐ ┌─────────┐
|
||||
│ │ recv_q.put() │ │ DROP │
|
||||
│ │ хеш по seq │ └─────────┘
|
||||
│ └──────┬───────┘
|
||||
│ │
|
||||
│ ▼
|
||||
│ ┌──────────────────────┐
|
||||
│ │ seq == rx_seq? │
|
||||
│ └──┬───────────────┬───┘
|
||||
│ │ YES │ NO
|
||||
│ ▼ │
|
||||
│ ┌──────────────┐ │
|
||||
│ │ try_assembly │ │
|
||||
│ │ while next в │ │
|
||||
│ │ recv_q: │ │
|
||||
│ │ deliver() │ │
|
||||
│ │ rx_seq++ │ │
|
||||
│ └──────┬───────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────┴──┐
|
||||
│ │ router_deliver() │
|
||||
│ │ вызывает callback │
|
||||
│ │ сервиса (svc_id) │
|
||||
│ │ entry: [svc_id|payload] │
|
||||
│ └──────────────┬──────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌────────────────────────────────────┐
|
||||
│ router_schedule_ack(rconn) │
|
||||
│ запускает ack_timer (100ms) если │
|
||||
│ ещё не запущен │
|
||||
└────────────────────────────────────┘
|
||||
```
|
||||
|
||||
```
|
||||
CLOSE / RST ветка:
|
||||
┌────────────────────────────────────────┐
|
||||
│ pl_len == 0 && seq & CLOSE/RST │
|
||||
│ → router_close_and_notify(rconn) │
|
||||
│ ├─ router_send_close_to_service() │
|
||||
│ │ (callback с NULL conn) │
|
||||
│ ├─ отправить CLOSE удалённой стороне│
|
||||
│ ├─ отмена всех таймеров │
|
||||
│ ├─ очистка recv_q + send_q │
|
||||
│ └─ удаление из inst->router_conns │
|
||||
└────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## ACK Mechanism
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ router_schedule_ack(rconn) │
|
||||
│ (вызывается при получении данных) │
|
||||
└────────────────┬────────────────────────┘
|
||||
│
|
||||
┌────────────────▼────────────────────────┐
|
||||
│ если idle_ack_timer активен — отменить │
|
||||
│ если ack_timer не активен — запустить │
|
||||
│ ROUTER_ACK_INTERVAL_TB = 1000 (100ms) │
|
||||
└────────────────┬────────────────────────┘
|
||||
│
|
||||
▼ (через 100ms)
|
||||
┌─────────────────────────────────────────┐
|
||||
│ router_ack_timer_cb() │
|
||||
│ │
|
||||
│ if rx_seq != last_sent_ack_seq: │
|
||||
│ router_send_ack(rconn) │
|
||||
│ last_sent_ack_seq = rx_seq │
|
||||
│ перезапустить ack_timer │
|
||||
│ else: │
|
||||
│ запустить idle_ack_timer (500ms) │
|
||||
└────────────────┬────────────────────────┘
|
||||
│
|
||||
▼ (через 500ms)
|
||||
┌─────────────────────────────────────────┐
|
||||
│ router_idle_ack_timer_cb() │
|
||||
│ │
|
||||
│ if rx_seq != last_sent_ack_seq: │
|
||||
│ router_send_ack(rconn) (досылка) │
|
||||
│ last_sent_ack_seq = rx_seq │
|
||||
└─────────────────────────────────────────┘
|
||||
|
||||
router_send_ack(rconn):
|
||||
hdr.seq = rx_seq
|
||||
hdr.svc_id = svc_id
|
||||
pl_len = 0 → это ACK-пакет
|
||||
etcp_send(conn, entry)
|
||||
```
|
||||
|
||||
```
|
||||
Обработка входящего ACK:
|
||||
┌────────────────────────────────────────┐
|
||||
│ pl_len == 0 && нет CLOSE/RST флагов │
|
||||
│ │
|
||||
│ if seq >= tx_acked: // только вперёд │
|
||||
│ tx_acked = seq // обновить │
|
||||
│ else: │
|
||||
│ DEBUG_WARN "stale ACK" │
|
||||
│ │
|
||||
│ if send_blocked: │
|
||||
│ router_drain_send_q(rconn) │
|
||||
└────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Inflight Control
|
||||
|
||||
```
|
||||
ROUTER_MAX_INFLIGHT = 256
|
||||
|
||||
Отправка возможна когда: tx_seq - tx_acked < 256
|
||||
Отправка заблокирована: tx_seq - tx_acked >= 256
|
||||
|
||||
При блокировке:
|
||||
данные → send_q (FIFO, без дропов)
|
||||
send_blocked = 1
|
||||
send_resume_timer = 50ms retry
|
||||
|
||||
Разблокировка:
|
||||
приход ACK от remote → tx_acked обновляется
|
||||
→ router_drain_send_q() разгребает send_q
|
||||
или send_resume_timer (50ms) → router_drain_send_q()
|
||||
|
||||
send_q полностью разобран → send_blocked = 0
|
||||
```
|
||||
|
||||
## Полная схема потоков
|
||||
|
||||
```
|
||||
┌──────────┐
|
||||
│ Сервис │ (routing, proxy, NAT, ...)
|
||||
│ (svc_id)│
|
||||
└────┬─────┘
|
||||
│
|
||||
etcp_route_send() │ callback(svc_id, entry)
|
||||
▼ ▲
|
||||
┌─────────┐ send ┌─────────────────────────────────┐ recv ┌─────────┐
|
||||
│ TUN │─────────▶│ ETCP ROUTER │─────────▶│ TUN │
|
||||
│ (выход) │ │ │ │ (вход) │
|
||||
└─────────┘ │ ┌──────────┐ ┌──────────┐ │ └─────────┘
|
||||
│ │ send_q │ │ recv_q │ │
|
||||
│ │ (FIFO) │ │ (хеш seq)│ │
|
||||
│ └────┬─────┘ └────┬─────┘ │
|
||||
│ │ │ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌─────────────────────────┐ │
|
||||
│ │ Inflight / Assembly │ │
|
||||
│ │ tx_seq - tx_acked │ │
|
||||
│ │ rx_seq → deliver │ │
|
||||
│ └───────────┬─────────────┘ │
|
||||
│ │ │
|
||||
└──────────────┼──────────────────┘
|
||||
│
|
||||
┌──────▼──────┐
|
||||
│ BGP │
|
||||
│ (нахождение│
|
||||
│ next hop) │
|
||||
└──────┬──────┘
|
||||
│
|
||||
┌──────▼──────┐
|
||||
│ ETCP conn │──▶ сеть
|
||||
└─────────────┘
|
||||
|
||||
──▶ send (исходящие данные)
|
||||
──▶ recv (входящие данные, callback сервису)
|
||||
──▶ транзит (через BGP к следующему hop)
|
||||
```
|
||||
|
||||
## Таймеры (один rconn)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ack_timer ──────── 100ms ─────▶ router_ack_timer_cb│
|
||||
│ (периодический) │
|
||||
│ │
|
||||
│ idle_ack_timer ─── 500ms ─────▶ router_idle_ack_cb │
|
||||
│ (досылка последнего ACK) │
|
||||
│ │
|
||||
│ send_resume_timer ─ 50ms ─────▶ router_send_resume │
|
||||
│ (retry когда send_q не пуст) │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Флаги seq
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────┐
|
||||
│ CLOSE: 0x80000000 нормальное закрытие conn │
|
||||
│ RST: 0x40000000 conn не найден (reset) │
|
||||
│ │
|
||||
│ CLOSE/RST-пакеты: seq=0 | флаг, pl_len=0 │
|
||||
│ Data-пакеты: seq=tx_seq, pl_len>0 │
|
||||
│ ACK-пакеты: seq=rx_seq, pl_len=0 │
|
||||
└──────────────────────────────────────────────────┘
|
||||
```
|
||||
-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
|
||||
+9
-2
@@ -26,14 +26,21 @@ libuasync_a_SOURCES = \
|
||||
radix.c \
|
||||
radix.h \
|
||||
serialize.c \
|
||||
serialize.h
|
||||
serialize.h \
|
||||
tcp_io.c \
|
||||
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:
|
||||
|
||||
+44
-68
@@ -13,30 +13,29 @@
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
void log_dump(const char* prefix, const uint8_t* data, size_t len) {
|
||||
void log_dump(int level, int category, const char* prefix, const uint8_t* data, size_t len) {
|
||||
if (!debug_should_output(level, category)) return;
|
||||
|
||||
char hex_buf[513] = {'\0'};
|
||||
size_t hex_len = 0;
|
||||
size_t show_len = (len > 128) ? 128 : len;
|
||||
|
||||
|
||||
for (size_t i = 0; i < show_len && hex_len < 512 - 3; i++) {
|
||||
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "%02x", data[i]);
|
||||
if (i < show_len - 1 && (i + 1) % 32 == 0) { // Add space every 32 bytes
|
||||
if (i < show_len - 1 && (i + 1) % 32 == 0)
|
||||
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, " ");
|
||||
}
|
||||
}
|
||||
|
||||
if (len > 128) {
|
||||
|
||||
if (len > 128)
|
||||
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "...");
|
||||
|
||||
switch (level) {
|
||||
case DEBUG_LEVEL_ERROR: DEBUG_ERROR(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
|
||||
case DEBUG_LEVEL_WARN: DEBUG_WARN(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
|
||||
case DEBUG_LEVEL_INFO: DEBUG_INFO(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
|
||||
case DEBUG_LEVEL_DEBUG: DEBUG_DEBUG(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
|
||||
case DEBUG_LEVEL_TRACE: DEBUG_TRACE(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
|
||||
}
|
||||
|
||||
// Single-line debug output with packet info
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DUMP, "%s: len=%zu hex=%s", prefix, len, hex_buf);
|
||||
|
||||
// Additional debug info for first few bytes
|
||||
// if (len >= 2) {
|
||||
// DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "%s: first_bytes=%02x%02x last_bytes=%02x%02x",
|
||||
// prefix, data[0], data[1], data[len-2], data[len-1]);
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
@@ -127,6 +126,10 @@ static const struct {
|
||||
{"general", DEBUG_CATEGORY_GENERAL},
|
||||
{"nat", DEBUG_CATEGORY_NAT},
|
||||
{"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}
|
||||
};
|
||||
@@ -135,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) {
|
||||
@@ -161,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 */
|
||||
}
|
||||
|
||||
@@ -198,31 +202,9 @@ void debug_apply_global_level(const char* level_name) {
|
||||
|
||||
/* Get category name for logging */
|
||||
const char* debug_get_category_name(debug_category_t category_idx) {
|
||||
switch (category_idx) {
|
||||
case DEBUG_CATEGORY_UASYNC: return "UASYNC";
|
||||
case DEBUG_CATEGORY_LL_QUEUE: return "LL_QUEUE";
|
||||
case DEBUG_CATEGORY_CONNECTION: return "CONNECTION";
|
||||
case DEBUG_CATEGORY_ETCP: return "ETCP";
|
||||
case DEBUG_CATEGORY_CRYPTO: return "CRYPTO";
|
||||
case DEBUG_CATEGORY_MEMORY: return "MEMORY";
|
||||
case DEBUG_CATEGORY_TIMING: return "TIMING";
|
||||
case DEBUG_CATEGORY_CONFIG: return "CONFIG";
|
||||
case DEBUG_CATEGORY_TUN: return "TUN";
|
||||
case DEBUG_CATEGORY_ROUTING: return "ROUTING";
|
||||
case DEBUG_CATEGORY_TIMERS: return "TIMERS";
|
||||
case DEBUG_CATEGORY_NORMALIZER: return "NORMALIZER";
|
||||
case DEBUG_CATEGORY_BGP: return "BGP";
|
||||
case DEBUG_CATEGORY_SOCKET: return "SOCKET";
|
||||
case DEBUG_CATEGORY_CONTROL: return "CONTROL";
|
||||
case DEBUG_CATEGORY_DUMP: return "DUMP";
|
||||
case DEBUG_CATEGORY_TRAFFIC: return "TRAFFIC";
|
||||
case DEBUG_CATEGORY_DEBUG: return "DEBUG";
|
||||
case DEBUG_CATEGORY_GENERAL: return "GENERAL";
|
||||
case DEBUG_CATEGORY_NAT: return "NAT";
|
||||
case DEBUG_CATEGORY_KEEPALIVE: return "KEEPALIVE";
|
||||
case DEBUG_CATEGORY_NONE: return "NONE";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
for (int i = 0; g_debug_categories[i].name; i++)
|
||||
if (g_debug_categories[i].category == category_idx) return g_debug_categories[i].name;
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
/* Global debug configuration */
|
||||
@@ -241,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) */
|
||||
@@ -301,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;
|
||||
}
|
||||
@@ -364,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;
|
||||
}
|
||||
@@ -416,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);
|
||||
@@ -454,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
|
||||
}
|
||||
|
||||
+17
-14
@@ -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) */
|
||||
@@ -59,7 +60,11 @@ typedef int debug_category_t;
|
||||
#define DEBUG_CATEGORY_GENERAL 19 // Messages for user (common log)
|
||||
#define DEBUG_CATEGORY_NAT 20 // EIM NAT module
|
||||
#define DEBUG_CATEGORY_KEEPALIVE 21 // Link keepalive logic
|
||||
#define DEBUG_CATEGORY_COUNT 22 // Total number of categories
|
||||
#define DEBUG_CATEGORY_ETCPROUTE 22 // ETCP routing
|
||||
#define DEBUG_CATEGORY_BBR 23 // BBR congestion control
|
||||
#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 */
|
||||
@@ -73,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 */
|
||||
@@ -85,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) */
|
||||
@@ -104,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);
|
||||
@@ -122,7 +125,7 @@ ip_str_t ip_to_str(const void *addr, int family);// big endian addr
|
||||
ip_str_t sockaddr_storage_to_str(const struct sockaddr_storage *addr);// автоопределение v4 или v6
|
||||
|
||||
// hex dump в лог
|
||||
void log_dump(const char* prefix, const uint8_t* data, size_t len);
|
||||
void log_dump(int level, int category, const char* prefix, const uint8_t* data, size_t len);
|
||||
|
||||
/* Check if debug output should be shown for given level and category */
|
||||
int debug_should_output(debug_level_t level, debug_category_t category_idx);
|
||||
|
||||
@@ -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>
|
||||
+198
-43
@@ -46,8 +46,23 @@ static inline void queue_check_thread(struct ll_queue* q) {
|
||||
// Предварительные объявления внутренних функций
|
||||
static void queue_resume_timeout_cb(void* arg);
|
||||
static void check_waiters(struct ll_queue* q);
|
||||
static void add_to_hash(struct ll_queue* q, struct ll_entry* entry);
|
||||
static void remove_from_hash(struct ll_queue* q, struct ll_entry* entry);
|
||||
static void empty_trampoline(void* arg);
|
||||
static void on_get_trampoline(void* arg);
|
||||
|
||||
static void empty_trampoline(void* arg) {
|
||||
struct ll_queue* q = (struct ll_queue*)arg;
|
||||
q->empty_call_soon_id = NULL;
|
||||
queue_callback_fn cb = q->empty_callback;
|
||||
void* cb_arg = q->empty_callback_arg;
|
||||
q->empty_callback = NULL;
|
||||
q->empty_callback_arg = NULL;
|
||||
if (q->count == 0 && cb) cb(q, cb_arg);
|
||||
}
|
||||
|
||||
static void on_get_trampoline(void* arg) {
|
||||
struct ll_queue* q = (struct ll_queue*)arg;
|
||||
if (q->on_get) q->on_get(q, q->on_get_arg);
|
||||
}
|
||||
|
||||
// ==================== Управление очередью ====================
|
||||
|
||||
@@ -60,6 +75,9 @@ struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size, uint16_t index_o
|
||||
q->name = name;
|
||||
q->ua = ua;
|
||||
q->size_limit = -1; // Без ограничения по умолчанию
|
||||
q->threshold_max_packets = 0; // По умолчанию: ждать пустой очереди
|
||||
q->threshold_max_bytes = 0; // По умолчанию: не проверять байты
|
||||
q->waiter_defer = 0;
|
||||
q->hash_size = hash_size;
|
||||
q->index_offset = index_offset;
|
||||
q->index_size = index_size;
|
||||
@@ -112,6 +130,17 @@ void queue_free(struct ll_queue* q) {
|
||||
// ВАЖНО: Не освобождаем элементы в очереди - они должны быть извлечены отдельно
|
||||
// Это упрощает архитектуру и предотвращает double-u_free
|
||||
|
||||
// Освободить список 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->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;
|
||||
}
|
||||
q->waiter_head = NULL;
|
||||
q->waiter_tail = NULL;
|
||||
|
||||
// Освободить хеш-таблицу
|
||||
if (q->hash_table) {
|
||||
u_free(q->hash_table);
|
||||
@@ -122,6 +151,14 @@ void queue_free(struct ll_queue* q) {
|
||||
uasync_call_soon_cancel(q->ua, q->resume_timeout_id);
|
||||
q->resume_timeout_id = NULL;
|
||||
}
|
||||
|
||||
void* old_eid = q->empty_call_soon_id;
|
||||
q->empty_call_soon_id = NULL;
|
||||
if (old_eid) uasync_call_soon_cancel(q->ua, old_eid);
|
||||
q->empty_callback = NULL;
|
||||
q->empty_callback_arg = NULL;
|
||||
q->on_get = NULL;
|
||||
q->on_get_arg = NULL;
|
||||
|
||||
u_free(q);
|
||||
}
|
||||
@@ -266,24 +303,47 @@ static uint32_t make_hash(const void* data, uint16_t len) {// алгоритм F
|
||||
return hash;
|
||||
}
|
||||
|
||||
// Проверить и запустить ожидающие коллбэки
|
||||
static void waiter_defer_cb(void* arg) {
|
||||
struct queue_waiter_handle* h = (struct queue_waiter_handle*)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;
|
||||
h->call_soon_id = uasync_call_soon(q->ua, h, waiter_defer_cb);
|
||||
} else {
|
||||
cb(q, arg);
|
||||
}
|
||||
}
|
||||
|
||||
// Проверить и запустить первый ожидающий коллбэк (FIFO / round-robin)
|
||||
static void check_waiters(struct ll_queue* q) {
|
||||
if (!q) return;
|
||||
if (!q || !q->waiter_head) return;
|
||||
|
||||
// DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: checking waiters, count=%d, bytes=%zu", q->count, q->total_bytes);
|
||||
const int cond = q->count <= q->threshold_max_packets
|
||||
&& (q->threshold_max_bytes == 0 || q->total_bytes <= q->threshold_max_bytes);
|
||||
if (!cond) return;
|
||||
|
||||
struct queue_waiter* waiter = &q->waiter;
|
||||
struct queue_waiter* waiter = q->waiter_head;
|
||||
q->waiter_head = waiter->next;
|
||||
if (!q->waiter_head) q->waiter_tail = NULL;
|
||||
|
||||
if (waiter->callback) {
|
||||
// Проверить условие: не больше max_packets и не больше max_bytes
|
||||
// max_bytes = 0 означает "не проверять байты"
|
||||
if (q->count <= waiter->max_packets && (waiter->max_bytes == 0 || q->total_bytes <= waiter->max_bytes)) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition met, calling callback, count=%d<=%d, bytes=%zu<=%zu (max_bytes_check=%s)",
|
||||
q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes,
|
||||
waiter->max_bytes == 0 ? "disabled" : "enabled");
|
||||
waiter->callback(q, waiter->callback_arg);
|
||||
memset(waiter, 0, sizeof(*waiter));
|
||||
}
|
||||
struct queue_waiter_handle* h = waiter->handle;
|
||||
if (h) h->internal = NULL;
|
||||
|
||||
queue_threshold_callback_fn cb = waiter->callback;
|
||||
void* arg = waiter->callback_arg;
|
||||
u_free(waiter);
|
||||
|
||||
if (cb) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: waking head waiter, count=%d<=%d, bytes=%zu<=%zu",
|
||||
q->count, q->threshold_max_packets, q->total_bytes, q->threshold_max_bytes);
|
||||
queue_waiter_call(q, cb, arg, h);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,6 +353,12 @@ int queue_data_put(struct ll_queue* q, struct ll_entry* entry) {
|
||||
queue_check_thread(q);
|
||||
#endif
|
||||
|
||||
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] double put detected! entry=%p prev=%p next=%p len=%u",
|
||||
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next, entry->len);
|
||||
abort();
|
||||
}
|
||||
|
||||
if (q->hash_size > 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] Put no-hash data to hash queue",q->name);
|
||||
queue_dgram_free(entry);
|
||||
@@ -334,6 +400,12 @@ int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry) {
|
||||
queue_check_thread(q);
|
||||
#endif
|
||||
|
||||
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] double put_with_index detected! entry=%p prev=%p next=%p",
|
||||
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next);
|
||||
abort();
|
||||
}
|
||||
|
||||
if (q->index_size == 0 || (size_t)q->index_offset + q->index_size > (size_t)entry->size) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] invalid index: offset=%u size=%u (data_size=%u)",q->name, q->index_offset, q->index_size, entry->size);
|
||||
queue_dgram_free(entry);
|
||||
@@ -377,6 +449,12 @@ int queue_data_put_first(struct ll_queue* q, struct ll_entry* entry) {
|
||||
queue_check_thread(q);
|
||||
#endif
|
||||
|
||||
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] double put_first detected! entry=%p prev=%p next=%p",
|
||||
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next);
|
||||
abort();
|
||||
}
|
||||
|
||||
if (q->hash_size > 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] Put no-hash data to hash queue",q->name);
|
||||
queue_dgram_free(entry);
|
||||
@@ -410,6 +488,12 @@ int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry)
|
||||
queue_check_thread(q);
|
||||
#endif
|
||||
|
||||
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] double put_first_with_index detected! entry=%p prev=%p next=%p",
|
||||
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next);
|
||||
abort();
|
||||
}
|
||||
|
||||
if (q->index_size == 0 || (size_t)q->index_offset + q->index_size > (size_t)entry->size) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] invalid index: offset=%u size=%u (data_size=%u)",q->name, q->index_offset, q->index_size, entry->size);
|
||||
queue_dgram_free(entry);
|
||||
@@ -476,6 +560,12 @@ struct ll_entry* queue_data_get(struct ll_queue* q) {
|
||||
// Проверить ожидающие коллбэки
|
||||
check_waiters(q);
|
||||
|
||||
if (q->count == 0 && q->empty_callback && !q->empty_call_soon_id)
|
||||
q->empty_call_soon_id = uasync_call_soon(q->ua, q, (timeout_callback_t)empty_trampoline);
|
||||
|
||||
if (q->on_get)
|
||||
uasync_call_soon(q->ua, q, (timeout_callback_t)on_get_trampoline);
|
||||
|
||||
#ifdef QUEUE_DEBUG
|
||||
queue_check_consistency(q);// !!!! for debug
|
||||
#endif
|
||||
@@ -489,14 +579,27 @@ int queue_entry_count(struct ll_queue* q) {
|
||||
// Функция проверки консистентности count и total_bytes
|
||||
// Возвращает 0 если ok, -1 если есть несоответствия
|
||||
int queue_check_consistency(struct ll_queue* q) {
|
||||
if (!q) return -1; // Недопустимая очередь
|
||||
if (!q) return -1;
|
||||
|
||||
// Проверка: если count > 0, то head не должен быть NULL
|
||||
if (q->count > 0 && !q->head) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "Queue '%s': count=%d but head is NULL!",
|
||||
q->name ? q->name : "unknown", q->count);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Floyd cycle detection — за O(N) без риска зависания
|
||||
{
|
||||
struct ll_entry *slow = q->head, *fast = q->head;
|
||||
while (fast && fast->next) {
|
||||
slow = slow->next;
|
||||
fast = fast->next->next;
|
||||
if (slow == fast) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "Queue '%s': CYCLE detected! entry=%p count=%d",
|
||||
q->name ? q->name : "unknown", (void*)slow, q->count);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int actual_count = 0;
|
||||
size_t actual_bytes = 0;
|
||||
@@ -507,7 +610,6 @@ int queue_check_consistency(struct ll_queue* q) {
|
||||
actual_bytes += current->int_len;
|
||||
if (current->next) {
|
||||
if (current->next->prev != current) {
|
||||
// Несоответствие в связях prev/next
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "Queue '%s': prev/next error at entry %p != %p entries: %d!=%d bytes: %zu!=%zu",
|
||||
q->name ? q->name : "unknown", (void*)current, (void*)current->next->prev, actual_count, q->count, actual_bytes, q->total_bytes);
|
||||
return -1;
|
||||
@@ -532,34 +634,87 @@ int queue_check_consistency(struct ll_queue* q) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ==================== Асинхронное ожидание ====================
|
||||
// ==================== Пороговое ожидание (backpressure) ====================
|
||||
|
||||
struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes,
|
||||
queue_threshold_callback_fn callback, void* arg) {
|
||||
if (!q || !callback) return NULL;
|
||||
|
||||
struct queue_waiter* waiter = &q->waiter;
|
||||
|
||||
// Проверить условие немедленно
|
||||
if (q->count <= max_packets && (max_bytes == 0 || q->total_bytes <= max_bytes)) {
|
||||
// Условие уже выполнено - вызвать коллбэк немедленно
|
||||
callback(q, arg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Установить waiter для отложенного вызова
|
||||
waiter->max_packets = max_packets;
|
||||
waiter->max_bytes = max_bytes;
|
||||
waiter->callback = callback;
|
||||
waiter->callback_arg = arg;
|
||||
|
||||
return waiter;
|
||||
void queue_set_threshold(struct ll_queue* q, int max_packets, size_t max_bytes) {
|
||||
if (!q) return;
|
||||
q->threshold_max_packets = max_packets;
|
||||
q->threshold_max_bytes = max_bytes;
|
||||
}
|
||||
|
||||
void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter) {
|
||||
if (!q || !waiter || waiter != &q->waiter) return;
|
||||
void queue_set_waiter_defer(struct ll_queue* q, int enable) {
|
||||
if (!q) return;
|
||||
q->waiter_defer = enable;
|
||||
}
|
||||
|
||||
void queue_set_empty_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg) {
|
||||
if (!q) return;
|
||||
void* old = q->empty_call_soon_id;
|
||||
q->empty_call_soon_id = NULL;
|
||||
if (old) uasync_call_soon_cancel(q->ua, old);
|
||||
q->empty_callback = cbk_fn;
|
||||
q->empty_callback_arg = arg;
|
||||
if (cbk_fn && q->count == 0)
|
||||
q->empty_call_soon_id = uasync_call_soon(q->ua, q, (timeout_callback_t)empty_trampoline);
|
||||
}
|
||||
|
||||
void queue_set_on_get(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg) {
|
||||
if (!q) return;
|
||||
q->on_get = cbk_fn;
|
||||
q->on_get_arg = arg;
|
||||
}
|
||||
|
||||
int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h,
|
||||
queue_threshold_callback_fn callback, void* arg) {
|
||||
if (!q || !h) return -1;
|
||||
|
||||
if (h->internal) queue_waiter_cancel(q, h);
|
||||
|
||||
if (q->count <= q->threshold_max_packets
|
||||
&& (q->threshold_max_bytes == 0 || q->total_bytes <= q->threshold_max_bytes)) {
|
||||
queue_waiter_call(q, callback, arg, h);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct queue_waiter* waiter = u_malloc(sizeof(struct queue_waiter));
|
||||
if (!waiter) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] queue_waiter_wait: u_malloc failed", q->name);
|
||||
return -1;
|
||||
}
|
||||
waiter->next = NULL;
|
||||
waiter->handle = h;
|
||||
waiter->callback = callback;
|
||||
waiter->callback_arg = arg;
|
||||
h->internal = waiter;
|
||||
|
||||
if (q->waiter_tail) q->waiter_tail->next = waiter; else q->waiter_head = waiter;
|
||||
q->waiter_tail = waiter;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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; }
|
||||
|
||||
memset(waiter, 0, sizeof(*waiter));
|
||||
struct queue_waiter* waiter = h->internal;
|
||||
struct queue_waiter* prev = NULL;
|
||||
struct queue_waiter* curr = q->waiter_head;
|
||||
while (curr && curr != waiter) {
|
||||
prev = curr;
|
||||
curr = curr->next;
|
||||
}
|
||||
if (!curr) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] queue_waiter_cancel: waiter not in list", q->name);
|
||||
h->internal = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (prev) prev->next = waiter->next; else q->waiter_head = waiter->next;
|
||||
if (waiter == q->waiter_tail) q->waiter_tail = prev;
|
||||
|
||||
h->internal = NULL;
|
||||
u_free(waiter);
|
||||
}
|
||||
|
||||
// ==================== Поиск и удаление по ID ====================
|
||||
|
||||
+95
-20
@@ -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
|
||||
@@ -12,7 +17,7 @@
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#define QUEUE_DEBUG 1
|
||||
//#define QUEUE_DEBUG 1 // раскомментировать для отладки очереди
|
||||
//#define QUEUE_THREAD_CHECK 1 // 0 to disable
|
||||
|
||||
/**
|
||||
@@ -26,7 +31,7 @@
|
||||
* - Обязательный вызов queue_resume_callback после обработки элемента
|
||||
* - Поддержка пулов памяти для entry и отдельно для dgram
|
||||
* - Хеш-таблица для быстрого поиска по ID (опционально)
|
||||
* - Встроенный одиночный waiter для ожидания освобождения очереди до заданного порога
|
||||
* - Связный список waiters для backpressure c общим порогом (round-robin)
|
||||
* - Использует uasync для отложенного возобновления callback'ов (без рекурсии в стеке)
|
||||
*
|
||||
* @note Важные правила использования автозабора (queue_set_callback):
|
||||
@@ -37,13 +42,15 @@
|
||||
* 5. queue_data_get() автоматически suspend'ит коллбэки для предотвращения рекурсии
|
||||
*
|
||||
* @note Память:
|
||||
* - queue_free() освобождает ТОЛЬКО структуру очереди и хеш-таблицу
|
||||
* - queue_free() освобождает структуру очереди, хеш-таблицу и список waiters
|
||||
* (handle->internal каждого ожидающего сбрасывается в NULL)
|
||||
* - Все элементы должны быть извлечены через queue_data_get() и освобождены через queue_entry_free()
|
||||
* - dgram освобождается отдельно через queue_dgram_free() или кастомную dgram_free_fn
|
||||
*/
|
||||
|
||||
// Forward declaration
|
||||
// Forward declarations
|
||||
struct ll_queue;
|
||||
struct queue_waiter_handle;
|
||||
|
||||
/**
|
||||
* @struct ll_entry
|
||||
@@ -84,7 +91,7 @@ typedef void (*queue_callback_fn)(struct ll_queue* q, void* arg);
|
||||
|
||||
/**
|
||||
* @typedef queue_threshold_callback_fn
|
||||
* @brief Одноразовый коллбэк при достижении порога очереди (queue_wait_threshold).
|
||||
* @brief Одноразовый коллбэк при достижении порога очереди.
|
||||
* @param q указатель на очередь
|
||||
* @param arg пользовательский аргумент
|
||||
*/
|
||||
@@ -92,13 +99,30 @@ typedef void (*queue_threshold_callback_fn)(struct ll_queue* q, void* arg);
|
||||
|
||||
/**
|
||||
* @struct queue_waiter
|
||||
* @brief Описание ожидания освобождения места в очереди (встроен в ll_queue — только один).
|
||||
* @brief Внутренний узел в связном списке ожидающих (очередь владеет, аллоцирует/освобождает сама).
|
||||
*/
|
||||
struct queue_waiter {
|
||||
int max_packets; ///< Максимально допустимое количество элементов
|
||||
size_t max_bytes; ///< Максимально допустимый объём данных (0 = не проверять)
|
||||
queue_threshold_callback_fn callback; ///< Коллбэк (вызывается один раз)
|
||||
void* callback_arg; ///< Аргумент коллбэка
|
||||
struct queue_waiter* next; ///< Следующий в списке ожидающих
|
||||
struct queue_waiter_handle* handle; ///< Обратная ссылка на публичную структуру
|
||||
queue_threshold_callback_fn callback;
|
||||
void* callback_arg;
|
||||
};
|
||||
|
||||
/**
|
||||
* @struct queue_waiter_handle
|
||||
* @brief Публичная управляющая структура (встраивается в структуру вызывающей стороны).
|
||||
*
|
||||
* Вызывающая сторона:
|
||||
* 1. Объявляет поле struct queue_waiter_handle в своей структуре (= {0} или u_calloc)
|
||||
* 3. Вызывает queue_waiter_wait() когда хочет дождаться освобождения очереди
|
||||
* 4. Вызывает queue_waiter_cancel() при деинициализации для отмены ожидания
|
||||
*/
|
||||
struct queue_waiter_handle {
|
||||
struct queue_waiter* internal; ///< NULL = не ждём; иначе — внутренний узел
|
||||
struct ll_queue* defer_q;
|
||||
queue_threshold_callback_fn defer_cb;
|
||||
void* defer_arg;
|
||||
void* call_soon_id; ///< хендл отложенного вызова (для отмены)
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -120,7 +144,18 @@ struct ll_queue {
|
||||
void* resume_timeout_id; // ID таймера uasync для отложенного resume
|
||||
struct UASYNC* ua; // Экземпляр uasync (обязателен для таймеров)
|
||||
|
||||
struct queue_waiter waiter; // Встроенный waiter (только один)
|
||||
struct queue_waiter* waiter_head; // Голова списка ожидающих
|
||||
struct queue_waiter* waiter_tail; // Хвост списка ожидающих
|
||||
int threshold_max_packets; // Общий порог: макс. кол-во элементов (по умолчанию 0)
|
||||
size_t threshold_max_bytes; // Общий порог: макс. объём данных (0 = не проверять)
|
||||
int waiter_defer; // 0=immediate callback, 1=defer via uasync_call_soon
|
||||
|
||||
queue_callback_fn empty_callback; // одноразовый коллбэк при опустошении очереди
|
||||
void* empty_callback_arg;
|
||||
void* empty_call_soon_id; // handle от uasync_call_soon
|
||||
|
||||
queue_callback_fn on_get; // коллбэк после каждого queue_data_get (deferred)
|
||||
void* on_get_arg;
|
||||
|
||||
struct ll_entry** hash_table; // Хеш-таблица для поиска по id (если hash_size > 0)
|
||||
size_t hash_size; // Размер хеш-таблицы
|
||||
@@ -190,26 +225,62 @@ void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg)
|
||||
*/
|
||||
void queue_resume_callback(struct ll_queue* q);
|
||||
|
||||
/* ==================== Пороговое ожидание ==================== */
|
||||
/* ==================== Пороговое ожидание (backpressure) ==================== */
|
||||
|
||||
/**
|
||||
* @brief Регистрирует одноразовый коллбэк, который сработает, когда очередь освободится до заданного порога.
|
||||
* @brief Устанавливает общий порог освобождения очереди.
|
||||
* @param q очередь
|
||||
* @param max_packets максимальное количество элементов
|
||||
* @param max_packets максимальное количество элементов (условие: count <= max_packets)
|
||||
* @param max_bytes максимальный объём данных (0 = не проверять)
|
||||
*
|
||||
* Порог — общий на всю очередь. Все waiters в связном списке используют этот порог.
|
||||
* При достижении порога вызывается следующий waiter из списка (FIFO / round-robin).
|
||||
* По умолчанию: max_packets=0, max_bytes=0 (пустая очередь).
|
||||
*/
|
||||
void queue_set_threshold(struct ll_queue* q, int max_packets, size_t max_bytes);
|
||||
|
||||
void queue_set_waiter_defer(struct ll_queue* q, int enable);
|
||||
|
||||
/**
|
||||
* @brief Устанавливает одноразовый коллбэк при опустошении очереди (count==0).
|
||||
* @param q очередь
|
||||
* @param cbk_fn коллбэк (NULL для отмены)
|
||||
* @param arg пользовательский аргумент
|
||||
*
|
||||
* Коллбэк вызывается асинхронно (через uasync_call_soon) когда count становится 0.
|
||||
* После вызова сбрасывается — для повторного использования нужно вызвать снова.
|
||||
*/
|
||||
void queue_set_empty_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
|
||||
|
||||
/**
|
||||
* @brief Устанавливает коллбэк вызываемый после каждого queue_data_get (deferred через call_soon).
|
||||
* @param q очередь
|
||||
* @param cbk_fn коллбэк (NULL для отмены)
|
||||
* @param arg пользовательский аргумент
|
||||
*/
|
||||
void queue_set_on_get(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
|
||||
|
||||
/**
|
||||
* @brief Регистрирует ожидание освобождения очереди до общего порога.
|
||||
* @param q очередь
|
||||
* @param h указатель на handle (создаётся как `struct queue_waiter_handle h = {0}` или `h.internal = NULL`)
|
||||
* @param callback функция, вызываемая при достижении порога
|
||||
* @param arg аргумент коллбэка
|
||||
* @return указатель на waiter (для отмены) или NULL, если условие уже выполнено (коллбэк вызван сразу)
|
||||
* @return 1 — условие уже выполнено (callback вызван немедленно), 0 — зарегистрирован в списке, -1 — ошибка
|
||||
*
|
||||
* Если handle уже ожидает (h->internal != NULL) — старый waiter отменяется и создаётся новый.
|
||||
*/
|
||||
struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes,
|
||||
queue_threshold_callback_fn callback, void* arg);
|
||||
int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h,
|
||||
queue_threshold_callback_fn callback, void* arg);
|
||||
|
||||
/**
|
||||
* @brief Отменяет ранее установленное ожидание порога.
|
||||
* @brief Отменяет ожидание (удаляет из списка, если зарегистрирован).
|
||||
* @param q очередь
|
||||
* @param waiter указатель, возвращённый queue_wait_threshold()
|
||||
* @param h указатель на handle
|
||||
*
|
||||
* Безопасно вызывать в любом состоянии: если waiter не зарегистрирован — ничего не делает.
|
||||
*/
|
||||
void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter);
|
||||
void queue_waiter_cancel(struct ll_queue* q, struct queue_waiter_handle* h);
|
||||
|
||||
/* ==================== Работа с данными ==================== */
|
||||
|
||||
@@ -346,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
|
||||
@@ -14,8 +14,9 @@
|
||||
#define PADDING_FILL 0xAA
|
||||
#define METADATA_SIZE 128
|
||||
#define POINTER_SIZE sizeof(void*)
|
||||
#define LOCATION_MAX (METADATA_SIZE - POINTER_SIZE)
|
||||
#define NEXT_OFFSET LOCATION_MAX
|
||||
#define LOCATION_MAX (METADATA_SIZE - POINTER_SIZE * 2)
|
||||
#define PREV_OFFSET LOCATION_MAX
|
||||
#define NEXT_OFFSET (LOCATION_MAX + POINTER_SIZE)
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
|
||||
#include <execinfo.h>
|
||||
@@ -136,8 +137,12 @@ void* u_malloc_impl(uint32_t size, const char* location) {
|
||||
*(uint32_t*)(prefix_start + BOUNDARY_CHECK_SIZE + 8 + size) = CANARY;
|
||||
// Add to list (thread-safe)
|
||||
LOCK();
|
||||
void** prev_ptr = (void**)(base + PREV_OFFSET);
|
||||
void** next_ptr = (void**)(base + NEXT_OFFSET);
|
||||
*prev_ptr = NULL;
|
||||
*next_ptr = allocated_head;
|
||||
if (allocated_head)
|
||||
*(void**)((uint8_t*)allocated_head + PREV_OFFSET) = base;
|
||||
allocated_head = base;
|
||||
allocated_count++;
|
||||
allocations_count++;
|
||||
@@ -175,29 +180,14 @@ void u_free_impl(void* ptr, const char* location) {
|
||||
uint8_t* base = (uint8_t*)ptr - 8 - BOUNDARY_CHECK_SIZE - METADATA_SIZE;
|
||||
char* alloc_loc = (char*)base;
|
||||
uint32_t size = *(uint32_t*)(base + METADATA_SIZE + BOUNDARY_CHECK_SIZE + 4);
|
||||
// Remove from linked list (thread-safe)
|
||||
// Remove from doubly-linked list (O(1))
|
||||
LOCK();
|
||||
void* prev = NULL;
|
||||
void* curr = allocated_head;
|
||||
bool found = false;
|
||||
while (curr) {
|
||||
if (curr == base) {
|
||||
if (prev) {
|
||||
*(void**)((uint8_t*)prev + NEXT_OFFSET) = *(void**)((uint8_t*)curr + NEXT_OFFSET);
|
||||
} else {
|
||||
allocated_head = *(void**)((uint8_t*)curr + NEXT_OFFSET);
|
||||
}
|
||||
allocated_count--;
|
||||
frees_count++;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
prev = curr;
|
||||
curr = *(void**)((uint8_t*)curr + NEXT_OFFSET);
|
||||
}
|
||||
void* prev = *(void**)(base + PREV_OFFSET);
|
||||
void* next = *(void**)(base + NEXT_OFFSET);
|
||||
size_t active_count = allocated_count;
|
||||
UNLOCK();
|
||||
if (!found) {
|
||||
|
||||
if (prev == NULL && allocated_head != base) {
|
||||
UNLOCK();
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "========================================");
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "DOUBLE FREE DETECTED");
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "========================================");
|
||||
@@ -215,24 +205,19 @@ void u_free_impl(void* ptr, const char* location) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "--- User data dump at %p (%u of %u bytes) ---", ptr, dump_size, size);
|
||||
hex_dump(ptr, dump_size);
|
||||
}
|
||||
#if BACKTRACE_ENABLED
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "--- Backtrace ---");
|
||||
# if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
|
||||
void* bt_buf[32];
|
||||
int bt_size = backtrace(bt_buf, 32);
|
||||
char** bt_symbols = backtrace_symbols(bt_buf, bt_size);
|
||||
if (bt_symbols) {
|
||||
for (int i = 0; i < bt_size; i++) DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, " #%d %s", i, bt_symbols[i]);
|
||||
free(bt_symbols);
|
||||
}
|
||||
# elif defined(_WIN32)
|
||||
void* bt_buf[32];
|
||||
USHORT frames = CaptureStackBackTrace(0, 32, bt_buf, NULL);
|
||||
for (USHORT i = 0; i < frames; i++) DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, " #%d %p", i, bt_buf[i]);
|
||||
# endif
|
||||
#endif
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (prev)
|
||||
*(void**)((uint8_t*)prev + NEXT_OFFSET) = next;
|
||||
else
|
||||
allocated_head = next;
|
||||
if (next)
|
||||
*(void**)((uint8_t*)next + PREV_OFFSET) = prev;
|
||||
|
||||
allocated_count--;
|
||||
frees_count++;
|
||||
UNLOCK();
|
||||
uint32_t total = size + 12 + 2 * BOUNDARY_CHECK_SIZE + METADATA_SIZE;
|
||||
memset(base, 0, total);
|
||||
free(base);
|
||||
|
||||
+66
-13
@@ -5,6 +5,49 @@
|
||||
#include "memory_pool.h"
|
||||
#include "mem.h"
|
||||
|
||||
// Layout: [user_data: object_size] [canary: 4 bytes] [alloc_loc: pointer] [counter: 1 byte]
|
||||
#define POOL_CANARY_OFF (pool->object_size)
|
||||
#define POOL_LOC_OFF (pool->object_size + 4)
|
||||
#define POOL_COUNTER_OFF (pool->object_size + 4 + sizeof(const char*))
|
||||
#define POOL_ALLOC_SIZE (pool->object_size + 4 + sizeof(const char*) + 1)
|
||||
#define POOL_CANARY_VAL 0xDEADBEAF
|
||||
|
||||
static void pool_init_tags(struct memory_pool* pool, void* obj, const char* location)
|
||||
{
|
||||
uint32_t* canary = (uint32_t*)((uint8_t*)obj + POOL_CANARY_OFF);
|
||||
const char** loc = (const char**)((uint8_t*)obj + POOL_LOC_OFF);
|
||||
uint8_t* counter = (uint8_t*)obj + POOL_COUNTER_OFF;
|
||||
*canary = POOL_CANARY_VAL;
|
||||
*loc = location;
|
||||
*counter = 1;
|
||||
}
|
||||
|
||||
static void pool_check_and_clear_tags(struct memory_pool* pool, void* obj, const char* location)
|
||||
{
|
||||
uint32_t* canary = (uint32_t*)((uint8_t*)obj + POOL_CANARY_OFF);
|
||||
const char** loc = (const char**)((uint8_t*)obj + POOL_LOC_OFF);
|
||||
uint8_t* counter = (uint8_t*)obj + POOL_COUNTER_OFF;
|
||||
|
||||
if (*canary != POOL_CANARY_VAL) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "pool_free BUFFER OVERFLOW pool=%p name=%s sz=%zu allocs=%zu reuse=%zu free=%d",
|
||||
pool, pool->name ? pool->name : "?", pool->object_size, pool->allocations, pool->reuse_count, pool->free_count);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, " obj=%p alloc=%s free=%s canary=0x%08x expected=0x%08x",
|
||||
obj, *loc ? *loc : "(null)", location, *canary, POOL_CANARY_VAL);
|
||||
if (pool->object_size)
|
||||
log_dump(DEBUG_LEVEL_ERROR, DEBUG_CATEGORY_MEMORY, " user_data", obj, pool->object_size);
|
||||
volatile int _halt = 1;
|
||||
while (_halt) {}
|
||||
}
|
||||
if (*counter == 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "pool_free DOUBLE FREE pool=%p name=%s sz=%zu allocs=%zu reuse=%zu alloc=%s free=%s",
|
||||
pool, pool->name ? pool->name : "?", pool->object_size, pool->allocations, pool->reuse_count,
|
||||
*loc ? *loc : "(null)", location);
|
||||
volatile int _halt = 1;
|
||||
while (_halt) {}
|
||||
}
|
||||
*counter = 0;
|
||||
}
|
||||
|
||||
static size_t g_total_free = 0;
|
||||
|
||||
size_t memory_pool_get_total_free_blocks(void) {
|
||||
@@ -12,7 +55,7 @@ size_t memory_pool_get_total_free_blocks(void) {
|
||||
}
|
||||
|
||||
// Инициализировать пул памяти
|
||||
struct memory_pool* memory_pool_init(size_t object_size) {
|
||||
struct memory_pool* memory_pool_init(size_t object_size, const char* name) {
|
||||
struct memory_pool* pool = u_calloc(1, sizeof(struct memory_pool));
|
||||
if (!pool) {
|
||||
return NULL;
|
||||
@@ -22,6 +65,8 @@ struct memory_pool* memory_pool_init(size_t object_size) {
|
||||
pool->free_count = 0;
|
||||
pool->allocations = 0;
|
||||
pool->reuse_count = 0;
|
||||
pool->alloc_tag_counter = 1;
|
||||
pool->name = name;
|
||||
return pool;
|
||||
}
|
||||
|
||||
@@ -33,18 +78,21 @@ void* memory_pool_alloc_impl(struct memory_pool* pool, const char* location) {
|
||||
|
||||
if (pool->free_head) {
|
||||
void* obj = pool->free_head;
|
||||
pool->free_head = *(void**)obj; // Извлекаем next из начала блока
|
||||
pool->free_head = *(void**)obj;
|
||||
pool->free_count--;
|
||||
g_total_free--;
|
||||
pool->reuse_count++;
|
||||
memset(obj, 0, pool->object_size); // Опционально: очищаем память для безопасности
|
||||
memset(obj, 0, pool->object_size);
|
||||
pool_init_tags(pool, obj, location);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_alloc reused: %s, remaining=%zu", location, pool->free_count);
|
||||
return obj;
|
||||
}
|
||||
|
||||
pool->allocations++;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_alloc: %s, total_allocs=%zu", location, pool->allocations);
|
||||
return u_calloc_impl(1, pool->object_size, location); // Используем calloc для инициализации нулями
|
||||
void* obj = u_calloc_impl(1, POOL_ALLOC_SIZE, location);
|
||||
if (obj) pool_init_tags(pool, obj, location);
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Освободить объект в пул или в free
|
||||
@@ -53,9 +101,10 @@ void memory_pool_free_impl(struct memory_pool* pool, void* obj, const char* loca
|
||||
return;
|
||||
}
|
||||
|
||||
// Если пул не заполнен, сохранить объект для повторного использования
|
||||
pool_check_and_clear_tags(pool, obj, location);
|
||||
|
||||
if (pool->free_count < MEMORY_POOL_MAX_FREE) {
|
||||
*(void**)obj = pool->free_head; // Сохраняем next в начало блока
|
||||
*(void**)obj = pool->free_head;
|
||||
pool->free_head = obj;
|
||||
pool->free_count++;
|
||||
g_total_free++;
|
||||
@@ -63,7 +112,6 @@ void memory_pool_free_impl(struct memory_pool* pool, void* obj, const char* loca
|
||||
return;
|
||||
}
|
||||
|
||||
// Иначе освободить через free
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_free direct: %s", location);
|
||||
u_free_impl(obj, location);
|
||||
}
|
||||
@@ -87,21 +135,26 @@ void memory_pool_destroy(struct memory_pool* pool) {
|
||||
return;
|
||||
}
|
||||
|
||||
int cnt=0;
|
||||
// Освободить все объекты в пуле
|
||||
void* obj = pool->free_head;
|
||||
while (obj) {
|
||||
void* next = *(void**)obj;
|
||||
g_total_free--;
|
||||
u_free(obj);
|
||||
cnt++;
|
||||
obj = next;
|
||||
}
|
||||
|
||||
// if (cnt>MEMORY_POOL_MAX_FREE)
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "pool_destroy: allocations=%zu, reuse=%zu, current pool size=%d", pool->allocations, pool->reuse_count, cnt);
|
||||
|
||||
pool->free_head = NULL;
|
||||
pool->free_count = 0;
|
||||
u_free(pool);
|
||||
}
|
||||
|
||||
int memory_pool_is_freed(struct memory_pool* pool, void* obj)
|
||||
{
|
||||
if (!pool || !obj) return 0;
|
||||
void* cur = pool->free_head;
|
||||
while (cur) {
|
||||
if (cur == obj) return 1;
|
||||
cur = *(void**)cur;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
+19
-6
@@ -2,6 +2,11 @@
|
||||
#ifndef MEMORY_POOL_H
|
||||
#define MEMORY_POOL_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#include <stddef.h>
|
||||
#include "debug_config.h"
|
||||
|
||||
@@ -12,16 +17,18 @@
|
||||
#define PLOCATION __FILE__ ":" XSTR(__LINE__) "[POOL]"
|
||||
|
||||
struct memory_pool {
|
||||
void* free_head; // Голова linked list свободных блоков (next хранится в начале каждого блока)
|
||||
int free_count; // Количество свободных блоков
|
||||
size_t object_size; // Размер объектов в пуле
|
||||
size_t allocations; // Общее количество аллокаций (включая новые malloc)
|
||||
size_t reuse_count; // Количество повторных использований из пула
|
||||
void* free_head; // Голова linked list свободных блоков (next хранится в начале каждого блока)
|
||||
int free_count; // Количество свободных блоков
|
||||
size_t object_size; // Размер объектов в пуле (пользовательские данные)
|
||||
size_t allocations; // Общее количество аллокаций (включая новые malloc)
|
||||
size_t reuse_count; // Количество повторных использований из пула
|
||||
uint8_t alloc_tag_counter; // монотонный счётчик для детекции double-free (1 байт)
|
||||
const char* name; // Имя пула для диагностики (напр. "data_pool", "pkt_pool")
|
||||
};
|
||||
|
||||
// сам пул:
|
||||
// если используем с элементами ll_queue то не забываем что к object_size надо прибавить sizeof(struct ll_entry)
|
||||
struct memory_pool* memory_pool_init(size_t object_size);
|
||||
struct memory_pool* memory_pool_init(size_t object_size, const char* name);
|
||||
void memory_pool_destroy(struct memory_pool* pool);
|
||||
|
||||
// элементы пула:
|
||||
@@ -33,4 +40,10 @@ size_t memory_pool_get_total_free_blocks(void);
|
||||
#define memory_pool_alloc(pool) memory_pool_alloc_impl(pool, PLOCATION)
|
||||
#define memory_pool_free(pool, obj) memory_pool_free_impl(pool, obj, PLOCATION)
|
||||
|
||||
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
|
||||
+459
@@ -0,0 +1,459 @@
|
||||
// tcp_io.c — управление TCP-соединением через uasync + ll_queue
|
||||
#include "tcp_io.h"
|
||||
#include "debug_config.h"
|
||||
#include "mem.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
#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);
|
||||
static void error_cb(socket_t sock, void* arg);
|
||||
static void resume_read_cb(struct ll_queue* q, void* arg);
|
||||
static void fin_deferred_cb(struct ll_queue* q, void* arg);
|
||||
static void write_queue_fetch_cb(struct ll_queue* q, void* arg);
|
||||
static int flush_write_buf(struct tcp_conn* tc);
|
||||
static void tcp_conn_handle_error(struct tcp_conn* tc, int err);
|
||||
static const uint8_t tcp_fin_sentinel;
|
||||
|
||||
struct tcp_conn* tcp_conn_create(
|
||||
struct UASYNC* ua, socket_t sock,
|
||||
size_t entry_data_size, size_t write_chunk_size,
|
||||
int read_high_water, int read_low_water, int rcvbuf_size,
|
||||
void (*on_fin)(struct tcp_conn* tc, void* arg),
|
||||
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", (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; }
|
||||
|
||||
tc->ua = ua;
|
||||
tc->sock = sock;
|
||||
tc->entry_data_size = entry_data_size;
|
||||
tc->write_chunk_size = write_chunk_size;
|
||||
tc->read_high_water = read_high_water;
|
||||
tc->read_low_water = read_low_water;
|
||||
tc->rcvbuf_size = rcvbuf_size;
|
||||
tc->on_fin = on_fin;
|
||||
tc->on_error = on_error;
|
||||
tc->arg = arg;
|
||||
|
||||
tc->entry_pool = memory_pool_init(sizeof(struct ll_entry), "entry_pool");
|
||||
{
|
||||
size_t ds = entry_data_size > write_chunk_size ? entry_data_size : write_chunk_size;
|
||||
tc->data_pool = memory_pool_init(ds, "data_pool");
|
||||
}
|
||||
if (!tc->entry_pool || !tc->data_pool) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: memory_pool_init failed");
|
||||
if (tc->entry_pool) memory_pool_destroy(tc->entry_pool);
|
||||
if (tc->data_pool) memory_pool_destroy(tc->data_pool);
|
||||
u_free(tc); return NULL;
|
||||
}
|
||||
|
||||
tc->read_queue = queue_new(ua, 0, 0, 0, "tcp_io_rq");
|
||||
tc->write_queue = queue_new(ua, 0, 0, 0, "tcp_io_wq");
|
||||
if (!tc->read_queue || !tc->write_queue) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: queue_new failed");
|
||||
if (tc->read_queue) queue_free(tc->read_queue);
|
||||
if (tc->write_queue) queue_free(tc->write_queue);
|
||||
memory_pool_destroy(tc->entry_pool);
|
||||
memory_pool_destroy(tc->data_pool);
|
||||
u_free(tc); return NULL;
|
||||
}
|
||||
queue_set_threshold(tc->read_queue, read_low_water, 0);
|
||||
queue_set_threshold(tc->write_queue, 32, 0);
|
||||
queue_set_callback(tc->write_queue, write_queue_fetch_cb, tc);
|
||||
queue_set_waiter_defer(tc->write_queue, 1);
|
||||
|
||||
tc->socket_id = uasync_add_socket_t(ua, sock, read_cb, write_cb, error_cb, tc);
|
||||
if (!tc->socket_id) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: uasync_add_socket_t failed");
|
||||
queue_free(tc->read_queue);
|
||||
queue_free(tc->write_queue);
|
||||
memory_pool_destroy(tc->entry_pool);
|
||||
memory_pool_destroy(tc->data_pool);
|
||||
u_free(tc); return NULL;
|
||||
}
|
||||
tc->write_monitor = 1;
|
||||
|
||||
if (rcvbuf_size > 0) {
|
||||
if (socket_set_buffers(sock, 0, rcvbuf_size) < 0)
|
||||
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: socket_set_buffers rcvbuf=%d failed fd=%d", rcvbuf_size, (int)sock);
|
||||
else
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: socket_set_buffers rcvbuf=%d ok fd=%d", rcvbuf_size, (int)sock);
|
||||
}
|
||||
|
||||
{
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t alen = sizeof(addr);
|
||||
if (getpeername(sock, (struct sockaddr*)&addr, &alen) == 0) tc->connected = 1;
|
||||
}
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: fd=%d entry=%zu chunk=%zu hw=%d lw=%d rcvbuf=%d connected=%d",
|
||||
(int)sock, entry_data_size, write_chunk_size, read_high_water, read_low_water, rcvbuf_size, tc->connected);
|
||||
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);
|
||||
|
||||
if (tc->socket_id) {
|
||||
uasync_remove_socket_t(tc->ua, tc->sock);
|
||||
tc->socket_id = NULL;
|
||||
}
|
||||
queue_waiter_cancel(tc->read_queue, &tc->read_waiter);
|
||||
queue_set_empty_callback(tc->read_queue, NULL, NULL);
|
||||
queue_set_callback(tc->read_queue, NULL, NULL);
|
||||
queue_set_callback(tc->write_queue, NULL, NULL);
|
||||
|
||||
struct ll_entry* e;
|
||||
while ((e = queue_data_get(tc->read_queue)) != NULL) {
|
||||
if (e->dgram) memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e); queue_resume_callback(tc->read_queue);
|
||||
}
|
||||
while ((e = queue_data_get(tc->write_queue)) != NULL) {
|
||||
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); 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); 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)
|
||||
{
|
||||
tc->error = 1;
|
||||
|
||||
queue_waiter_cancel(tc->read_queue, &tc->read_waiter);
|
||||
queue_set_empty_callback(tc->read_queue, NULL, NULL);
|
||||
queue_set_callback(tc->read_queue, NULL, NULL);
|
||||
|
||||
if (tc->sock != SOCKET_INVALID) {
|
||||
while (1) {
|
||||
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
|
||||
if (!e) break;
|
||||
uint8_t* buf = memory_pool_alloc(tc->data_pool);
|
||||
if (!buf) { queue_entry_free(e); break; }
|
||||
ssize_t n = recv(tc->sock, buf, tc->entry_data_size, 0);
|
||||
if (n > 0) { e->dgram = buf; e->len = (uint16_t)n; queue_data_put(tc->read_queue, e); }
|
||||
else { memory_pool_free(tc->data_pool, buf); queue_entry_free(e); break; }
|
||||
}
|
||||
}
|
||||
|
||||
if (tc->sock != SOCKET_INVALID) {
|
||||
uasync_remove_socket_t(tc->ua, tc->sock);
|
||||
tc->socket_id = NULL;
|
||||
#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; // ДО on_error: read/write в том же epoll event увидят INVALID
|
||||
}
|
||||
|
||||
queue_set_callback(tc->write_queue, NULL, NULL);
|
||||
|
||||
if (tc->on_error) {
|
||||
void (*on_err)(struct tcp_conn*, int, void*) = tc->on_error;
|
||||
void* arg = tc->arg;
|
||||
tc->on_error = NULL;
|
||||
on_err(tc, err, arg);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Чтение из сокета
|
||||
// ====================================================================
|
||||
|
||||
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 || !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);
|
||||
if (!e || !buf) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: read_cb alloc failed fd=%d", (int)tc->sock);
|
||||
if (e) queue_entry_free(e);
|
||||
if (buf) memory_pool_free(tc->data_pool, buf);
|
||||
uasync_set_socket_read(tc->ua, tc->socket_id, 0);
|
||||
tc->read_paused = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
ssize_t n = recv(tc->sock, buf, tc->entry_data_size, 0);
|
||||
if (n > 0) {
|
||||
e->dgram = buf;
|
||||
e->len = (uint16_t)n;
|
||||
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);
|
||||
}
|
||||
} else if (n == 0) {
|
||||
memory_pool_free(tc->data_pool, buf);
|
||||
queue_entry_free(e);
|
||||
tc->fin_remote = 1;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: FIN fd=%d", (int)tc->sock);
|
||||
uasync_set_socket_read(tc->ua, tc->socket_id, 0);
|
||||
queue_waiter_cancel(tc->read_queue, &tc->read_waiter);
|
||||
if (tc->read_queue->count == 0) {
|
||||
if (tc->on_fin) tc->on_fin(tc, tc->arg);
|
||||
} else {
|
||||
queue_set_empty_callback(tc->read_queue, fin_deferred_cb, tc);
|
||||
}
|
||||
} else {
|
||||
memory_pool_free(tc->data_pool, buf);
|
||||
queue_entry_free(e);
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: recv error fd=%d errno=%d", (int)tc->sock, errno);
|
||||
tcp_conn_handle_error(tc, errno);
|
||||
}
|
||||
}
|
||||
|
||||
static void resume_read_cb(struct ll_queue* q, void* arg) {
|
||||
(void)q;
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
if (!tc || tc->sock == SOCKET_INVALID) return;
|
||||
uasync_set_socket_read(tc->ua, tc->socket_id, 1);
|
||||
tc->read_paused = 0;
|
||||
}
|
||||
|
||||
static void fin_deferred_cb(struct ll_queue* q, void* arg) {
|
||||
(void)q;
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
if (tc->on_fin) tc->on_fin(tc, tc->arg);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Запись в сокет
|
||||
// ====================================================================
|
||||
|
||||
static int flush_write_buf(struct tcp_conn* tc) {
|
||||
while (tc->write_buf && tc->write_offset < tc->write_len) {
|
||||
ssize_t n = send(tc->sock, tc->write_buf + tc->write_offset, tc->write_len - tc->write_offset, MSG_NOSIGNAL);
|
||||
if (n > 0) {
|
||||
tc->write_offset += (size_t)n;
|
||||
if (tc->write_offset >= tc->write_len) {
|
||||
memory_pool_free(tc->data_pool, tc->write_buf);
|
||||
tc->write_buf = NULL;
|
||||
tc->write_len = 0;
|
||||
tc->write_offset = 0;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: flush_wbuf done fd=%d", (int)tc->sock);
|
||||
return 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) return 0;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: send error fd=%d errno=%d", (int)tc->sock, errno);
|
||||
tcp_conn_handle_error(tc, errno);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
|
||||
if (tc->error || tc->closed) { DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch skip fd=%d error=%d closed=%d", (int)tc->sock, tc->error, tc->closed); return; }
|
||||
|
||||
if (tc->write_buf) {
|
||||
if (flush_write_buf(tc)) return;
|
||||
if (tc->write_buf) return; // остался остаток, ждём write_cb
|
||||
}
|
||||
|
||||
if (!tc->connected) { DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch skip fd=%d not connected", (int)tc->sock); return; }
|
||||
|
||||
struct ll_entry* e = queue_data_get(q);
|
||||
if (!e) {
|
||||
uasync_set_socket_write(tc->ua, tc->socket_id, 0);
|
||||
tc->write_monitor = 0;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch POLLOUT OFF fd=%d (queue empty)", (int)tc->sock);
|
||||
queue_resume_callback(q);
|
||||
if (tc->on_flushed) {
|
||||
void (*cb)(struct tcp_conn*, void*) = tc->on_flushed;
|
||||
tc->on_flushed = NULL;
|
||||
cb(tc, tc->arg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (e->len == 0) {
|
||||
if (!e->dgram) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: CLOSE fd=%d", (int)tc->sock);
|
||||
queue_entry_free(e); queue_resume_callback(q);
|
||||
uasync_remove_socket_t(tc->ua, tc->sock);
|
||||
socket_close_wrapper(tc->sock);
|
||||
tc->socket_id = NULL; tc->sock = SOCKET_INVALID; tc->closed = 1;
|
||||
if (tc->on_closed) tc->on_closed(tc, tc->arg);
|
||||
} else if (e->dgram == &tcp_fin_sentinel) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: FIN fd=%d", (int)tc->sock);
|
||||
queue_entry_free(e); queue_resume_callback(q);
|
||||
shutdown(tc->sock, SHUT_WR);
|
||||
tc->fin_local = 1;
|
||||
if (tc->on_fin_sent) tc->on_fin_sent(tc, tc->arg);
|
||||
} else {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch zero-len entry with unknown dgram=%p fd=%d", (void*)e->dgram, (int)tc->sock);
|
||||
queue_entry_free(e); queue_resume_callback(q);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ssize_t n = send(tc->sock, e->dgram, e->len, MSG_NOSIGNAL);
|
||||
if (n == (ssize_t)e->len) {
|
||||
memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
queue_resume_callback(q);
|
||||
} else if (n > 0) {
|
||||
size_t rem = e->len - (size_t)n;
|
||||
tc->write_buf = memory_pool_alloc(tc->data_pool);
|
||||
if (!tc->write_buf) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: write_buf alloc failed fd=%d", (int)tc->sock);
|
||||
memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
tcp_conn_handle_error(tc, ENOMEM);
|
||||
return;
|
||||
}
|
||||
memcpy(tc->write_buf, e->dgram + n, rem);
|
||||
tc->write_len = rem;
|
||||
tc->write_offset = 0;
|
||||
memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
uasync_set_socket_write(tc->ua, tc->socket_id, 1);
|
||||
tc->write_monitor = 1;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch POLLOUT ON fd=%d (partial send, wbuf=%zu bytes)", (int)tc->sock, tc->write_len);
|
||||
} else if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) {
|
||||
tc->write_buf = memory_pool_alloc(tc->data_pool);
|
||||
if (!tc->write_buf) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: write_buf alloc failed fd=%d", (int)tc->sock);
|
||||
memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
tcp_conn_handle_error(tc, ENOMEM);
|
||||
return;
|
||||
}
|
||||
memcpy(tc->write_buf, e->dgram, e->len);
|
||||
tc->write_len = e->len;
|
||||
tc->write_offset = 0;
|
||||
memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
uasync_set_socket_write(tc->ua, tc->socket_id, 1);
|
||||
tc->write_monitor = 1;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch POLLOUT ON fd=%d (EAGAIN, wbuf=%zu bytes)", (int)tc->sock, tc->write_len);
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: send error fd=%d errno=%d", (int)tc->sock, errno);
|
||||
memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
tcp_conn_handle_error(tc, errno);
|
||||
}
|
||||
}
|
||||
|
||||
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 || !tc->write_queue) return;
|
||||
|
||||
if (!tc->connected) {
|
||||
int err = 0;
|
||||
socklen_t len = sizeof(err);
|
||||
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 {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: connect fail fd=%d err=%d", (int)tc->sock, err);
|
||||
tcp_conn_handle_error(tc, err ? err : -1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Асинхронная ошибка сокета
|
||||
// ====================================================================
|
||||
|
||||
static void error_cb(socket_t sock, void* arg) {
|
||||
(void)sock;
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
if (!tc || tc->sock == SOCKET_INVALID) return;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: async error fd=%d connected=%d err=%d fin_r=%d fin_l=%d closed=%d wmon=%d",
|
||||
(int)tc->sock, tc->connected, tc->error, tc->fin_remote, tc->fin_local, tc->closed, tc->write_monitor);
|
||||
tcp_conn_handle_error(tc, -1);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// FIN / Close через очередь отправки
|
||||
// ====================================================================
|
||||
|
||||
int tcp_conn_push_fin(struct tcp_conn* tc) {
|
||||
if (!tc || tc->sock == SOCKET_INVALID) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin invalid tc"); return -1; }
|
||||
if (tc->fin_local) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin already fin_local fd=%d", (int)tc->sock); return -1; }
|
||||
if (tc->closed) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin already closed fd=%d", (int)tc->sock); return -1; }
|
||||
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
|
||||
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin entry_pool exhausted fd=%d", (int)tc->sock); return -1; }
|
||||
e->dgram = (uint8_t*)&tcp_fin_sentinel; e->len = 0;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin fd=%d wq=%d", (int)tc->sock, tc->write_queue->count + 1);
|
||||
queue_data_put(tc->write_queue, e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tcp_conn_push_close(struct tcp_conn* tc) {
|
||||
if (!tc || tc->sock == SOCKET_INVALID) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close invalid tc"); return -1; }
|
||||
if (tc->closed) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close already closed fd=%d", (int)tc->sock); return -1; }
|
||||
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
|
||||
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close entry_pool exhausted fd=%d", (int)tc->sock); return -1; }
|
||||
e->dgram = NULL; e->len = 0;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close fd=%d wq=%d", (int)tc->sock, tc->write_queue->count + 1);
|
||||
queue_data_put(tc->write_queue, e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tcp_conn_set_flushed(struct tcp_conn* tc, void (*on_flushed)(struct tcp_conn* tc, void* arg)) {
|
||||
if (!tc) return;
|
||||
tc->on_flushed = on_flushed;
|
||||
}
|
||||
|
||||
void tcp_conn_pause_read(struct tcp_conn* tc) {
|
||||
if (!tc || tc->sock == SOCKET_INVALID) return;
|
||||
uasync_set_socket_read(tc->ua, tc->socket_id, 0);
|
||||
queue_waiter_cancel(tc->read_queue, &tc->read_waiter);
|
||||
tc->read_paused = 1;
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
// tcp_io.h — управление TCP-соединением через uasync + ll_queue
|
||||
//
|
||||
// Один tcp_conn = одно TCP-соединение. Два пула:
|
||||
// entry_pool — struct ll_entry (без inline data)
|
||||
// data_pool — буферы (max(entry_data_size, write_chunk_size))
|
||||
//
|
||||
// Чтение: read_cb → recv() в data_pool → entry в read_queue → callback внешнего кода.
|
||||
// high_water/low_water управляют backpressure (EPOLLIN on/off).
|
||||
// FIN (recv==0) → on_fin (сразу или через empty_callback после дренажа очереди).
|
||||
//
|
||||
// Запись: внешний код кладёт entry в write_queue через queue_data_put().
|
||||
// write_queue_fetch_cb (deferred) → send(), при EAGAIN → write_buf + EPOLLOUT.
|
||||
// Всё отправлено → on_flushed.
|
||||
//
|
||||
// FIN/Close: tcp_conn_push_fin/close ставят сентинел в write_queue.
|
||||
// FIN → shutdown(SHUT_WR) → on_fin_sent
|
||||
// CLOSE → close сокета → on_closed
|
||||
//
|
||||
// Ошибка: любая ошибка сокета → tcp_conn_handle_error → close + null коллбэков → on_error.
|
||||
// Реализация on_error обязана вызвать tcp_conn_destroy. После возврата tc недействителен.
|
||||
|
||||
#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"
|
||||
#include "memory_pool.h"
|
||||
|
||||
struct tcp_conn {
|
||||
socket_t sock;
|
||||
struct UASYNC* ua;
|
||||
void* socket_id;
|
||||
|
||||
struct ll_queue* read_queue; // сокет → данные (блоки до entry_data_size)
|
||||
struct ll_queue* write_queue; // данные → сокет (блоки до write_chunk_size)
|
||||
|
||||
int read_high_water;
|
||||
int read_low_water;
|
||||
int rcvbuf_size;
|
||||
uint8_t read_paused;
|
||||
|
||||
uint8_t write_monitor; // 1 = EPOLLOUT активен
|
||||
uint8_t connected;
|
||||
uint8_t error;
|
||||
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;
|
||||
size_t write_len;
|
||||
size_t write_offset;
|
||||
size_t write_chunk_size;
|
||||
|
||||
// Пулы памяти
|
||||
struct memory_pool* entry_pool; // sizeof(struct ll_entry)
|
||||
struct memory_pool* data_pool; // max(entry_data_size, write_chunk_size)
|
||||
size_t entry_data_size;
|
||||
|
||||
struct queue_waiter_handle read_waiter;
|
||||
|
||||
// Коллбэки
|
||||
void (*on_fin)(struct tcp_conn* tc, void* arg); // FIN получен от удалённой стороны (fin_remote=1)
|
||||
void (*on_fin_sent)(struct tcp_conn* tc, void* arg); // FIN отправлен удалённой стороне (fin_local=1)
|
||||
void (*on_error)(struct tcp_conn* tc, int err, void* arg); // фатальная ошибка (вызывается после close сокета; обязан вызвать tcp_conn_destroy)
|
||||
void (*on_flushed)(struct tcp_conn* tc, void* arg); // все данные записи отправлены (write_queue + write_buf пусты)
|
||||
void (*on_closed)(struct tcp_conn* tc, void* arg); // сокет закрыт через очередь (после close)
|
||||
void* arg;
|
||||
};
|
||||
|
||||
struct tcp_conn* tcp_conn_create(
|
||||
struct UASYNC* ua,
|
||||
socket_t sock,
|
||||
size_t entry_data_size,
|
||||
size_t write_chunk_size,
|
||||
int read_high_water,
|
||||
int read_low_water,
|
||||
int rcvbuf_size,
|
||||
void (*on_fin)(struct tcp_conn* tc, void* arg),
|
||||
void (*on_error)(struct tcp_conn* tc, int err, void* arg),
|
||||
void* arg);
|
||||
|
||||
// Дренирует очереди, закрывает сокет, уничтожает пулы, освобождает tc.
|
||||
void tcp_conn_destroy(struct tcp_conn* tc);
|
||||
|
||||
// Запись: queue_data_put(tc->write_queue, entry) напрямую.
|
||||
// Автозабор (deferred) сам шлёт когда сокет готов.
|
||||
// Порог: 32 записи, при заполнении queue_waiter_wait.
|
||||
|
||||
// Поставить сигналы в очередь отправки. Все данные в очереди перед сигналом
|
||||
// будут отправлены до его обработки. Кодирование: dgram=NULL → close, len=0+dgram!=NULL → FIN.
|
||||
// tcp_conn_push_fin(tc) — FIN: после отправки предшествующих данных вызывает
|
||||
// shutdown(SHUT_WR) и on_fin_sent.
|
||||
// Повторный вызов игнорируется (fin_sent уже установлен).
|
||||
// tcp_conn_push_close(tc) — CLOSE: после отправки предшествующих данных закрывает
|
||||
// сокет (close), вызывает on_closed.
|
||||
// После close сокет удалён из uasync, tc->closed=1.
|
||||
// Повторный вызов игнорируется.
|
||||
int tcp_conn_push_fin(struct tcp_conn* tc);
|
||||
int tcp_conn_push_close(struct tcp_conn* tc);
|
||||
|
||||
// Одноразовый коллбэк: вызывается когда write_queue + write_buf полностью опустели.
|
||||
// После вызова сбрасывается. Установить повторно можно в любой момент.
|
||||
void tcp_conn_set_flushed(struct tcp_conn* tc, void (*on_flushed)(struct tcp_conn* tc, void* arg));
|
||||
|
||||
// Принудительная остановка чтения: убирает 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
|
||||
+590
-483
File diff suppressed because it is too large.
Load diff
+78
-60
@@ -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,46 +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
|
||||
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);
|
||||
|
||||
// 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);
|
||||
|
||||
|
||||
// Динамическое управление мониторингом чтения/записи для сокета
|
||||
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);
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -115,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` |
|
||||
@@ -0,0 +1,9 @@
|
||||
tui enable
|
||||
stepi # шаг по инструкции
|
||||
nexti
|
||||
step # шаг по строке исходника
|
||||
next
|
||||
|
||||
bt
|
||||
frame n
|
||||
p *var
|
||||
+2408
File diff suppressed because it is too large.
Load diff
+79
-99
@@ -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 \
|
||||
@@ -20,135 +24,111 @@ utun_CORE_SOURCES = \
|
||||
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 \
|
||||
tcp_proxy.c \
|
||||
proxy/tcp_proxy_client.c \
|
||||
etcp_router.c \
|
||||
remote_proxy.c \
|
||||
udp_proxy.c \
|
||||
icmp_proxy.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).
|
||||
если чатовая - то только связность между узлами.
|
||||
|
||||
+178
-54
@@ -45,8 +45,10 @@ typedef enum {
|
||||
SECTION_CONTROL,
|
||||
SECTION_ALLOWED_KEYS,
|
||||
SECTION_NAT,
|
||||
SECTION_TCP_PROXY,
|
||||
SECTION_REMOTE_PROXY
|
||||
SECTION_TCP_PROXY_CLIENT,
|
||||
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,12 +382,12 @@ static int parse_global(const char *key, const char *value, struct global_config
|
||||
global->keepalive_interval = atoi(value);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "inflight_min_bytes") == 0) {
|
||||
global->inflight_min_bytes = atoi(value);
|
||||
if (strcmp(key, "keepalive_adaptive") == 0) {
|
||||
global->keepalive_adaptive = atoi(value);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "inflight_max_bytes") == 0) {
|
||||
global->inflight_max_bytes = atoi(value);
|
||||
if (strcmp(key, "bbr_max_cwnd") == 0) {
|
||||
global->bbr_max_cwnd = atoi(value);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "debug_level") == 0) {
|
||||
@@ -390,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);
|
||||
@@ -411,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, inflight_min_bytes, inflight_max_bytes, 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;
|
||||
}
|
||||
|
||||
@@ -440,30 +457,57 @@ static int parse_control(const char *key, const char *value, struct global_confi
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int parse_tcp_proxy(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
|
||||
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_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
|
||||
global->tcp_proxy_client_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "tun_name") == 0) {
|
||||
strncpy(global->tcp_proxy_tun_name, value, sizeof(global->tcp_proxy_tun_name) - 1);
|
||||
strncpy(global->tcp_proxy_client_tun_name, value, sizeof(global->tcp_proxy_client_tun_name) - 1);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "tun_ip") == 0) {
|
||||
strncpy(global->tcp_proxy_tun_ip, value, sizeof(global->tcp_proxy_tun_ip) - 1);
|
||||
strncpy(global->tcp_proxy_client_tun_ip, value, sizeof(global->tcp_proxy_client_tun_ip) - 1);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "mtu") == 0) {
|
||||
global->tcp_proxy_mtu = atoi(value);
|
||||
global->tcp_proxy_client_mtu = atoi(value);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "via_node") == 0) {
|
||||
global->tcp_proxy_via_node_id = strtoull(value, NULL, 16);
|
||||
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_mapping_count >= MAX_TCP_PROXY_MAPPINGS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Too many tcp_proxy forward rules (max %d)", MAX_TCP_PROXY_MAPPINGS);
|
||||
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);
|
||||
return -1;
|
||||
}
|
||||
char buf[256];
|
||||
@@ -475,29 +519,29 @@ static int parse_tcp_proxy(const char *key, const char *value, struct global_con
|
||||
char* arrow = strtok(NULL, " ");
|
||||
char* remote_str = strtok(NULL, "");
|
||||
if (!local_port_str || !arrow || !remote_str || strcmp(arrow, "->") != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward format: %s (expected: port -> ip:port)", value);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_client forward format: %s (expected: port -> ip:port)", value);
|
||||
return -1;
|
||||
}
|
||||
int local_port = atoi(local_port_str);
|
||||
if (local_port <= 0 || local_port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward local port: %s", local_port_str); return -1; }
|
||||
if (local_port <= 0 || local_port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_client forward local port: %s", local_port_str); return -1; }
|
||||
|
||||
char* remote_ip_str = strtok(remote_str, ":");
|
||||
char* remote_port_str = strtok(NULL, "");
|
||||
if (!remote_ip_str || !remote_port_str) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward remote: %s (expected ip:port)", remote_str);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_client forward remote: %s (expected ip:port)", remote_str);
|
||||
return -1;
|
||||
}
|
||||
int remote_port = atoi(remote_port_str);
|
||||
if (remote_port <= 0 || remote_port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward remote port: %s", remote_port_str); return -1; }
|
||||
if (remote_port <= 0 || remote_port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_client forward remote port: %s", remote_port_str); return -1; }
|
||||
|
||||
int idx = global->tcp_proxy_mapping_count;
|
||||
global->tcp_proxy_mappings[idx].local_port = (uint16_t)local_port;
|
||||
strncpy(global->tcp_proxy_mappings[idx].remote_ip, remote_ip_str, sizeof(global->tcp_proxy_mappings[idx].remote_ip) - 1);
|
||||
global->tcp_proxy_mappings[idx].remote_port = (uint16_t)remote_port;
|
||||
global->tcp_proxy_mapping_count++;
|
||||
int idx = global->tcp_proxy_client_mapping_count;
|
||||
global->tcp_proxy_client_mappings[idx].local_port = (uint16_t)local_port;
|
||||
strncpy(global->tcp_proxy_client_mappings[idx].remote_ip, remote_ip_str, sizeof(global->tcp_proxy_client_mappings[idx].remote_ip) - 1);
|
||||
global->tcp_proxy_client_mappings[idx].remote_port = (uint16_t)remote_port;
|
||||
global->tcp_proxy_client_mapping_count++;
|
||||
return 0;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown tcp_proxy 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;
|
||||
}
|
||||
|
||||
@@ -615,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;
|
||||
}
|
||||
@@ -628,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;
|
||||
}
|
||||
|
||||
@@ -674,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;
|
||||
|
||||
@@ -694,8 +760,9 @@ static section_type_t parse_section_header(const char *line, char *name, size_t
|
||||
if (strcasecmp(section, "control") == 0) return SECTION_CONTROL;
|
||||
if (strcasecmp(section, "allowed_keys") == 0) return SECTION_ALLOWED_KEYS;
|
||||
if (strcasecmp(section, "nat") == 0) return SECTION_NAT;
|
||||
if (strcasecmp(section, "tcp_proxy") == 0) return SECTION_TCP_PROXY;
|
||||
if (strcasecmp(section, "remote_proxy") == 0) return SECTION_REMOTE_PROXY;
|
||||
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;
|
||||
@@ -709,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;
|
||||
}
|
||||
|
||||
@@ -724,17 +792,21 @@ 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.inflight_min_bytes = 2000; // Default 2KB
|
||||
cfg->global.inflight_max_bytes = 100000; // Default 100KB
|
||||
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;
|
||||
cfg->global.firewall_rule_count = 0;
|
||||
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;
|
||||
|
||||
@@ -758,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));
|
||||
@@ -765,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);
|
||||
@@ -773,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;
|
||||
}
|
||||
@@ -866,16 +950,27 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
// parse_nat already printed the error
|
||||
}
|
||||
break;
|
||||
case SECTION_TCP_PROXY:
|
||||
cfg->global.tcp_proxy_enabled = 1;
|
||||
if (parse_tcp_proxy(key, value, &cfg->global, filename, line_num) < 0) {
|
||||
// parse_tcp_proxy already printed the error
|
||||
case SECTION_TCP_PROXY_CLIENT:
|
||||
cfg->global.tcp_proxy_client_enabled = 1;
|
||||
if (parse_tcp_proxy_client(key, value, &cfg->global, filename, line_num) < 0) {
|
||||
// parse_tcp_proxy_client already printed the error
|
||||
}
|
||||
break;
|
||||
case SECTION_REMOTE_PROXY:
|
||||
cfg->global.remote_proxy_enabled = 1;
|
||||
// remote_proxy section has no options — its presence alone enables it
|
||||
(void)key; (void)value;
|
||||
case SECTION_TCP_PROXY_SERVER:
|
||||
cfg->global.tcp_proxy_server_enabled = 1;
|
||||
if (strcmp(key, "tcp_recv_buf") == 0) {
|
||||
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);
|
||||
@@ -892,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;
|
||||
@@ -907,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;
|
||||
}
|
||||
@@ -945,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);
|
||||
@@ -991,9 +1107,9 @@ void print_config(const struct utun_config *cfg) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Servers:");
|
||||
struct CFG_SERVER *s = cfg->servers;
|
||||
while (s) {
|
||||
struct sockaddr_in *sin = (struct sockaddr_in *)&s->ip;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s: %s:%d (mark=%d, netif=%u, type=%u, mtu=%d)",
|
||||
s->name, ip_to_str(&sin->sin_addr, AF_INET).str, ntohs(sin->sin_port),
|
||||
ip_str_t addr_str = sockaddr_storage_to_str(&s->ip);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s: %s (mark=%d, netif=%u, type=%u, mtu=%d)",
|
||||
s->name, addr_str.str,
|
||||
s->so_mark, s->netif_index, s->type, s->mtu);
|
||||
s = s->next;
|
||||
}
|
||||
@@ -1004,9 +1120,9 @@ void print_config(const struct utun_config *cfg) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s: peer_key=%s, keepalive=%d", c->name, c->peer_public_key_hex, c->keepalive);
|
||||
struct CFG_CLIENT_LINK *link = c->links;
|
||||
while (link) {
|
||||
struct sockaddr_in *sin = (struct sockaddr_in *)&link->remote_addr;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " Link: %s:%d (via %s)",
|
||||
ip_to_str(&sin->sin_addr, AF_INET).str, ntohs(sin->sin_port), link->local_srv->name);
|
||||
ip_str_t addr_str = sockaddr_storage_to_str(&link->remote_addr);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " Link: %s (via %s)",
|
||||
addr_str.str, link->local_srv->name);
|
||||
link = link->next;
|
||||
}
|
||||
c = c->next;
|
||||
@@ -1039,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");
|
||||
}
|
||||
|
||||
+38
-17
@@ -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,12 +88,12 @@ 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;
|
||||
};
|
||||
|
||||
#define MAX_TCP_PROXY_MAPPINGS 32
|
||||
struct tcp_proxy_mapping_config {
|
||||
#define MAX_TCP_PROXY_CLIENT_MAPPINGS 32
|
||||
struct tcp_proxy_client_mapping_config {
|
||||
uint16_t local_port;
|
||||
char remote_ip[64];
|
||||
uint16_t remote_port;
|
||||
@@ -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,8 +133,8 @@ 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 inflight_min_bytes; // inflight window starting size (default: 2000)
|
||||
int inflight_max_bytes; // inflight window max cap (default: 100000)
|
||||
int keepalive_adaptive; // 1 = adaptive period (default), 0 = fixed interval
|
||||
int bbr_max_cwnd; // BBR cwnd cap in bytes (default: 100000)
|
||||
|
||||
// Firewall configuration
|
||||
struct CFG_FIREWALL_RULE *firewall_rules;
|
||||
@@ -151,17 +166,22 @@ struct global_config {
|
||||
} nat_forwards[MAX_NAT_FORWARDS];
|
||||
int nat_forward_count;
|
||||
|
||||
// TCP proxy configuration ([tcp_proxy] section)
|
||||
int tcp_proxy_enabled;
|
||||
char tcp_proxy_tun_name[16];
|
||||
char tcp_proxy_tun_ip[64];
|
||||
int tcp_proxy_mtu;
|
||||
uint64_t tcp_proxy_via_node_id; // через этот узел проксируются все forward-правила
|
||||
struct tcp_proxy_mapping_config tcp_proxy_mappings[MAX_TCP_PROXY_MAPPINGS];
|
||||
int tcp_proxy_mapping_count;
|
||||
// TCP proxy client configuration ([tcp_proxy_client] section)
|
||||
int tcp_proxy_client_enabled;
|
||||
char tcp_proxy_client_tun_name[16];
|
||||
char tcp_proxy_client_tun_ip[64];
|
||||
int tcp_proxy_client_mtu;
|
||||
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"
|
||||
|
||||
// Remote proxy (exit node) configuration ([remote_proxy] section)
|
||||
int remote_proxy_enabled;
|
||||
// TCP proxy server (exit node) configuration ([tcp_proxy_server] section)
|
||||
int tcp_proxy_server_enabled;
|
||||
int tcp_recv_buf; // SO_RCVBUF для TCP сокетов (0=default ОС)
|
||||
};
|
||||
|
||||
struct utun_config {
|
||||
@@ -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
|
||||
+359
-46
@@ -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,14 +91,24 @@ 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);
|
||||
static void send_metrics(struct control_server* server, struct control_client* client, uint8_t seq_id);
|
||||
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
|
||||
* ============================================================================ */
|
||||
@@ -399,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);
|
||||
|
||||
@@ -453,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;
|
||||
@@ -543,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) {
|
||||
@@ -559,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",
|
||||
@@ -582,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) {
|
||||
@@ -613,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_ACTION) {
|
||||
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;
|
||||
@@ -689,13 +784,69 @@ 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;
|
||||
|
||||
case ETCPMON_CMD_GET_DEBUG_CONFIG:
|
||||
send_debug_config(server, client, req_seq);
|
||||
break;
|
||||
|
||||
case ETCPMON_CMD_SET_DEBUG_CONFIG:
|
||||
if (payload_size >= 2) {
|
||||
uint8_t global_lvl = payload[0];
|
||||
uint8_t cat_count = payload[1];
|
||||
if (cat_count > ETCPMON_MAX_DEBUG_CATEGORIES) cat_count = ETCPMON_MAX_DEBUG_CATEGORIES;
|
||||
if (global_lvl <= DEBUG_LEVEL_TRACE) debug_set_level((debug_level_t)global_lvl);
|
||||
const uint8_t* levels = payload + 2;
|
||||
for (uint8_t i = 0; i < cat_count && i + 1 < DEBUG_CATEGORY_COUNT; i++) {
|
||||
if (levels[i] <= DEBUG_LEVEL_TRACE)
|
||||
debug_set_category_level((debug_category_t)(i + 1), (debug_level_t)levels[i]);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Debug config applied: global=%d categories=%d", global_lvl, cat_count);
|
||||
if (server->log_file) {
|
||||
fprintf(server->log_file, "%llu: [LOG] Debug config set: global=%d categories=%d\n",
|
||||
(unsigned long long)get_timestamp_ms(), global_lvl, cat_count);
|
||||
fflush(server->log_file);
|
||||
}
|
||||
}
|
||||
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",
|
||||
@@ -711,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) {
|
||||
@@ -770,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) {
|
||||
@@ -794,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;
|
||||
}
|
||||
|
||||
@@ -843,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) */
|
||||
@@ -857,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) {
|
||||
@@ -1042,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;
|
||||
@@ -1068,7 +1253,7 @@ static void send_metrics(struct control_server* server, struct control_client* c
|
||||
link_info[i].bandwidth = link->bandwidth;
|
||||
link_info[i].nat_changes_count = link->nat_changes_count;
|
||||
link_info[i].rtt_last = link->rtt_last;
|
||||
link_info[i].rtt_avg10 = link->rtt_avg10;
|
||||
link_info[i].rtt_avg10 = link->bbr ? link->bbr->min_rtt_us / 100 : 0;
|
||||
link_info[i].tt_last = link->tt_last;
|
||||
link_info[i].init_timer_active = (link->init_timer != NULL) ? 1 : 0;
|
||||
link_info[i].keepalive_timer_active = (link->keepalive_timer != NULL) ? 1 : 0;
|
||||
@@ -1078,6 +1263,25 @@ static void send_metrics(struct control_server* server, struct control_client* c
|
||||
link_info[i].inflight_bytes = link->inflight_bytes;
|
||||
link_info[i].inflight_packets = link->inflight_packets;
|
||||
link_info[i].inflight_lim_bytes = link->inflight_lim_bytes;
|
||||
|
||||
/* BBR fields */
|
||||
if (link->bbr) {
|
||||
link_info[i].bbr_mode = link->bbr->mode;
|
||||
link_info[i].bbr_cycle_idx = link->bbr->cycle_idx;
|
||||
link_info[i].bbr_full_bw_reached = link->bbr->full_bw_reached;
|
||||
link_info[i].bbr_loss_in_round = link->bbr->loss_in_round;
|
||||
link_info[i].bbr_pacing_rate = link->bbr_pacing_rate;
|
||||
link_info[i].bbr_min_rtt_us = link->bbr->min_rtt_us;
|
||||
link_info[i].bbr_pacing_gain = link->bbr->pacing_gain;
|
||||
link_info[i].bbr_inflight_hi = link->bbr->inflight_hi;
|
||||
link_info[i].bbr_inflight_lo = link->bbr->inflight_lo;
|
||||
/* bw_hi/lo: convert from BW_UNIT scale (pkts/usec<<24) to bytes/sec */
|
||||
uint32_t max_bw = link->bbr->bw_hi[0] > link->bbr->bw_hi[1] ? link->bbr->bw_hi[0] : link->bbr->bw_hi[1];
|
||||
link_info[i].bbr_bw_hi = max_bw ? (uint32_t)((uint64_t)max_bw * link->mtu * 1000000ULL / 16777216ULL) : 0;
|
||||
link_info[i].bbr_bw_lo = link->bbr->bw_lo != ~0U ? (uint32_t)((uint64_t)link->bbr->bw_lo * link->mtu * 1000000ULL / 16777216ULL) : 0;
|
||||
} else {
|
||||
memset(&link_info[i].bbr_mode, 0, 11 * 4 + 2 + 2); // zero out all BBR fields
|
||||
}
|
||||
link = link->next;
|
||||
}
|
||||
|
||||
@@ -1092,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) {
|
||||
@@ -1126,13 +1324,46 @@ 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
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
}
|
||||
|
||||
u_free(buffer);
|
||||
static void send_debug_config(struct control_server* server, struct control_client* client, uint8_t seq_id) {
|
||||
uint8_t cat_count = 0;
|
||||
char names_buf[512];
|
||||
names_buf[0] = '\0';
|
||||
size_t off = 0;
|
||||
for (int i = 1; i < DEBUG_CATEGORY_COUNT; i++) {
|
||||
const char* name = debug_get_category_name(i);
|
||||
size_t name_len = strlen(name);
|
||||
if (off + name_len + 2 > sizeof(names_buf)) break;
|
||||
if (cat_count > 0) { names_buf[off++] = ','; }
|
||||
memcpy(names_buf + off, name, name_len); off += name_len;
|
||||
cat_count++;
|
||||
}
|
||||
names_buf[off] = '\0';
|
||||
uint16_t names_len = (uint16_t)(off + 1);
|
||||
|
||||
uint16_t payload_size = 2 + names_len + cat_count;
|
||||
uint16_t rsp_size = sizeof(struct etcpmon_msg_header) + payload_size;
|
||||
uint8_t* buffer = (uint8_t*)u_malloc(rsp_size);
|
||||
if (!buffer) { DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate debug config buffer"); return; }
|
||||
|
||||
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
|
||||
etcpmon_build_header(hdr, payload_size, ETCPMON_RSP_DEBUG_CONFIG, seq_id);
|
||||
|
||||
uint8_t* pay = buffer + sizeof(*hdr);
|
||||
pay[0] = (uint8_t)g_debug_config.level;
|
||||
pay[1] = cat_count;
|
||||
memcpy(pay + 2, names_buf, names_len);
|
||||
for (uint8_t i = 0; i < cat_count; i++) pay[2 + names_len + i] = (uint8_t)g_debug_config.category_levels[i + 1];
|
||||
|
||||
if (server->log_file) {
|
||||
fprintf(server->log_file, "%llu: [LOG] Sent RSP_DEBUG_CONFIG global=%d categories=%d\n",
|
||||
(unsigned long long)get_timestamp_ms(), g_debug_config.level, cat_count);
|
||||
fflush(server->log_file);
|
||||
}
|
||||
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
@@ -1151,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.
+36
-4
@@ -316,9 +316,6 @@ static void dummynet_delay_callback(void* user_arg) {
|
||||
dir->stats.queue_max = queue_count + 1;
|
||||
}
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DUMMYNET, "Dir %d: packet added to queue (size=%d)",
|
||||
dir_idx, queue_count + 1);
|
||||
|
||||
/* Запускаем шейпер если не активен */
|
||||
if (!dir->shaper_pending) {
|
||||
dir->shaper_pending = 1;
|
||||
@@ -358,7 +355,7 @@ struct dummynet* dummynet_create(struct UASYNC* ua, const char* bind_ip, uint16_
|
||||
dn->listen_port = listen_port;
|
||||
|
||||
/* Создаём пул для пакетов */
|
||||
dn->pkt_pool = memory_pool_init(sizeof(struct ll_entry) + sizeof(struct dummynet_pkt));
|
||||
dn->pkt_pool = memory_pool_init(sizeof(struct ll_entry) + sizeof(struct dummynet_pkt), "pkt_pool");
|
||||
if (!dn->pkt_pool) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Failed to create packet pool");
|
||||
u_free(dn);
|
||||
@@ -580,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,
|
||||
@@ -588,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) {
|
||||
@@ -640,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
|
||||
+417
-143
@@ -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,7 +16,9 @@
|
||||
#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"
|
||||
|
||||
// Enable comprehensive debug output for ETCP module
|
||||
#define DEBUG_CATEGORY_ETCP_DETAILED 1
|
||||
@@ -69,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) {
|
||||
@@ -77,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;
|
||||
@@ -92,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);
|
||||
@@ -137,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++) {
|
||||
@@ -192,19 +196,22 @@ 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;
|
||||
}
|
||||
|
||||
etcp->instance = instance;
|
||||
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
|
||||
etcp->ack_q = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, 0, 4, "ack_q");
|
||||
etcp->inflight_pool = memory_pool_init(sizeof(struct INFLIGHT_PACKET));
|
||||
etcp->io_pool = memory_pool_init(sizeof(struct ETCP_FRAGMENT));
|
||||
etcp->inflight_pool = memory_pool_init(sizeof(struct INFLIGHT_PACKET), "inflight_pool");
|
||||
etcp->io_pool = memory_pool_init(sizeof(struct ETCP_FRAGMENT), "io_pool");
|
||||
etcp->max_inflight = (uint32_t)instance->config->global.bbr_max_cwnd;
|
||||
etcp->optimal_inflight=100000;
|
||||
etcp->initialized=0;
|
||||
etcp->links_up=0;
|
||||
@@ -213,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;
|
||||
@@ -236,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);
|
||||
@@ -249,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;
|
||||
@@ -258,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; }
|
||||
}
|
||||
|
||||
|
||||
@@ -288,35 +299,47 @@ 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);
|
||||
etcp->normalizer = NULL;
|
||||
}
|
||||
|
||||
// Drain and free all queues using helper functions
|
||||
// Clear links BEFORE draining queues: etcp_link_close calls
|
||||
// etcp_conn_on_inflight_lim_changed -> input_queue_try_resume
|
||||
// which accesses input_send_q, input_queue, input_wait_ack.
|
||||
if (etcp->links) {
|
||||
struct ETCP_LINK* link = etcp->links;
|
||||
while (link) {
|
||||
struct ETCP_LINK* next = link->next;
|
||||
etcp_link_close(link);
|
||||
link = next;
|
||||
}
|
||||
etcp->links = NULL;
|
||||
}
|
||||
|
||||
// Drain and free all queues
|
||||
drain_and_free_queue(&etcp->input_queue);
|
||||
drain_and_free_queue(&etcp->output_queue);
|
||||
drain_and_free_queue(&etcp->input_send_q);
|
||||
drain_and_free_queue(&etcp->input_wait_ack);
|
||||
drain_and_free_queue(&etcp->recv_q);
|
||||
drain_and_free_queue(&etcp->ack_q);
|
||||
|
||||
// Drain and free ack_q (contains ACK_PACKET from ack_pool - special handling)
|
||||
if (etcp->ack_q) {
|
||||
struct ACK_PACKET* pkt;
|
||||
while ((pkt = (struct ACK_PACKET*)queue_data_get(etcp->ack_q)) != NULL) {
|
||||
queue_entry_free((struct ll_entry*)pkt);
|
||||
}
|
||||
queue_free(etcp->ack_q);
|
||||
etcp->ack_q = NULL;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@@ -329,19 +352,16 @@ void etcp_connection_close(struct ETCP_CONN* etcp) {
|
||||
etcp->io_pool = NULL;
|
||||
}
|
||||
|
||||
// Clear links list safely
|
||||
if (etcp->links) {
|
||||
struct ETCP_LINK* link = etcp->links;
|
||||
while (link) {
|
||||
struct ETCP_LINK* next = link->next;
|
||||
etcp_link_close(link);
|
||||
link = next;
|
||||
}
|
||||
etcp->links = NULL;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -379,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);
|
||||
@@ -389,17 +412,20 @@ void etcp_conn_reset(struct ETCP_CONN* etcp) {
|
||||
clear_queue(etcp->recv_q);
|
||||
clear_queue(etcp->ack_q);
|
||||
|
||||
// clear_queue leaves callback_suspended=1, resume to prevent deadlock
|
||||
queue_resume_callback(etcp->input_queue);
|
||||
queue_resume_callback(etcp->input_send_q);
|
||||
queue_resume_callback(etcp->input_wait_ack);
|
||||
|
||||
// В etcp_conn_reset(), после очистки очередей добавьте:
|
||||
struct ETCP_LINK* l = etcp->links;
|
||||
while (l) {
|
||||
l->window_pkt_transmitted = 0;
|
||||
l->window_retransmissions = 0;
|
||||
l->win_timebase = 50000;
|
||||
l->win_ptr = 0;
|
||||
memset(l->stat_win, 0, sizeof(l->stat_win));
|
||||
start_stats_timer(l); // перезапускаем с дефолтным интервалом
|
||||
l->inflight_bytes = 0;
|
||||
l->inflight_packets = 0;
|
||||
l->delivered_bytes = 0;
|
||||
l->acked_bytes = 0;
|
||||
l->acked_packets = 0;
|
||||
l->last_ack_time_tb = get_time_tb();
|
||||
l = l->next;
|
||||
}
|
||||
|
||||
@@ -455,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");
|
||||
}
|
||||
|
||||
@@ -475,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-статус у всех линков этого соединения
|
||||
@@ -504,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);
|
||||
}
|
||||
|
||||
@@ -614,6 +647,16 @@ static void input_queue_try_resume(struct ETCP_CONN* etcp) {// при ACK
|
||||
}
|
||||
}
|
||||
|
||||
// Called from link-level etcp_link_update_inflight_lim() after inflight_lim_bytes changes.
|
||||
// Recalculates connection-level optimal_inflight and resumes input_queue if room opened up.
|
||||
void etcp_conn_on_inflight_lim_changed(struct ETCP_CONN* etcp) {
|
||||
if (!etcp) return;
|
||||
uint32_t sum = 0;
|
||||
for (struct ETCP_LINK* tl = etcp->links; tl; tl = tl->next) sum += tl->inflight_lim_bytes;
|
||||
etcp->optimal_inflight = sum;
|
||||
// input_queue_try_resume(etcp);
|
||||
}
|
||||
|
||||
void etcp_stats(struct ETCP_CONN* etcp) {
|
||||
if (!etcp) return;
|
||||
|
||||
@@ -709,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);
|
||||
@@ -749,13 +792,14 @@ 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
|
||||
int64_t next_timeout=timeout - elapsed;
|
||||
if (next_timeout<0) next_timeout=0;
|
||||
etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, next_timeout+10, etcp, ack_timeout_cb, "etcp_retrans");
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] retransmission timer set for %llu units", etcp->log_name, next_timeout);
|
||||
etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, next_timeout+10, etcp, ack_timeout_cb, "etcp_retrans");
|
||||
return;
|
||||
}
|
||||
current = etcp->input_wait_ack->head;
|
||||
@@ -781,8 +825,8 @@ static void input_send_q_cb(struct ll_queue* q, void* arg) {// etcp->input_send_
|
||||
struct ETCP_CONN* etcp=(struct ETCP_CONN*)arg;
|
||||
etcp_conn_process_send_queue(etcp);
|
||||
if (etcp->tx_state==ETCP_TX_STATE_DATA_WAIT) {
|
||||
queue_resume_callback(etcp->input_send_q);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "resume input_send_q");
|
||||
queue_resume_callback(etcp->input_send_q);
|
||||
} else DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "no resume - link busy");
|
||||
}
|
||||
/*
|
||||
@@ -840,14 +884,12 @@ static void etcp_link_ready_callback(struct ETCP_CONN* etcp) {
|
||||
|
||||
if (etcp->tx_state!=ETCP_TX_STATE_LINK_WAIT) return;
|
||||
etcp->tx_state = ETCP_TX_STATE_DATA_WAIT;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "resume input_send_q+ack_q; link_wait->data_wait");
|
||||
queue_resume_callback(etcp->input_send_q);
|
||||
// queue_resume_callback(etcp->ack_q);
|
||||
if (etcp->ack_q->count && etcp->ack_resp_timer == NULL) {
|
||||
ack_response_timer_cb(etcp);
|
||||
}
|
||||
|
||||
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "resume input_send_q+ack_q; link_wait->data_wait");
|
||||
}
|
||||
|
||||
// Process packets in send queue and transmit them
|
||||
@@ -858,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);
|
||||
@@ -923,17 +965,21 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
|
||||
|
||||
// Always subtract from previous last_link (if any) – this handles retransmission
|
||||
if (inf_pkt->last_link) {
|
||||
inf_pkt->last_link->window_retransmissions++;
|
||||
inf_pkt->last_link->total_retransmissions++;
|
||||
inf_pkt->last_link->bbr_loss_since_ack += inf_pkt->ll.len;
|
||||
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 (link->send_blocked_inflight && link->inflight_bytes < link->inflight_lim_bytes) loadbalancer_link_ready(link);
|
||||
}
|
||||
|
||||
// Always add to the CURRENT link (first send or retransmission)
|
||||
link->inflight_bytes += inf_pkt->ll.len;
|
||||
link->inflight_packets++;
|
||||
link->window_pkt_transmitted++;
|
||||
|
||||
// BBR: сохраняем снэпшот на момент отправки
|
||||
inf_pkt->delivered_at_send = link->delivered_bytes;
|
||||
inf_pkt->inflight_at_send = link->inflight_bytes;
|
||||
inf_pkt->is_app_limited = (etcp->input_queue->count == 0) ? 1 : 0;
|
||||
|
||||
// Update last_link for future ACK/retrans
|
||||
inf_pkt->last_link = link;
|
||||
@@ -943,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);
|
||||
|
||||
@@ -1083,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;
|
||||
@@ -1161,11 +1208,12 @@ void etcp_output_try_assembly(struct ETCP_CONN* etcp) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "move: ETCP -> PN");
|
||||
|
||||
// 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++;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] moved packet id=%u to output_queue (qlen=%d)", etcp->log_name,
|
||||
next_expected_id, etcp->output_queue->count);
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to add packet id=%u to output_queue", etcp->log_name,
|
||||
next_expected_id);
|
||||
@@ -1208,15 +1256,54 @@ 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;
|
||||
}
|
||||
|
||||
// === NEW: subtract inflight from the LAST link the packet was sent on ===
|
||||
// === subtract inflight from the LAST link the packet was sent on ===
|
||||
if (acked_pkt->last_link) {
|
||||
acked_pkt->last_link->inflight_bytes -= acked_pkt->ll.len;
|
||||
acked_pkt->last_link->inflight_packets--;
|
||||
if (acked_pkt->last_link->send_blocked_inflight && acked_pkt->last_link->inflight_bytes < acked_pkt->last_link->inflight_lim_bytes) loadbalancer_link_ready(acked_pkt->last_link);
|
||||
struct ETCP_LINK* link = acked_pkt->last_link;
|
||||
link->inflight_bytes -= acked_pkt->ll.len;
|
||||
link->inflight_packets--;
|
||||
|
||||
// BBR: собираем rate_sample и вызываем bbr_main
|
||||
uint64_t now_tb = get_time_tb();
|
||||
uint32_t interval_us = link->last_ack_time_tb ? (uint32_t)((now_tb - link->last_ack_time_tb) * 100) : 0;
|
||||
struct bbr_rate_sample rs = {
|
||||
.delivered = acked_pkt->ll.len,
|
||||
.interval_us = interval_us,
|
||||
.rtt_us = (uint32_t)link->rtt_last * 100,
|
||||
.acked_sacked = acked_pkt->ll.len,
|
||||
.prior_delivered = (uint32_t)acked_pkt->delivered_at_send,
|
||||
.tx_in_flight = acked_pkt->inflight_at_send,
|
||||
.lost = (int)link->bbr_loss_since_ack,
|
||||
.is_app_limited = acked_pkt->is_app_limited,
|
||||
};
|
||||
link->delivered_bytes += rs.delivered;
|
||||
link->last_ack_time_tb = now_tb;
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] BBR in: seq=%u del=%u iv=%uus rtt=%uus pri=%u tx_infl=%u cur=%u/%u lost=%d app=%d",
|
||||
link->local_link_id, seq, rs.delivered, rs.interval_us, rs.rtt_us,
|
||||
rs.prior_delivered, rs.tx_in_flight, link->inflight_bytes, link->inflight_lim_bytes,
|
||||
rs.lost, rs.is_app_limited);
|
||||
|
||||
uint32_t old_cwnd = link->inflight_lim_bytes;
|
||||
uint32_t old_pacing = link->bbr_pacing_rate;
|
||||
uint32_t new_cwnd = old_cwnd;
|
||||
uint32_t new_pacing = old_pacing;
|
||||
bbr_main(link->bbr, &rs, &new_cwnd, &new_pacing, link->mtu, link->inflight_bytes,
|
||||
(link->inflight_bytes >= link->inflight_lim_bytes));
|
||||
etcp_link_update_inflight_lim(link, new_cwnd);
|
||||
link->bbr_pacing_rate = new_pacing;
|
||||
link->bandwidth = (uint32_t)((uint64_t)new_pacing * 8 / 1000);
|
||||
link->bbr_loss_since_ack = 0;
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] BBR out: cwnd %u→%u pace %u→%u bw=%uK mode=%d cyc=%d infl=%u/%u",
|
||||
link->local_link_id, old_cwnd, new_cwnd, old_pacing, new_pacing,
|
||||
link->bandwidth, link->bbr->mode, link->bbr->cycle_idx, link->inflight_bytes, new_cwnd);
|
||||
|
||||
link->acked_bytes += acked_pkt->ll.len;
|
||||
link->acked_packets++;
|
||||
}
|
||||
|
||||
|
||||
@@ -1247,6 +1334,10 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d
|
||||
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, "pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt);
|
||||
volatile int _halt = 1; while (_halt) {}
|
||||
}
|
||||
if (!pkt->data_len) {
|
||||
memory_pool_free(pkt->link->etcp->instance->pkt_pool, pkt);
|
||||
return;
|
||||
@@ -1259,7 +1350,7 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
uint16_t len = pkt->data_len;
|
||||
uint16_t ts = pkt->timestamp; // Received timestamp
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX pkt", etcp->log_name);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX pkt dlen=%d", etcp->log_name, len);
|
||||
|
||||
while (len >= 1) {
|
||||
uint8_t type = data[0];
|
||||
@@ -1297,46 +1388,8 @@ 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);
|
||||
|
||||
uint8_t old_idx = pkt->link->rtt_history_index;
|
||||
uint16_t old_val = pkt->link->rtt_history[old_idx];
|
||||
|
||||
pkt->link->rtt_history[old_idx] = new_rtt;
|
||||
pkt->link->rtt_history_index = (old_idx + 1) % 10;
|
||||
if (pkt->link->rtt_history_count < 10) pkt->link->rtt_history_count++;
|
||||
|
||||
if (new_rtt >= pkt->link->rtt_max_val) {
|
||||
pkt->link->rtt_max_val = new_rtt;
|
||||
pkt->link->rtt_max_idx = old_idx;
|
||||
} else if (old_val == pkt->link->rtt_max_val) {
|
||||
pkt->link->rtt_max_idx = 255;
|
||||
}
|
||||
|
||||
uint8_t rtt_cnt = pkt->link->rtt_history_count;
|
||||
if (rtt_cnt == 1) {
|
||||
pkt->link->rtt_avg10 = new_rtt;
|
||||
} else {
|
||||
uint32_t sum = 0;
|
||||
if (pkt->link->rtt_max_idx == 255) {
|
||||
uint16_t max_val = 0;
|
||||
uint8_t max_pos = 0;
|
||||
for (uint8_t i = 0; i < rtt_cnt; i++) {
|
||||
uint16_t v = pkt->link->rtt_history[i];
|
||||
sum += v;
|
||||
if (v > max_val) {
|
||||
max_val = v;
|
||||
max_pos = i;
|
||||
}
|
||||
}
|
||||
pkt->link->rtt_max_val = max_val;
|
||||
pkt->link->rtt_max_idx = max_pos;
|
||||
} else {
|
||||
for (uint8_t i = 0; i < rtt_cnt; i++) {
|
||||
sum += pkt->link->rtt_history[i];
|
||||
}
|
||||
}
|
||||
pkt->link->rtt_avg10 = (sum - pkt->link->rtt_max_val) / (rtt_cnt - 1);
|
||||
}
|
||||
int recv_dt_tx1=data[3] | (data[4]<<8);// localtime удаленной стороны момента принятия пакета - timestamp этого пакета (на стороне отправителя, т.е. у нас)
|
||||
|
||||
int recv_dt_rx=cur_ts - pkt->link->rtt_last/2 - 1000 - ts;
|
||||
@@ -1347,29 +1400,30 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
pkt->link->recv_dt_avg_tx +=((int32_t)(recv_dt_tx*65536 - pkt->link->recv_dt_avg_tx))/32;
|
||||
pkt->link->rt_last = cur_ts - ts - pkt->link->recv_dt_avg_rx/65536;
|
||||
pkt->link->tt_last = recv_dt_tx1 - pkt->link->recv_dt_avg_tx/65536;
|
||||
//tts_correction += ((NOW - RTT/2 - TTS) - tts_correction)/16 (инициализируем сразу по 1 пакету)
|
||||
//tts_correction += ((NOW - RTT/2 - TTS) - tts_correction)/16 (инициализируем сразу по 1 пакету)
|
||||
data+=5; len-=5;
|
||||
|
||||
int d_rtt=pkt->link->rtt_avg10 - pkt->link->rtt_last;
|
||||
int prev_rtt = pkt->link->rtt_last;
|
||||
int d_rtt = new_rtt - prev_rtt;
|
||||
if (d_rtt<0) d_rtt=-d_rtt;
|
||||
pkt->link->jitter +=((int32_t)(d_rtt*65536 - pkt->link->jitter))/32;
|
||||
|
||||
struct ETCP_LINK* c=etcp->links;
|
||||
int rtt_sum=0;
|
||||
int rtt_a10_max=0;
|
||||
int rtt_max=0;
|
||||
int tt_sum=0;
|
||||
int j_sum=0;
|
||||
int cnt=0;
|
||||
while (c) {
|
||||
rtt_sum += c->rtt_last;
|
||||
if (rtt_a10_max < c->rtt_avg10) rtt_a10_max = c->rtt_avg10;
|
||||
if (rtt_max < c->rtt_last) rtt_max = c->rtt_last;
|
||||
tt_sum += c->tt_last;
|
||||
j_sum += c->jitter/32768;
|
||||
cnt++;
|
||||
c=c->next;
|
||||
}
|
||||
etcp->rtt_last=rtt_sum/cnt;
|
||||
etcp->rtt_avg_10=rtt_a10_max;
|
||||
etcp->rtt_avg_10=rtt_max;
|
||||
etcp->tt_last=tt_sum/cnt;
|
||||
etcp->jitter=j_sum/cnt;
|
||||
break;
|
||||
@@ -1380,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;
|
||||
@@ -1395,21 +1449,23 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
struct ACK_PACKET* p = (struct ACK_PACKET*)queue_entry_new_from_pool(etcp->instance->ack_pool);
|
||||
if (!p) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to allocate ACK_PACKET", etcp->log_name);
|
||||
len = 0;
|
||||
break;
|
||||
}
|
||||
p->seq=seq;
|
||||
p->pkt_timestamp=pkt->timestamp;
|
||||
p->recv_timestamp=get_current_timestamp();
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX add to ack_q seq=%d", etcp->log_name, seq);
|
||||
if (queue_find_data_by_index(etcp->ack_q, &seq) == NULL) {
|
||||
struct ACK_PACKET* p = (struct ACK_PACKET*)queue_entry_new_from_pool(etcp->instance->ack_pool);
|
||||
if (!p) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to allocate ACK_PACKET", etcp->log_name);
|
||||
len = 0;
|
||||
break;
|
||||
}
|
||||
p->seq=seq;
|
||||
p->pkt_timestamp=pkt->timestamp;
|
||||
p->recv_timestamp=get_current_timestamp();
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX add to ack_q seq=%d", etcp->log_name, seq);
|
||||
|
||||
queue_data_put_with_index(etcp->ack_q, (struct ll_entry*)p);
|
||||
if (etcp->ack_resp_timer == NULL) {
|
||||
etcp->ack_resp_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_TB, etcp, ack_response_timer_cb, "etcp_ack_resp");
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] set ack_timer for delayed ACK send", etcp->log_name);
|
||||
queue_data_put_with_index(etcp->ack_q, (struct ll_entry*)p);
|
||||
if (etcp->ack_resp_timer == NULL) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] set ack_timer for delayed ACK send", etcp->log_name);
|
||||
etcp->ack_resp_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_TB, etcp, ack_response_timer_cb, "etcp_ack_resp");
|
||||
}
|
||||
}
|
||||
if (((int32_t)(etcp->last_delivered_id-seq)<0) && (queue_find_data_by_index(etcp->recv_q, &seq)==NULL)) {// проверяем есть ли пакет с этим seq
|
||||
uint32_t pkt_len=len-5;
|
||||
@@ -1450,8 +1506,9 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
}
|
||||
// Copy the actual payload data
|
||||
memcpy(payload_data, data + 5, pkt_len);
|
||||
queue_data_put_with_index(etcp->recv_q, (struct ll_entry*)rx_pkt);
|
||||
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++;
|
||||
@@ -1477,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;
|
||||
@@ -1523,18 +1583,13 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
uint64_t bw_kbps = (uint64_t)link->burst_pkt_size * 8000ULL / gap_min;
|
||||
if (bw_kbps > 10000000) bw_kbps = 10000000;
|
||||
link->bandwidth = (uint32_t)bw_kbps;
|
||||
float rtt_sec = (float)link->rtt_min / 10000.f;
|
||||
float rtt_sec = (float)link->bbr->min_rtt_us / 1000000.f;
|
||||
if (rtt_sec < 0.005f) rtt_sec = 0.005f;
|
||||
link->burst_target_bdp = (uint32_t)((float)bw_kbps * 1000.f / 8.f * rtt_sec);
|
||||
link->slow_start_threshold = link->burst_target_bdp * 9 / 10;
|
||||
if (link->inflight_phase == INFLIGHT_PHASE_SLOW_START) {
|
||||
if (link->inflight_lim_bytes > link->slow_start_threshold) {
|
||||
link->inflight_lim_bytes = link->slow_start_threshold;
|
||||
etcp_link_update_inflight_lim(link, link->inflight_lim_bytes);
|
||||
}
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] burst resp: BW=%u Kbps, BDP=%u, slow_start_thr=%u gap_min=%u us pkt_cnt=%u",
|
||||
link->etcp->log_name, (uint32_t)bw_kbps, link->burst_target_bdp, link->slow_start_threshold, gap_min, pkt_cnt);
|
||||
// BBR: инициализируем верхнюю границу из burst
|
||||
if (link->bbr->inflight_hi == ~0U) link->bbr->inflight_hi = link->burst_target_bdp;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] burst resp: BW=%u Kbps, BDP=%u gap_min=%u us pkt_cnt=%u",
|
||||
link->etcp->log_name, (uint32_t)bw_kbps, link->burst_target_bdp, gap_min, pkt_cnt);
|
||||
}
|
||||
}
|
||||
data += MEAS_RESP_SECTION_SIZE; len -= MEAS_RESP_SECTION_SIZE;
|
||||
@@ -1549,14 +1604,58 @@ 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (memory_pool_is_freed(pkt->link->etcp->instance->pkt_pool, pkt)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt);
|
||||
volatile int _halt = 1; while (_halt) {}
|
||||
}
|
||||
|
||||
|
||||
memory_pool_free(etcp->instance->pkt_pool, pkt); // Free the incoming dgram
|
||||
}
|
||||
|
||||
@@ -1577,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);
|
||||
}
|
||||
}
|
||||
|
||||
+70
-10
@@ -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
|
||||
@@ -72,6 +114,9 @@ struct INFLIGHT_PACKET {// выделяется из etcp->inflight_pool
|
||||
uint8_t retrans_req_count; // Number of retrans requests
|
||||
uint8_t state; // WAIT_ACK or WAIT_SEND
|
||||
uint8_t send_hist[8]; // через какие каналы передавался пакет (NEW). send_count - head ptr
|
||||
uint64_t delivered_at_send; // link->delivered_bytes на момент отправки (для BBR prior_delivered)
|
||||
uint32_t inflight_at_send; // link->inflight_bytes на момент отправки (для BBR tx_in_flight)
|
||||
uint8_t is_app_limited; // данные ограничены приложением на момент отправки
|
||||
};
|
||||
|
||||
// Список пакетов для сборки. собирается в ll_queue (используем быстрый поиск с хешем)
|
||||
@@ -101,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;
|
||||
@@ -108,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)
|
||||
@@ -125,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
|
||||
|
||||
@@ -151,7 +200,7 @@ struct ETCP_CONN {
|
||||
|
||||
// Window and inflight management
|
||||
uint32_t unacked_bytes; // Current inflight bytes
|
||||
// uint32_t window_size; // Receive window - Not used
|
||||
uint32_t max_inflight; // Max inflight cap from config (bbr_max_cwnd)
|
||||
uint32_t optimal_inflight; // Sum over links
|
||||
|
||||
// Timers
|
||||
@@ -170,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
|
||||
@@ -193,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
|
||||
@@ -227,6 +276,9 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp);
|
||||
// Process ACK receipt - remove acknowledged packet from inflight queues
|
||||
void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t dts);
|
||||
|
||||
// Recalculate optimal_inflight and resume input queue after link inflight_lim change
|
||||
void etcp_conn_on_inflight_lim_changed(struct ETCP_CONN* etcp);
|
||||
|
||||
// Update log_name when peer_node_id becomes known
|
||||
void etcp_update_log_name(struct ETCP_CONN* etcp);
|
||||
|
||||
@@ -237,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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_send: queued to [%s]", conn->log_name);
|
||||
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
-7
@@ -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,24 +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 через удаленный узел (remote_proxy)
|
||||
#define ETCP_ID_UDP_PROXY 0x05 // UDP datagram прокси (client ↔ exit)
|
||||
#define ETCP_ID_ICMP_PROXY 0x06 // ICMP echo прокси (ping через exit)
|
||||
#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 соединение от которого получен пакет
|
||||
@@ -46,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 соединения
|
||||
*
|
||||
@@ -62,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 */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -113,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
|
||||
*
|
||||
@@ -127,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
|
||||
+938
@@ -0,0 +1,938 @@
|
||||
#include "etcp_bbr.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
static uint64_t bbr_now(const struct bbr* bbr) { return bbr->now_tb ? bbr->now_tb : get_time_tb(); }
|
||||
|
||||
static void bbr_start_bw_probe_down(struct bbr* bbr);
|
||||
static void bbr_start_bw_probe_cruise(struct bbr* bbr);
|
||||
static void bbr_exit_probe_rtt(struct bbr* bbr);
|
||||
|
||||
static const int bbr_pacing_gain[] = {
|
||||
BBR_UNIT * 5 / 4, /* PROBE_UP: 1.25 */
|
||||
BBR_UNIT * 3 / 4, /* PROBE_DOWN: 0.75 */
|
||||
BBR_UNIT, /* CRUISE: 1.00 */
|
||||
BBR_UNIT, /* REFILL: 1.00 */
|
||||
};
|
||||
|
||||
static uint32_t bbr_max_bw(const struct bbr* bbr)
|
||||
{
|
||||
return (uint32_t)(bbr->bw_hi[0] > bbr->bw_hi[1] ? bbr->bw_hi[0] : bbr->bw_hi[1]);
|
||||
}
|
||||
|
||||
static uint32_t bbr_bw(const struct bbr* bbr)
|
||||
{
|
||||
uint32_t max_bw = bbr_max_bw(bbr);
|
||||
return (uint32_t)(max_bw < bbr->bw_lo ? max_bw : bbr->bw_lo);
|
||||
}
|
||||
|
||||
static int bbr_full_bw_reached(const struct bbr* bbr)
|
||||
{
|
||||
return bbr->full_bw_reached;
|
||||
}
|
||||
|
||||
static uint16_t bbr_extra_acked(const struct bbr* bbr)
|
||||
{
|
||||
return (uint16_t)(bbr->extra_acked[0] > bbr->extra_acked[1] ?
|
||||
bbr->extra_acked[0] : bbr->extra_acked[1]);
|
||||
}
|
||||
|
||||
static uint64_t bbr_rate_bytes_per_sec(uint64_t rate, int gain, int margin, uint32_t mss)
|
||||
{
|
||||
rate *= mss;
|
||||
rate *= (uint64_t)gain;
|
||||
rate >>= BBR_SCALE;
|
||||
rate *= (uint64_t)(1000000 / 100) * (uint64_t)(100 - margin);
|
||||
rate >>= BW_SCALE;
|
||||
if (rate < 1) rate = 1;
|
||||
return rate;
|
||||
}
|
||||
|
||||
static uint32_t bbr_bw_to_pacing_rate(uint32_t bw, int gain, uint32_t mss)
|
||||
{
|
||||
uint64_t rate = bbr_rate_bytes_per_sec(bw, gain, 1, mss);
|
||||
return (uint32_t)rate;
|
||||
}
|
||||
|
||||
static uint32_t bbr_bdp(const struct bbr* bbr, uint32_t bw, int gain, uint32_t mss)
|
||||
{
|
||||
uint64_t w;
|
||||
if (bbr->min_rtt_us == ~0U)
|
||||
return (uint32_t)bbr->init_cwnd * mss;
|
||||
w = (uint64_t)bw * bbr->min_rtt_us;
|
||||
return (uint32_t)((((w * (uint64_t)gain) >> BBR_SCALE) + BW_UNIT - 1) / BW_UNIT) * mss;
|
||||
}
|
||||
|
||||
static uint32_t bbr_quantization_budget(struct bbr* bbr, uint32_t cwnd, uint32_t mss)
|
||||
{
|
||||
cwnd = (uint32_t)(cwnd > BBR_CWND_MIN_TARGET * mss ? cwnd : BBR_CWND_MIN_TARGET * mss);
|
||||
if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_UP)
|
||||
cwnd += 2;
|
||||
return cwnd;
|
||||
}
|
||||
|
||||
static uint32_t bbr_inflight(const struct bbr* bbr, uint32_t bw, int gain, uint32_t mss)
|
||||
{
|
||||
uint32_t inflight = bbr_bdp(bbr, bw, gain, mss);
|
||||
return bbr_quantization_budget((struct bbr*)bbr, inflight, mss);
|
||||
}
|
||||
|
||||
static uint32_t bbr_ack_aggregation_cwnd(const struct bbr* bbr, uint32_t mss)
|
||||
{
|
||||
uint32_t max_aggr_cwnd, aggr_cwnd = 0;
|
||||
(void)mss;
|
||||
max_aggr_cwnd = (uint32_t)((uint64_t)bbr_bw(bbr) * 100000U / BW_UNIT);
|
||||
aggr_cwnd = (uint32_t)((uint32_t)BBR_EXTRA_ACKED_GAIN * bbr_extra_acked(bbr) >> BBR_SCALE);
|
||||
aggr_cwnd = (uint32_t)(aggr_cwnd < max_aggr_cwnd ? aggr_cwnd : max_aggr_cwnd);
|
||||
return aggr_cwnd;
|
||||
}
|
||||
|
||||
static uint32_t bbr_target_inflight(const struct bbr* bbr, uint32_t cwnd, uint32_t mss)
|
||||
{
|
||||
uint32_t bdp = bbr_inflight(bbr, bbr_bw(bbr), BBR_UNIT, mss);
|
||||
return (uint32_t)(bdp < cwnd ? bdp : cwnd);
|
||||
}
|
||||
|
||||
static int bbr_is_probing_bandwidth(const struct bbr* bbr)
|
||||
{
|
||||
return (bbr->mode == BBR_STARTUP) ||
|
||||
(bbr->mode == BBR_PROBE_BW &&
|
||||
(bbr->cycle_idx == BBR_BW_PROBE_REFILL ||
|
||||
bbr->cycle_idx == BBR_BW_PROBE_UP));
|
||||
}
|
||||
|
||||
static int bbr_has_elapsed_in_phase(const struct bbr* bbr, uint32_t interval_us)
|
||||
{
|
||||
uint64_t now_tb = bbr_now(bbr);
|
||||
uint64_t expire_tb = bbr->cycle_mstamp_tb + (uint64_t)(interval_us / 100U);
|
||||
return (int32_t)((uint32_t)expire_tb - (uint32_t)now_tb) <= 0;
|
||||
}
|
||||
|
||||
static uint32_t bbr_update_round_start(struct bbr* bbr, const struct bbr_rate_sample* rs)
|
||||
{
|
||||
uint32_t round_delivered = 0;
|
||||
bbr->round_start = 0;
|
||||
if (rs->interval_us > 0 && !((int32_t)(rs->prior_delivered - bbr->next_rtt_delivered) < 0)) {
|
||||
round_delivered = bbr->delivered - bbr->next_rtt_delivered;
|
||||
bbr->next_rtt_delivered = bbr->delivered;
|
||||
bbr->round_start = 1;
|
||||
}
|
||||
return round_delivered;
|
||||
}
|
||||
|
||||
static uint32_t bbr_calculate_bw_sample(const struct bbr_rate_sample* rs)
|
||||
{
|
||||
if (rs->interval_us > 0)
|
||||
return (uint32_t)(((uint64_t)rs->delivered * BW_UNIT + rs->interval_us - 1) / rs->interval_us);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bbr_update_latest_delivery_signals(struct bbr* bbr,
|
||||
const struct bbr_rate_sample* rs, uint32_t sample_bw)
|
||||
{
|
||||
bbr->loss_round_start = 0;
|
||||
if (rs->interval_us <= 0 || !rs->acked_sacked)
|
||||
return;
|
||||
bbr->bw_latest = (uint32_t)(bbr->bw_latest > sample_bw ? bbr->bw_latest : sample_bw);
|
||||
bbr->inflight_latest = (uint32_t)(bbr->inflight_latest > (uint32_t)rs->delivered ?
|
||||
bbr->inflight_latest : (uint32_t)rs->delivered);
|
||||
if (!((int32_t)(rs->prior_delivered - bbr->loss_round_delivered) < 0)) {
|
||||
bbr->loss_round_delivered = bbr->delivered;
|
||||
bbr->loss_round_start = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void bbr_advance_latest_delivery_signals(struct bbr* bbr,
|
||||
const struct bbr_rate_sample* rs, uint32_t sample_bw)
|
||||
{
|
||||
if (bbr->loss_round_start) {
|
||||
bbr->bw_latest = sample_bw;
|
||||
bbr->inflight_latest = (uint32_t)rs->delivered;
|
||||
}
|
||||
}
|
||||
|
||||
static void bbr_take_max_bw_sample(struct bbr* bbr, uint32_t bw)
|
||||
{
|
||||
bbr->bw_hi[1] = (uint32_t)(bw > bbr->bw_hi[1] ? bw : bbr->bw_hi[1]);
|
||||
}
|
||||
|
||||
static void bbr_init_lower_bounds(struct bbr* bbr, int init_bw, uint32_t cwnd)
|
||||
{
|
||||
if (init_bw && bbr->bw_lo == ~0U)
|
||||
bbr->bw_lo = bbr_max_bw(bbr);
|
||||
if (bbr->inflight_lo == ~0U)
|
||||
bbr->inflight_lo = cwnd;
|
||||
if (bbr->inflight_hi == ~0U)
|
||||
bbr->inflight_hi = (uint32_t)(cwnd > 4U * (uint32_t)bbr->init_cwnd ? cwnd : 4U * (uint32_t)bbr->init_cwnd);
|
||||
}
|
||||
|
||||
static void bbr_loss_lower_bounds(struct bbr* bbr)
|
||||
{
|
||||
uint32_t loss_cut = BBR_UNIT - BBR_BETA;
|
||||
bbr->bw_lo = (uint32_t)(bbr->bw_latest >
|
||||
(uint32_t)((uint64_t)bbr->bw_lo * loss_cut >> BBR_SCALE) ?
|
||||
bbr->bw_latest :
|
||||
(uint32_t)((uint64_t)bbr->bw_lo * loss_cut >> BBR_SCALE));
|
||||
bbr->inflight_lo = (uint32_t)(bbr->inflight_latest >
|
||||
(uint32_t)((uint64_t)bbr->inflight_lo * loss_cut >> BBR_SCALE) ?
|
||||
bbr->inflight_latest :
|
||||
(uint32_t)((uint64_t)bbr->inflight_lo * loss_cut >> BBR_SCALE));
|
||||
}
|
||||
|
||||
static void bbr_adapt_lower_bounds(struct bbr* bbr, uint32_t cwnd)
|
||||
{
|
||||
if (bbr_is_probing_bandwidth(bbr))
|
||||
return;
|
||||
if (bbr->loss_in_round) {
|
||||
bbr_init_lower_bounds(bbr, 1, cwnd);
|
||||
bbr_loss_lower_bounds(bbr);
|
||||
}
|
||||
bbr->bw_lo = (uint32_t)(bbr->bw_lo > 1U ? bbr->bw_lo : 1U);
|
||||
}
|
||||
|
||||
static void bbr_reset_lower_bounds(struct bbr* bbr)
|
||||
{
|
||||
bbr->bw_lo = ~0U;
|
||||
bbr->inflight_lo = ~0U;
|
||||
}
|
||||
|
||||
static void bbr_reset_congestion_signals(struct bbr* bbr)
|
||||
{
|
||||
bbr->loss_in_round = 0;
|
||||
bbr->loss_in_cycle = 0;
|
||||
bbr->bw_latest = 0;
|
||||
bbr->inflight_latest = 0;
|
||||
}
|
||||
|
||||
static int bbr_is_inflight_too_high(const struct bbr* bbr, const struct bbr_rate_sample* rs)
|
||||
{
|
||||
uint32_t loss_thresh;
|
||||
(void)bbr;
|
||||
if (rs->lost > 0 && rs->tx_in_flight) {
|
||||
loss_thresh = (uint32_t)((uint64_t)rs->tx_in_flight * BBR_LOSS_THRESH >> BBR_SCALE);
|
||||
if ((uint32_t)rs->lost > loss_thresh)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bbr_handle_queue_too_high_in_startup(struct bbr* bbr, uint32_t mss)
|
||||
{
|
||||
uint32_t bdp;
|
||||
(void)mss;
|
||||
bbr->full_bw_reached = 1;
|
||||
bdp = bbr_inflight(bbr, bbr_max_bw(bbr), BBR_UNIT, mss);
|
||||
bbr->inflight_hi = (uint32_t)(bdp > bbr->inflight_latest ? bdp : bbr->inflight_latest);
|
||||
}
|
||||
|
||||
static void bbr_check_loss_too_high_in_startup(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t mss)
|
||||
{
|
||||
if (bbr->full_bw_reached)
|
||||
return;
|
||||
if (rs->lost > 0 && bbr->loss_events_in_round < 0xf)
|
||||
bbr->loss_events_in_round++;
|
||||
if (bbr->loss_round_start &&
|
||||
bbr->loss_events_in_round >= BBR_FULL_LOSS_CNT &&
|
||||
bbr_is_inflight_too_high(bbr, rs)) {
|
||||
bbr_handle_queue_too_high_in_startup(bbr, mss);
|
||||
return;
|
||||
}
|
||||
if (bbr->loss_round_start)
|
||||
bbr->loss_events_in_round = 0;
|
||||
}
|
||||
|
||||
static void bbr_reset_full_bw(struct bbr* bbr)
|
||||
{
|
||||
bbr->full_bw = 0;
|
||||
bbr->full_bw_cnt = 0;
|
||||
bbr->full_bw_now = 0;
|
||||
}
|
||||
|
||||
static void bbr_advance_max_bw_filter(struct bbr* bbr)
|
||||
{
|
||||
if (!bbr->bw_hi[1])
|
||||
return;
|
||||
bbr->bw_hi[0] = bbr->bw_hi[1];
|
||||
bbr->bw_hi[1] = 0;
|
||||
}
|
||||
|
||||
static void bbr_check_full_bw_reached(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t sample_bw)
|
||||
{
|
||||
uint32_t bw_thresh;
|
||||
if (bbr->full_bw_now || rs->is_app_limited)
|
||||
return;
|
||||
bw_thresh = (uint32_t)((uint64_t)bbr->full_bw * BBR_FULL_BW_THRESH >> BBR_SCALE);
|
||||
if (sample_bw >= bw_thresh) {
|
||||
bbr_reset_full_bw(bbr);
|
||||
bbr->full_bw = sample_bw;
|
||||
return;
|
||||
}
|
||||
if (!bbr->round_start)
|
||||
return;
|
||||
bbr->full_bw_cnt = (uint8_t)(bbr->full_bw_cnt + 1);
|
||||
bbr->full_bw_now = (uint8_t)(bbr->full_bw_cnt >= BBR_FULL_BW_CNT ? 1 : 0);
|
||||
bbr->full_bw_reached |= bbr->full_bw_now;
|
||||
}
|
||||
|
||||
static void bbr_raise_inflight_hi_slope(struct bbr* bbr, uint32_t cwnd)
|
||||
{
|
||||
uint32_t growth_this_round = 1U << bbr->bw_probe_up_rounds;
|
||||
uint32_t cnt;
|
||||
bbr->bw_probe_up_rounds = (uint8_t)(bbr->bw_probe_up_rounds + 1 < 30 ?
|
||||
bbr->bw_probe_up_rounds + 1 : 30);
|
||||
cnt = cwnd / growth_this_round;
|
||||
cnt = (uint32_t)(cnt > 1U ? cnt : 1U);
|
||||
bbr->bw_probe_up_cnt = cnt;
|
||||
}
|
||||
|
||||
static void bbr_set_cycle_idx(struct bbr* bbr, int cycle_idx)
|
||||
{
|
||||
bbr->cycle_idx = (uint8_t)cycle_idx;
|
||||
bbr->try_fast_path = 0;
|
||||
}
|
||||
|
||||
static void bbr_start_bw_probe_refill(struct bbr* bbr, uint32_t bw_probe_up_rounds)
|
||||
{
|
||||
bbr_reset_lower_bounds(bbr);
|
||||
bbr->bw_probe_up_rounds = (uint8_t)bw_probe_up_rounds;
|
||||
bbr->bw_probe_up_acks = 0;
|
||||
bbr->stopped_risky_probe = 0;
|
||||
bbr->ack_phase = BBR_ACKS_REFILLING;
|
||||
bbr->next_rtt_delivered = bbr->delivered;
|
||||
bbr_set_cycle_idx(bbr, BBR_BW_PROBE_REFILL);
|
||||
}
|
||||
|
||||
static void bbr_start_bw_probe_up(struct bbr* bbr, uint32_t sample_bw, uint32_t cwnd)
|
||||
{
|
||||
bbr->ack_phase = BBR_ACKS_PROBE_STARTING;
|
||||
bbr->next_rtt_delivered = bbr->delivered;
|
||||
bbr->cycle_mstamp_tb = bbr_now(bbr);
|
||||
bbr_reset_full_bw(bbr);
|
||||
bbr->full_bw = sample_bw;
|
||||
bbr_set_cycle_idx(bbr, BBR_BW_PROBE_UP);
|
||||
bbr_raise_inflight_hi_slope(bbr, cwnd);
|
||||
}
|
||||
|
||||
static void bbr_save_cwnd(struct bbr* bbr, uint32_t cwnd)
|
||||
{
|
||||
if (bbr->mode != BBR_PROBE_RTT)
|
||||
bbr->prior_cwnd = cwnd;
|
||||
else
|
||||
bbr->prior_cwnd = (uint32_t)(bbr->prior_cwnd > cwnd ? bbr->prior_cwnd : cwnd);
|
||||
}
|
||||
|
||||
static void bbr_exit_probe_rtt(struct bbr* bbr)
|
||||
{
|
||||
bbr_reset_lower_bounds(bbr);
|
||||
if (bbr->full_bw_reached) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d exit probe_rtt → PROBE_BW",
|
||||
bbr->link_id, bbr->mode, BBR_PROBE_BW);
|
||||
bbr->mode = BBR_PROBE_BW;
|
||||
bbr_start_bw_probe_down(bbr);
|
||||
bbr_start_bw_probe_cruise(bbr);
|
||||
} else {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d exit probe_rtt → STARTUP",
|
||||
bbr->link_id, bbr->mode, BBR_STARTUP);
|
||||
bbr->mode = BBR_STARTUP;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t bbr_probe_rtt_cwnd(const struct bbr* bbr, uint32_t mss)
|
||||
{
|
||||
(void)bbr;
|
||||
return BBR_CWND_MIN_TARGET * mss;
|
||||
}
|
||||
|
||||
static uint32_t bbr_inflight_with_headroom(const struct bbr* bbr)
|
||||
{
|
||||
uint32_t headroom;
|
||||
if (bbr->inflight_hi == ~0U)
|
||||
return ~0U;
|
||||
headroom = (uint32_t)((uint64_t)bbr->inflight_hi * BBR_INFLIGHT_HEADROOM >> BBR_SCALE);
|
||||
headroom = (uint32_t)(headroom > 1U ? headroom : 1U);
|
||||
return bbr->inflight_hi - headroom;
|
||||
}
|
||||
|
||||
static void bbr_pick_probe_wait(struct bbr* bbr)
|
||||
{
|
||||
bbr->rounds_since_probe = (uint8_t)((uint32_t)rand() % BBR_BW_PROBE_RAND_ROUNDS);
|
||||
bbr->probe_wait_us = BBR_BW_PROBE_BASE_US + (uint32_t)rand() % BBR_BW_PROBE_RAND_US;
|
||||
}
|
||||
|
||||
static void bbr_start_bw_probe_down(struct bbr* bbr)
|
||||
{
|
||||
bbr_reset_congestion_signals(bbr);
|
||||
bbr->bw_probe_up_cnt = ~0U;
|
||||
bbr_pick_probe_wait(bbr);
|
||||
bbr->cycle_mstamp_tb = bbr_now(bbr);
|
||||
bbr->ack_phase = BBR_ACKS_PROBE_STOPPING;
|
||||
bbr->next_rtt_delivered = bbr->delivered;
|
||||
bbr_set_cycle_idx(bbr, BBR_BW_PROBE_DOWN);
|
||||
}
|
||||
|
||||
static void bbr_start_bw_probe_cruise(struct bbr* bbr)
|
||||
{
|
||||
if (bbr->inflight_lo != ~0U)
|
||||
bbr->inflight_lo = (uint32_t)(bbr->inflight_lo < bbr->inflight_hi ?
|
||||
bbr->inflight_lo : bbr->inflight_hi);
|
||||
bbr_set_cycle_idx(bbr, BBR_BW_PROBE_CRUISE);
|
||||
}
|
||||
|
||||
static void bbr_handle_inflight_too_high(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t cwnd, uint32_t mss)
|
||||
{
|
||||
const uint32_t beta = BBR_BETA;
|
||||
bbr->prev_probe_too_high = 1;
|
||||
bbr->bw_probe_samples = 0;
|
||||
if (!rs->is_app_limited) {
|
||||
uint32_t ti = bbr_target_inflight(bbr, cwnd, mss);
|
||||
bbr->inflight_hi = (uint32_t)(rs->tx_in_flight >
|
||||
(uint32_t)((uint64_t)ti * (BBR_UNIT - beta) >> BBR_SCALE) ?
|
||||
rs->tx_in_flight :
|
||||
(uint32_t)((uint64_t)ti * (BBR_UNIT - beta) >> BBR_SCALE));
|
||||
}
|
||||
if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_UP)
|
||||
bbr_start_bw_probe_down(bbr);
|
||||
}
|
||||
|
||||
static void bbr_probe_inflight_hi_upward(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
int is_cwnd_limited, uint32_t cwnd)
|
||||
{
|
||||
uint32_t delta;
|
||||
if (!is_cwnd_limited || cwnd < bbr->inflight_hi)
|
||||
return;
|
||||
bbr->bw_probe_up_acks += rs->acked_sacked;
|
||||
if (bbr->bw_probe_up_acks >= bbr->bw_probe_up_cnt &&
|
||||
bbr->bw_probe_up_cnt > 0) {
|
||||
delta = bbr->bw_probe_up_acks / bbr->bw_probe_up_cnt;
|
||||
bbr->bw_probe_up_acks -= delta * bbr->bw_probe_up_cnt;
|
||||
bbr->inflight_hi += delta;
|
||||
bbr->try_fast_path = 0;
|
||||
}
|
||||
if (bbr->round_start)
|
||||
bbr_raise_inflight_hi_slope(bbr, cwnd);
|
||||
}
|
||||
|
||||
static void bbr_adapt_upper_bounds(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t sample_bw, int is_cwnd_limited,
|
||||
uint32_t cwnd, uint32_t mss, int* decided)
|
||||
{
|
||||
(void)sample_bw;
|
||||
*decided = 0;
|
||||
if (bbr->ack_phase == BBR_ACKS_PROBE_STARTING && bbr->round_start)
|
||||
bbr->ack_phase = BBR_ACKS_PROBE_FEEDBACK;
|
||||
if (bbr->ack_phase == BBR_ACKS_PROBE_STOPPING && bbr->round_start) {
|
||||
bbr->bw_probe_samples = 0;
|
||||
bbr->ack_phase = BBR_ACKS_INIT;
|
||||
if (bbr->mode == BBR_PROBE_BW && !rs->is_app_limited)
|
||||
bbr_advance_max_bw_filter(bbr);
|
||||
if (bbr->mode == BBR_PROBE_BW && bbr->stopped_risky_probe && !bbr->prev_probe_too_high) {
|
||||
bbr_start_bw_probe_refill(bbr, 0);
|
||||
*decided = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (bbr_is_inflight_too_high(bbr, rs)) {
|
||||
if (bbr->bw_probe_samples)
|
||||
bbr_handle_inflight_too_high(bbr, rs, cwnd, mss);
|
||||
} else {
|
||||
if (bbr->inflight_hi == ~0U)
|
||||
return;
|
||||
if (rs->tx_in_flight > bbr->inflight_hi)
|
||||
bbr->inflight_hi = rs->tx_in_flight;
|
||||
if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_UP)
|
||||
bbr_probe_inflight_hi_upward(bbr, rs, is_cwnd_limited, cwnd);
|
||||
}
|
||||
}
|
||||
|
||||
static int bbr_is_reno_coexistence_probe_time(struct bbr* bbr, uint32_t cwnd, uint32_t mss)
|
||||
{
|
||||
uint32_t rounds = bbr_target_inflight(bbr, cwnd, mss);
|
||||
if (rounds > BBR_BW_PROBE_MAX_ROUNDS)
|
||||
rounds = BBR_BW_PROBE_MAX_ROUNDS;
|
||||
return bbr->rounds_since_probe >= rounds;
|
||||
}
|
||||
|
||||
static int bbr_check_time_to_probe_bw(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t cwnd, uint32_t mss)
|
||||
{
|
||||
(void)rs;
|
||||
if (bbr_has_elapsed_in_phase(bbr, bbr->probe_wait_us) ||
|
||||
bbr_is_reno_coexistence_probe_time(bbr, cwnd, mss)) {
|
||||
bbr_start_bw_probe_refill(bbr, 0);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bbr_check_time_to_cruise(const struct bbr* bbr, uint32_t inflight,
|
||||
uint32_t bw, uint32_t mss)
|
||||
{
|
||||
if (inflight > bbr_inflight_with_headroom(bbr))
|
||||
return 0;
|
||||
return inflight <= bbr_inflight(bbr, bw, BBR_UNIT, mss);
|
||||
}
|
||||
|
||||
static void bbr_update_cycle_phase(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t sample_bw, uint32_t mss,
|
||||
uint32_t inflight_packets, int is_cwnd_limited,
|
||||
uint32_t cwnd)
|
||||
{
|
||||
int decided = 0;
|
||||
int is_bw_probe_done;
|
||||
uint32_t inflight, bw;
|
||||
|
||||
if (!bbr->full_bw_reached)
|
||||
return;
|
||||
bbr_adapt_upper_bounds(bbr, rs, sample_bw, is_cwnd_limited, cwnd, mss, &decided);
|
||||
if (decided)
|
||||
return;
|
||||
if (bbr->mode != BBR_PROBE_BW)
|
||||
return;
|
||||
|
||||
inflight = inflight_packets;
|
||||
bw = bbr_max_bw(bbr);
|
||||
|
||||
switch (bbr->cycle_idx) {
|
||||
case BBR_BW_PROBE_CRUISE:
|
||||
if (bbr_check_time_to_probe_bw(bbr, rs, cwnd, mss))
|
||||
return;
|
||||
break;
|
||||
case BBR_BW_PROBE_REFILL:
|
||||
if (bbr->round_start) {
|
||||
bbr->bw_probe_samples = 1;
|
||||
bbr_start_bw_probe_up(bbr, sample_bw, cwnd);
|
||||
}
|
||||
break;
|
||||
case BBR_BW_PROBE_UP:
|
||||
is_bw_probe_done = 0;
|
||||
if (bbr->prev_probe_too_high && inflight >= bbr->inflight_hi) {
|
||||
bbr->stopped_risky_probe = 1;
|
||||
is_bw_probe_done = 1;
|
||||
} else {
|
||||
if (is_cwnd_limited && cwnd >= bbr->inflight_hi) {
|
||||
bbr_reset_full_bw(bbr);
|
||||
bbr->full_bw = sample_bw;
|
||||
} else if (bbr->full_bw_now) {
|
||||
is_bw_probe_done = 1;
|
||||
}
|
||||
}
|
||||
if (is_bw_probe_done) {
|
||||
bbr->prev_probe_too_high = 0;
|
||||
bbr_start_bw_probe_down(bbr);
|
||||
}
|
||||
break;
|
||||
case BBR_BW_PROBE_DOWN:
|
||||
if (bbr_check_time_to_probe_bw(bbr, rs, cwnd, mss))
|
||||
return;
|
||||
if (bbr_check_time_to_cruise(bbr, inflight, bw, mss))
|
||||
bbr_start_bw_probe_cruise(bbr);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void bbr_check_drain(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t sample_bw, uint32_t mss, uint32_t inflight_packets)
|
||||
{
|
||||
(void)rs; (void)sample_bw;
|
||||
if (bbr->mode == BBR_STARTUP && bbr->full_bw_reached) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d STARTUP→DRAIN", bbr->link_id, bbr->mode, BBR_DRAIN);
|
||||
bbr->mode = BBR_DRAIN;
|
||||
bbr_reset_congestion_signals(bbr);
|
||||
}
|
||||
if (bbr->mode == BBR_DRAIN &&
|
||||
inflight_packets <= bbr_inflight(bbr, bbr_max_bw(bbr), BBR_UNIT, mss)) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d DRAIN→PROBE_BW infl_pkt=%u", bbr->link_id, bbr->mode, BBR_PROBE_BW, inflight_packets);
|
||||
bbr->mode = BBR_PROBE_BW;
|
||||
bbr_start_bw_probe_down(bbr);
|
||||
}
|
||||
}
|
||||
|
||||
static void bbr_check_probe_rtt_done(struct bbr* bbr, uint32_t* cwnd)
|
||||
{
|
||||
if (!bbr->probe_rtt_done_stamp_tb)
|
||||
return;
|
||||
if (!((int32_t)((uint32_t)bbr_now(bbr) - (uint32_t)bbr->probe_rtt_done_stamp_tb) >= 0))
|
||||
return;
|
||||
bbr->probe_rtt_min_stamp_tb = bbr_now(bbr);
|
||||
*cwnd = (uint32_t)(*cwnd > bbr->prior_cwnd ? *cwnd : bbr->prior_cwnd);
|
||||
bbr_exit_probe_rtt(bbr);
|
||||
}
|
||||
|
||||
static void bbr_update_min_rtt(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t* cwnd, uint32_t mss, uint32_t inflight_packets)
|
||||
{
|
||||
uint32_t expire;
|
||||
int probe_rtt_expired, min_rtt_expired;
|
||||
(void)mss;
|
||||
|
||||
expire = (uint32_t)(bbr->probe_rtt_min_stamp_tb + BBR_PROBE_RTT_WIN_MS * 10U);
|
||||
probe_rtt_expired = ((int32_t)(expire - (uint32_t)bbr_now(bbr)) < 0);
|
||||
if (rs->rtt_us > 0 &&
|
||||
(rs->rtt_us < bbr->probe_rtt_min_us || probe_rtt_expired)) {
|
||||
bbr->probe_rtt_min_us = rs->rtt_us;
|
||||
bbr->probe_rtt_min_stamp_tb = bbr_now(bbr);
|
||||
bbr->has_seen_rtt = 1;
|
||||
}
|
||||
if (bbr->probe_rtt_min_us == 0)
|
||||
bbr->probe_rtt_min_us = ~0U;
|
||||
|
||||
expire = (uint32_t)(bbr->min_rtt_stamp_tb + BBR_MIN_RTT_WIN_SEC * 10000U);
|
||||
min_rtt_expired = ((int32_t)(expire - (uint32_t)bbr_now(bbr)) < 0);
|
||||
if (bbr->probe_rtt_min_us <= bbr->min_rtt_us || min_rtt_expired) {
|
||||
bbr->min_rtt_us = bbr->probe_rtt_min_us;
|
||||
bbr->min_rtt_stamp_tb = bbr_now(bbr);
|
||||
}
|
||||
|
||||
if (BBR_PROBE_RTT_MS > 0 && probe_rtt_expired &&
|
||||
!bbr->idle_restart && bbr->mode != BBR_PROBE_RTT) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d →PROBE_RTT cwnd=%u infl_pkt=%u",
|
||||
bbr->link_id, bbr->mode, BBR_PROBE_RTT, *cwnd, inflight_packets);
|
||||
bbr->mode = BBR_PROBE_RTT;
|
||||
bbr_save_cwnd(bbr, *cwnd);
|
||||
bbr->probe_rtt_done_stamp_tb = 0;
|
||||
bbr->ack_phase = BBR_ACKS_PROBE_STOPPING;
|
||||
bbr->next_rtt_delivered = bbr->delivered;
|
||||
}
|
||||
|
||||
if (bbr->mode == BBR_PROBE_RTT) {
|
||||
if (!bbr->probe_rtt_done_stamp_tb &&
|
||||
inflight_packets <= bbr_probe_rtt_cwnd(bbr, mss)) {
|
||||
bbr->probe_rtt_done_stamp_tb = (uint32_t)bbr_now(bbr) + BBR_PROBE_RTT_MS * 10U;
|
||||
bbr->probe_rtt_round_done = 0;
|
||||
bbr->next_rtt_delivered = bbr->delivered;
|
||||
} else if (bbr->probe_rtt_done_stamp_tb) {
|
||||
if (bbr->round_start)
|
||||
bbr->probe_rtt_round_done = 1;
|
||||
if (bbr->probe_rtt_round_done)
|
||||
bbr_check_probe_rtt_done(bbr, cwnd);
|
||||
}
|
||||
}
|
||||
if (rs->delivered > 0)
|
||||
bbr->idle_restart = 0;
|
||||
}
|
||||
|
||||
static void bbr_reset_startup_mode(struct bbr* bbr)
|
||||
{
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d →STARTUP (reset)", bbr->link_id, bbr->mode, BBR_STARTUP);
|
||||
bbr->mode = BBR_STARTUP;
|
||||
}
|
||||
|
||||
static void bbr_update_gains(struct bbr* bbr)
|
||||
{
|
||||
switch (bbr->mode) {
|
||||
case BBR_STARTUP:
|
||||
bbr->pacing_gain = BBR_STARTUP_PACING_GAIN;
|
||||
bbr->cwnd_gain = BBR_STARTUP_CWND_GAIN;
|
||||
break;
|
||||
case BBR_DRAIN:
|
||||
bbr->pacing_gain = BBR_DRAIN_GAIN;
|
||||
bbr->cwnd_gain = BBR_STARTUP_CWND_GAIN;
|
||||
break;
|
||||
case BBR_PROBE_BW:
|
||||
bbr->pacing_gain = bbr_pacing_gain[bbr->cycle_idx];
|
||||
bbr->cwnd_gain = BBR_CWND_GAIN;
|
||||
break;
|
||||
case BBR_PROBE_RTT:
|
||||
bbr->pacing_gain = BBR_UNIT;
|
||||
bbr->cwnd_gain = BBR_UNIT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void bbr_update_congestion_signals(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t sample_bw, uint32_t cwnd)
|
||||
{
|
||||
uint32_t bw;
|
||||
if (rs->interval_us <= 0 || !rs->acked_sacked)
|
||||
return;
|
||||
bw = sample_bw;
|
||||
if (!rs->is_app_limited || bw >= bbr_max_bw(bbr))
|
||||
bbr_take_max_bw_sample(bbr, bw);
|
||||
bbr->loss_in_round |= (rs->lost > 0);
|
||||
if (!bbr->loss_round_start)
|
||||
return;
|
||||
bbr_adapt_lower_bounds(bbr, cwnd);
|
||||
bbr->loss_in_round = 0;
|
||||
}
|
||||
|
||||
static void bbr_update_ack_aggregation(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t cwnd, uint32_t mss)
|
||||
{
|
||||
uint32_t epoch_us, expected_acked, extra_acked;
|
||||
uint32_t extra_acked_win_rtts_thresh = BBR_EXTRA_ACKED_WIN_RTTS;
|
||||
uint64_t now_us;
|
||||
(void)mss;
|
||||
|
||||
if (rs->acked_sacked <= 0 || rs->interval_us <= 0)
|
||||
return;
|
||||
|
||||
if (bbr->round_start) {
|
||||
bbr->extra_acked_win_rtts = (uint8_t)(bbr->extra_acked_win_rtts + 1 < 0x1F ?
|
||||
bbr->extra_acked_win_rtts + 1 : 0x1F);
|
||||
if (!bbr->full_bw_reached)
|
||||
extra_acked_win_rtts_thresh = 1;
|
||||
if (bbr->extra_acked_win_rtts >= extra_acked_win_rtts_thresh) {
|
||||
bbr->extra_acked_win_rtts = 0;
|
||||
bbr->extra_acked_win_idx = bbr->extra_acked_win_idx ? 0 : 1;
|
||||
bbr->extra_acked[bbr->extra_acked_win_idx] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
now_us = bbr_now(bbr) * 100ULL;
|
||||
if (now_us > bbr->ack_epoch_mstamp_tb * 100ULL)
|
||||
epoch_us = (uint32_t)(now_us - bbr->ack_epoch_mstamp_tb * 100ULL);
|
||||
else
|
||||
epoch_us = 0;
|
||||
expected_acked = (uint32_t)((uint64_t)bbr_bw(bbr) * epoch_us / BW_UNIT);
|
||||
|
||||
if (bbr->ack_epoch_acked <= expected_acked ||
|
||||
(bbr->ack_epoch_acked + rs->acked_sacked >= BBR_ACK_EPOCH_ACKED_RESET_THRESH)) {
|
||||
bbr->ack_epoch_acked = 0;
|
||||
bbr->ack_epoch_mstamp_tb = bbr_now(bbr);
|
||||
expected_acked = 0;
|
||||
}
|
||||
|
||||
bbr->ack_epoch_acked = (uint32_t)(bbr->ack_epoch_acked + rs->acked_sacked < 0xFFFFF ?
|
||||
bbr->ack_epoch_acked + rs->acked_sacked : 0xFFFFF);
|
||||
extra_acked = bbr->ack_epoch_acked - expected_acked;
|
||||
extra_acked = (uint32_t)(extra_acked < cwnd ? extra_acked : cwnd);
|
||||
if (extra_acked > bbr->extra_acked[bbr->extra_acked_win_idx])
|
||||
bbr->extra_acked[bbr->extra_acked_win_idx] = (uint16_t)extra_acked;
|
||||
}
|
||||
|
||||
static void bbr_init_pacing_rate_from_rtt(struct bbr* bbr, uint32_t cwnd,
|
||||
uint32_t mss, uint32_t* pacing_rate_out)
|
||||
{
|
||||
uint64_t bw;
|
||||
uint32_t rtt_us;
|
||||
|
||||
if (bbr->has_seen_rtt && bbr->min_rtt_us != ~0U) {
|
||||
rtt_us = bbr->min_rtt_us;
|
||||
if (rtt_us < 1) rtt_us = 1;
|
||||
} else {
|
||||
rtt_us = 1000;
|
||||
}
|
||||
bw = (uint64_t)cwnd * BW_UNIT / rtt_us;
|
||||
*pacing_rate_out = (uint32_t)bbr_rate_bytes_per_sec(bw, BBR_STARTUP_PACING_GAIN, 1, mss);
|
||||
}
|
||||
|
||||
static void bbr_set_pacing_rate(struct bbr* bbr, uint32_t bw, int gain,
|
||||
uint32_t mss, uint32_t* pacing_rate_out)
|
||||
{
|
||||
uint32_t rate = bbr_bw_to_pacing_rate(bw, gain, mss);
|
||||
|
||||
if (!bbr->has_seen_rtt && bbr->min_rtt_us != ~0U)
|
||||
bbr_init_pacing_rate_from_rtt(bbr, *pacing_rate_out > 0 ? *pacing_rate_out : 4, mss, pacing_rate_out);
|
||||
if (bbr->full_bw_reached || rate > *pacing_rate_out)
|
||||
*pacing_rate_out = rate;
|
||||
}
|
||||
|
||||
static void bbr_set_cwnd(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t acked, uint32_t bw, int gain,
|
||||
uint32_t mss, uint32_t* cwnd)
|
||||
{
|
||||
uint32_t target_cwnd = 0;
|
||||
uint32_t local_cwnd = *cwnd;
|
||||
|
||||
(void)rs;
|
||||
if (!acked)
|
||||
goto done;
|
||||
|
||||
target_cwnd = bbr_bdp(bbr, bw, gain, mss);
|
||||
target_cwnd += bbr_ack_aggregation_cwnd(bbr, mss);
|
||||
target_cwnd = bbr_quantization_budget(bbr, target_cwnd, mss);
|
||||
|
||||
bbr->try_fast_path = 0;
|
||||
if (bbr->full_bw_reached) {
|
||||
local_cwnd += acked;
|
||||
if (local_cwnd >= target_cwnd) {
|
||||
local_cwnd = target_cwnd;
|
||||
bbr->try_fast_path = 1;
|
||||
}
|
||||
} else if (local_cwnd < target_cwnd || local_cwnd < 2 * (uint32_t)bbr->init_cwnd * mss) {
|
||||
local_cwnd += acked;
|
||||
} else {
|
||||
bbr->try_fast_path = 1;
|
||||
}
|
||||
|
||||
local_cwnd = (uint32_t)(local_cwnd > BBR_CWND_MIN_TARGET * mss ?
|
||||
local_cwnd : BBR_CWND_MIN_TARGET * mss);
|
||||
done:
|
||||
*cwnd = local_cwnd;
|
||||
if (bbr->mode == BBR_PROBE_RTT) {
|
||||
uint32_t pr_cwnd = bbr_probe_rtt_cwnd(bbr, mss);
|
||||
*cwnd = (uint32_t)(*cwnd < pr_cwnd ? *cwnd : pr_cwnd);
|
||||
}
|
||||
}
|
||||
|
||||
static void bbr_bound_cwnd_for_inflight_model(struct bbr* bbr, uint32_t* cwnd, uint32_t mss)
|
||||
{
|
||||
uint32_t cap = ~0U;
|
||||
|
||||
if (!bbr->initialized)
|
||||
return;
|
||||
|
||||
if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx != BBR_BW_PROBE_CRUISE)
|
||||
cap = bbr->inflight_hi;
|
||||
else if (bbr->mode == BBR_PROBE_RTT ||
|
||||
(bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_CRUISE))
|
||||
cap = bbr_inflight_with_headroom(bbr);
|
||||
|
||||
cap = (uint32_t)(cap > BBR_CWND_MIN_TARGET * mss ? cap : BBR_CWND_MIN_TARGET * mss);
|
||||
cap = (uint32_t)(cap < bbr->inflight_lo ? cap : bbr->inflight_lo);
|
||||
*cwnd = (uint32_t)(cap < *cwnd ? cap : *cwnd);
|
||||
}
|
||||
|
||||
static int bbr_run_fast_path(struct bbr* bbr, int* update_model,
|
||||
const struct bbr_rate_sample* rs, uint32_t sample_bw,
|
||||
uint32_t mss, uint32_t inflight_packets,
|
||||
int is_cwnd_limited, uint32_t* cwnd)
|
||||
{
|
||||
uint32_t prev_min_rtt_us;
|
||||
uint8_t prev_mode;
|
||||
|
||||
if (bbr->try_fast_path && rs->is_app_limited &&
|
||||
sample_bw < bbr_max_bw(bbr) && !bbr->loss_in_round) {
|
||||
prev_mode = bbr->mode;
|
||||
prev_min_rtt_us = bbr->min_rtt_us;
|
||||
bbr_check_drain(bbr, rs, sample_bw, mss, inflight_packets);
|
||||
bbr_update_cycle_phase(bbr, rs, sample_bw, mss, inflight_packets,
|
||||
is_cwnd_limited, *cwnd);
|
||||
bbr_update_min_rtt(bbr, rs, cwnd, mss, inflight_packets);
|
||||
|
||||
if (bbr->mode == prev_mode &&
|
||||
bbr->min_rtt_us == prev_min_rtt_us &&
|
||||
bbr->try_fast_path)
|
||||
return 1;
|
||||
*update_model = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void bbr_init(struct bbr* bbr)
|
||||
{
|
||||
bbr->initialized = 1;
|
||||
bbr->init_cwnd = 0;
|
||||
bbr->prior_cwnd = 0;
|
||||
bbr->delivered = 0;
|
||||
bbr->next_rtt_delivered = 0;
|
||||
|
||||
bbr->probe_rtt_done_stamp_tb = 0;
|
||||
bbr->probe_rtt_round_done = 0;
|
||||
bbr->probe_rtt_min_us = ~0U;
|
||||
bbr->probe_rtt_min_stamp_tb = bbr_now(bbr);
|
||||
bbr->min_rtt_us = ~0U;
|
||||
bbr->min_rtt_stamp_tb = bbr_now(bbr);
|
||||
|
||||
bbr->has_seen_rtt = 0;
|
||||
|
||||
bbr->round_start = 0;
|
||||
bbr->idle_restart = 0;
|
||||
bbr->full_bw_reached = 0;
|
||||
bbr->full_bw = 0;
|
||||
bbr->full_bw_cnt = 0;
|
||||
bbr->full_bw_now = 0;
|
||||
bbr->cycle_mstamp_tb = 0;
|
||||
bbr->cycle_idx = 0;
|
||||
|
||||
bbr_reset_startup_mode(bbr);
|
||||
|
||||
bbr->ack_epoch_mstamp_tb = bbr_now(bbr);
|
||||
bbr->ack_epoch_acked = 0;
|
||||
bbr->extra_acked_win_rtts = 0;
|
||||
bbr->extra_acked_win_idx = 0;
|
||||
bbr->extra_acked[0] = 0;
|
||||
bbr->extra_acked[1] = 0;
|
||||
|
||||
bbr->try_fast_path = 0;
|
||||
bbr->loss_round_delivered = 1;
|
||||
bbr->loss_round_start = 0;
|
||||
bbr->loss_events_in_round = 0;
|
||||
bbr_reset_congestion_signals(bbr);
|
||||
bbr->bw_lo = ~0U;
|
||||
bbr->bw_hi[0] = 0;
|
||||
bbr->bw_hi[1] = 0;
|
||||
bbr->inflight_lo = ~0U;
|
||||
bbr->inflight_hi = ~0U;
|
||||
bbr_reset_full_bw(bbr);
|
||||
bbr->bw_probe_up_cnt = ~0U;
|
||||
bbr->bw_probe_up_acks = 0;
|
||||
bbr->bw_probe_up_rounds = 0;
|
||||
bbr->probe_wait_us = 0;
|
||||
bbr->stopped_risky_probe = 0;
|
||||
bbr->ack_phase = BBR_ACKS_INIT;
|
||||
bbr->rounds_since_probe = 0;
|
||||
bbr->bw_probe_samples = 0;
|
||||
bbr->prev_probe_too_high = 0;
|
||||
bbr->on_cwnd_update = NULL;
|
||||
bbr->cwnd_update_ctx = NULL;
|
||||
}
|
||||
|
||||
void bbr_main(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t* cwnd_out, uint32_t* pacing_rate_out,
|
||||
uint32_t mss, uint32_t inflight_bytes, int is_cwnd_limited)
|
||||
{
|
||||
uint32_t cwnd = *cwnd_out;
|
||||
uint32_t sample_bw, bw;
|
||||
int update_model = 1;
|
||||
|
||||
bbr->delivered += rs->delivered;
|
||||
bbr_update_round_start(bbr, rs);
|
||||
|
||||
if (bbr->round_start)
|
||||
bbr->rounds_since_probe = (uint8_t)(bbr->rounds_since_probe + 1 < 0xFF ?
|
||||
bbr->rounds_since_probe + 1 : 0xFF);
|
||||
|
||||
sample_bw = bbr_calculate_bw_sample(rs);
|
||||
bbr_update_latest_delivery_signals(bbr, rs, sample_bw);
|
||||
|
||||
if (bbr_run_fast_path(bbr, &update_model, rs, sample_bw, mss,
|
||||
inflight_bytes, is_cwnd_limited, &cwnd))
|
||||
goto out;
|
||||
|
||||
if (update_model) {
|
||||
bbr_update_congestion_signals(bbr, rs, sample_bw, cwnd);
|
||||
bbr_update_ack_aggregation(bbr, rs, cwnd, mss);
|
||||
bbr_check_loss_too_high_in_startup(bbr, rs, mss);
|
||||
bbr_check_full_bw_reached(bbr, rs, sample_bw);
|
||||
bbr_check_drain(bbr, rs, sample_bw, mss, inflight_bytes);
|
||||
bbr_update_cycle_phase(bbr, rs, sample_bw, mss, inflight_bytes,
|
||||
is_cwnd_limited, cwnd);
|
||||
bbr_update_min_rtt(bbr, rs, &cwnd, mss, inflight_bytes);
|
||||
}
|
||||
|
||||
bbr_update_gains(bbr);
|
||||
bw = bbr_bw(bbr);
|
||||
bbr_set_pacing_rate(bbr, bw, bbr->pacing_gain, mss, pacing_rate_out);
|
||||
bbr_set_cwnd(bbr, rs, rs->acked_sacked, bw, bbr->cwnd_gain, mss, &cwnd);
|
||||
bbr_bound_cwnd_for_inflight_model(bbr, &cwnd, mss);
|
||||
|
||||
out:
|
||||
bbr_advance_latest_delivery_signals(bbr, rs, sample_bw);
|
||||
bbr->loss_in_cycle |= (rs->lost > 0);
|
||||
if (bbr->on_cwnd_update) bbr->on_cwnd_update(bbr->cwnd_update_ctx, cwnd);
|
||||
*cwnd_out = cwnd;
|
||||
}
|
||||
|
||||
void bbr_note_loss(struct bbr* bbr)
|
||||
{
|
||||
if (!bbr->loss_in_round)
|
||||
bbr->loss_round_delivered = bbr->delivered;
|
||||
bbr->loss_in_round = 1;
|
||||
bbr->loss_in_cycle = 1;
|
||||
}
|
||||
|
||||
void bbr_tx_start(struct bbr* bbr)
|
||||
{
|
||||
bbr->idle_restart = 1;
|
||||
bbr->ack_epoch_mstamp_tb = bbr_now(bbr);
|
||||
bbr->ack_epoch_acked = 0;
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define BBR_SCALE 8
|
||||
#define BBR_UNIT (1 << BBR_SCALE)
|
||||
#define BW_SCALE 24
|
||||
#define BW_UNIT (1 << BW_SCALE)
|
||||
|
||||
#define BBR_STARTUP_PACING_GAIN (BBR_UNIT * 277 / 100 + 1)
|
||||
#define BBR_STARTUP_CWND_GAIN (BBR_UNIT * 2)
|
||||
#define BBR_DRAIN_GAIN (BBR_UNIT * 1000 / 2885)
|
||||
#define BBR_CWND_GAIN (BBR_UNIT * 2)
|
||||
#define BBR_PROBE_RTT_CWND_GAIN (BBR_UNIT * 1 / 2)
|
||||
#define BBR_CWND_MIN_TARGET 4
|
||||
#define BBR_FULL_BW_THRESH (BBR_UNIT * 5 / 4)
|
||||
#define BBR_FULL_BW_CNT 3
|
||||
#define BBR_LOSS_THRESH (BBR_UNIT * 2 / 100)
|
||||
#define BBR_BETA (BBR_UNIT * 30 / 100)
|
||||
#define BBR_INFLIGHT_HEADROOM (BBR_UNIT * 15 / 100)
|
||||
#define BBR_EXTRA_ACKED_GAIN BBR_UNIT
|
||||
#define BBR_EXTRA_ACKED_WIN_RTTS 5
|
||||
#define BBR_ACK_EPOCH_ACKED_RESET_THRESH (1U << 20)
|
||||
#define BBR_EXTRA_ACKED_MAX_US (100 * 1000)
|
||||
#define BBR_FULL_LOSS_CNT 6
|
||||
#define BBR_BW_PROBE_MAX_ROUNDS 63
|
||||
#define BBR_BW_PROBE_RAND_ROUNDS 2
|
||||
#define BBR_BW_PROBE_BASE_US (2 * 1000000)
|
||||
#define BBR_BW_PROBE_RAND_US (1 * 1000000)
|
||||
#define BBR_PROBE_RTT_WIN_MS 5000
|
||||
#define BBR_MIN_RTT_WIN_SEC 10
|
||||
#define BBR_PROBE_RTT_MS 200
|
||||
|
||||
enum bbr_mode { BBR_STARTUP = 0, BBR_DRAIN = 1, BBR_PROBE_BW = 2, BBR_PROBE_RTT = 3 };
|
||||
enum bbr_bw_probe_cyc { BBR_BW_PROBE_UP = 0, BBR_BW_PROBE_DOWN = 1, BBR_BW_PROBE_CRUISE = 2, BBR_BW_PROBE_REFILL = 3 };
|
||||
enum bbr_ack_phase { BBR_ACKS_INIT = 0, BBR_ACKS_REFILLING, BBR_ACKS_PROBE_STARTING, BBR_ACKS_PROBE_FEEDBACK, BBR_ACKS_PROBE_STOPPING };
|
||||
|
||||
struct bbr_rate_sample {
|
||||
uint32_t delivered;
|
||||
uint32_t interval_us;
|
||||
uint32_t rtt_us;
|
||||
uint32_t acked_sacked;
|
||||
uint32_t prior_delivered;
|
||||
uint32_t tx_in_flight;
|
||||
int lost;
|
||||
int is_app_limited;
|
||||
};
|
||||
|
||||
typedef void (*bbr_cwnd_update_fn)(void* ctx, uint32_t new_cwnd);
|
||||
|
||||
struct bbr {
|
||||
uint32_t min_rtt_us;
|
||||
uint32_t bw_lo;
|
||||
uint32_t bw_hi[2];
|
||||
uint32_t inflight_lo;
|
||||
uint32_t inflight_hi;
|
||||
uint32_t bw_latest;
|
||||
uint32_t inflight_latest;
|
||||
uint8_t mode;
|
||||
uint8_t cycle_idx;
|
||||
uint16_t pacing_gain;
|
||||
uint16_t cwnd_gain;
|
||||
uint32_t next_rtt_delivered;
|
||||
uint8_t round_start : 1;
|
||||
uint8_t full_bw_reached : 1;
|
||||
uint8_t loss_in_round : 1;
|
||||
uint8_t loss_in_cycle : 1;
|
||||
uint8_t idle_restart : 1;
|
||||
uint8_t try_fast_path : 1;
|
||||
uint8_t has_seen_rtt : 1;
|
||||
uint8_t initialized : 1;
|
||||
uint32_t loss_round_delivered;
|
||||
uint32_t bw_probe_up_cnt;
|
||||
uint32_t bw_probe_up_acks;
|
||||
uint8_t bw_probe_up_rounds;
|
||||
uint8_t bw_probe_samples : 1;
|
||||
uint8_t prev_probe_too_high: 1;
|
||||
uint8_t stopped_risky_probe: 1;
|
||||
uint8_t rounds_since_probe;
|
||||
uint32_t probe_wait_us;
|
||||
uint64_t cycle_mstamp_tb;
|
||||
uint32_t probe_rtt_min_us;
|
||||
uint64_t probe_rtt_min_stamp_tb;
|
||||
uint64_t probe_rtt_done_stamp_tb;
|
||||
uint8_t probe_rtt_round_done : 1;
|
||||
uint16_t extra_acked[2];
|
||||
uint32_t ack_epoch_acked;
|
||||
uint64_t ack_epoch_mstamp_tb;
|
||||
uint8_t extra_acked_win_idx : 1;
|
||||
uint8_t extra_acked_win_rtts : 5;
|
||||
uint32_t prior_cwnd;
|
||||
uint8_t init_cwnd : 7;
|
||||
uint8_t full_bw_cnt : 2;
|
||||
uint8_t ack_phase : 3;
|
||||
uint8_t loss_events_in_round : 4;
|
||||
uint32_t delivered;
|
||||
uint32_t full_bw;
|
||||
uint64_t min_rtt_stamp_tb;
|
||||
uint8_t loss_round_start : 1;
|
||||
uint8_t full_bw_now : 1;
|
||||
uint8_t pad_unused : 6;
|
||||
uint8_t link_id; // local_link_id for debug logging
|
||||
uint64_t now_tb; // 0 = real get_time_tb(); >0 = test-controlled time
|
||||
bbr_cwnd_update_fn on_cwnd_update;
|
||||
void* cwnd_update_ctx;
|
||||
};
|
||||
|
||||
void bbr_init(struct bbr* bbr);
|
||||
void bbr_main(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t* cwnd_out, uint32_t* pacing_rate_out,
|
||||
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;
|
||||
}
|
||||
+613
-627
File diff suppressed because it is too large.
Load diff
+72
-38
@@ -1,12 +1,17 @@
|
||||
#ifndef ETCP_CONNECTIONS_H
|
||||
#define ETCP_CONNECTIONS_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
// подмодуль ETCP который обслуживает сокеты ETCP для приёма-передачи пакетов и одно ETCP подключение через несколько каналов связи (failover)
|
||||
|
||||
#include "secure_channel.h"
|
||||
#include "utun_instance.h"
|
||||
#include "etcp_bbr.h"
|
||||
#include "../lib/socket_compat.h"
|
||||
#include "../lib/swm_min.h"
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
@@ -14,6 +19,9 @@
|
||||
#define UDP_SC_HDR_SIZE (13+8+4 + 5)// 13+8+4 - sc_nonce+tag size+crc, 5 - payload hdr
|
||||
#define ACK_REZERV 100// сколько байт резервировать под ack и прочие заголовки
|
||||
|
||||
#define INFLIGHT_LIM_MIN 8192 // 8K
|
||||
#define INFLIGHT_LIM_MAX 1048576 // 1M
|
||||
|
||||
#define PACKET_DATA_SIZE 1600//1536
|
||||
#define PACKET_DATA_MAX_MTU 1600
|
||||
#define ETCP_MAX_PAYLOAD_SIZE (PACKET_DATA_SIZE - ETCP_ACK_BASE_SIZE - 5) /* 1587 */
|
||||
@@ -26,6 +34,12 @@
|
||||
#define ETCP_INIT_RESPONSE_NOINIT 0x05
|
||||
#define ETCP_PING 0x06
|
||||
#define ETCP_PONG 0x07
|
||||
#define ETCP_KEEPALIVE 0x08
|
||||
|
||||
/* Адаптивный keepalive */
|
||||
#define KA_PERIOD_MIN_MS 200
|
||||
#define KA_PERIOD_MAX_MS 10000
|
||||
#define KA_TIMEOUT_MULT 10 /* timeout = period * mult */
|
||||
|
||||
|
||||
#pragma pack(push, 1)
|
||||
@@ -45,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 {
|
||||
@@ -97,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
|
||||
@@ -121,7 +176,6 @@ struct ETCP_LINK {
|
||||
|
||||
struct ETCP_CONN* etcp; // подключение (parent)
|
||||
struct ETCP_SOCKET* conn; // сокет через который работаем
|
||||
SlidingWindowMin* rtt_swm;
|
||||
// Путь соединения
|
||||
struct sockaddr_storage remote_addr; // Удалённый адрес
|
||||
|
||||
@@ -139,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)
|
||||
@@ -163,26 +218,6 @@ struct ETCP_LINK {
|
||||
uint32_t inflight_packets;
|
||||
uint32_t inflight_lim_bytes;
|
||||
|
||||
/* Биты блокировки отправки по причине (для корректного resume) */
|
||||
// unsigned send_blocked_input_queue : 1;
|
||||
// unsigned send_blocked_sack_full : 1;
|
||||
// unsigned send_blocked_bandwidth : 1;
|
||||
unsigned send_blocked_inflight : 1;
|
||||
// unsigned send_blocked_other : 4;
|
||||
|
||||
// lim периодически обновляется. = (rtt_avg10 + jitter * K2[=1.0]) * K1[=1.5] * channel_bandwidth
|
||||
uint32_t win_timebase; // x1 us (по умолчанию 50 000 = 50 ms, потом подстраивается под rtt/2)
|
||||
uint32_t window_pkt_transmitted; // текущие накопители для следующего окна (обнуляются в таймере)
|
||||
uint32_t window_retransmissions;
|
||||
void* stats_timer; // Таймер для обновления окна
|
||||
uint32_t win_ptr; // текущий слот в кольцевом буфере (0..31)
|
||||
struct {
|
||||
uint16_t rtt; // x 0.1ms (avg10 в конеце окна)
|
||||
// uint16_t avg_rtt; // x 0.1ms (avg10 усредненный за период)
|
||||
uint16_t pkt_loss; // штук (за период)
|
||||
uint32_t pkt_transmitted; // штук (за период)
|
||||
} stat_win[32]; // последние 32 замера статистики
|
||||
|
||||
// statistics
|
||||
size_t encrypt_errors;
|
||||
size_t decrypt_errors;
|
||||
@@ -191,16 +226,12 @@ struct ETCP_LINK {
|
||||
size_t total_encrypted;
|
||||
size_t total_decrypted;
|
||||
uint32_t total_retransmissions;
|
||||
uint64_t acked_bytes; // подтверждённых байт на этом линке
|
||||
uint64_t acked_packets; // подтверждённых пакетов на этом линке
|
||||
|
||||
uint16_t rtt_last; // round trip (время отправки + приёма)
|
||||
uint16_t rtt_history[10]; // Circular buffer for last 10 RTT values
|
||||
uint8_t rtt_history_count; // Number of valid entries in history (0-10)
|
||||
uint8_t rtt_history_index; // Current write position in history buffer
|
||||
uint32_t jitter; // Current jitter [>>16] x0.1 ms
|
||||
uint16_t rtt_max_val; // Current max RTT in history
|
||||
uint8_t rtt_max_idx; // Index of max (255 = needs recalc)
|
||||
uint16_t rtt_avg10; // round trip average (excl. max)
|
||||
uint16_t rtt_min; // round trip min (rtt_swm floating window)
|
||||
// rtt_avg10 удалён — заменён BBR (bbr->min_rtt_us/100)
|
||||
uint32_t recv_dt_avg_tx; // дельта времени для отправленных пакетов (относительное время отправки) x256
|
||||
uint32_t recv_dt_avg_rx; // дельта времени для принятых пакетов (относительное время отправки) x256
|
||||
|
||||
@@ -218,6 +249,7 @@ struct ETCP_LINK {
|
||||
// Keepalive state
|
||||
void* keepalive_timer; // Таймер для отправки keepalive пакетов
|
||||
uint32_t keepalive_timeout; // таймаут (ms)
|
||||
uint16_t ka_period_ms; // адаптивный период отправки keepalive (200→10000->200)
|
||||
uint8_t pkt_sent_since_keepalive; // Флаг: был ли отправлен пакет с последнего keepalive тика
|
||||
uint32_t keepalive_sent_count; // Счётчик отправленных keepalive
|
||||
uint32_t keepalive_recv_count; // Счётчик полученных keepalive
|
||||
@@ -249,11 +281,12 @@ struct ETCP_LINK {
|
||||
uint8_t burst_resp_pkt_count; // число пакетов в измерении
|
||||
uint8_t burst_resp_valid; // валидность измерения
|
||||
|
||||
// === Фазы управления inflight ===
|
||||
// 0 = slow_start, 1 = congestion_avoidance
|
||||
uint8_t inflight_phase;
|
||||
uint64_t last_window_update_tb; // время последнего обновления окна (0.1ms)
|
||||
uint32_t slow_start_threshold; // порог выхода из slow start (burst_target_bdp * 0.9)
|
||||
// === BBR congestion control ===
|
||||
struct bbr* bbr; // BBR state (per-link, создаётся в etcp_link_new)
|
||||
uint64_t delivered_bytes; // кумулятивно delivered bytes на линке (для round detection)
|
||||
uint64_t last_ack_time_tb; // 0.1ms timestamp последнего ACK (для interval_us)
|
||||
uint32_t bbr_pacing_rate; // bytes/sec output BBR → shaper
|
||||
uint32_t bbr_loss_since_ack; // счётчик потерь на линке с последнего ACK (для rs->lost)
|
||||
|
||||
etcp_udp_send_fn_t send_hook; // NULL = socket_sendto напрямую
|
||||
void* send_hook_ctx;
|
||||
@@ -278,20 +311,17 @@ void etcp_socket_remove(struct ETCP_SOCKET* conn);
|
||||
// connection functions
|
||||
// создает новый канал связи для etcp подключения (ETCP_CONN)
|
||||
struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn, struct sockaddr_storage* remote_addr, uint8_t is_server);
|
||||
void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim);
|
||||
void etcp_link_close(struct ETCP_LINK* link);
|
||||
//int etcp_input_cbk(struct packet_buffer* pkt, struct ETCP_SOCKET* conn);// получает расшифрованный пакет
|
||||
int etcp_encrypt_send(struct ETCP_DGRAM* dgram);// зашифровывает и отправляет пакет
|
||||
// find link by address
|
||||
struct ETCP_LINK* etcp_link_find_by_addr(struct ETCP_SOCKET* e_sock, struct sockaddr_storage* addr);
|
||||
|
||||
// обновляет лимит inflight_bytes для линка и корректно снимает блокировку если нужно
|
||||
void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim);
|
||||
|
||||
// find free local_link_id for connection
|
||||
// scans all links in connection, marks used ids in bit array
|
||||
// returns first free id (0-255) or -1 if all occupied
|
||||
int etcp_find_free_local_link_id(struct ETCP_CONN* etcp);
|
||||
void start_stats_timer(struct ETCP_LINK* link);
|
||||
|
||||
// Burst measurement functions
|
||||
void etcp_link_burst_start(struct ETCP_LINK* link);
|
||||
@@ -311,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
|
||||
+14
-23
@@ -4,7 +4,6 @@
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/memory_pool.h"
|
||||
#include "../lib/socket_compat.h"
|
||||
#include "../lib/swm_min.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -153,32 +152,29 @@ void etcp_dump_conn_state(struct ETCP_CONN* conn) {
|
||||
link->keepalive_interval, link->keepalive_timeout);
|
||||
|
||||
/* timers */
|
||||
DUMP_LINE("TIMERS: init=%s(%u/%u) ka=%s shaper=%s stats=%s burst=%s keepalive=%s",
|
||||
DUMP_LINE("TIMERS: init=%s(%u/%u) ka=%s shaper=%s burst=%s keepalive=%s",
|
||||
link->init_timer ? "ACTIVE" : "free",
|
||||
link->init_timeout, link->init_retry_count,
|
||||
link->keepalive_timer ? "ACTIVE" : "free",
|
||||
link->shaper_timer ? "ACTIVE" : "free",
|
||||
link->stats_timer ? "ACTIVE" : "free",
|
||||
link->burst_resp_timer ? "ACTIVE" : "free",
|
||||
link->keepalive_sent_count > 0 ? "has_sent" : "idle");
|
||||
|
||||
/* inflight */
|
||||
DUMP_LINE("INFLIGHT: bytes=%u pkts=%u lim=%u blocked=%d phase=%d sst=%u last_win_tb=%llu",
|
||||
/* inflight (BBR) */
|
||||
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->inflight_phase,
|
||||
link->slow_start_threshold, (unsigned long long)link->last_window_update_tb);
|
||||
link->bbr ? link->bbr->mode : -1,
|
||||
link->bbr ? link->bbr->cycle_idx : -1,
|
||||
link->bbr_pacing_rate);
|
||||
|
||||
/* rtt */
|
||||
char hist_str[128] = "";
|
||||
int hist_pos = 0;
|
||||
for (int i = 0; i < 10 && i < (int)link->rtt_history_count; i++) {
|
||||
hist_pos += snprintf(hist_str + hist_pos, sizeof(hist_str) - hist_pos,
|
||||
"%s%u", i == 0 ? "" : ",", link->rtt_history[i]);
|
||||
}
|
||||
DUMP_LINE("RTT: last=%u avg10=%u min=%u max=%u jitter=%u hist=[%s] cnt=%d swm=%u",
|
||||
link->rtt_last, link->rtt_avg10, link->rtt_min, link->rtt_max_val,
|
||||
link->jitter, hist_str, link->rtt_history_count,
|
||||
link->rtt_swm ? swm_get_min(link->rtt_swm) : 0);
|
||||
/* rtt (BBR) */
|
||||
DUMP_LINE("RTT: last=%u jitter=%u min_rtt_us=%u bw_lo=%u bw_hi=%u inflight_lo=%u inflight_hi=%u",
|
||||
link->rtt_last, link->jitter,
|
||||
link->bbr ? link->bbr->min_rtt_us : 0,
|
||||
link->bbr ? link->bbr->bw_lo : 0,
|
||||
link->bbr ? (link->bbr->bw_hi[0] > link->bbr->bw_hi[1] ? link->bbr->bw_hi[0] : link->bbr->bw_hi[1]) : 0,
|
||||
link->bbr ? link->bbr->inflight_lo : 0,
|
||||
link->bbr ? link->bbr->inflight_hi : 0);
|
||||
|
||||
/* tt/rt/bandwidth */
|
||||
DUMP_LINE("TT/RT: tt=%u rt=%u recv_dt_tx=%u recv_dt_rx=%u bw=%u",
|
||||
@@ -201,11 +197,6 @@ void etcp_dump_conn_state(struct ETCP_CONN* conn) {
|
||||
link->nat_changes_count, link->nat_hits_count,
|
||||
link->nat_check_status, link->nat_type);
|
||||
|
||||
/* stats win */
|
||||
DUMP_LINE("WIN: ptr=%u tb=%u tx=%u retrans=%u",
|
||||
link->win_ptr, link->win_timebase,
|
||||
link->window_pkt_transmitted, link->window_retransmissions);
|
||||
|
||||
/* last recv */
|
||||
DUMP_LINE("LAST_RECV: time=%llu ts=%u updated=%d",
|
||||
(unsigned long long)link->last_recv_local_time,
|
||||
|
||||
@@ -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
|
||||
+7
-19
@@ -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;
|
||||
}
|
||||
|
||||
@@ -164,9 +164,9 @@ void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram) {
|
||||
uint64_t now_tb=get_time_tb();
|
||||
if (link->shaper_load_time_tb >= now_tb + SHAPER_BURST_DELAY_TB) {
|
||||
uint64_t wait_tb = link->shaper_load_time_tb - now_tb;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] scheduling shaper timer (wait_tb=%llu) BW=%d pkt_size=%d", etcp->log_name, (unsigned long long)wait_tb, link->bandwidth, pkt_size);
|
||||
link->shaper_timer = uasync_set_timeout(link->etcp->instance->ua, wait_tb, link, shaper_timer_cb, "shaper");
|
||||
if (link->shaper_timer == NULL) DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Filed to allocate timer");
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] scheduled shaper timer (wait_tb=%llu) BW=%d pkt_size=%d", etcp->log_name, (unsigned long long)wait_tb, link->bandwidth, pkt_size);
|
||||
}
|
||||
|
||||
// Inactivity correction
|
||||
@@ -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);
|
||||
|
||||
+1034
-231
File diff suppressed because it is too large.
Load diff
Loaded 100 of 654 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user