Author SHA1 Message Date
Evgeny 80177c9b3e fix: test_conn_mgr flakiness — wait for BGP nodeinfo, not just INIT links
Root cause: test1 used lks() (INIT links up) but conn_mgr needs BGP NODEINFO.
Nodeinfo arrives after INIT, so test1 must wait for it.
Also made result volatile to prevent loop optimization.
2026-06-30 00:34:06 +03:00
Evgeny 8f13f0ec4d refactor: extract handle_ping, handle_pong, send_init_response, handle_init_response_client from read callback
Reduce etcp_connections_read_callback_socket from ~600 to ~300 lines.
All 4 helpers are static file-scope functions, no logic changes.
2026-06-30 00:16:34 +03:00
Evgeny 5420e414ce fix: DIRECT detection read srс_ipv4/src_port before INIT RESPONSE overwrites the buffer
Both req (INIT_REQUEST) and resp (INIT_RESPONSE) point to pkt->data.
resp->peer_port at offset 23-24 overwrites req->src_ipv4[0..1] at same offset.
Save src_ipv4/src_port before building the response.
2026-06-29 23:58:46 +03:00
Evgeny 69112ffde1 feat: NAT_DIRECT detection during INIT handshake + refactor init packet structs
- Extract ETCP_INIT_REQUEST_PKT / ETCP_INIT_RESPONSE_PKT named structs
- Replace anonymous structs and manual offset parsing with typed struct access
- Add src_ipv4/src_port fields (V2) to INIT REQUEST for DIRECT detection
- Server compares reported client addr with observed src; match + public IP → NAT_TYPE_DIRECT
- Skip STUN NAT check for DIRECT links
- route_bgp: handle NAT_TYPE_DIRECT → NAT_VERIFIED_DIRECT in handle_nat_info
2026-06-29 23:15:38 +03:00
Evgeny 89d675f15d fix: drain input_send_q and input_wait_ack at end of etcp_conn_reset to ensure inflight queues are cleared on remote node restart 2026-06-29 20:17:53 +03:00
Evgeny 914f252b12 fix: remove send_blocked_inflight — per-link inflight gate was blocking input_send_q and ACK send, causing bidirectional deadlock 2026-06-29 20:04:20 +03:00
Evgeny a7b20f268f feat: conn_mgr, dummynet block-addr, indirect routing, alien nodes, nodeinfo refactor 2026-06-29 20:01:40 +03:00
Evgeny c0207ad41d fix: move nodeinfo_dyn_size declaration to route_bgp.h 2026-06-25 09:47:27 +03:00
Evgeny 8051dfe4e5 zxing-cpp: strip to core library only (remove tests/wrappers/examples/docs); .gitignore: fix /core to allow zxing-cpp/core/ dir 2026-06-25 09:37:45 +03:00
Evgeny 22c24d135e chatgui: invite link dialogs, zxing-cpp QR code, crypto transport, channel improvements; msg_transport: chat protocol support 2026-06-25 09:29:44 +03:00
Evgeny d8c8c1c5b0 control_server: node subscription protocol with output queue, push notifications on routing changes 2026-06-25 09:28:07 +03:00
Evgeny 231081dd2f docs: add chatgui animated emoji system and tdesktop-dev reference to AGENTS.md 2026-06-24 13:50:10 +03:00
Evgeny 62b3695a77 chatgui: rlottie animated emoji with hover-to-animate, drag-to-select
- Add LottieIcon class (TGS gzip decompress + rlottie C API render)
- Add AnimTimer singleton (33ms tick, activate/deactivate refcount)
- Add EmojiButton (hover-triggered animation in emoji picker)
- Add AnimatedEmoji support to EmojiData (5 TGS mappings)
- MessageDelegate: QTextLayout-based animated emoji overlay (text + reactions)
- ChatView: drag-to-select (click deselects, 5px drag starts selection)
- ChatView: hoveredIndexChanged signal for hover-triggered animations
- MessageList: wire AnimTimer tick to viewport update, hover → activate
- CMake: AUTORCC, rlottie, zlib dependencies
- Add 5 TGS animations as Qt resources
2026-06-24 13:45:25 +03:00
Evgeny 0e2b0e28d8 tools/chatgui: Makefile cmake wrapper, author name in bubble, emoji panel height ~1/1.7 2026-06-23 14:39:09 +03:00
Evgeny e50ca9978e tools/chatgui: input bar with emoji panel, custom tab bar, Ctrl+Enter multiline, scroll fixes 2026-06-23 00:17:07 +03:00
Evgeny b667d6f352 tools/chatgui: Qt5 GUI chat app stub with tray, channel list, message bubbles 2026-06-22 22:48:27 +03:00
Evgeny 6717c863e7 etcp_router: Ed25519 digital signature for service messages
- Add ed25519_public_key[32] to NODEINFO struct for pubkey distribution
- Add ed25519_public_key[32] to ROUTE_BGP (derived from X25519 privkey at init)
- Add ROUTER_FLAG_SIGNED (0x08) to SVC_ROUTE_HDR flags byte
- router_send_one_flags: when is_signed, sign [hdr][payload] and append 64-byte sig
- etcp_router_recv_cb: verify Ed25519 signature when ROUTER_FLAG_SIGNED set
- New API: etcp_router_conn_send_signed()
- Drop signed packets if sender node not in routing table or no Ed25519 key
- 5 unit tests in test_etcp_router_unit.c covering OK/tampered/unknown/no-key/short
2026-06-22 15:53:24 +03:00
Evgeny 2bbdf8195f fix: TASSERT return value in test_etcp_stcp.c and test_stcp_link.c 2026-06-18 22:11:25 +03:00
Evgeny 73f707ef9b crypto: migrate ECDH(secp256r1) to X25519, drop TinyCrypt
- SC_PUBKEY_SIZE: 64 -> 32 bytes, SC_PUBKEY_ENC_SIZE: 72 -> 40
- sc_set_peer_public_key uses EVP_PKEY_derive with X25519
- sc_generate_keypair via EVP_PKEY_keygen(EVP_PKEY_X25519)
- sc_compute_public_key_from_private via EVP_PKEY_new_raw_private_key
- sc_validate_key: check non-zero (X25519 has no invalid keys)
- sc_obfuscate_pubkey: XOR now 32 bytes
- Remove all TinyCrypt code (~860 lines), always use OpenSSL
- Remove TinyCrypt from build system, drop test_crypto/test_ecc_encrypt
- Update all test keys (128 hex -> 64 hex)
- Fix bbr_integration.c split-line pubkeys
2026-06-17 21:28:07 +03:00
495 changed files with 368814 additions and 1475 deletions

No files matched your search

+5 -1
View File
@@ -1,5 +1,6 @@
# Build directory (out-of-tree build)
/build/
tools/chatgui/build/
# Build outputs
*.o
@@ -59,7 +60,7 @@ Makefile.in
.cache/
# Debug outputs
core
/core
vgcore.*
callgrind.out.*
@@ -96,6 +97,9 @@ utun.conf
tools/bping/bping
tools/proxy/udp_proxy
# Reference projects (not part of utun)
tools/tdesktop-dev/
# Debug/temp artifacts
*.bak
*.debug
+143 -6
View File
@@ -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
@@ -142,14 +142,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
@@ -211,6 +211,8 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
├── 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)
@@ -246,7 +248,7 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
- `route_node.c/h` - Route node (peer) management
**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/)**
@@ -285,7 +287,7 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
- **LL_QUEUE:** Lock-free queue with auto-callback, hash index lookup, threshold waiter
- **Memory Pool:** Fast allocation for hot-path objects (packets, inflight entries, fragments)
- **ETCP:** TCP-like reliable protocol with encryption, multi-link, load balancing
- **Secure Channel:** AES-CCM + ECC key exchange, nonce-based encryption, pubkey obfuscation
- **Secure Channel:** AES-CCM + X25519 key exchange, nonce-based encryption, pubkey obfuscation
## Queue Usage Rules
@@ -337,6 +339,141 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
- Для каждого отладочного вывода подумай какая из доступной информация будет полезна чтобы можно было наиболее завершенно оценить состояние алгоритма и состояний влияющих на алгоритм.
## chatgui (GUI Chat Client)
Chatgui — десктопный GUI-чат на Qt 5, отдельный проект внутри репозитория. Не связан с autotools-сборкой utun.
### Технологии
- **Язык:** C++17
- **Фреймворк:** 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 qtbase5-dev
# Сборка
cd tools/chatgui && mkdir -p build && cd build
cmake .. && make -j4
./chatgui
```
### Структура файлов (src/)
| Файл | Назначение |
|------|-----------|
| `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` | Список аккаунтов/пользователей |
### Другие поддиректории
- `db/` — SQLite3 amalgamation (`sqlite3.c/h`), `db_manager.h/cpp` (схема: nodes, channels, messages, keys, addresses)
- `transport/` — TCP-клиент для связи с uTun (`msg_client.h/cpp`, бинарный протокол `[size:2][type:1][id:8][options:1][data...]`)
- `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 архитектура
-28
View File
@@ -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
+7 -2
View File
@@ -28,14 +28,19 @@ libuasync_a_SOURCES = \
serialize.c \
serialize.h \
tcp_io.c \
tcp_io.h
tcp_io.h \
liblmdb/mdb.c \
liblmdb/midl.c \
liblmdb/lmdb.h \
liblmdb/midl.h
libuasync_a_CFLAGS = \
-D_ISOC99_SOURCE \
-DDEBUG_OUTPUT_STDERR \
-g \
-I$(top_srcdir)/src \
-I$(top_srcdir)/lib
-I$(top_srcdir)/lib \
-I$(srcdir)/liblmdb
# Clean build directory
clean-local:
+23
View File
@@ -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/
+317
View File
@@ -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
+20
View File
@@ -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/>.
+1614
View File
File diff suppressed because it is too large. Load diff
+47
View File
@@ -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.
+192
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+10504
View File
File diff suppressed because it is too large. Load diff
+55
View File
@@ -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>
+82
View File
@@ -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;
}
+75
View File
@@ -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>
+319
View File
@@ -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;
}
+91
View File
@@ -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>
+515
View File
@@ -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;
}
+64
View File
@@ -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>
+263
View File
@@ -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;
}
+359
View File
@@ -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;
}
/** @} */
/** @} */
+188
View File
@@ -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_ */
+580
View File
@@ -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);
}
+177
View File
@@ -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;
}
+124
View File
@@ -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;
}
+133
View File
@@ -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;
}
+168
View File
@@ -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;
}
+135
View File
@@ -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;
}
+141
View File
@@ -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;
}
+73
View File
@@ -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;
}
+62
View File
@@ -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;
}
+22
View File
@@ -0,0 +1,22 @@
<tagfile>
<compound kind="page">
<name>mdb_copy_1</name>
<title>mdb_copy - environment copy tool</title>
<filename>mdb_copy.1</filename>
</compound>
<compound kind="page">
<name>mdb_dump_1</name>
<title>mdb_dump - environment export tool</title>
<filename>mdb_dump.1</filename>
</compound>
<compound kind="page">
<name>mdb_load_1</name>
<title>mdb_load - environment import tool</title>
<filename>mdb_load.1</filename>
</compound>
<compound kind="page">
<name>mdb_stat_1</name>
<title>mdb_stat - environment status tool</title>
<filename>mdb_stat.1</filename>
</compound>
</tagfile>
+8 -92
View File
@@ -11,7 +11,9 @@ utun_CORE_SOURCES = \
route_bgp.c \
route_ping.c \
route_node.c \
route_node_lmdb.c \
route_connectivity.c \
conn_mgr.c \
routing.c \
tun_if.c \
tun_route.c \
@@ -34,6 +36,7 @@ utun_CORE_SOURCES = \
packet_dump.c \
etcp_api.c \
control_server.c \
msg_transport.c \
firewall.c \
eim_nat.c \
nat_transport.c \
@@ -51,109 +54,22 @@ utun_CORE_SOURCES = \
# 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
# 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 = \
$(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
+23 -2
View File
@@ -46,7 +46,8 @@ typedef enum {
SECTION_ALLOWED_KEYS,
SECTION_NAT,
SECTION_TCP_PROXY_CLIENT,
SECTION_TCP_PROXY_SERVER
SECTION_TCP_PROXY_SERVER,
SECTION_MSG_TRANSPORT
} section_type_t;
static char* trim(char *str) {
@@ -386,6 +387,9 @@ 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);
}
/* debug_categories key is deprecated - use [debug] section instead */
if (strcmp(key, "enable_timestamp") == 0) {
global->enable_timestamp = atoi(value);
@@ -407,7 +411,7 @@ static int parse_global(const char *key, const char *value, struct global_config
global->tun_test_mode = atoi(value);
return 0;
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown global option '%s'. Valid: my_node_name, my_private_key, my_public_key, my_node_id, tun_ifname, tun_ip, mtu, keepalive_timeout, keepalive_interval, bbr_max_cwnd, debug_level, log_file, enable_timestamp, enable_function_names, enable_file_lines, enable_colors, tun_test_mode", filename, line_num, key);
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown global option '%s'. Valid: my_node_name, my_private_key, my_public_key, my_node_id, tun_ifname, tun_ip, mtu, keepalive_timeout, keepalive_interval, bbr_max_cwnd, debug_level, log_file, db_path, enable_timestamp, enable_function_names, enable_file_lines, enable_colors, tun_test_mode", filename, line_num, key);
return -1;
}
@@ -436,6 +440,17 @@ static int parse_control(const char *key, const char *value, struct global_confi
return -1;
}
static int parse_msg_transport(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
if (strcmp(key, "port") == 0) {
char port_str[16];
snprintf(port_str, sizeof(port_str), "%s", value);
parse_sockaddr("127.0.0.1", port_str, &global->msg_transport_sock);
return 0;
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown msg_transport option '%s'. Valid: port", filename, line_num, key);
return -1;
}
static int parse_tcp_proxy_client(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
if (strcmp(key, "enabled") == 0) {
global->tcp_proxy_client_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
@@ -697,6 +712,7 @@ static section_type_t parse_section_header(const char *line, char *name, size_t
if (strcasecmp(section, "nat") == 0) return SECTION_NAT;
if (strcasecmp(section, "tcp_proxy_client") == 0) return SECTION_TCP_PROXY_CLIENT;
if (strcasecmp(section, "tcp_proxy_server") == 0) return SECTION_TCP_PROXY_SERVER;
if (strcasecmp(section, "msg_transport") == 0) return SECTION_MSG_TRANSPORT;
char *colon = strchr(section, ':');
if (!colon) return SECTION_UNKNOWN;
@@ -879,6 +895,11 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
cfg->global.tcp_recv_buf = atoi(value);
}
break;
case SECTION_MSG_TRANSPORT:
if (parse_msg_transport(key, value, &cfg->global, filename, line_num) < 0) {
// parse_msg_transport already printed the error
}
break;
default:
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Key outside section: %s", filename, line_num, key);
break;
+3 -1
View File
@@ -79,7 +79,7 @@ struct CFG_CONTROL_ALLOW {
};
struct CFG_ALLOWED_KEY {
uint8_t key_bin[SC_PUBKEY_SIZE]; // public key in binary (64 bytes)
uint8_t key_bin[SC_PUBKEY_SIZE]; // public key in binary (32 bytes, X25519)
struct CFG_ALLOWED_KEY *next;
};
@@ -100,9 +100,11 @@ struct global_config {
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)
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
+3 -3
View File
@@ -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;
+1000
View File
@@ -0,0 +1,1000 @@
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#endif
#include "../lib/platform_compat.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/u_async.h"
#include "../lib/ll_queue.h"
#include "utun_instance.h"
#include "etcp.h"
#include "etcp_connections.h"
#include "etcp_router.h"
#include "config_parser.h"
#include "secure_channel.h"
#include "route_node.h"
#include "route_bgp.h"
#include "route_ping.h"
#include "route_connectivity.h"
#include "conn_mgr.h"
static struct CONN_MGR_ENTRY* cm_find_entry(struct CONN_MGR* mgr, uint64_t node_id);
static void cm_start_phase_direct(struct CONN_MGR_ENTRY* entry);
static void cm_start_phase_reverse(struct CONN_MGR_ENTRY* entry);
static int cm_start_phase_indirect(struct CONN_MGR_ENTRY* entry);
static void cm_start_local_scan(struct CONN_MGR_ENTRY* entry);
static void cm_idle_timer_cb(void* arg);
static void cm_deliver_result(struct CONN_MGR_ENTRY* entry, int result);
static void cm_bg_ping_timer_cb(void* arg);
static int cm_nat_compatible(struct ETCP_SOCKET* our, uint8_t target_meta_type);
static uint16_t cm_get_node_min_rtt(struct NODEINFO_Q* nq);
static int cm_has_direct_ip(struct NODEINFO_Q* nq);
static int cm_has_local_addr(struct NODEINFO_Q* nq);
static void cm_entry_destroy(struct CONN_MGR_ENTRY* entry);
static uint64_t cm_get_node_max_probe_time(struct NODEINFO_Q* nq);
static int cm_is_rtt_fresh(struct NODEINFO_Q* nq, uint64_t now_tb);
static void conn_mgr_router_recv_handler(struct ETCP_CONN* conn, struct ll_entry* entry);
static void cm_handle_direct_req(struct ETCP_CONN* conn, const uint8_t* data, size_t len);
static void cm_handle_interm_exchange_req(struct ETCP_CONN* conn, struct CONN_MGR_INTERM_EXCHANGE_REQ* req);
static void cm_handle_interm_exchange_resp(struct CONN_MGR* mgr, const uint8_t* data, size_t len);
static void cm_handle_interm_selected(struct CONN_MGR* mgr, const uint8_t* data, size_t len);
static void cm_handle_disconnect(struct CONN_MGR* mgr, uint64_t node_id);
static void cm_direct_ready_cb(struct ETCP_CONN* conn, void* arg);
static void cm_reverse_ready_cb(struct ETCP_CONN* conn, void* arg);
static void cm_reverse_timeout_cb(void* arg);
static void cm_exchange_timeout_cb(void* arg);
static void cm_candidate_ping_timer_cb(void* arg);
struct cm_reverse_pending {
struct cm_reverse_pending* next;
uint32_t request_id;
struct CONN_MGR_ENTRY* entry;
struct ETCP_CONN** conns;
uint8_t conn_count;
};
struct cm_exchange_pending {
struct cm_exchange_pending* next;
uint32_t request_id;
struct CONN_MGR_ENTRY* entry;
void* timeout_timer;
struct CONN_MGR_INTERM_EXCHANGE_RESP cached_resp;
uint8_t resp_received;
uint8_t probes_done;
};
struct CONN_MGR* conn_mgr_init(struct UTUN_INSTANCE* instance) {
if (!instance) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_init: NULL instance"); return NULL; }
struct CONN_MGR* mgr = u_calloc(1, sizeof(struct CONN_MGR));
if (!mgr) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_init: alloc failed"); return NULL; }
mgr->instance = instance;
mgr->entry_capacity = 8;
mgr->entries = u_calloc(mgr->entry_capacity, sizeof(struct CONN_MGR_ENTRY));
if (!mgr->entries) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_init: entries alloc failed"); u_free(mgr); return NULL; }
etcp_router_bind(instance, ETCP_ID_CONN_MGR, conn_mgr_router_recv_handler);
mgr->bg_ping_timer = uasync_set_timeout(instance->ua, CONN_MGR_BG_PING_INTERVAL_TB, mgr, cm_bg_ping_timer_cb, "conn_mgr_bg_ping");
mgr->candidate_ping_timer = uasync_set_timeout(instance->ua, CONN_MGR_CANDIDATE_PING_TB, mgr, cm_candidate_ping_timer_cb, "conn_mgr_cand_ping");
mgr->bg_ping_cycle_start_tb = get_time_tb();
mgr->initialized = 1;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: initialized, bg_ping started");
return mgr;
}
void conn_mgr_destroy(struct CONN_MGR* mgr) {
if (!mgr) return;
if (mgr->bg_ping_timer) { uasync_cancel_timeout(mgr->instance->ua, mgr->bg_ping_timer); mgr->bg_ping_timer = NULL; }
if (mgr->candidate_ping_timer) { uasync_cancel_timeout(mgr->instance->ua, mgr->candidate_ping_timer); mgr->candidate_ping_timer = NULL; }
etcp_router_unbind(mgr->instance, ETCP_ID_CONN_MGR);
for (size_t i = 0; i < mgr->entry_count; i++) cm_entry_destroy(&mgr->entries[i]);
u_free(mgr->entries);
mgr->initialized = 0;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: destroyed, entries=%zu", (size_t)mgr->entry_count);
u_free(mgr);
}
static void cm_entry_destroy(struct CONN_MGR_ENTRY* entry) {
if (!entry || entry->state == CONN_MGR_STATE_DISCONNECTED) return;
if (entry->idle_timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->idle_timer); entry->idle_timer = NULL; }
if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; }
entry->state = CONN_MGR_STATE_DISCONNECTED;
entry->conn_type = CONN_TYPE_NONE;
}
static struct CONN_MGR_ENTRY* cm_ensure_entry(struct CONN_MGR* mgr, uint64_t node_id) {
struct CONN_MGR_ENTRY* e = cm_find_entry(mgr, node_id);
if (e) return e;
if (mgr->entry_count >= mgr->entry_capacity) {
size_t new_cap = mgr->entry_capacity * 2;
struct CONN_MGR_ENTRY* new_e = u_realloc(mgr->entries, new_cap * sizeof(struct CONN_MGR_ENTRY));
if (!new_e) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: realloc entries failed"); return NULL; }
memset(new_e + mgr->entry_capacity, 0, (new_cap - mgr->entry_capacity) * sizeof(struct CONN_MGR_ENTRY));
mgr->entries = new_e;
mgr->entry_capacity = new_cap;
}
struct CONN_MGR_ENTRY* new_entry = &mgr->entries[mgr->entry_count++];
memset(new_entry, 0, sizeof(*new_entry));
new_entry->node_id = node_id;
new_entry->mgr = mgr;
return new_entry;
}
static struct CONN_MGR_ENTRY* cm_find_entry(struct CONN_MGR* mgr, uint64_t node_id) {
for (size_t i = 0; i < mgr->entry_count; i++)
if (mgr->entries[i].node_id == node_id && mgr->entries[i].state != CONN_MGR_STATE_DISCONNECTED)
return &mgr->entries[i];
return NULL;
}
static void cm_deliver_result(struct CONN_MGR_ENTRY* entry, int result) {
struct cm_cb_node* node = entry->cb_list;
while (node) { struct cm_cb_node* next = node->next; node->cb(result, entry->node_id, node->arg); u_free(node); node = next; }
entry->cb_list = NULL;
}
static void cm_update_nodeinfo(struct CONN_MGR_ENTRY* entry) {
struct ROUTE_BGP* bgp = entry->mgr->instance->bgp;
if (!bgp) return;
struct NODEINFO_Q* nq = nodeinfo_find_by_id(bgp, entry->node_id);
if (!nq) return;
nq->conn_mgr_type = entry->conn_type;
memcpy(nq->conn_mgr_intermediaries, entry->intermediaries, sizeof(entry->intermediaries));
nq->conn_mgr_intermediariy_count = entry->intermediariy_count;
}
static void cm_clear_nodeinfo(struct CONN_MGR* mgr, uint64_t node_id) {
struct NODEINFO_Q* nq = nodeinfo_find_by_id(mgr->instance->bgp, node_id);
if (!nq) return;
nq->conn_mgr_type = CONN_TYPE_NONE;
nq->conn_mgr_intermediariy_count = 0;
}
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) {
if (!mgr) return CONN_MGR_ERR_INTERNAL;
struct ROUTE_BGP* bgp = mgr->instance->bgp;
if (!bgp) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_connect: BGP not initialized"); return CONN_MGR_ERR_INTERNAL; }
struct NODEINFO_Q* target = nodeinfo_find_by_id(bgp, node_id);
if (!target) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_connect: node 0x%016llx not found", (unsigned long long)node_id); return CONN_MGR_ERR_NOT_FOUND; }
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id);
if (entry && entry->state == CONN_MGR_STATE_CONNECTED) {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_connect: node 0x%016llx already connected", (unsigned long long)node_id);
return CONN_MGR_ERR_ALREADY_CONNECTED;
}
if (entry && entry->state == CONN_MGR_STATE_CONNECTING) {
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr_connect: node 0x%016llx already connecting, adding cb", (unsigned long long)node_id);
struct cm_cb_node* cn = u_calloc(1, sizeof(struct cm_cb_node));
if (cn) { cn->cb = cb; cn->arg = cb_arg; cn->next = entry->cb_list; entry->cb_list = cn; }
return CONN_MGR_OK;
}
struct ETCP_CONN* existing = route_bgp_find_conn_for_node(bgp, node_id);
if (existing && existing->peer_node_id == node_id && existing->links) {
struct ETCP_LINK* l = existing->links;
while (l) { if (l->link_status && l->initialized) break; l = l->next; }
if (l) {
entry = cm_ensure_entry(mgr, node_id);
if (!entry) return CONN_MGR_ERR_INTERNAL;
entry->state = CONN_MGR_STATE_CONNECTED;
entry->conn_type = CONN_TYPE_DIRECT;
entry->idle_timeout_ms = idle_timeout_ms;
entry->alien = target->alien;
entry->last_traffic_tb = get_time_tb();
{ struct cm_cb_node* cn = u_calloc(1, sizeof(struct cm_cb_node));
if (cn) { cn->cb = cb; cn->arg = cb_arg; entry->cb_list = cn; } }
cm_update_nodeinfo(entry);
cm_deliver_result(entry, CONN_MGR_OK);
return CONN_MGR_OK;
}
}
entry = cm_ensure_entry(mgr, node_id);
if (!entry) return CONN_MGR_ERR_INTERNAL;
entry->state = CONN_MGR_STATE_CONNECTING;
entry->alien = target->alien;
{ struct cm_cb_node* cn = u_calloc(1, sizeof(struct cm_cb_node));
if (cn) { cn->cb = cb; cn->arg = cb_arg; cn->next = entry->cb_list; entry->cb_list = cn; } }
entry->idle_timeout_ms = idle_timeout_ms;
entry->last_traffic_tb = get_time_tb();
entry->local_scan_state = CM_TRY_NONE;
entry->main_connect_state = CM_TRY_NONE;
entry->main.phase = 0;
entry->main.timer = NULL;
entry->main.request_id = 0;
int has_our_local = cm_has_local_addr(bgp->local_node);
int has_target_local = cm_has_local_addr(target);
if (has_our_local && has_target_local) { entry->local_scan_state = CM_TRY_PENDING; cm_start_local_scan(entry); }
entry->main_connect_state = CM_TRY_PENDING;
cm_start_phase_direct(entry);
return CONN_MGR_OK;
}
int conn_mgr_disconnect_node(struct CONN_MGR* mgr, uint64_t node_id) {
if (!mgr) return CONN_MGR_ERR_INTERNAL;
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id);
if (!entry) return CONN_MGR_ERR_NOT_FOUND;
struct CONN_MGR_DISCONNECT pkt;
pkt.cmd = ETCP_ID_CONN_MGR;
pkt.subcmd = CONN_MGR_SUBCMD_DISCONNECT;
pkt.node_id = node_id;
struct ll_entry* qe = queue_entry_new(sizeof(pkt));
if (qe) { memcpy(qe->data, &pkt, sizeof(pkt)); etcp_route_send(mgr->instance, node_id, qe, 1); }
cm_clear_nodeinfo(mgr, node_id);
cm_entry_destroy(entry);
entry->state = CONN_MGR_STATE_DISCONNECTED;
entry->conn_type = CONN_TYPE_NONE;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: disconnected node 0x%016llx", (unsigned long long)node_id);
return CONN_MGR_OK;
}
int conn_mgr_set_idle_timeout(struct CONN_MGR* mgr, uint64_t node_id, uint32_t timeout_ms) {
if (!mgr) return CONN_MGR_ERR_INTERNAL;
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id);
if (!entry) return CONN_MGR_ERR_NOT_FOUND;
entry->idle_timeout_ms = timeout_ms;
if (entry->state == CONN_MGR_STATE_CONNECTED && timeout_ms > 0 && !entry->idle_timer)
entry->idle_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_IDLE_CHECK_INTERVAL_TB, entry, cm_idle_timer_cb, "conn_mgr_idle");
if (timeout_ms == 0 && entry->idle_timer) { uasync_cancel_timeout(mgr->instance->ua, entry->idle_timer); entry->idle_timer = NULL; }
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) {
if (!mgr || !out_state || !out_conn_type) return CONN_MGR_ERR_INTERNAL;
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id);
if (!entry) { *out_state = CONN_MGR_STATE_DISCONNECTED; *out_conn_type = CONN_TYPE_NONE; return CONN_MGR_OK; }
*out_state = entry->state;
*out_conn_type = entry->conn_type;
return CONN_MGR_OK;
}
int conn_mgr_add_alien_node(struct CONN_MGR* mgr, const uint8_t* nodeinfo_data, size_t len) {
if (!mgr || !nodeinfo_data || len < sizeof(struct NODEINFO)) return CONN_MGR_ERR_INTERNAL;
struct ROUTE_BGP* bgp = mgr->instance->bgp;
if (!bgp) return CONN_MGR_ERR_INTERNAL;
const struct NODEINFO* ni = (const struct NODEINFO*)nodeinfo_data;
uint64_t node_id = ni->node_id;
struct NODEINFO_Q* existing = nodeinfo_find_by_id(bgp, node_id);
if (existing) { DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: alien node 0x%016llx already exists, ignoring", (unsigned long long)node_id); return CONN_MGR_OK; }
size_t dyn_sz = len - sizeof(struct NODEINFO);
size_t data_sz = sizeof(struct NODEINFO_Q) - sizeof(struct ll_entry) + dyn_sz + 8;
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)queue_entry_new(data_sz);
if (!nq) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: alien node alloc failed"); return CONN_MGR_ERR_INTERNAL; }
memcpy(&nq->node, nodeinfo_data, len);
nq->alien = 1;
nq->dirty = 0;
nq->last_ver = ni->ver;
nq->connectivity.ping_req_time = 0;
if (bgp->nodes) queue_data_put_with_index(bgp->nodes, &nq->ll);
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: added alien node 0x%016llx", (unsigned long long)node_id);
return CONN_MGR_OK;
}
int conn_mgr_send(struct CONN_MGR* mgr, uint64_t node_id, struct ll_entry* entry) {
if (!mgr || !entry) return -1;
struct CONN_MGR_ENTRY* e = cm_find_entry(mgr, node_id);
if (!e || e->state != CONN_MGR_STATE_CONNECTED) { queue_entry_free(entry); return -1; }
e->last_traffic_tb = get_time_tb();
struct ETCP_ROUTER_CONN* rconn = etcp_router_conn_get(mgr->instance, node_id, ETCP_ID_CONN_MGR);
if (!rconn) { queue_entry_free(entry); return -1; }
int ret = etcp_router_conn_send(rconn, entry->dgram, entry->len);
queue_entry_free(entry);
return ret;
}
void conn_mgr_update_best_candidates(struct CONN_MGR* mgr, uint64_t node_id, uint16_t rtt) {
if (!mgr || node_id == mgr->instance->node_id || rtt == 0) return;
for (uint8_t i = 0; i < mgr->best_candidate_count; i++) {
if (mgr->best_candidates[i].node_id == node_id) {
mgr->best_candidates[i].rtt = rtt;
for (uint8_t j = i; j > 0 && mgr->best_candidates[j].rtt < mgr->best_candidates[j-1].rtt; j--) {
struct CONN_MGR_CANDIDATE tmp = mgr->best_candidates[j];
mgr->best_candidates[j] = mgr->best_candidates[j-1];
mgr->best_candidates[j-1] = tmp;
}
for (uint8_t j = i; j + 1 < mgr->best_candidate_count && mgr->best_candidates[j].rtt > mgr->best_candidates[j+1].rtt; j++) {
struct CONN_MGR_CANDIDATE tmp = mgr->best_candidates[j];
mgr->best_candidates[j] = mgr->best_candidates[j+1];
mgr->best_candidates[j+1] = tmp;
}
return;
}
}
if (mgr->best_candidate_count < CONN_MGR_MAX_CANDIDATES) {
mgr->best_candidates[mgr->best_candidate_count].node_id = node_id;
mgr->best_candidates[mgr->best_candidate_count].rtt = rtt;
mgr->best_candidate_count++;
} else if (rtt < mgr->best_candidates[CONN_MGR_MAX_CANDIDATES - 1].rtt) {
mgr->best_candidates[CONN_MGR_MAX_CANDIDATES - 1].node_id = node_id;
mgr->best_candidates[CONN_MGR_MAX_CANDIDATES - 1].rtt = rtt;
} else return;
for (uint8_t i = mgr->best_candidate_count - 1; i > 0; i--) {
if (mgr->best_candidates[i].rtt >= mgr->best_candidates[i-1].rtt) break;
struct CONN_MGR_CANDIDATE tmp = mgr->best_candidates[i];
mgr->best_candidates[i] = mgr->best_candidates[i-1];
mgr->best_candidates[i-1] = tmp;
}
}
static void cm_idle_timer_cb(void* arg) {
struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg;
struct CONN_MGR* mgr = entry->mgr;
uint64_t now_tb = get_time_tb();
if (entry->state != CONN_MGR_STATE_CONNECTED || entry->idle_timeout_ms == 0) return;
uint64_t idle = now_tb - entry->last_traffic_tb;
if (idle > (uint64_t)entry->idle_timeout_ms * 10) {
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: node 0x%016llx idle %llums, disconnecting",
(unsigned long long)entry->node_id, (unsigned long long)(idle / 10));
conn_mgr_disconnect_node(mgr, entry->node_id);
return;
}
entry->idle_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_IDLE_CHECK_INTERVAL_TB, entry, cm_idle_timer_cb, "conn_mgr_idle");
}
static int cm_nat_compatible(struct ETCP_SOCKET* our, uint8_t target_meta_type) {
uint8_t tt = target_meta_type;
if (tt == NAT_VERIFIED_DIRECT) tt = CFG_SERVER_TYPE_PUBLIC;
else if (tt >= NAT_VERIFIED_UNKNOWN) tt = CFG_SERVER_TYPE_PUBLIC;
uint8_t ot = our->type;
if (ot == CFG_SERVER_TYPE_PUBLIC) return 1;
if (ot == CFG_SERVER_TYPE_NAT && our->nat_type == NAT_TYPE_EIM && tt == CFG_SERVER_TYPE_PUBLIC) return 1;
if (ot == CFG_SERVER_TYPE_NAT && our->nat_type == NAT_TYPE_EIM && tt == CFG_SERVER_TYPE_NAT) return 1;
if (ot == CFG_SERVER_TYPE_PRIVATE && tt == CFG_SERVER_TYPE_PRIVATE) return 2;
return 0;
}
static uint16_t cm_get_node_min_rtt(struct NODEINFO_Q* nq) {
uint16_t rtt = 0xFFFF;
if (nq->connectivity.interface_status == PROBE_RESULT_REACHABLE && nq->connectivity.interface_min_rtt < rtt)
rtt = nq->connectivity.interface_min_rtt;
if (nq->connectivity.nat_status == PROBE_RESULT_REACHABLE && nq->connectivity.nat_min_rtt < rtt)
rtt = nq->connectivity.nat_min_rtt;
if (nq->connectivity.real_status == PROBE_RESULT_REACHABLE && nq->connectivity.real_min_rtt < rtt)
rtt = nq->connectivity.real_min_rtt;
return rtt;
}
static uint64_t cm_get_node_max_probe_time(struct NODEINFO_Q* nq) {
uint64_t t = nq->connectivity.interface_probe_time;
if (nq->connectivity.nat_probe_time > t) t = nq->connectivity.nat_probe_time;
if (nq->connectivity.real_probe_time > t) t = nq->connectivity.real_probe_time;
return t;
}
static int cm_is_rtt_fresh(struct NODEINFO_Q* nq, uint64_t now_tb) {
uint64_t last = cm_get_node_max_probe_time(nq);
return (last > 0 && (now_tb - last) < (uint64_t)(CONN_MGR_CANDIDATE_CACHE_MS * 10));
}
static int cm_has_direct_ip(struct NODEINFO_Q* nq) {
if (!nq) return 0;
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
int addr_count = get_node_v4_addrs(nq, &addrs);
int meta_count = get_node_v4_sockets_meta(nq, &metas);
for (int i = 0; i < addr_count; i++) {
if (addrs[i].type == ADDR_TYPE_REAL || addrs[i].type == ADDR_TYPE_NAT) {
for (int j = 0; j < meta_count; j++) {
if (metas[j].id == addrs[i].socket_id &&
(metas[j].type == CFG_SERVER_TYPE_PUBLIC || metas[j].type == NAT_VERIFIED_EIM || metas[j].type == NAT_VERIFIED_DIRECT))
return 1;
}
}
}
return 0;
}
static int cm_has_local_addr(struct NODEINFO_Q* nq) {
if (!nq) return 0;
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
int addr_count = get_node_v4_addrs(nq, &addrs);
int meta_count = get_node_v4_sockets_meta(nq, &metas);
for (int i = 0; i < addr_count; i++) {
if (addrs[i].type == ADDR_TYPE_INTERFACE) {
for (int j = 0; j < meta_count; j++) {
if (metas[j].id == addrs[i].socket_id && metas[j].type == CFG_SERVER_TYPE_PRIVATE)
return 1;
}
}
}
return 0;
}
static void cm_ping_cb(int success, uint16_t rtt, void* arg, uint64_t nonce,
const uint8_t* resp_data, size_t resp_data_len);
static void cm_direct_timeout_cb(void* arg);
struct cm_ping_ctx {
struct CONN_MGR_ENTRY* entry;
struct sockaddr_storage addr;
struct ETCP_SOCKET* sock;
uint8_t phase; /* 0=local_scan, 1=direct */
uint8_t attempt;
};
static void cm_start_local_scan(struct CONN_MGR_ENTRY* entry) {
struct ROUTE_BGP* bgp = entry->mgr->instance->bgp;
struct NODEINFO_Q* target = nodeinfo_find_by_id(bgp, entry->node_id);
if (!target) { entry->local_scan_state = CM_TRY_FAILED; return; }
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
int addr_count = get_node_v4_addrs(target, &addrs);
int meta_count = get_node_v4_sockets_meta(target, &metas);
for (int i = 0; i < addr_count; i++) {
if (addrs[i].type != ADDR_TYPE_INTERFACE) continue;
for (int j = 0; j < meta_count; j++) {
if (metas[j].id != addrs[i].socket_id || metas[j].type != CFG_SERVER_TYPE_PRIVATE) continue;
struct ETCP_SOCKET* s = entry->mgr->instance->etcp_sockets;
while (s) {
if (s->type == CFG_SERVER_TYPE_PRIVATE && s->local_addr.ss_family == AF_INET) {
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
memcpy(&sin.sin_addr.s_addr, addrs[i].addr, 4);
sin.sin_port = htons(addrs[i].port);
struct sockaddr_storage sa;
memset(&sa, 0, sizeof(sa));
memcpy(&sa, &sin, sizeof(sin));
struct cm_ping_ctx* ctx = u_calloc(1, sizeof(struct cm_ping_ctx));
if (!ctx) continue;
ctx->entry = entry;
ctx->addr = sa;
ctx->sock = s;
ctx->phase = 0;
ctx->attempt = 0;
etcp_send_ping_to_socket(entry->mgr->instance, s, target->node.public_key, &sa,
CONN_MGR_LOCAL_SCAN_TIMEOUT_MS, cm_ping_cb, ctx, NULL, 0);
return;
}
s = s->next;
}
}
}
entry->local_scan_state = CM_TRY_FAILED;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: local scan no candidates for 0x%016llx", (unsigned long long)entry->node_id);
}
static void cm_start_phase_direct(struct CONN_MGR_ENTRY* entry) {
if (entry->main_connect_state == CM_TRY_OK) return;
if (entry->local_scan_state == CM_TRY_OK) return;
struct ROUTE_BGP* bgp = entry->mgr->instance->bgp;
struct NODEINFO_Q* target = nodeinfo_find_by_id(bgp, entry->node_id);
if (!target) { cm_deliver_result(entry, CONN_MGR_ERR_NOT_FOUND); return; }
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
int addr_count = get_node_v4_addrs(target, &addrs);
int meta_count = get_node_v4_sockets_meta(target, &metas);
uint8_t priority_order[] = { ADDR_TYPE_REAL, ADDR_TYPE_NAT, ADDR_TYPE_INTERFACE };
for (int pri = 0; pri < 3; pri++) {
for (int i = 0; i < addr_count; i++) {
if (addrs[i].type != priority_order[pri]) continue;
for (int j = 0; j < meta_count; j++) {
if (metas[j].id != addrs[i].socket_id) continue;
struct ETCP_SOCKET* s = entry->mgr->instance->etcp_sockets;
while (s) {
int compat = cm_nat_compatible(s, metas[j].type);
if (compat && s->local_addr.ss_family == AF_INET) {
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
memcpy(&sin.sin_addr.s_addr, addrs[i].addr, 4);
sin.sin_port = htons(addrs[i].port);
struct sockaddr_storage sa;
memset(&sa, 0, sizeof(sa));
memcpy(&sa, &sin, sizeof(sin));
struct ETCP_CONN* conn = etcp_connection_create(entry->mgr->instance, NULL);
if (conn) {
etcp_conn_set_ready_cbk(conn, cm_direct_ready_cb, entry);
sc_set_peer_public_key(&conn->crypto_ctx, target->node.public_key, 0);
struct ETCP_LINK* link = etcp_link_new(conn, s, &sa, 0);
if (link) {
entry->main.timer = uasync_set_timeout(entry->mgr->instance->ua,
CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS * 10, entry, cm_direct_timeout_cb, "conn_mgr_direct");
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 1 (direct) started INIT to 0x%016llx",
(unsigned long long)entry->node_id);
return;
}
}
}
s = s->next;
}
}
}
}
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 1 (direct) no compatible pairs for 0x%016llx",
(unsigned long long)entry->node_id);
int has_our = cm_has_direct_ip(bgp->local_node);
int has_target = cm_has_direct_ip(target);
if (has_our && !has_target) { cm_start_phase_reverse(entry); return; }
cm_start_phase_indirect(entry);
}
static void cm_direct_timeout_cb(void* arg) {
struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg;
entry->main.timer = NULL;
struct ROUTE_BGP* bgp = entry->mgr->instance->bgp;
struct NODEINFO_Q* target = nodeinfo_find_by_id(bgp, entry->node_id);
int has_our = cm_has_direct_ip(bgp->local_node);
int has_target = target ? cm_has_direct_ip(target) : 0;
if (entry->local_scan_state != CM_TRY_OK) {
if (has_our && !has_target) { cm_start_phase_reverse(entry); return; }
cm_start_phase_indirect(entry);
return;
}
if (entry->local_scan_state == CM_TRY_FAILED && entry->main_connect_state == CM_TRY_FAILED)
cm_deliver_result(entry, CONN_MGR_ERR_UNREACHABLE);
}
static void cm_direct_ready_cb(struct ETCP_CONN* conn, void* arg) {
struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg;
if (!conn || conn->peer_node_id != entry->node_id) {
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: direct ready cb peer mismatch conn=%p peer=0x%llx entry=0x%llx",
(void*)conn, (unsigned long long)conn->peer_node_id, (unsigned long long)entry->node_id);
return;
}
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: direct ready for 0x%016llx", (unsigned long long)entry->node_id);
if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; }
entry->main_connect_state = CM_TRY_OK;
entry->conn_type = CONN_TYPE_DIRECT;
entry->state = CONN_MGR_STATE_CONNECTED;
cm_update_nodeinfo(entry);
cm_deliver_result(entry, CONN_MGR_OK);
}
static void cm_ping_cb(int success, uint16_t rtt, void* arg, uint64_t nonce,
const uint8_t* resp_data, size_t resp_data_len) {
struct cm_ping_ctx* ctx = (struct cm_ping_ctx*)arg;
struct CONN_MGR_ENTRY* entry = ctx->entry;
(void)nonce; (void)resp_data; (void)resp_data_len;
if (entry->main_connect_state == CM_TRY_OK) { u_free(ctx); return; }
if (success && ctx->phase == 0) {
entry->local_scan_state = CM_TRY_OK;
entry->conn_type = CONN_TYPE_DIRECT;
entry->state = CONN_MGR_STATE_CONNECTED;
if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; }
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: local scan OK for 0x%016llx rtt=%u", (unsigned long long)entry->node_id, rtt);
cm_update_nodeinfo(entry);
cm_deliver_result(entry, CONN_MGR_OK);
u_free(ctx);
return;
}
ctx->attempt++;
if (ctx->attempt < CONN_MGR_LOCAL_SCAN_ATTEMPTS && ctx->phase == 0) {
etcp_send_ping_to_socket(entry->mgr->instance, ctx->sock,
nodeinfo_find_by_id(entry->mgr->instance->bgp, entry->node_id)->node.public_key,
&ctx->addr, CONN_MGR_LOCAL_SCAN_TIMEOUT_MS, cm_ping_cb, ctx, NULL, 0);
return;
}
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: %s failed for 0x%016llx after %d attempts",
ctx->phase == 0 ? "local scan" : "direct", (unsigned long long)entry->node_id, ctx->attempt);
if (ctx->phase == 0) entry->local_scan_state = CM_TRY_FAILED;
u_free(ctx);
}
static void cm_start_phase_reverse(struct CONN_MGR_ENTRY* entry) {
struct ROUTE_BGP* bgp = entry->mgr->instance->bgp;
struct NODEINFO_Q* local = bgp->local_node;
if (!local) { cm_deliver_result(entry, CONN_MGR_ERR_INTERNAL); return; }
uint32_t req_id = ++entry->mgr->next_request_id;
entry->main.request_id = req_id;
entry->main.phase = 2;
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
int addr_count = get_node_v4_addrs(local, &addrs);
int meta_count = get_node_v4_sockets_meta(local, &metas);
uint8_t direct_count = 0;
struct { uint8_t type; uint8_t ip[4]; uint16_t port; uint8_t socket_id; } out_addrs[8];
for (int i = 0; i < addr_count && direct_count < 8; i++) {
if (addrs[i].type != ADDR_TYPE_REAL && addrs[i].type != ADDR_TYPE_NAT) continue;
for (int j = 0; j < meta_count; j++) {
if (metas[j].id == addrs[i].socket_id &&
(metas[j].type == CFG_SERVER_TYPE_PUBLIC || metas[j].type == NAT_VERIFIED_EIM || metas[j].type == NAT_VERIFIED_DIRECT)) {
out_addrs[direct_count].type = addrs[i].type;
memcpy(out_addrs[direct_count].ip, addrs[i].addr, 4);
out_addrs[direct_count].port = htons(addrs[i].port);
out_addrs[direct_count].socket_id = addrs[i].socket_id;
direct_count++;
break;
}
}
}
if (direct_count == 0) { cm_start_phase_indirect(entry); return; }
size_t pkt_size = CONN_MGR_DIRECT_REQ_HDR_SIZE + (size_t)direct_count * 8;
uint8_t* pkt = u_malloc(pkt_size);
if (!pkt) { cm_deliver_result(entry, CONN_MGR_ERR_INTERNAL); return; }
struct CONN_MGR_DIRECT_REQ* req = (struct CONN_MGR_DIRECT_REQ*)pkt;
req->cmd = ETCP_ID_CONN_MGR;
req->subcmd = CONN_MGR_SUBCMD_DIRECT_REQ;
req->request_id = req_id;
req->addr_count = direct_count;
uint8_t* p = pkt + CONN_MGR_DIRECT_REQ_HDR_SIZE;
for (uint8_t i = 0; i < direct_count; i++) {
*p++ = out_addrs[i].type;
memcpy(p, out_addrs[i].ip, 4); p += 4;
memcpy(p, &out_addrs[i].port, 2); p += 2;
*p++ = out_addrs[i].socket_id;
}
struct ll_entry* qe = queue_entry_new(pkt_size);
if (!qe) { u_free(pkt); cm_deliver_result(entry, CONN_MGR_ERR_INTERNAL); return; }
memcpy(qe->data, pkt, pkt_size);
qe->len = (uint16_t)pkt_size;
u_free(pkt);
etcp_route_send(entry->mgr->instance, entry->node_id, qe, 1);
struct cm_reverse_pending* rp = u_calloc(1, sizeof(struct cm_reverse_pending));
if (rp) { rp->request_id = req_id; rp->entry = entry; rp->next = entry->mgr->reverse_pending;
entry->mgr->reverse_pending = rp; }
entry->main.timer = uasync_set_timeout(entry->mgr->instance->ua, CONN_MGR_CONNECT_REVERSE_TIMEOUT_MS * 10,
entry, cm_reverse_timeout_cb, "conn_mgr_reverse");
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 2 (reverse) sent DIRECT_REQ to 0x%016llx with %u addrs",
(unsigned long long)entry->node_id, direct_count);
}
static void cm_reverse_timeout_cb(void* arg) {
struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg;
entry->main.timer = NULL;
struct cm_reverse_pending** pp = &entry->mgr->reverse_pending;
while (*pp) { if ((*pp)->entry == entry) { struct cm_reverse_pending* rp = *pp; *pp = rp->next; u_free(rp); break; } pp = &(*pp)->next; }
cm_start_phase_indirect(entry);
}
static void cm_reverse_ready_cb(struct ETCP_CONN* conn, void* arg) {
struct cm_reverse_pending* rp = (struct cm_reverse_pending*)arg;
struct CONN_MGR_ENTRY* entry = rp->entry;
if (!conn || conn->peer_node_id != entry->node_id) { u_free(rp); return; }
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: reverse ready for 0x%016llx", (unsigned long long)entry->node_id);
if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; }
struct cm_reverse_pending** pp = &entry->mgr->reverse_pending;
while (*pp) { if (*pp == rp) { *pp = rp->next; break; } pp = &(*pp)->next; }
entry->main_connect_state = CM_TRY_OK;
entry->conn_type = CONN_TYPE_REVERSE;
entry->state = CONN_MGR_STATE_CONNECTED;
cm_update_nodeinfo(entry);
cm_deliver_result(entry, CONN_MGR_OK);
u_free(rp);
}
static int cm_start_phase_indirect(struct CONN_MGR_ENTRY* entry) {
struct CONN_MGR* mgr = entry->mgr;
if (mgr->best_candidate_count == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 3 (indirect) no candidates for 0x%016llx",
(unsigned long long)entry->node_id);
return CONN_MGR_ERR_UNREACHABLE;
}
uint32_t req_id = ++mgr->next_request_id;
entry->main.request_id = req_id;
entry->main.phase = 3;
struct CONN_MGR_INTERM_EXCHANGE_REQ req;
memset(&req, 0, sizeof(req));
req.cmd = ETCP_ID_CONN_MGR;
req.subcmd = CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ;
req.request_id = req_id;
req.candidate_count = mgr->best_candidate_count > 4 ? 4 : mgr->best_candidate_count;
for (uint8_t i = 0; i < req.candidate_count; i++)
req.candidates[i] = mgr->best_candidates[i];
struct ll_entry* qe = queue_entry_new(sizeof(req));
if (!qe) return CONN_MGR_ERR_INTERNAL;
memcpy(qe->data, &req, sizeof(req));
etcp_route_send(entry->mgr->instance, entry->node_id, qe, 1);
entry->main.timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS * 10,
entry, cm_exchange_timeout_cb, "conn_mgr_interm_exch");
{
struct cm_exchange_pending* ep = u_calloc(1, sizeof(struct cm_exchange_pending));
if (ep) { ep->request_id = req_id; ep->entry = entry; ep->timeout_timer = entry->main.timer;
ep->next = mgr->exchange_pending; mgr->exchange_pending = ep; }
}
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 3 (indirect) sent EXCHANGE_REQ to 0x%016llx with %u candidates",
(unsigned long long)entry->node_id, req.candidate_count);
return CONN_MGR_OK;
}
static void cm_exchange_timeout_cb(void* arg) {
struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg;
entry->main.timer = NULL;
struct cm_exchange_pending** pp = &entry->mgr->exchange_pending;
while (*pp) { if ((*pp)->entry == entry) { struct cm_exchange_pending* ep = *pp; *pp = ep->next; u_free(ep); break; } pp = &(*pp)->next; }
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: exchange timeout for 0x%016llx", (unsigned long long)entry->node_id);
cm_deliver_result(entry, CONN_MGR_ERR_TIMEOUT);
}
static void cm_compute_intermediaries(struct CONN_MGR_ENTRY* entry, struct CONN_MGR_INTERM_EXCHANGE_RESP* resp) {
struct { uint64_t node_id; uint64_t total_rtt; } all[8];
uint8_t count = 0;
for (uint8_t i = 0; i < resp->your_count && count < 8; i++) {
uint64_t id = resp->your_candidates[i].node_id;
uint16_t our_rtt = 0;
for (uint8_t j = 0; j < CONN_MGR_MAX_CANDIDATES; j++) {
if (entry->mgr->best_candidates[j].node_id == id) { our_rtt = entry->mgr->best_candidates[j].rtt; break; }
}
all[count].node_id = id;
all[count].total_rtt = (uint64_t)our_rtt + resp->your_candidates[i].rtt;
count++;
}
for (uint8_t i = 0; i < resp->my_count && count < 8; i++) {
uint64_t id = resp->my_candidates[i].node_id;
struct NODEINFO_Q* nq = nodeinfo_find_by_id(entry->mgr->instance->bgp, id);
uint16_t our_rtt = nq ? cm_get_node_min_rtt(nq) : 0;
if (our_rtt == 0xFFFF) our_rtt = 0;
all[count].node_id = id;
all[count].total_rtt = (uint64_t)our_rtt + resp->my_candidates[i].rtt;
count++;
}
for (uint8_t i = 0; i < count; i++)
for (uint8_t j = i + 1; j < count; j++)
if (all[j].total_rtt < all[i].total_rtt) { typeof(all[0]) t = all[i]; all[i] = all[j]; all[j] = t; }
uint8_t sel = count > CONN_MGR_MAX_INTERMEDIARIES ? CONN_MGR_MAX_INTERMEDIARIES : count;
for (uint8_t i = 0; i < sel; i++) entry->intermediaries[i] = all[i].node_id;
entry->intermediariy_count = sel;
entry->rr_idx = 0;
struct CONN_MGR_INTERM_SELECTED pkt;
memset(&pkt, 0, sizeof(pkt));
pkt.cmd = ETCP_ID_CONN_MGR;
pkt.subcmd = CONN_MGR_SUBCMD_INTERM_SELECTED;
pkt.request_id = 0;
pkt.count = sel;
for (uint8_t i = 0; i < sel; i++) {
pkt.selected[i].node_id = all[i].node_id;
pkt.selected[i].rtt = 0;
struct NODEINFO_Q* nq = nodeinfo_find_by_id(entry->mgr->instance->bgp, all[i].node_id);
if (nq) pkt.selected[i].rtt = cm_get_node_min_rtt(nq);
}
struct ll_entry* qe = queue_entry_new(sizeof(pkt));
if (qe) { memcpy(qe->data, &pkt, sizeof(pkt)); etcp_route_send(entry->mgr->instance, entry->node_id, qe, 1); }
entry->conn_type = CONN_TYPE_INDIRECT;
entry->state = CONN_MGR_STATE_CONNECTED;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: indirect OK for 0x%016llx via %u intermediaries",
(unsigned long long)entry->node_id, sel);
cm_update_nodeinfo(entry);
cm_deliver_result(entry, CONN_MGR_OK);
}
static void cm_handle_interm_exchange_resp(struct CONN_MGR* mgr, const uint8_t* data, size_t len) {
const struct CONN_MGR_INTERM_EXCHANGE_RESP* resp = (const struct CONN_MGR_INTERM_EXCHANGE_RESP*)data;
size_t min_size = offsetof(struct CONN_MGR_INTERM_EXCHANGE_RESP, your_candidates);
if (len < min_size) return;
struct cm_exchange_pending* ep = mgr->exchange_pending;
while (ep) { if (ep->request_id == resp->request_id) break; ep = ep->next; }
if (!ep) return;
if (ep->entry->main.timer) { uasync_cancel_timeout(mgr->instance->ua, ep->entry->main.timer); ep->entry->main.timer = NULL; }
memcpy(&ep->cached_resp, resp, len < sizeof(ep->cached_resp) ? len : sizeof(ep->cached_resp));
ep->resp_received = 1;
ep->probes_done = 0;
uint8_t need_probe = 0;
uint64_t now_tb = get_time_tb();
for (uint8_t i = 0; i < ep->cached_resp.my_count && i < 4; i++) {
struct NODEINFO_Q* nq = nodeinfo_find_by_id(mgr->instance->bgp, ep->cached_resp.my_candidates[i].node_id);
if (!nq || !cm_is_rtt_fresh(nq, now_tb)) { need_probe++; route_connectivity_probe_node(mgr->instance, nq); }
}
if (need_probe == 0) { ep->probes_done = 1; cm_compute_intermediaries(ep->entry, &ep->cached_resp);
struct cm_exchange_pending** pp = &mgr->exchange_pending;
while (*pp) { if (*pp == ep) { *pp = ep->next; break; } pp = &(*pp)->next; }
u_free(ep); return; }
uasync_set_timeout(mgr->instance->ua, 2000, ep, NULL, "cm_exch_probe");
}
static void cm_handle_interm_selected(struct CONN_MGR* mgr, const uint8_t* data, size_t len) {
const struct CONN_MGR_INTERM_SELECTED* sel = (const struct CONN_MGR_INTERM_SELECTED*)data;
if (len < offsetof(struct CONN_MGR_INTERM_SELECTED, selected)) return;
struct CONN_MGR_ENTRY* entry = NULL;
for (size_t i = 0; i < mgr->entry_count; i++)
if (mgr->entries[i].state == CONN_MGR_STATE_CONNECTING) { entry = &mgr->entries[i]; break; }
if (!entry) return;
uint8_t cnt = sel->count > CONN_MGR_MAX_INTERMEDIARIES ? CONN_MGR_MAX_INTERMEDIARIES : sel->count;
for (uint8_t i = 0; i < cnt; i++) entry->intermediaries[i] = sel->selected[i].node_id;
entry->intermediariy_count = cnt;
entry->rr_idx = 0;
entry->conn_type = CONN_TYPE_INDIRECT;
entry->state = CONN_MGR_STATE_CONNECTED;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: got INTERM_SELECTED count=%u", cnt);
}
static void conn_mgr_router_recv_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (!entry || !entry->dgram || entry->len < 3) return;
uint8_t* dgram = entry->dgram + 1; // skip svc_id byte from router
size_t len = entry->len - 1;
uint8_t subcmd = dgram[1];
struct CONN_MGR* mgr = conn->instance->conn_mgr;
if (mgr) {
struct CONN_MGR_ENTRY* e = cm_find_entry(mgr, conn->peer_node_id);
if (e && e->state == CONN_MGR_STATE_CONNECTED) e->last_traffic_tb = get_time_tb();
}
switch (subcmd) {
case CONN_MGR_SUBCMD_DIRECT_REQ:
cm_handle_direct_req(conn, dgram, len);
break;
case CONN_MGR_SUBCMD_DIRECT_RESP:
break;
case CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ:
if (len >= CONN_MGR_INTERM_EXCHANGE_REQ_SIZE)
cm_handle_interm_exchange_req(conn, (struct CONN_MGR_INTERM_EXCHANGE_REQ*)dgram);
break;
case CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP:
if (mgr) cm_handle_interm_exchange_resp(mgr, dgram, len);
break;
case CONN_MGR_SUBCMD_INTERM_SELECTED:
if (mgr) cm_handle_interm_selected(mgr, dgram, len);
break;
case CONN_MGR_SUBCMD_DISCONNECT:
if (len >= CONN_MGR_DISCONNECT_SIZE && mgr) {
struct CONN_MGR_DISCONNECT* disc = (struct CONN_MGR_DISCONNECT*)dgram;
cm_handle_disconnect(mgr, disc->node_id);
}
break;
}
}
static void cm_handle_direct_req(struct ETCP_CONN* conn, const uint8_t* data, size_t len) {
struct CONN_MGR* mgr = conn->instance->conn_mgr;
if (!mgr) return;
struct CONN_MGR_DIRECT_REQ* req = (struct CONN_MGR_DIRECT_REQ*)data;
size_t expected = CONN_MGR_DIRECT_REQ_HDR_SIZE + (size_t)req->addr_count * 8;
if (len < expected) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: DIRECT_REQ too short"); return; }
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: got DIRECT_REQ from 0x%016llx with %u addrs",
(unsigned long long)conn->peer_node_id, req->addr_count);
uint8_t* p = (uint8_t*)data + CONN_MGR_DIRECT_REQ_HDR_SIZE;
uint64_t src_node_id = conn->peer_node_id;
for (uint8_t i = 0; i < req->addr_count; i++) {
uint8_t type = *p++;
uint8_t ip[4]; memcpy(ip, p, 4); p += 4;
uint16_t port; memcpy(&port, p, 2); p += 2;
uint8_t sock_id = *p++;
(void)sock_id; (void)type;
struct ETCP_SOCKET* s = conn->instance->etcp_sockets;
while (s) {
if (s->type == CFG_SERVER_TYPE_PUBLIC && s->local_addr.ss_family == AF_INET) {
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET;
memcpy(&sin.sin_addr.s_addr, ip, 4); sin.sin_port = port;
struct sockaddr_storage sa;
memset(&sa, 0, sizeof(sa)); memcpy(&sa, &sin, sizeof(sin));
struct ETCP_CONN* new_conn = etcp_connection_create(conn->instance, NULL);
if (new_conn) {
struct NODEINFO_Q* nq = nodeinfo_find_by_id(conn->instance->bgp, src_node_id);
if (nq) sc_set_peer_public_key(&new_conn->crypto_ctx, nq->node.public_key, 0);
struct cm_reverse_pending* rp = u_calloc(1, sizeof(struct cm_reverse_pending));
if (rp) { rp->request_id = req->request_id; rp->entry = NULL; rp->next = mgr->reverse_pending;
etcp_conn_set_ready_cbk(new_conn, cm_reverse_ready_cb, rp); mgr->reverse_pending = rp; }
etcp_link_new(new_conn, s, &sa, 0);
}
break;
}
s = s->next;
}
}
}
static void cm_handle_interm_exchange_req(struct ETCP_CONN* conn,
struct CONN_MGR_INTERM_EXCHANGE_REQ* req) {
struct CONN_MGR* mgr = conn->instance->conn_mgr;
if (!mgr) return;
uint64_t now_tb = get_time_tb();
for (uint8_t i = 0; i < req->candidate_count && i < 4; i++) {
uint64_t cand_id = req->candidates[i].node_id;
struct NODEINFO_Q* nq = nodeinfo_find_by_id(mgr->instance->bgp, cand_id);
if (nq && cm_is_rtt_fresh(nq, now_tb)) continue;
route_connectivity_probe_node(mgr->instance, nq);
}
struct CONN_MGR_INTERM_EXCHANGE_RESP resp;
memset(&resp, 0, sizeof(resp));
resp.cmd = ETCP_ID_CONN_MGR;
resp.subcmd = CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP;
resp.request_id = req->request_id;
resp.my_count = mgr->best_candidate_count > 4 ? 4 : mgr->best_candidate_count;
for (uint8_t i = 0; i < resp.my_count; i++)
resp.my_candidates[i] = mgr->best_candidates[i];
resp.your_count = req->candidate_count > 4 ? 4 : req->candidate_count;
for (uint8_t i = 0; i < resp.your_count; i++) {
resp.your_candidates[i].node_id = req->candidates[i].node_id;
struct NODEINFO_Q* nq2 = nodeinfo_find_by_id(mgr->instance->bgp, req->candidates[i].node_id);
uint16_t rtt = nq2 ? cm_get_node_min_rtt(nq2) : 0;
resp.your_candidates[i].rtt = rtt;
}
size_t resp_size = offsetof(struct CONN_MGR_INTERM_EXCHANGE_RESP, your_candidates) +
(size_t)resp.your_count * sizeof(struct CONN_MGR_CANDIDATE);
struct ll_entry* qe = queue_entry_new(resp_size);
if (qe) {
memcpy(qe->data, &resp, resp_size);
etcp_route_send(mgr->instance, conn->peer_node_id, qe, 1);
}
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: sent EXCHANGE_RESP to 0x%016llx my=%u your=%u",
(unsigned long long)conn->peer_node_id, resp.my_count, resp.your_count);
}
static void cm_handle_disconnect(struct CONN_MGR* mgr, uint64_t node_id) {
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id);
if (!entry) return;
cm_clear_nodeinfo(mgr, node_id);
cm_entry_destroy(entry);
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: remote disconnect from 0x%016llx", (unsigned long long)node_id);
}
static void cm_bg_ping_timer_cb(void* arg) {
struct CONN_MGR* mgr = (struct CONN_MGR*)arg;
struct ROUTE_BGP* bgp = mgr->instance->bgp;
if (!bgp || !bgp->nodes) {
mgr->bg_ping_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_BG_PING_INTERVAL_TB, mgr, cm_bg_ping_timer_cb, "conn_mgr_bg_ping");
return;
}
uint64_t now_tb = get_time_tb();
struct ll_entry* e = bgp->nodes->head;
size_t total = 0;
while (e) { total++; e = e->next; }
if (total == 0) {
mgr->bg_ping_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_BG_PING_INTERVAL_TB, mgr, cm_bg_ping_timer_cb, "conn_mgr_bg_ping");
return;
}
if (mgr->bg_ping_cursor >= total) {
mgr->bg_ping_cursor = 0;
if ((now_tb - mgr->bg_ping_cycle_start_tb) < CONN_MGR_BG_PING_CYCLE_MIN_TB) {
uint64_t delay = CONN_MGR_BG_PING_CYCLE_MIN_TB - (now_tb - mgr->bg_ping_cycle_start_tb);
mgr->bg_ping_timer = uasync_set_timeout(mgr->instance->ua, (int)delay, mgr, cm_bg_ping_timer_cb, "conn_mgr_bg_ping");
return;
}
mgr->bg_ping_cycle_start_tb = now_tb;
}
size_t cursor = 0;
e = bgp->nodes->head;
while (e && cursor < mgr->bg_ping_cursor) { cursor++; e = e->next; }
if (e) {
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)e;
uint64_t node_id = nq->node.node_id;
if (node_id != mgr->instance->node_id) {
struct ETCP_CONN* dconn = route_bgp_find_conn_for_node(bgp, node_id);
int has_active = 0;
if (dconn && dconn->links) {
struct ETCP_LINK* l = dconn->links;
while (l) { if (l->initialized && l->link_status) { has_active = 1; break; } l = l->next; }
}
if (!has_active) {
etcp_send_ping(mgr->instance, nq->node.public_key, NULL, CONN_PROBE_TIMEOUT_MS,
NULL, NULL, NULL, 0);
}
}
mgr->bg_ping_cursor++;
}
mgr->bg_ping_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_BG_PING_INTERVAL_TB, mgr, cm_bg_ping_timer_cb, "conn_mgr_bg_ping");
}
static void cm_candidate_ping_timer_cb(void* arg) {
struct CONN_MGR* mgr = (struct CONN_MGR*)arg;
uint64_t now_tb = get_time_tb();
for (uint8_t i = 0; i < mgr->best_candidate_count; i++) {
uint64_t cand_id = mgr->best_candidates[i].node_id;
struct NODEINFO_Q* nq = nodeinfo_find_by_id(mgr->instance->bgp, cand_id);
if (!nq) {
memmove(&mgr->best_candidates[i], &mgr->best_candidates[i+1], (mgr->best_candidate_count - i - 1) * sizeof(mgr->best_candidates[0]));
mgr->best_candidate_count--; i--; continue;
}
uint64_t last = cm_get_node_max_probe_time(nq);
if (last > 0 && (now_tb - last) >= CONN_MGR_CANDIDATE_STALE_TB) {
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: candidate 0x%016llx stale, removing", (unsigned long long)cand_id);
memmove(&mgr->best_candidates[i], &mgr->best_candidates[i+1], (mgr->best_candidate_count - i - 1) * sizeof(mgr->best_candidates[0]));
mgr->best_candidate_count--; i--; continue;
}
struct ETCP_CONN* dconn = route_bgp_find_conn_for_node(mgr->instance->bgp, cand_id);
int has_active = 0;
if (dconn && dconn->links) {
struct ETCP_LINK* l = dconn->links;
while (l) { if (l->initialized && l->link_status) { has_active = 1; break; } l = l->next; }
}
if (has_active)
etcp_send_ping(mgr->instance, nq->node.public_key, &dconn->links->remote_addr, CONN_PROBE_TIMEOUT_MS, NULL, NULL, NULL, 0);
else
route_connectivity_probe_node(mgr->instance, nq);
}
mgr->candidate_ping_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_CANDIDATE_PING_TB,
mgr, cm_candidate_ping_timer_cb, "conn_mgr_cand_ping");
}
+190
View File
@@ -0,0 +1,190 @@
#ifndef CONN_MGR_H
#define CONN_MGR_H
#include <stdint.h>
#include <stddef.h>
#include "../lib/ll_queue.h"
#include "etcp_api.h"
#include "etcp_router.h"
#include "route_node.h"
struct UTUN_INSTANCE;
struct ETCP_CONN;
struct NODEINFO_Q;
struct ROUTE_BGP;
#define CONN_MGR_MAX_CANDIDATES 3
#define CONN_MGR_CANDIDATE_CACHE_MS 20000
#define CONN_MGR_CANDIDATE_STALE_TB 300000
#define CONN_MGR_CANDIDATE_PING_TB 20000
#define CONN_MGR_BG_PING_INTERVAL_TB 1000
#define CONN_MGR_BG_PING_CYCLE_MIN_TB 100000
#define CONN_MGR_IDLE_CHECK_INTERVAL_TB 10000
#define CONN_MGR_LOCAL_SCAN_ATTEMPTS 3
#define CONN_MGR_LOCAL_SCAN_TIMEOUT_MS 100
#define CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS 5000
#define CONN_MGR_CONNECT_REVERSE_TIMEOUT_MS 15000
#define CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS 15000
#define CONN_MGR_OK 0
#define CONN_MGR_ERR_NOT_FOUND -1
#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 {
CONN_MGR_STATE_DISCONNECTED = 0,
CONN_MGR_STATE_CONNECTING = 1,
CONN_MGR_STATE_CONNECTED = 2,
};
enum CM_TRY_STATE {
CM_TRY_NONE = 0,
CM_TRY_PENDING = 1,
CM_TRY_OK = 2,
CM_TRY_FAILED = 3,
};
#define CONN_MGR_SUBCMD_DIRECT_REQ 0x01
#define CONN_MGR_SUBCMD_DIRECT_RESP 0x02
#define CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ 0x03
#define CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP 0x04
#define CONN_MGR_SUBCMD_INTERM_SELECTED 0x05
#define CONN_MGR_SUBCMD_DISCONNECT 0x06
struct CONN_MGR_CANDIDATE {
uint64_t node_id;
uint16_t rtt;
} __attribute__((packed));
#pragma pack(push, 1)
struct CONN_MGR_DIRECT_REQ {
uint8_t cmd;
uint8_t subcmd;
uint32_t request_id;
uint8_t addr_count;
};
struct CONN_MGR_DIRECT_RESP {
uint8_t cmd;
uint8_t subcmd;
uint32_t request_id;
uint8_t accepted;
};
struct CONN_MGR_INTERM_EXCHANGE_REQ {
uint8_t cmd;
uint8_t subcmd;
uint32_t request_id;
uint8_t candidate_count;
struct CONN_MGR_CANDIDATE candidates[4];
};
struct CONN_MGR_INTERM_EXCHANGE_RESP {
uint8_t cmd;
uint8_t subcmd;
uint32_t request_id;
uint8_t my_count;
uint8_t your_count;
struct CONN_MGR_CANDIDATE my_candidates[4];
struct CONN_MGR_CANDIDATE your_candidates[4];
};
struct CONN_MGR_INTERM_SELECTED {
uint8_t cmd;
uint8_t subcmd;
uint32_t request_id;
uint8_t count;
struct CONN_MGR_CANDIDATE selected[3];
};
struct CONN_MGR_DISCONNECT {
uint8_t cmd;
uint8_t subcmd;
uint64_t node_id;
};
#pragma pack(pop)
#define CONN_MGR_DIRECT_REQ_HDR_SIZE sizeof(struct CONN_MGR_DIRECT_REQ)
#define CONN_MGR_DIRECT_RESP_HDR_SIZE sizeof(struct CONN_MGR_DIRECT_RESP)
#define CONN_MGR_INTERM_EXCHANGE_REQ_SIZE sizeof(struct CONN_MGR_INTERM_EXCHANGE_REQ)
#define CONN_MGR_INTERM_EXCHANGE_RESP_SIZE sizeof(struct CONN_MGR_INTERM_EXCHANGE_RESP)
#define CONN_MGR_INTERM_SELECTED_SIZE sizeof(struct CONN_MGR_INTERM_SELECTED)
#define CONN_MGR_DISCONNECT_SIZE sizeof(struct CONN_MGR_DISCONNECT)
typedef void (*conn_mgr_connect_callback_t)(int result, uint64_t node_id, void* arg);
struct cm_cb_node {
conn_mgr_connect_callback_t cb;
void* arg;
struct cm_cb_node* next;
};
struct CONN_MGR_ENTRY {
uint64_t node_id;
uint8_t state;
uint8_t conn_type;
uint8_t alien;
uint32_t idle_timeout_ms;
uint64_t last_traffic_tb;
void* idle_timer;
struct cm_cb_node* cb_list;
uint64_t intermediaries[CONN_MGR_MAX_INTERMEDIARIES];
uint8_t intermediariy_count;
uint8_t rr_idx;
uint8_t local_scan_state;
uint8_t main_connect_state;
struct {
uint8_t phase;
void* timer;
uint32_t request_id;
void* conn_ctx;
} main;
struct CONN_MGR* mgr;
};
struct CONN_MGR {
struct UTUN_INSTANCE* instance;
struct CONN_MGR_ENTRY* entries;
size_t entry_count;
size_t entry_capacity;
void* bg_ping_timer;
size_t bg_ping_cursor;
uint64_t bg_ping_cycle_start_tb;
void* candidate_ping_timer;
struct CONN_MGR_CANDIDATE best_candidates[CONN_MGR_MAX_CANDIDATES];
uint8_t best_candidate_count;
uint32_t next_request_id;
uint8_t initialized;
struct cm_reverse_pending* reverse_pending;
struct cm_exchange_pending* exchange_pending;
};
struct CONN_MGR* conn_mgr_init(struct UTUN_INSTANCE* instance);
void conn_mgr_destroy(struct CONN_MGR* mgr);
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);
int conn_mgr_disconnect_node(struct CONN_MGR* mgr, uint64_t node_id);
int conn_mgr_set_idle_timeout(struct CONN_MGR* mgr, uint64_t node_id, uint32_t timeout_ms);
int conn_mgr_get_status(struct CONN_MGR* mgr, uint64_t node_id,
uint8_t* out_state, uint8_t* out_conn_type);
int conn_mgr_add_alien_node(struct CONN_MGR* mgr, const uint8_t* nodeinfo_data, size_t len);
int conn_mgr_send(struct CONN_MGR* mgr, uint64_t node_id, struct ll_entry* entry);
void conn_mgr_update_best_candidates(struct CONN_MGR* mgr, uint64_t node_id, uint16_t rtt);
#endif
+180 -45
View File
@@ -11,6 +11,7 @@
#include "tun_if.h"
#include "route_lib.h"
#include "route_bgp.h"
#include "route_node.h"
#include "pkt_normalizer.h"
#include "../tools/etcpmon/etcpmon_protocol.h"
#include "../lib/u_async.h"
@@ -96,8 +97,16 @@ static void send_metrics(struct control_server* server, struct control_client* c
static void send_debug_config(struct control_server* server, struct control_client* client, uint8_t seq_id);
static void send_error(struct control_client* client, uint8_t error_code, const char* msg, uint8_t seq_id);
static void control_server_send_node_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
static void send_single_node_info(struct control_server* server, struct control_client* client, struct NODEINFO_Q* nq);
static int control_client_send(struct control_server* server, struct control_client* client, uint8_t* dgram, uint16_t len);
static struct ETCP_CONN* find_connection_by_peer_id(struct UTUN_INSTANCE* instance, uint64_t peer_id);
/* ETCP ID/subcmd constants for BGP_NODEINFO_PACKET (from route_bgp.h) */
#define CTRL_BGP_CMD 0x01
#define CTRL_BGP_SUBCMD 0x04
/* ============================================================================
* Server Initialization
* ============================================================================ */
@@ -400,13 +409,26 @@ 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;
/* 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);
@@ -544,9 +566,44 @@ 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;
(void)fd;
(void)arg;
}
static void client_except_callback(socket_t fd, void* arg) {
@@ -560,6 +617,21 @@ 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 close_client(struct control_server* server, struct control_client* client) {
if (!server || !client) return;
@@ -583,7 +655,9 @@ static void close_client(struct control_server* server, struct control_client* c
close(client->fd);
}
#endif
if (client->output_queue) { queue_free(client->output_queue); client->output_queue = NULL; }
/* Remove from list */
struct control_client** curr = &server->clients;
while (*curr) {
@@ -614,7 +688,7 @@ static void handle_client_data(struct control_server* server, struct control_cli
close_client(server, client);
return;
}
if (hdr->type < ETCPMON_CMD_LIST_CONN || hdr->type > ETCPMON_CMD_SET_DEBUG_CONFIG) {
if (hdr->type < ETCPMON_CMD_LIST_CONN || hdr->type > ETCPMON_CMD_SUBSCRIBE_NODES) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Invalid command type from client: 0x%02X", hdr->type);
close_client(server, client);
return;
@@ -721,6 +795,17 @@ static void handle_client_data(struct control_server* server, struct control_cli
}
break;
case ETCPMON_CMD_SUBSCRIBE_NODES:
client->subscribed_nodes = 1;
if (server->log_file) {
fprintf(server->log_file, "%llu: [LOG] Client subscribed to node updates\n",
(unsigned long long)get_timestamp_ms());
fflush(server->log_file);
}
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Client subscribed to node updates");
control_server_send_node_list(server, client, req_seq);
break;
case ETCPMON_CMD_DISCONNECT:
if (server->log_file) {
fprintf(server->log_file, "%llu: [LOG] Client requested disconnect\n",
@@ -795,13 +880,7 @@ static void send_conn_list(struct control_server* server, struct control_client*
fflush(server->log_file);
}
#ifdef _WIN32
send(client->fd, (const char*)buffer, rsp_size, 0);
#else
send(client->fd, buffer, rsp_size, 0);
#endif
u_free(buffer);
control_client_send(server, client, buffer, rsp_size);
}
static void send_socket_list(struct control_server* server, struct control_client* client, uint8_t seq_id) {
@@ -819,8 +898,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;
}
@@ -889,17 +967,11 @@ static void send_socket_list(struct control_server* server, struct control_clien
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) {
@@ -1136,13 +1208,7 @@ static void send_metrics(struct control_server* server, struct control_client* c
fflush(server->log_file);
}
#ifdef _WIN32
send(client->fd, (const char*)buffer, rsp_size, 0);
#else
send(client->fd, buffer, rsp_size, 0);
#endif
u_free(buffer);
control_client_send(server, client, buffer, rsp_size);
}
static void send_error(struct control_client* client, uint8_t error_code, const char* msg, uint8_t seq_id) {
@@ -1170,13 +1236,7 @@ static void send_error(struct control_client* client, uint8_t error_code, const
fflush(server->log_file);
}
#ifdef _WIN32
send(client->fd, (const char*)buffer, rsp_size, 0);
#else
send(client->fd, buffer, rsp_size, 0);
#endif
u_free(buffer);
control_client_send(server, client, buffer, rsp_size);
}
static void send_debug_config(struct control_server* server, struct control_client* client, uint8_t seq_id) {
@@ -1215,13 +1275,7 @@ static void send_debug_config(struct control_server* server, struct control_clie
fflush(server->log_file);
}
#ifdef _WIN32
send(client->fd, (const char*)buffer, rsp_size, 0);
#else
send(client->fd, buffer, rsp_size, 0);
#endif
u_free(buffer);
control_client_send(server, client, buffer, rsp_size);
}
/* ============================================================================
@@ -1240,6 +1294,87 @@ 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 NODEINFO_Q* nq) {
int dyn = nodeinfo_dyn_size(&nq->node);
size_t np_size = sizeof(struct etcpmon_msg_header) + 2 + sizeof(struct NODEINFO) + dyn;
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, 2 + sizeof(struct NODEINFO) + dyn, ETCPMON_RSP_NODE_INFO, 0);
uint8_t* pay = buffer + sizeof(*hdr);
pay[0] = CTRL_BGP_CMD; pay[1] = CTRL_BGP_SUBCMD;
memcpy(pay + 2, &nq->node, sizeof(struct NODEINFO));
memcpy(pay + 2 + sizeof(struct NODEINFO), (uint8_t*)&nq->node + sizeof(struct NODEINFO), dyn);
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 ROUTE_BGP* bgp = server->instance->bgp;
if (!bgp) { send_error(client, ETCPMON_ERR_SERVER_BUSY, "BGP not available", seq_id); return; }
/* Send local node first */
if (bgp->local_node) send_single_node_info(server, client, bgp->local_node);
/* Send all known remote nodes */
struct ll_entry* e = bgp->nodes ? bgp->nodes->head : NULL;
while (e) {
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)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 NODEINFO_Q* 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
* ============================================================================ */
+9 -3
View File
@@ -19,6 +19,8 @@ extern "C" {
/* Forward declarations */
struct UTUN_INSTANCE;
struct UASYNC;
struct NODEINFO_Q;
struct ll_queue;
/* Forward declaration */
struct control_server;
@@ -36,9 +38,10 @@ 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 */
uint8_t subscribed_nodes; /* 1 = subscribed to node updates */
uint8_t write_registered; /* 1 = write events registered in uasync */
};
/* Control server state */
@@ -113,6 +116,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 NODEINFO_Q* node);
void control_server_notify_node_removed(struct control_server* server, uint64_t node_id);
#ifdef __cplusplus
}
#endif
+35
View File
@@ -577,6 +577,11 @@ struct dummynet_filter {
uint32_t jitter_ms;
struct ETCP_LINK* link;
struct dummynet_stats stats;
struct {
uint32_t ip;
uint16_t port;
} block_addrs[4];
uint8_t block_count;
};
static ssize_t dummynet_filter_send_hook(socket_t fd, const void* buf, size_t len,
@@ -585,6 +590,16 @@ static ssize_t dummynet_filter_send_hook(socket_t fd, const void* buf, size_t le
struct dummynet_filter* df = (struct dummynet_filter*)ctx;
(void)link;
df->stats.recv++;
if (addr && addr->sa_family == AF_INET && df->block_count > 0) {
const struct sockaddr_in* sin = (const struct sockaddr_in*)addr;
for (uint8_t i = 0; i < df->block_count; i++) {
if (sin->sin_addr.s_addr == df->block_addrs[i].ip &&
sin->sin_port == df->block_addrs[i].port) {
df->stats.lost++;
return (ssize_t)len;
}
}
}
if (df->loss_permille > 0) {
uint32_t roll = (uint32_t)rand() % 1000;
if (roll < df->loss_permille) {
@@ -637,6 +652,26 @@ void dummynet_filter_destroy(struct dummynet_filter* df) {
u_free(df);
}
void dummynet_filter_block_addr(struct dummynet_filter* df, uint32_t ip, uint16_t port) {
if (!df || df->block_count >= 4) return;
for (uint8_t i = 0; i < df->block_count; i++)
if (df->block_addrs[i].ip == ip && df->block_addrs[i].port == port) return;
df->block_addrs[df->block_count].ip = ip;
df->block_addrs[df->block_count].port = port;
df->block_count++;
}
void dummynet_filter_unblock_addr(struct dummynet_filter* df, uint32_t ip, uint16_t port) {
if (!df) return;
for (uint8_t i = 0; i < df->block_count; i++) {
if (df->block_addrs[i].ip == ip && df->block_addrs[i].port == port) {
df->block_count--;
if (i < df->block_count) memmove(&df->block_addrs[i], &df->block_addrs[i+1], (df->block_count - i) * sizeof(df->block_addrs[0]));
return;
}
}
}
const struct dummynet_stats* dummynet_filter_get_stats(struct dummynet_filter* df) {
return df ? &df->stats : NULL;
}
+2
View File
@@ -176,5 +176,7 @@ 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);
#endif // DUMMYNET_H
+6 -3
View File
@@ -453,6 +453,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");
}
@@ -864,7 +869,7 @@ static void etcp_conn_process_send_queue(struct ETCP_CONN* etcp) {// вызыв
char l_status[256]={0};
struct ETCP_LINK* link = etcp->links;
while (link) {
snprintf (l_status+strlen(l_status), 256-strlen(l_status), "L%d%d%s,%s ", link->recv_keepalive, link->remote_keepalive, (link->shaper_timer)?"wait":"rdy", link->send_blocked_inflight?"inf_block":"rdy");
snprintf (l_status+strlen(l_status), 256-strlen(l_status), "L%d%d%s ", link->recv_keepalive, link->remote_keepalive, (link->shaper_timer)?"wait":"rdy");
link = link->next;
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] TX state: %d (link not ready, skip send) %s", etcp->log_name, etcp->tx_state, l_status);
@@ -934,8 +939,6 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
bbr_note_loss(inf_pkt->last_link->bbr);
inf_pkt->last_link->inflight_bytes -= inf_pkt->ll.len;
inf_pkt->last_link->inflight_packets--;
if (inf_pkt->last_link->send_blocked_inflight && inf_pkt->last_link->inflight_bytes < inf_pkt->last_link->inflight_lim_bytes)
loadbalancer_link_ready(inf_pkt->last_link);
}
// Always add to the CURRENT link (first send or retransmission)
+1 -1
View File
@@ -133,7 +133,7 @@ struct ETCP_CONN {
// 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
+5 -1
View File
@@ -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 - пакет для передачи адресату
@@ -28,7 +30,9 @@
#define ETCP_ID_TCP_PROXY 0x04 // TCP proxy exit node (server)
#define ETCP_ID_UDP_PROXY 0x05 // UDP datagram прокси (client ↔ exit)
#define ETCP_ID_ICMP_PROXY 0x06 // ICMP echo прокси (ping через exit)
#define ETCP_ID_TCP_PROXY_CLIENT 0x07 // TCP proxy client (клиент)
#define ETCP_ID_TCP_PROXY_CLIENT 0x07 // TCP proxy client (клиент)
#define ETCP_ID_MSG_TRANSPORT 0x10 // msg_transport — локальный IPC транспорт сообщений
#define ETCP_ID_CONN_MGR 0x11 // Connection Manager — management connections
// Forward declarations
struct ETCP_CONN;
+363 -364
View File
@@ -112,41 +112,27 @@ static void etcp_link_send_init(struct ETCP_LINK* link, uint8_t reset) {
dgram->link = link;
dgram->noencrypt_len = SC_PUBKEY_ENC_SIZE;
size_t offset = 0;
// reset=1: ETCP_INIT_REQUEST (0x02), reset=0: ETCP_INIT_REQUEST_NOINIT (0x04)
dgram->data[offset++] = reset ? ETCP_INIT_REQUEST : ETCP_INIT_REQUEST_NOINIT;
uint64_t node_id = link->etcp->instance->node_id;
dgram->data[offset++] = (node_id >> 56) & 0xFF;
dgram->data[offset++] = (node_id >> 48) & 0xFF;
dgram->data[offset++] = (node_id >> 40) & 0xFF;
dgram->data[offset++] = (node_id >> 32) & 0xFF;
dgram->data[offset++] = (node_id >> 24) & 0xFF;
dgram->data[offset++] = (node_id >> 16) & 0xFF;
dgram->data[offset++] = (node_id >> 8) & 0xFF;
dgram->data[offset++] = node_id & 0xFF;
// session_id (4 bytes) - для защиты от ложного reinit
uint32_t session_id = link->etcp->session_id;
dgram->data[offset++] = (session_id >> 24) & 0xFF;
dgram->data[offset++] = (session_id >> 16) & 0xFF;
dgram->data[offset++] = (session_id >> 8) & 0xFF;
dgram->data[offset++] = session_id & 0xFF;
dgram->data[offset++] = (link->mtu_local >> 8) & 0xFF;
dgram->data[offset++] = link->mtu_local & 0xFF;
dgram->data[offset++] = (link->keepalive_interval >> 8) & 0xFF;
dgram->data[offset++] = link->keepalive_interval & 0xFF;
dgram->data[offset++] = ((link->recovery_interval/100) >> 8) & 0xFF;
dgram->data[offset++] = (link->recovery_interval/100) & 0xFF;
dgram->data[offset++] = link->local_link_id;
dgram->data[offset++] = link->conn ? link->conn->sock_id : 0;
dgram->data[offset++] = link->conn ? link->conn->only_local : 0;
dgram->data[offset++] = link->conn ? link->conn->type : CFG_SERVER_TYPE_UNKNOWN;
struct ETCP_INIT_REQUEST_PKT* req = (struct ETCP_INIT_REQUEST_PKT*)dgram->data;
req->code = reset ? ETCP_INIT_REQUEST : ETCP_INIT_REQUEST_NOINIT;
*(uint64_t*)req->node_id = htobe64(link->etcp->instance->node_id);
*(uint32_t*)req->session_id = htobe32(link->etcp->session_id);
*(uint16_t*)req->mtu = htobe16(link->mtu_local);
*(uint16_t*)req->keepalive = htobe16(link->keepalive_interval);
*(uint16_t*)req->recovery = htobe16(link->recovery_interval / 100);
req->link_id = link->local_link_id;
req->socket_id = link->conn ? link->conn->sock_id : 0;
req->only_local = link->conn ? link->conn->only_local : 0;
req->type = link->conn ? link->conn->type : CFG_SERVER_TYPE_UNKNOWN;
if (link->conn && link->conn->interface_addr.ss_family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)&link->conn->interface_addr;
memcpy(req->src_ipv4, &sin->sin_addr.s_addr, 4);
*(uint16_t*)req->src_port = sin->sin_port;
} else {
memset(req->src_ipv4, 0, 4);
memset(req->src_port, 0, 2);
}
size_t offset = ETCP_INIT_REQ_V2_SIZE;
// padding
int s = rand() % (link->handshake_maxsize - link->handshake_minsize) + link->handshake_minsize;
@@ -179,7 +165,7 @@ static void etcp_link_send_init(struct ETCP_LINK* link, uint8_t reset) {
dgram->data_len = offset;
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Sending INIT request to link, node_id=%016llx, retry=%d", (unsigned long long)node_id, link->init_retry_count);
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Sending INIT request to link, node_id=%016llx, retry=%d", (unsigned long long)link->etcp->instance->node_id, link->init_retry_count);
// Debug: print remote address before sending
if (link->remote_addr.ss_family == AF_INET) {
@@ -869,16 +855,8 @@ void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim) {
if (new_lim < INFLIGHT_LIM_MIN) new_lim = INFLIGHT_LIM_MIN;
if (new_lim > link->etcp->max_inflight) new_lim = link->etcp->max_inflight;
uint32_t old = link->inflight_lim_bytes;
link->inflight_lim_bytes = new_lim;
if (link->inflight_bytes < new_lim && link->send_blocked_inflight) {// разблокируем линк
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] unblocking link %p (inflight_lim %u -> %u)", link->etcp->log_name, link, old, new_lim);
etcp_conn_on_inflight_lim_changed(link->etcp);// сперва обновим inflight в etcp
link->send_blocked_inflight = 0;
if (!link->shaper_timer) loadbalancer_link_ready(link);// потом вызовем ready -> разблок
} else etcp_conn_on_inflight_lim_changed(link->etcp);//
etcp_conn_on_inflight_lim_changed(link->etcp);
}
void etcp_link_close(struct ETCP_LINK* link) {
@@ -1170,6 +1148,306 @@ int etcp_send_ping(struct UTUN_INSTANCE* instance, const uint8_t* peer_pubkey_bi
cb, user_arg, user_data, user_data_len);
}
// === Helpers extracted from etcp_connections_read_callback_socket ===
static int handle_ping(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, const struct sockaddr_storage* addr, const uint8_t* decrypted_pubkey, size_t pkt_len) {
if (pkt_len < 22) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "PING too short: pkt_len=%zu from %s", pkt_len, sockaddr_storage_to_str(addr).str);
return 7;
}
uint64_t nonce = be64toh(*(uint64_t*)(pkt->data + 9));
uint16_t ulen = be16toh(*(uint16_t*)(pkt->data + 17));
const uint8_t* udata = (ulen > 0) ? (pkt->data + 19) : NULL;
struct ETCP_DGRAM* resp = u_malloc(PACKET_DATA_SIZE);
if (resp) {
resp->link = NULL;
resp->noencrypt_len = SC_PUBKEY_ENC_SIZE;
uint8_t* p = resp->data;
*p++ = ETCP_PONG;
uint64_t nid = htobe64(e_sock->instance->node_id);
memcpy(p, &nid, 8); p += 8;
uint64_t n = htobe64(nonce);
memcpy(p, &n, 8); p += 8;
uint16_t resp_ulen_be = htobe16(ulen);
memcpy(p, &resp_ulen_be, 2); p += 2;
size_t copied = etcp_build_ping_response_data(udata, ulen, p, PACKET_DATA_SIZE - (p - resp->data) - SC_PUBKEY_ENC_SIZE);
p += copied;
uint8_t salt[SC_PUBKEY_ENC_SALT_SIZE];
random_bytes(salt, sizeof(salt));
memcpy(p, salt, SC_PUBKEY_ENC_SALT_SIZE); p += SC_PUBKEY_ENC_SALT_SIZE;
uint8_t obfuscated_pubkey[SC_PUBKEY_SIZE];
sc_obfuscate_pubkey(salt, decrypted_pubkey, e_sock->instance->my_keys.public_key, obfuscated_pubkey);
memcpy(p, obfuscated_pubkey, SC_PUBKEY_SIZE); p += SC_PUBKEY_SIZE;
resp->data_len = (uint16_t)(p - resp->data);
sc_context_t resp_sc;
sc_init_ctx(&resp_sc, &e_sock->instance->my_keys);
if (sc_set_peer_public_key(&resp_sc, decrypted_pubkey, SC_PEER_PUBKEY_BIN) == SC_OK) {
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "PONG send nonce=%016llx to=%s fd=%d",
(unsigned long long)nonce, sockaddr_storage_to_str(addr).str, e_sock->fd);
etcp_send_ping_raw(resp, e_sock->fd, &resp_sc, addr);
}
u_free(resp);
}
memory_pool_free(e_sock->instance->pkt_pool, pkt);
return 0;
}
static int handle_pong(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, const struct sockaddr_storage* addr, size_t pkt_len) {
if (pkt_len < 20) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "PONG too short: pkt_len=%zu from %s", pkt_len, sockaddr_storage_to_str(addr).str);
return 7;
}
uint64_t nonce = be64toh(*(uint64_t*)(pkt->data + 9));
uint16_t ulen = 0;
const uint8_t* udata = NULL;
if (pkt->data_len >= 19) {
ulen = be16toh(*(uint16_t*)(pkt->data + 17));
if (ulen > 0 && pkt->data_len >= 19 + ulen) {
udata = pkt->data + 19;
}
}
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "PONG recv nonce=%016llx data_len=%u from=%s socket=%s",
(unsigned long long)nonce, (unsigned)pkt->data_len,
sockaddr_storage_to_str(addr).str, e_sock->name);
struct PING_CONTEXT* ctx = e_sock->instance->pending_pings;
struct PING_CONTEXT* prev = NULL;
int found = 0;
while (ctx) {
if (ctx->nonce == nonce) {
found = 1;
if (prev) prev->next = ctx->next;
else e_sock->instance->pending_pings = ctx->next;
if (ctx->timeout_timer) {
uasync_cancel_timeout(e_sock->instance->ua, ctx->timeout_timer);
ctx->timeout_timer = NULL;
}
uint64_t now = get_time_tb();
uint16_t rtt = (now >= ctx->send_time) ? (uint16_t)(now - ctx->send_time) : 0;
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "PONG matched nonce=%016llx rtt=%u",
(unsigned long long)nonce, (unsigned)rtt);
ctx->cb(1, rtt, ctx->arg, nonce, udata, ulen);
if (ctx->user_data) u_free(ctx->user_data);
u_free(ctx);
break;
}
prev = ctx;
ctx = ctx->next;
}
if (!found) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "PONG nonce=%016llx NOT FOUND in pending (timeout?)",
(unsigned long long)nonce);
}
memory_pool_free(e_sock->instance->pkt_pool, pkt);
return 0;
}
static void send_init_response(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, struct ETCP_LINK* link, struct ETCP_CONN* conn, const struct ETCP_INIT_REQUEST_PKT* req, const struct sockaddr_storage* addr, uint8_t send_reset, uint32_t req_src_ip, uint16_t req_src_port, size_t pkt_len) {
struct ETCP_INIT_RESPONSE_PKT* resp = (struct ETCP_INIT_RESPONSE_PKT*)pkt->data;
// Set response code: 0x03 (with reset) or 0x05 (without reset)
// response with init (0x03) only if reinit was actually done on server side
if (send_reset != 0) {
DEBUG_TRACE(DEBUG_CATEGORY_CONNECTION, "send init_response with reset");
resp->code = ETCP_INIT_RESPONSE; // 0x03 - with reset
} else {
DEBUG_TRACE(DEBUG_CATEGORY_CONNECTION, "send init_response without reset");
resp->code = ETCP_INIT_RESPONSE_NOINIT; // 0x05 - without reset
}
*(uint64_t*)resp->node_id = htobe64(e_sock->instance->node_id);
*(uint32_t*)resp->session_id = htobe32(conn->session_id);
resp->mtu[0]=link->mtu_local>>8;
resp->mtu[1]=link->mtu_local;
resp->link_id = link->local_link_id;
resp->remote_socket_id = req->socket_id;
resp->only_local = e_sock->only_local;
resp->type = e_sock->type;
// Add client's IP:port (so client behind NAT can know its external address)
if (addr->ss_family == AF_INET) {
struct sockaddr_in *sin = (struct sockaddr_in*)addr;
memcpy(resp->peer_ipv4, &sin->sin_addr.s_addr, 4);
uint16_t port = ntohs(sin->sin_port);
resp->peer_port[0] = port >> 8;
resp->peer_port[1] = port & 0xFF;
link->nat_ip = sin->sin_addr.s_addr;
link->nat_port = port;
} else {
// For IPv6, set to 0 (not supported for NAT traversal)
memset(resp->peer_ipv4, 0, 4);
memset(resp->peer_port, 0, 2);
link->nat_ip = 0;
link->nat_port = 0;
}
// DIRECT detection: if client reports its own address and it matches observed source → real public IP
if (pkt_len >= ETCP_INIT_REQ_V2_SIZE && addr->ss_family == AF_INET && link->nat_ip != 0) {
if (req_src_ip != 0 && req_src_ip == link->nat_ip
&& req_src_port == link->nat_port
&& !is_local_subnet(link->nat_ip))
{
link->nat_type = NAT_TYPE_DIRECT;
link->nat_check_status = NAT_CHECK_EIM;
if (link->etcp->instance->bgp) {
route_bgp_send_nat_info(link->etcp, link->remote_socket_id,
link->nat_ip, link->nat_port, NAT_TYPE_DIRECT);
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "DIRECT IP: %s:%u for %s",
ip_to_str(&link->nat_ip, AF_INET).str, link->nat_port,
link->etcp->log_name);
}
}
pkt->noencrypt_len=0;
pkt->link=link;
link->recv_keepalive = 1;
link->last_recv_local_time = get_time_tb();
link->last_recv_timestamp = pkt->timestamp;
int xoffset=sizeof(struct ETCP_INIT_RESPONSE_PKT);
// padding
int s = rand() % (link->handshake_maxsize - link->handshake_minsize) + link->handshake_minsize;
if (s > (int)(link->mtu)) s = (int)(link->mtu);
if (s < 0) s = 0;
int to_add=s - xoffset - UDP_HDR_SIZE - UDP_SC_HDR_SIZE;
if (to_add<0) to_add=0;
if (xoffset + to_add > PACKET_DATA_SIZE) { to_add = PACKET_DATA_SIZE - xoffset; if (to_add<0) to_add=0; }
for (int i=0; i<to_add; i++) pkt->data[xoffset++]=rand();// fill pad
// padding end
pkt->data_len=xoffset;
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "Sending INIT RESPONSE, link=%p, local_link_id=%d, remote_link_id=%d", link, link->local_link_id, link->remote_link_id);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP DEBUG] Send INIT RESPONSE");
etcp_encrypt_send(pkt);
memory_pool_free(e_sock->instance->pkt_pool, pkt);
link->initialized = 1;
link->link_state = 3;
if (link->init_timer) {
uasync_cancel_timeout(link->etcp->instance->ua, link->init_timer);
link->init_timer = NULL;
}
if (link->etcp->initialized == 0) {
etcp_conn_ready(link->etcp);
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "Connection established: log_name=%s socket=%s link_id=%d status=UP", link->etcp->log_name, e_sock->name, link->local_link_id);
}
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] Link %p (local_id=%d) initialized and marked as UP (server)", link->etcp->log_name, link, link->local_link_id);
start_keepalive_timer(link);
loadbalancer_link_ready(link);
// Restart NAT check after link is up (e.g. after address change or reinit)
if (link->etcp->instance->bgp) {
if (link->nat_check_status < NAT_CHECK_IN_PROGRESS) route_bgp_start_link_nat_check(link->etcp->instance->bgp, link);
}
}
static int handle_init_response_client(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, struct ETCP_LINK* link, uint8_t pkt_code, size_t pkt_len) {
if (pkt_len < ETCP_INIT_RESP_V1_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "INIT_RESPONSE too short: pkt_len=%zu", pkt_len);
return 46;
}
// ETCP_INIT_RESPONSE (0x03) - reset entire ETCP_CONN
// ETCP_INIT_RESPONSE_NOINIT (0x05) - no reset
struct ETCP_INIT_RESPONSE_PKT* resp = (struct ETCP_INIT_RESPONSE_PKT*)pkt->data;
uint64_t server_node_id = be64toh(*(uint64_t*)resp->node_id);
uint32_t resp_session_id = be32toh(*(uint32_t*)resp->session_id);
DEBUG_TRACE(DEBUG_CATEGORY_CONNECTION, "INIT_RESPONSE session_id=%08x", resp_session_id);
// Check session_id: ignore response if it doesn't match our session
if (resp_session_id != link->etcp->session_id) {
DEBUG_WARN(DEBUG_CATEGORY_CONNECTION, "[%s] INIT_RESPONSE session_id mismatch: got %08x, expected %08x, ignoring",
link->etcp->log_name, resp_session_id, link->etcp->session_id);
memory_pool_free(e_sock->instance->pkt_pool, pkt);
return 0;
}
link->mtu_remote = be16toh(*(uint16_t*)resp->mtu);
if (link->mtu_remote > PACKET_DATA_MAX_MTU) link->mtu_remote = PACKET_DATA_MAX_MTU;
link->mtu = link->mtu_local < link->mtu_remote ? link->mtu_local : link->mtu_remote;
etcp_update_mtu(link->etcp);
link->remote_link_id = resp->link_id;
link->remote_socket_id = resp->remote_socket_id;
link->remote_only_local = resp->only_local;
link->remote_type = resp->type;
// Parse NAT IP:port from response (new format includes 4+2 bytes)
if (pkt_len >= ETCP_INIT_RESP_V2_SIZE) {
uint32_t new_nat_ip;
memcpy(&new_nat_ip, resp->peer_ipv4, 4);
uint16_t new_nat_port = be16toh(*(uint16_t*)resp->peer_port);
// Check if NAT address changed
if (link->nat_ip == 0 && link->nat_port == 0) {
// First time receiving NAT info
link->nat_ip = new_nat_ip;
link->nat_port = new_nat_port;
struct in_addr addr;
addr.s_addr = new_nat_ip;
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] NAT address initialized: %s:%u",
link->etcp->log_name, ip_to_str(&addr, AF_INET).str, new_nat_port);
} else if (link->nat_ip != new_nat_ip || link->nat_port != new_nat_port) {
// NAT address changed
struct in_addr old_addr, new_addr;
old_addr.s_addr = link->nat_ip;
new_addr.s_addr = new_nat_ip;
link->nat_ip = new_nat_ip;
link->nat_port = new_nat_port;
link->nat_changes_count++;
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] NAT address changed: %s:%u -> %s:%u (change #%u)",
link->etcp->log_name, ip_to_str(&old_addr.s_addr, AF_INET).str, link->nat_port, ip_to_str(&new_addr.s_addr, AF_INET).str, new_nat_port,
link->nat_changes_count);
}
// Update socket NAT address
{
struct sockaddr_in* sin = (struct sockaddr_in*)&e_sock->nat_addr;
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = new_nat_ip;
sin->sin_port = htons(new_nat_port);
}
} else {
// Legacy format without NAT info
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "[%s] Received legacy INIT_RESPONSE without NAT info",
link->etcp->log_name);
}
// DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Received INIT_RESPONSE from server_node_id=%llu, mtu=%d", (unsigned long long)server_node_id, link->mtu);
link->etcp->peer_node_id = server_node_id; // If not set
etcp_update_log_name(link->etcp); // Update log_name with peer_node_id
link->initialized = 1;// получен init response (client)
link->link_state = 3; // connected
if (link->init_timer) {
uasync_cancel_timeout(link->etcp->instance->ua, link->init_timer);
link->init_timer = NULL;
}
if (pkt_code == ETCP_INIT_RESPONSE) {
DEBUG_TRACE(DEBUG_CATEGORY_CONNECTION, "do reinit 3 %p", link->etcp);
etcp_conn_reinit(link->etcp);
}
if (link->etcp->initialized == 0) {
etcp_conn_ready(link->etcp);
}
loadbalancer_link_ready(link);
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] Link %p (local_id=%d) initialized and marked as UP (client): rk=%d lk=%d up=%d ki=%d",
link->etcp->log_name, link, link->local_link_id, link->recv_keepalive, link->remote_keepalive, link->link_status, link->keepalive_interval);
// Start keepalive timer
etcp_link_send_keepalive(link);
start_keepalive_timer(link);
loadbalancer_link_ready(link);
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "etcp client: Link initialized successfully! Server node_id=%016llx, mtu=%d, local_link_id=%d, remote_link_id=%d", (unsigned long long)server_node_id, link->mtu, link->local_link_id, link->remote_link_id);
memory_pool_free(e_sock->instance->pkt_pool, pkt);
return 0;
}
void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
DEBUG_TRACE(DEBUG_CATEGORY_CONNECTION, "");
// DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connections_read_callback fd=%d, socket=%p", fd, arg);
@@ -1271,116 +1549,28 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
pkt->data_len=pkt_len-3;
pkt->noencrypt_len=0;
struct {
uint8_t code;
uint8_t id[8];
uint8_t session_id[4];
uint8_t mtu[2];
uint8_t keepalive[2];
uint8_t recovery[2];
uint8_t link_id;
uint8_t remote_socket_id;
uint8_t only_local;
uint8_t type;
uint8_t pubkey[SC_PUBKEY_SIZE];
} *ack_hdr=(void*)&pkt->data[0];
uint64_t peer_id = be64toh(*(uint64_t*)ack_hdr->id);
if (ack_hdr->code == ETCP_PING) {
if (pkt_len < 22) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "PING too short: pkt_len=%zu from %s", pkt_len, sockaddr_storage_to_str(&addr).str);
errorcode=7;
goto ec_fr;
}
uint64_t nonce = be64toh(*(uint64_t*)(pkt->data + 9));
uint16_t ulen = be16toh(*(uint16_t*)(pkt->data + 17));
const uint8_t* udata = (ulen > 0) ? (pkt->data + 19) : NULL;
struct ETCP_DGRAM* resp = u_malloc(PACKET_DATA_SIZE);
if (resp) {
resp->link = NULL;
resp->noencrypt_len = SC_PUBKEY_ENC_SIZE;
uint8_t* p = resp->data;
*p++ = ETCP_PONG;
uint64_t nid = htobe64(e_sock->instance->node_id);
memcpy(p, &nid, 8); p += 8;
uint64_t n = htobe64(nonce);
memcpy(p, &n, 8); p += 8;
uint16_t resp_ulen_be = htobe16(ulen);
memcpy(p, &resp_ulen_be, 2); p += 2;
size_t copied = etcp_build_ping_response_data(udata, ulen, p, PACKET_DATA_SIZE - (p - resp->data) - SC_PUBKEY_ENC_SIZE);
p += copied;
uint8_t salt[SC_PUBKEY_ENC_SALT_SIZE];
random_bytes(salt, sizeof(salt));
memcpy(p, salt, SC_PUBKEY_ENC_SALT_SIZE); p += SC_PUBKEY_ENC_SALT_SIZE;
uint8_t obfuscated_pubkey[SC_PUBKEY_SIZE];
sc_obfuscate_pubkey(salt, decrypted_pubkey, e_sock->instance->my_keys.public_key, obfuscated_pubkey);
memcpy(p, obfuscated_pubkey, SC_PUBKEY_SIZE); p += SC_PUBKEY_SIZE;
resp->data_len = (uint16_t)(p - resp->data);
sc_context_t resp_sc;
sc_init_ctx(&resp_sc, &e_sock->instance->my_keys);
if (sc_set_peer_public_key(&resp_sc, decrypted_pubkey, SC_PEER_PUBKEY_BIN) == SC_OK) {
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "PONG send nonce=%016llx to=%s fd=%d",
(unsigned long long)nonce, sockaddr_storage_to_str(&addr).str, e_sock->fd);
etcp_send_ping_raw(resp, e_sock->fd, &resp_sc, &addr);
}
u_free(resp);
}
memory_pool_free(e_sock->instance->pkt_pool, pkt);
uint8_t code = pkt->data[0];
uint64_t peer_id = be64toh(*(uint64_t*)(pkt->data + 1));
if (code == ETCP_PING) {
int ret = handle_ping(e_sock, pkt, &addr, decrypted_pubkey, pkt_len);
if (ret) { errorcode = ret; goto ec_fr; }
return;
}
if (ack_hdr->code == ETCP_PONG) {
if (pkt_len < 20) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "PONG too short: pkt_len=%zu from %s", pkt_len, sockaddr_storage_to_str(&addr).str);
errorcode=7;
goto ec_fr;
}
uint64_t nonce = be64toh(*(uint64_t*)(pkt->data + 9));
uint16_t ulen = 0;
const uint8_t* udata = NULL;
if (pkt->data_len >= 19) {
ulen = be16toh(*(uint16_t*)(pkt->data + 17));
if (ulen > 0 && pkt->data_len >= 19 + ulen) {
udata = pkt->data + 19;
}
}
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "PONG recv nonce=%016llx data_len=%u from=%s socket=%s",
(unsigned long long)nonce, (unsigned)pkt->data_len,
sockaddr_storage_to_str(&addr).str, e_sock->name);
struct PING_CONTEXT* ctx = e_sock->instance->pending_pings;
struct PING_CONTEXT* prev = NULL;
int found = 0;
while (ctx) {
if (ctx->nonce == nonce) {
found = 1;
if (prev) prev->next = ctx->next;
else e_sock->instance->pending_pings = ctx->next;
if (ctx->timeout_timer) {
uasync_cancel_timeout(e_sock->instance->ua, ctx->timeout_timer);
ctx->timeout_timer = NULL;
}
uint64_t now = get_time_tb();
uint16_t rtt = (now >= ctx->send_time) ? (uint16_t)(now - ctx->send_time) : 0;
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "PONG matched nonce=%016llx rtt=%u",
(unsigned long long)nonce, (unsigned)rtt);
ctx->cb(1, rtt, ctx->arg, nonce, udata, ulen);
if (ctx->user_data) u_free(ctx->user_data);
u_free(ctx);
break;
}
prev = ctx;
ctx = ctx->next;
}
if (!found) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "PONG nonce=%016llx NOT FOUND in pending (timeout?)",
(unsigned long long)nonce);
}
memory_pool_free(e_sock->instance->pkt_pool, pkt);
if (code == ETCP_PONG) {
int ret = handle_pong(e_sock, pkt, &addr, pkt_len);
if (ret) { errorcode = ret; goto ec_fr; }
return;
}
if (ack_hdr->code!=ETCP_INIT_REQUEST && ack_hdr->code!=ETCP_INIT_REQUEST_NOINIT) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "not an init packet, code=%02x", ack_hdr->code);
if (code!=ETCP_INIT_REQUEST && code!=ETCP_INIT_REQUEST_NOINIT) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "not an init packet, code=%02x", code);
errorcode=4;
goto ec_fr;
}// не init
if (pkt_len < ETCP_INIT_REQ_V1_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "INIT REQUEST too short: pkt_len=%zu from %s", pkt_len, sockaddr_storage_to_str(&addr).str);
errorcode=7;
goto ec_fr;
}
// Check allowed keys for incoming connections
struct global_config *global = &e_sock->instance->config->global;
@@ -1406,7 +1596,8 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
}
}
uint32_t session_id = be32toh(*(uint32_t*)ack_hdr->session_id);
struct ETCP_INIT_REQUEST_PKT* req = (struct ETCP_INIT_REQUEST_PKT*)pkt->data;
uint32_t session_id = be32toh(*(uint32_t*)req->session_id);
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "INIT request session_id=%08x", session_id);
struct ETCP_CONN* conn=e_sock->instance->connections;
@@ -1436,15 +1627,15 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
// Check if link already exists (for CHANNEL_INIT recovery)
struct ETCP_LINK* existing_link = etcp_link_find_by_remote_id(conn, ack_hdr->link_id);
struct ETCP_LINK* existing_link = etcp_link_find_by_remote_id(conn, req->link_id);
uint8_t send_reset = 0;
if (existing_link && existing_link->etcp == conn) {// существующий линк
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "[%s] found existing link for id=%d, socket=[%s]", conn->log_name, ack_hdr->link_id, e_sock->name);
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "[%s] found existing link for id=%d, socket=[%s]", conn->log_name, req->link_id, e_sock->name);
link = existing_link;
if (!sockaddr_equal(&link->remote_addr, &addr)) {
DEBUG_WARN(DEBUG_CATEGORY_CONNECTION, "[%s] IP:port changed for remote_link_id=%d socket:[%s]", conn->log_name, ack_hdr->link_id, e_sock->name);
DEBUG_WARN(DEBUG_CATEGORY_CONNECTION, "[%s] IP:port changed for remote_link_id=%d socket:[%s]", conn->log_name, req->link_id, e_sock->name);
if (link->conn) remove_link(link->conn, link->ip_port_hash); // remove old connection from old socket
link->conn=e_sock;
memcpy(&link->remote_addr, &addr, sizeof(addr));
@@ -1462,10 +1653,10 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
}
// Link exists - reuse it for recovery
link->remote_link_id = ack_hdr->link_id;
link->remote_socket_id = ack_hdr->remote_socket_id;
link->remote_only_local = ack_hdr->only_local;
link->remote_type = ack_hdr->type;
link->remote_link_id = req->link_id;
link->remote_socket_id = req->socket_id;
link->remote_only_local = req->only_local;
link->remote_type = req->type;
// For CHANNEL_INIT (0x04): if link already initialized - no reset, otherwise reset
// For INIT_REQUEST (0x02): always reset
@@ -1486,14 +1677,14 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
link->init_timer = NULL;
}
} else {
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "[%s] NO existing link for id=%d, socket=[%s]", conn->log_name, ack_hdr->link_id, e_sock->name);
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "[%s] NO existing link for id=%d, socket=[%s]", conn->log_name, req->link_id, e_sock->name);
// Create new link
link = etcp_link_new(conn, e_sock, &addr, 1);
if (!link) { if (new_conn) etcp_connection_close(conn); errorcode=66; DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "etcp_connections_read_callback: failed to create link for connection"); goto ec_fr; }// облом
link->remote_link_id = ack_hdr->link_id;
link->remote_socket_id = ack_hdr->remote_socket_id;
link->remote_only_local = ack_hdr->only_local;
link->remote_type = ack_hdr->type;
link->remote_link_id = req->link_id;
link->remote_socket_id = req->socket_id;
link->remote_only_local = req->only_local;
link->remote_type = req->type;
// For new links: check session_id to avoid false reinit
if (conn->session_id != session_id) {
@@ -1502,105 +1693,21 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
conn->session_id = session_id;
etcp_conn_reinit(conn);
}
link->keepalive_interval=(ack_hdr->keepalive[0]<<8) | ack_hdr->keepalive[1];
link->recovery_interval=((ack_hdr->recovery[0]<<8) | ack_hdr->recovery[1])*100;// timebase в link, timebase/100 в кодограмме
link->keepalive_interval=(req->keepalive[0]<<8) | req->keepalive[1];
link->recovery_interval=((req->recovery[0]<<8) | req->recovery[1])*100;// timebase в link, timebase/100 в кодограмме
if (link->keepalive_interval < 10) link->keepalive_interval = 10;
DEBUG_DEBUG(DEBUG_CATEGORY_KEEPALIVE, "set keepalive for link=%d", link->keepalive_interval);
}
link->mtu_remote = (ack_hdr->mtu[0] << 8) | ack_hdr->mtu[1];
link->mtu_remote = (req->mtu[0] << 8) | req->mtu[1];
if (link->mtu_remote > PACKET_DATA_MAX_MTU) link->mtu_remote = PACKET_DATA_MAX_MTU;
link->mtu = link->mtu_local < link->mtu_remote ? link->mtu_local : link->mtu_remote;
etcp_update_mtu(link->etcp);
struct {
uint8_t code;
uint8_t id[8];
uint8_t session_id[4];
uint8_t mtu[2];
uint8_t link_id;
uint8_t remote_socket_id;
uint8_t only_local;
uint8_t type;
uint8_t peer_ipv4[4];
uint8_t peer_port[2];
} *ack_repl_hdr=(void*)&pkt->data[0];
uint32_t req_src_ip; memcpy(&req_src_ip, req->src_ipv4, 4);
uint16_t req_src_port = be16toh(*(uint16_t*)req->src_port);
// Set response code: 0x03 (with reset) or 0x05 (without reset)
// response with init (0x03) only if reinit was actually done on server side
if (send_reset != 0) {
DEBUG_TRACE(DEBUG_CATEGORY_CONNECTION, "send init_response with reset");
ack_repl_hdr->code = ETCP_INIT_RESPONSE; // 0x03 - with reset
} else {
DEBUG_TRACE(DEBUG_CATEGORY_CONNECTION, "send init_response without reset");
ack_repl_hdr->code = ETCP_INIT_RESPONSE_NOINIT; // 0x05 - without reset
}
*(uint64_t*)ack_repl_hdr->id = htobe64(e_sock->instance->node_id);
*(uint32_t*)ack_repl_hdr->session_id = htobe32(conn->session_id);
ack_repl_hdr->mtu[0]=link->mtu_local>>8;
ack_repl_hdr->mtu[1]=link->mtu_local;
ack_repl_hdr->link_id = link->local_link_id;
ack_repl_hdr->remote_socket_id = ack_hdr->remote_socket_id;
ack_repl_hdr->only_local = e_sock->only_local;
ack_repl_hdr->type = e_sock->type;
// Add client's IP:port (so client behind NAT can know its external address)
if (addr.ss_family == AF_INET) {
struct sockaddr_in *sin = (struct sockaddr_in*)&addr;
memcpy(ack_repl_hdr->peer_ipv4, &sin->sin_addr.s_addr, 4);
uint16_t port = ntohs(sin->sin_port);
ack_repl_hdr->peer_port[0] = port >> 8;
ack_repl_hdr->peer_port[1] = port & 0xFF;
link->nat_ip = sin->sin_addr.s_addr;
link->nat_port = port;
} else {
// For IPv6, set to 0 (not supported for NAT traversal)
memset(ack_repl_hdr->peer_ipv4, 0, 4);
memset(ack_repl_hdr->peer_port, 0, 2);
link->nat_ip = 0;
link->nat_port = 0;
}
pkt->noencrypt_len=0;
pkt->link=link;
link->recv_keepalive = 1;
link->last_recv_local_time = get_time_tb();
link->last_recv_timestamp = pkt->timestamp;
int xoffset=sizeof(*ack_repl_hdr);
// padding
int s = rand() % (link->handshake_maxsize - link->handshake_minsize) + link->handshake_minsize;
if (s > (int)(link->mtu)) s = (int)(link->mtu);
if (s < 0) s = 0;
int to_add=s - xoffset - UDP_HDR_SIZE - UDP_SC_HDR_SIZE;
if (to_add<0) to_add=0;
if (xoffset + to_add > PACKET_DATA_SIZE) { to_add = PACKET_DATA_SIZE - xoffset; if (to_add<0) to_add=0; }
for (int i=0; i<to_add; i++) pkt->data[xoffset++]=rand();// fill pad
// padding end
pkt->data_len=xoffset;
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "Sending INIT RESPONSE, link=%p, local_link_id=%d, remote_link_id=%d", link, link->local_link_id, link->remote_link_id);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP DEBUG] Send INIT RESPONSE");
etcp_encrypt_send(pkt);
memory_pool_free(e_sock->instance->pkt_pool, pkt);
link->initialized = 1;
link->link_state = 3;
if (link->init_timer) {
uasync_cancel_timeout(link->etcp->instance->ua, link->init_timer);
link->init_timer = NULL;
}
if (link->etcp->initialized == 0) {
etcp_conn_ready(link->etcp);
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "Connection established: log_name=%s socket=%s link_id=%d status=UP", link->etcp->log_name, e_sock->name, link->local_link_id);
}
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] Link %p (local_id=%d) initialized and marked as UP (server)", link->etcp->log_name, link, link->local_link_id);
start_keepalive_timer(link);
loadbalancer_link_ready(link);
// Restart NAT check after link is up (e.g. after address change or reinit)
if (link->etcp->instance->bgp) {
if (link->nat_check_status < NAT_CHECK_IN_PROGRESS) route_bgp_start_link_nat_check(link->etcp->instance->bgp, link);
}
send_init_response(e_sock, pkt, link, conn, req, &addr, send_reset, req_src_ip, req_src_port, pkt_len);
return;
@@ -1633,9 +1740,9 @@ process_decrypted:
link->total_decrypted += pkt->data_len;
size_t offset = 0;
uint8_t code = pkt->data[offset++];
uint8_t pkt_code = pkt->data[offset++];
if (code == ETCP_KEEPALIVE) {
if (pkt_code == ETCP_KEEPALIVE) {
if (pkt->data_len >= 3) {
uint16_t peer_period = pkt->data[1] | ((uint16_t)pkt->data[2] << 8);
link->keepalive_timeout = (uint32_t)peer_period * KA_TIMEOUT_MULT;
@@ -1645,118 +1752,10 @@ process_decrypted:
return; // KA handled, nothing more to process
}
if (code == ETCP_INIT_RESPONSE || code == ETCP_INIT_RESPONSE_NOINIT) {
if (pkt_len < 22) { errorcode = 46; DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "INIT_RESPONSE too short: pkt_len=%zu", pkt_len); goto ec_fr; }
// ETCP_INIT_RESPONSE (0x03) - reset entire ETCP_CONN
// ETCP_INIT_RESPONSE_NOINIT (0x05) - no reset
uint64_t server_node_id = 0;
for (int i = 0; i < 8; i++) {
server_node_id = (server_node_id << 8) | pkt->data[offset++];
}
uint32_t resp_session_id = (pkt->data[offset] << 24) | (pkt->data[offset+1] << 16) | (pkt->data[offset+2] << 8) | pkt->data[offset+3];
offset += 4;
DEBUG_TRACE(DEBUG_CATEGORY_CONNECTION, "INIT_RESPONSE session_id=%08x", resp_session_id);
// Check session_id: ignore response if it doesn't match our session
if (resp_session_id != link->etcp->session_id) {
DEBUG_WARN(DEBUG_CATEGORY_CONNECTION, "[%s] INIT_RESPONSE session_id mismatch: got %08x, expected %08x, ignoring",
link->etcp->log_name, resp_session_id, link->etcp->session_id);
memory_pool_free(e_sock->instance->pkt_pool, pkt);
return;
}
link->mtu_remote = ((uint16_t)pkt->data[offset] << 8) | pkt->data[offset + 1];
offset += 2;
if (link->mtu_remote > PACKET_DATA_MAX_MTU) link->mtu_remote = PACKET_DATA_MAX_MTU;
link->mtu = link->mtu_local < link->mtu_remote ? link->mtu_local : link->mtu_remote;
etcp_update_mtu(link->etcp);
link->remote_link_id = pkt->data[offset++];
link->remote_socket_id = pkt->data[offset++];
link->remote_only_local = pkt->data[offset++];
link->remote_type = pkt->data[offset++];
// Parse NAT IP:port from response (new format includes 4+2 bytes)
if (pkt_len >= 24) {
uint32_t new_nat_ip;
memcpy(&new_nat_ip, &pkt->data[offset], 4);
offset += 4;
uint16_t new_nat_port = (pkt->data[offset] << 8) | pkt->data[offset+1];
offset += 2;
// Check if NAT address changed
if (link->nat_ip == 0 && link->nat_port == 0) {
// First time receiving NAT info
link->nat_ip = new_nat_ip;
link->nat_port = new_nat_port;
struct in_addr addr;
addr.s_addr = new_nat_ip;
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] NAT address initialized: %s:%u",
link->etcp->log_name, ip_to_str(&addr, AF_INET).str, new_nat_port);
} else if (link->nat_ip != new_nat_ip || link->nat_port != new_nat_port) {
// NAT address changed
struct in_addr old_addr, new_addr;
old_addr.s_addr = link->nat_ip;
new_addr.s_addr = new_nat_ip;
link->nat_ip = new_nat_ip;
link->nat_port = new_nat_port;
link->nat_changes_count++;
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] NAT address changed: %s:%u -> %s:%u (change #%u)",
link->etcp->log_name, ip_to_str(&old_addr.s_addr, AF_INET).str, link->nat_port, ip_to_str(&new_addr.s_addr, AF_INET).str, new_nat_port,
link->nat_changes_count);
}
// Update socket NAT address
{
struct sockaddr_in* sin = (struct sockaddr_in*)&e_sock->nat_addr;
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = new_nat_ip;
sin->sin_port = htons(new_nat_port);
}
} else {
// Legacy format without NAT info
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "[%s] Received legacy INIT_RESPONSE without NAT info",
link->etcp->log_name);
}
if (offset > pkt_len) { errorcode=13; DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connections_read_callback: packet parsing overflow, offset=%zu, pkt_len=%zu", offset, pkt_len); goto ec_fr; }
// DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Received INIT_RESPONSE from server_node_id=%llu, mtu=%d", (unsigned long long)server_node_id, link->mtu);
link->etcp->peer_node_id = server_node_id; // If not set
etcp_update_log_name(link->etcp); // Update log_name with peer_node_id
link->initialized = 1;// получен init response (client)
link->link_state = 3; // connected
if (link->init_timer) {
uasync_cancel_timeout(link->etcp->instance->ua, link->init_timer);
link->init_timer = NULL;
}
if (code == ETCP_INIT_RESPONSE) {
DEBUG_TRACE(DEBUG_CATEGORY_CONNECTION, "do reinit 3 %p", link->etcp);
etcp_conn_reinit(link->etcp);
}
if (link->etcp->initialized == 0) {
etcp_conn_ready(link->etcp);
}
loadbalancer_link_ready(link);
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] Link %p (local_id=%d) initialized and marked as UP (client): rk=%d lk=%d up=%d ki=%d",
link->etcp->log_name, link, link->local_link_id, link->recv_keepalive, link->remote_keepalive, link->link_status, link->keepalive_interval);
// Start keepalive timer
etcp_link_send_keepalive(link);
start_keepalive_timer(link);
loadbalancer_link_ready(link);
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "etcp client: Link initialized successfully! Server node_id=%016llx, mtu=%d, local_link_id=%d, remote_link_id=%d", (unsigned long long)server_node_id, link->mtu, link->local_link_id, link->remote_link_id);
memory_pool_free(e_sock->instance->pkt_pool, pkt);
return; // INIT_RESPONSE is handled, no further processing needed
if (pkt_code == ETCP_INIT_RESPONSE || pkt_code == ETCP_INIT_RESPONSE_NOINIT) {
int ret = handle_init_response_client(e_sock, pkt, link, pkt_code, pkt_len);
if (ret) { errorcode = ret; goto ec_fr; }
return;
}
if (link->link_state == 2) {// из recovery получен нормальный пакет - восстанавливаем линк в нормальный режим
+40 -3
View File
@@ -54,6 +54,45 @@ 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)
} __attribute__((packed));
#define ETCP_INIT_REQ_V1_SIZE 23 // size without src_ipv4/src_port (backward compat)
#define ETCP_INIT_REQ_V2_SIZE 29 // size with src_ipv4/src_port
// 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)
} __attribute__((packed));
#define ETCP_INIT_RESP_V1_SIZE 19 // size without peer_ipv4/peer_port
#define ETCP_INIT_RESP_V2_SIZE 25 // size with peer_ipv4/peer_port
#define ETCP_INIT_RESP_MIN_SIZE ETCP_INIT_RESP_V1_SIZE
typedef void (*etcp_ping_callback_t)(int success, uint16_t rtt, void* arg, uint64_t nonce,
const uint8_t* resp_data, size_t resp_data_len);
struct PING_CONTEXT {
@@ -106,6 +145,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
@@ -171,9 +211,6 @@ struct ETCP_LINK {
uint32_t inflight_packets;
uint32_t inflight_lim_bytes;
/* Биты блокировки отправки по причине (для корректного resume) */
unsigned send_blocked_inflight : 1;
// statistics
size_t encrypt_errors;
size_t decrypt_errors;
+1 -2
View File
@@ -161,9 +161,8 @@ void etcp_dump_conn_state(struct ETCP_CONN* conn) {
link->keepalive_sent_count > 0 ? "has_sent" : "idle");
/* inflight (BBR) */
DUMP_LINE("BBR: bytes=%u pkts=%u lim=%u blocked=%d mode=%d cycle=%d pacing=%u",
DUMP_LINE("BBR: bytes=%u pkts=%u lim=%u mode=%d cycle=%d pacing=%u",
link->inflight_bytes, link->inflight_packets, link->inflight_lim_bytes,
link->send_blocked_inflight,
link->bbr ? link->bbr->mode : -1,
link->bbr ? link->bbr->cycle_idx : -1,
link->bbr_pacing_rate);
+6 -18
View File
@@ -57,8 +57,8 @@ struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp) {
if (available_count == 0) {
if (etcp->links)
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] no suitable link found: inf:%s, tmr:%s",
etcp->log_name, etcp->links->send_blocked_inflight?"wait":"rdy", etcp->links->shaper_timer?"wait":"rdy");
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] no suitable link found: tmr:%s",
etcp->log_name, etcp->links->shaper_timer?"wait":"rdy");
return NULL;
}
@@ -181,20 +181,8 @@ void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram) {
int loadbalancer_link_can_send(struct ETCP_LINK* link) {
if (!link) return 0;
if (link->burst_active) return 1; // burst bypasses all limits
int can = 1;
if (link->inflight_bytes >= link->inflight_lim_bytes) {
link->send_blocked_inflight = 1;
can = 0;
} else {
link->send_blocked_inflight = 0;
}
if (link->shaper_timer) {
// link->send_blocked_bandwidth = 1;
can = 0;
} else {
// link->send_blocked_bandwidth = 0;
}
return can;
if (link->shaper_timer) return 0;
return 1;
}
void loadbalancer_link_ready(struct ETCP_LINK* link) {
@@ -202,8 +190,8 @@ void loadbalancer_link_ready(struct ETCP_LINK* link) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "invalid link (%p)", link);
return;
}
if (loadbalancer_link_can_send(link) == 0) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "link still blocked");
if (link->shaper_timer) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "link still blocked by shaper");
return;
}
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "link=%p now ready, notifying ETCP_CONN", link);
+105 -12
View File
@@ -22,7 +22,7 @@ static void router_schedule_ack(struct ETCP_ROUTER_CONN* rconn);
static void router_try_assembly(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* conn);
static void router_deliver(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* conn, const uint8_t* payload, size_t payload_len);
static int router_send_one(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len);
static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, uint32_t seq_flags);
static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, uint32_t seq_flags, int is_signed);
static void router_send_to(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t svc_id, uint32_t seq_flags);
static void router_drain_send_q(struct ETCP_ROUTER_CONN* rconn);
static void router_send_resume_cb(void* arg);
@@ -47,15 +47,16 @@ static struct ETCP_ROUTER_CONN* router_conn_find(struct UTUN_INSTANCE* inst,
// ====================================================================
static int router_send_one(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len) {
return router_send_one_flags(rconn, payload, pl_len, 0);
return router_send_one_flags(rconn, payload, pl_len, 0, 0);
}
static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, uint32_t seq_flags) {
static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, uint32_t seq_flags, int is_signed) {
struct UTUN_INSTANCE* inst = rconn->inst;
uint32_t seq = (seq_flags & (ROUTER_SEQ_CLOSE_FLAG | ROUTER_SEQ_RST_FLAG))
? 0 : rconn->tx_seq++;
seq |= seq_flags;
size_t total_len = SVC_ROUTE_HDR_SIZE + pl_len;
size_t sig_len = is_signed ? SC_SIGN_SIZE : 0;
size_t total_len = SVC_ROUTE_HDR_SIZE + pl_len + sig_len;
uint8_t* dgram = u_malloc(total_len);
if (!dgram) { if (!seq_flags) rconn->tx_seq--; return -1; }
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)dgram;
@@ -70,25 +71,57 @@ static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t*
f |= ROUTER_FLAG_START;
rconn->start_sent = 1;
}
if (is_signed) f |= ROUTER_FLAG_SIGNED;
hdr->flags = f;
}
if (pl_len > 0) memcpy(dgram + SVC_ROUTE_HDR_SIZE, payload, pl_len);
struct ETCP_CONN* conn = NULL;
struct ROUTE_BGP* bgp = inst->bgp;
if (bgp) {
struct NODEINFO_Q* nq = nodeinfo_find_by_id(bgp, rconn->remote_node_id);
if (nq && nq->conn_mgr_type == CONN_TYPE_INDIRECT && nq->conn_mgr_intermediariy_count > 0) {
for (uint8_t i = 0; i < nq->conn_mgr_intermediariy_count; i++) {
conn = route_bgp_find_conn_for_node(bgp, nq->conn_mgr_intermediaries[i]);
if (conn) break;
}
}
}
if (!conn) conn = route_bgp_find_conn_for_node(bgp, rconn->remote_node_id);
if (!conn) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router_send_one: no route to %016llx svc_id=%u",
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
u_free(dgram); if (!seq_flags) rconn->tx_seq--; return -1;
}
if (is_signed) {
struct sc_stream_sign_state sign_state;
if (sc_stream_sign_init(&conn->crypto_ctx, &sign_state) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_send_one: sign_init failed svc_id=%u", rconn->svc_id);
u_free(dgram); if (!seq_flags) rconn->tx_seq--; return -1;
}
size_t data_len = SVC_ROUTE_HDR_SIZE + pl_len;
if (sc_stream_sign_update(&sign_state, dgram, data_len) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_send_one: sign_update failed svc_id=%u", rconn->svc_id);
sc_stream_sign_cleanup(&sign_state); u_free(dgram); if (!seq_flags) rconn->tx_seq--; return -1;
}
size_t actual_sig_len = SC_SIGN_SIZE;
if (sc_stream_sign_final(&sign_state, dgram + data_len, &actual_sig_len) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_send_one: sign_final failed svc_id=%u", rconn->svc_id);
u_free(dgram); if (!seq_flags) rconn->tx_seq--; return -1;
}
}
struct ll_entry* entry = queue_entry_new(0);
if (!entry) { u_free(dgram); if (!seq_flags) rconn->tx_seq--; return -1; }
entry->dgram = dgram;
entry->len = total_len;
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, rconn->remote_node_id);
if (!conn) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router_send_one: no route to %016llx svc_id=%u",
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
queue_entry_free(entry); queue_dgram_free(entry); if (!seq_flags) rconn->tx_seq--; return -1;
}
rconn->last_dgram_ts = get_current_timestamp();
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "ETCP_SEND: svc_id=%u seq=%u len=%zu inflight=%d → %016llx",
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "ETCP_SEND: svc_id=%u seq=%u len=%zu inflight=%d signed=%d → %016llx",
rconn->svc_id, seq, pl_len,
(int32_t)(rconn->tx_seq - rconn->tx_acked),
is_signed,
(unsigned long long)rconn->remote_node_id);
return etcp_send(conn, entry);
}
@@ -136,7 +169,7 @@ static void router_close_and_notify(struct ETCP_ROUTER_CONN* rconn) {
router_send_close_to_service(rconn);
// Отправляем CLOSE удалённой стороне
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, rconn->remote_node_id);
if (conn) router_send_one_flags(rconn, NULL, 0, ROUTER_SEQ_CLOSE_FLAG);
if (conn) router_send_one_flags(rconn, NULL, 0, ROUTER_SEQ_CLOSE_FLAG, 0);
// Таймеры
if (rconn->ack_timer) { uasync_cancel_timeout(inst->ua, rconn->ack_timer); rconn->ack_timer = NULL; }
if (rconn->idle_ack_timer) { uasync_cancel_timeout(inst->ua, rconn->idle_ack_timer); rconn->idle_ack_timer = NULL; }
@@ -343,6 +376,52 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
hdr->svc_id, hdr->seq, pl_len, (unsigned long long)hdr->src_node_id);
if (hdr->dst_node_id == inst->node_id) {
// ========== Проверка подписи (если флаг ROUTER_FLAG_SIGNED) ==========
size_t actual_payload = pl_len;
if (hdr->flags & ROUTER_FLAG_SIGNED) {
if (pl_len < SC_SIGN_SIZE) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: SIGNED packet too short len=%zu from %016llx", pl_len, (unsigned long long)hdr->src_node_id);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
actual_payload = pl_len - SC_SIGN_SIZE;
uint8_t* sig = pl + actual_payload;
struct ROUTE_BGP* bgp = inst->bgp;
struct NODEINFO_Q* nq = bgp ? nodeinfo_find_by_id(bgp, hdr->src_node_id) : NULL;
if (!nq) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: SIGNED packet from unknown node %016llx — dropping", (unsigned long long)hdr->src_node_id);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
{
uint8_t zero[SC_PUBKEY_SIZE] = {0};
if (memcmp(nq->node.ed25519_public_key, zero, SC_PUBKEY_SIZE) == 0) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: no Ed25519 pubkey for node %016llx — dropping", (unsigned long long)hdr->src_node_id);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
}
struct sc_stream_sign_state vfy_state;
if (sc_stream_sign_verify_init(&vfy_state, nq->node.ed25519_public_key) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router: sign_verify_init failed for node %016llx", (unsigned long long)hdr->src_node_id);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
size_t signed_len = SVC_ROUTE_HDR_SIZE + actual_payload;
if (sc_stream_sign_update(&vfy_state, entry->dgram, signed_len) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router: sign_update failed for node %016llx", (unsigned long long)hdr->src_node_id);
sc_stream_sign_cleanup(&vfy_state); queue_dgram_free(entry); queue_entry_free(entry);
return;
}
if (sc_stream_sign_verify(&vfy_state, sig, SC_SIGN_SIZE) != SC_OK) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: signature verification FAILED for node %016llx svc_id=%u — dropping", (unsigned long long)hdr->src_node_id, hdr->svc_id);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
pl_len = actual_payload;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router: signature verified for node %016llx svc_id=%u", (unsigned long long)hdr->src_node_id, hdr->svc_id);
}
// ========== Мы — целевая нода ==========
struct ETCP_ROUTER_CONN* rconn = router_conn_find(inst, hdr->src_node_id, hdr->svc_id);
@@ -713,6 +792,20 @@ int etcp_router_conn_send(struct ETCP_ROUTER_CONN* rconn,
return router_send_one(rconn, data, len);
}
int etcp_router_conn_send_signed(struct ETCP_ROUTER_CONN* rconn,
const uint8_t* data, size_t len) {
if (!rconn || !data || len == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_conn_send_signed: invalid args rconn=%p data=%p len=%zu",
(void*)rconn, (const void*)data, len);
return -1;
}
if ((int32_t)(rconn->tx_seq - rconn->tx_acked) >= ROUTER_MAX_INFLIGHT) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router_conn_send_signed: inflight full svc_id=%u", rconn->svc_id);
return -1;
}
return router_send_one_flags(rconn, data, len, 0, 1);
}
void etcp_router_conn_close(struct ETCP_ROUTER_CONN* rconn) {
router_close_and_notify(rconn);
}
+5
View File
@@ -28,6 +28,7 @@ struct SVC_ROUTE_HDR {
// Биты в flags
#define ROUTER_FLAG_START 0x80
#define ROUTER_FLAG_RST 0x40
#define ROUTER_FLAG_SIGNED 0x08 // пакет содержит Ed25519 подпись (64 байта после payload)
#define ROUTER_SESS_ID_SHIFT 4
#define ROUTER_SESS_ID_MASK 0x30
@@ -96,6 +97,10 @@ struct ETCP_ROUTER_CONN* etcp_router_conn_get(struct UTUN_INSTANCE* inst,
int etcp_router_conn_send(struct ETCP_ROUTER_CONN* rconn,
const uint8_t* data, size_t len);
// Отправить данные с Ed25519 подписью (аналог etcp_router_conn_send с флагом ROUTER_FLAG_SIGNED)
int etcp_router_conn_send_signed(struct ETCP_ROUTER_CONN* rconn,
const uint8_t* data, size_t len);
// Закрыть seq-подключение
void etcp_router_conn_close(struct ETCP_ROUTER_CONN* rconn);
+638
View File
@@ -0,0 +1,638 @@
/*
* msg_transport.c — TCP-сервер транспорта сообщений (local IPC → ETCP)
*
* Принимает TCP-соединения от локальных приложений (протокол msg_transport.h),
* привязывает к remote peer и пересылает данные через etcp_router.
*/
#include "msg_transport.h"
#include "utun_instance.h"
#include "etcp_router.h"
#include "etcp.h"
#include "secure_channel.h"
#include "route_bgp.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#endif
#ifndef DEBUG_CATEGORY_MSGTRANSPORT
#define DEBUG_CATEGORY_MSGTRANSPORT DEBUG_CATEGORY_GENERAL
#endif
/* ============================================================================
* Внутренние структуры
* ============================================================================ */
struct msg_client {
struct msg_client* next;
struct msg_transport* transport;
socket_t fd;
void* socket_id;
uint8_t recv_buffer[MSG_MAX_MSG_SIZE];
uint16_t recv_len;
uint64_t bound_peer_id; /* 0 = не забинден */
uint8_t is_localhost; /* 1 = подключение с loopback */
};
struct msg_transport {
struct UTUN_INSTANCE* instance;
struct UASYNC* ua;
socket_t listen_fd;
void* listen_socket_id;
struct sockaddr_storage bind_addr;
struct msg_client* clients;
uint32_t client_count;
uint32_t max_clients;
uint8_t svc_id;
struct msg_transport_counters counters;
};
/* ============================================================================
* Forward declarations
* ============================================================================ */
static void accept_callback(socket_t fd, void* arg);
static void client_read_callback(socket_t fd, void* arg);
static void client_except_callback(socket_t fd, void* arg);
static void close_client(struct msg_transport* t, struct msg_client* client);
static void handle_client_data(struct msg_transport* t, struct msg_client* client);
static void on_etcp_recv(struct ETCP_CONN* conn, struct ll_entry* entry);
static int forward_to_peer(struct msg_transport* t, uint64_t dst_peer_id,
const uint8_t* data, size_t data_len, int is_signed);
static void send_response(struct msg_client* client, uint8_t type, uint64_t id,
const uint8_t* data, size_t data_len);
static void send_error(struct msg_client* client, uint8_t error_code, const char* msg);
/* ============================================================================
* Инициализация / завершение
* ============================================================================ */
int msg_transport_init(struct msg_transport** out,
struct UTUN_INSTANCE* instance,
struct UASYNC* ua,
struct sockaddr_storage* bind_addr,
uint32_t max_clients,
uint8_t svc_id) {
if (!out || !instance || !ua || !bind_addr) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport_init: invalid args out=%p inst=%p ua=%p addr=%p",
(void*)out, (void*)instance, (void*)ua, (void*)bind_addr);
return -1;
}
if (bind_addr->ss_family == 0) {
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport_init: bind_addr has no family, skipping");
*out = NULL;
return 0;
}
struct msg_transport* t = u_calloc(1, sizeof(*t));
if (!t) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport_init: u_calloc failed");
return -1;
}
t->instance = instance;
t->ua = ua;
t->max_clients = max_clients ? max_clients : 8;
t->svc_id = svc_id;
memcpy(&t->bind_addr, bind_addr, sizeof(*bind_addr));
int family = bind_addr->ss_family;
t->listen_fd = socket(family, SOCK_STREAM, IPPROTO_TCP);
#ifdef _WIN32
if (t->listen_fd == INVALID_SOCKET) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "socket() failed: %d", WSAGetLastError());
u_free(t); return -1;
}
u_long nonblock = 1;
ioctlsocket(t->listen_fd, FIONBIO, &nonblock);
int reuse = 1;
setsockopt(t->listen_fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse));
#else
if (t->listen_fd < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "socket() failed: %s", strerror(errno));
u_free(t); return -1;
}
int reuse = 1;
setsockopt(t->listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
int flags = fcntl(t->listen_fd, F_GETFL, 0);
if (flags >= 0) fcntl(t->listen_fd, F_SETFL, flags | O_NONBLOCK);
#endif
socklen_t addr_len = (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
#ifdef _WIN32
if (bind(t->listen_fd, (struct sockaddr*)bind_addr, addr_len) == SOCKET_ERROR) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "bind() failed: %d", WSAGetLastError());
closesocket(t->listen_fd); u_free(t); return -1;
}
if (listen(t->listen_fd, 5) == SOCKET_ERROR) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "listen() failed: %d", WSAGetLastError());
closesocket(t->listen_fd); u_free(t); return -1;
}
#else
if (bind(t->listen_fd, (struct sockaddr*)bind_addr, addr_len) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "bind() failed: %s", strerror(errno));
close(t->listen_fd); u_free(t); return -1;
}
if (listen(t->listen_fd, 5) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "listen() failed: %s", strerror(errno));
close(t->listen_fd); u_free(t); return -1;
}
#endif
t->listen_socket_id = uasync_add_socket_t(ua, t->listen_fd,
accept_callback, NULL, NULL, t);
if (!t->listen_socket_id) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "uasync_add_socket_t failed for listen_fd");
#ifdef _WIN32
closesocket(t->listen_fd);
#else
close(t->listen_fd);
#endif
u_free(t); return -1;
}
if (etcp_router_bind(instance, svc_id, on_etcp_recv) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "etcp_router_bind(svc_id=%u) failed", svc_id);
uasync_remove_socket_t(ua, t->listen_fd);
#ifdef _WIN32
closesocket(t->listen_fd);
#else
close(t->listen_fd);
#endif
u_free(t); return -1;
}
if (family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)bind_addr;
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport listening on %s:%d svc_id=%u",
ip_to_str(&sin->sin_addr, AF_INET).str, ntohs(sin->sin_port), svc_id);
} else {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)bind_addr;
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport listening on [%s]:%d svc_id=%u",
ip_to_str(&sin6->sin6_addr, AF_INET6).str, ntohs(sin6->sin6_port), svc_id);
}
*out = t;
return 0;
}
void msg_transport_shutdown(struct msg_transport* t) {
if (!t) return;
while (t->clients) close_client(t, t->clients);
#ifdef _WIN32
if (t->listen_fd != INVALID_SOCKET) {
uasync_remove_socket_t(t->ua, t->listen_fd);
t->listen_socket_id = NULL;
closesocket(t->listen_fd);
t->listen_fd = INVALID_SOCKET;
}
#else
if (t->listen_fd >= 0) {
uasync_remove_socket_t(t->ua, t->listen_fd);
t->listen_socket_id = NULL;
close(t->listen_fd);
t->listen_fd = -1;
}
#endif
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport shutdown complete svc_id=%u", t->svc_id);
memset(t, 0, sizeof(*t));
u_free(t);
}
void msg_transport_get_counters(struct msg_transport* t, struct msg_transport_counters* c) {
if (t && c) memcpy(c, &t->counters, sizeof(*c));
}
/* ============================================================================
* TCP-клиенты: accept / read / close
* ============================================================================ */
static void accept_callback(socket_t fd, void* arg) {
struct msg_transport* t = (struct msg_transport*)arg;
struct sockaddr_storage client_addr;
socklen_t addr_len = sizeof(client_addr);
#ifdef _WIN32
socket_t client_fd = accept(fd, (struct sockaddr*)&client_addr, &addr_len);
if (client_fd == INVALID_SOCKET) {
int err = WSAGetLastError();
if (err != WSAEWOULDBLOCK)
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "accept() failed: %d", err);
return;
}
u_long nonblock = 1;
ioctlsocket(client_fd, FIONBIO, &nonblock);
#else
socket_t client_fd = accept(fd, (struct sockaddr*)&client_addr, &addr_len);
if (client_fd < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK)
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "accept() failed: %s", strerror(errno));
return;
}
int flags = fcntl(client_fd, F_GETFL, 0);
if (flags >= 0) fcntl(client_fd, F_SETFL, flags | O_NONBLOCK);
#endif
if (t->client_count >= t->max_clients) {
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "max_clients=%u reached, rejecting", t->max_clients);
#ifdef _WIN32
closesocket(client_fd);
#else
close(client_fd);
#endif
return;
}
struct msg_client* client = u_calloc(1, sizeof(*client));
if (!client) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "u_calloc(msg_client) failed");
#ifdef _WIN32
closesocket(client_fd);
#else
close(client_fd);
#endif
return;
}
client->fd = client_fd;
client->transport = t;
/* Определяем loopback */
if (client_addr.ss_family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)&client_addr;
client->is_localhost = (ntohl(sin->sin_addr.s_addr) == 0x7F000001u); /* 127.0.0.1 */
} else if (client_addr.ss_family == AF_INET6) {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&client_addr;
static const uint8_t loopback_v6[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};
client->is_localhost = (memcmp(&sin6->sin6_addr, loopback_v6, 16) == 0);
} else {
client->is_localhost = 0;
}
client->socket_id = uasync_add_socket_t(t->ua, client_fd,
client_read_callback, NULL,
client_except_callback, client);
if (!client->socket_id) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "uasync_add_socket_t failed for client");
u_free(client);
#ifdef _WIN32
closesocket(client_fd);
#else
close(client_fd);
#endif
return;
}
client->next = t->clients;
t->clients = client;
t->client_count++;
t->counters.accepted++;
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport client connected (total=%u)", t->client_count);
}
static void client_read_callback(socket_t fd, void* arg) {
struct msg_client* client = (struct msg_client*)arg;
struct msg_transport* t = client->transport;
uint8_t* buf = client->recv_buffer + client->recv_len;
size_t buf_space = MSG_MAX_MSG_SIZE - client->recv_len;
#ifdef _WIN32
int received = recv(fd, (char*)buf, (int)buf_space, 0);
if (received == SOCKET_ERROR) {
int err = WSAGetLastError();
if (err != WSAEWOULDBLOCK) {
if (err == 10054) { close_client(t, client); return; }
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "recv() error: %d", err);
close_client(t, client);
}
return;
}
#else
ssize_t received = recv(fd, buf, buf_space, 0);
if (received < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "recv() error: %s", strerror(errno));
close_client(t, client);
}
return;
}
#endif
if (received == 0) {
close_client(t, client);
return;
}
client->recv_len += (uint16_t)received;
if (client->recv_len > MSG_MAX_MSG_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "recv buffer overflow");
close_client(t, client);
return;
}
handle_client_data(t, client);
}
static void client_except_callback(socket_t fd, void* arg) {
struct msg_client* client = (struct msg_client*)arg;
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "client socket exception");
close_client(client->transport, client);
(void)fd;
}
static void close_client(struct msg_transport* t, struct msg_client* client) {
if (!t || !client) return;
#ifdef _WIN32
if (client->fd != INVALID_SOCKET) {
uasync_remove_socket_t(t->ua, client->fd);
closesocket(client->fd);
}
#else
if (client->fd >= 0) {
uasync_remove_socket_t(t->ua, client->fd);
close(client->fd);
}
#endif
struct msg_client** curr = &t->clients;
while (*curr) {
if (*curr == client) { *curr = client->next; break; }
curr = &(*curr)->next;
}
t->client_count--;
t->counters.closed++;
u_free(client);
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport client disconnected (total=%u)", t->client_count);
}
/* ============================================================================
* Обработка входящих сообщений от TCP-клиента
* ============================================================================ */
static void handle_client_data(struct msg_transport* t, struct msg_client* client) {
while (client->recv_len >= MSG_HDR_SIZE) {
struct msg_header* hdr = (struct msg_header*)client->recv_buffer;
if (msg_validate_header(hdr) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "invalid header size=%u type=0x%02x", hdr->size, hdr->type);
send_error(client, MSG_ERR_INVALID_MSG, "Invalid message header");
close_client(t, client);
return;
}
if (client->recv_len < hdr->size) break;
uint8_t* payload = client->recv_buffer + MSG_HDR_SIZE;
uint16_t payload_size = hdr->size - MSG_HDR_SIZE;
switch (hdr->type) {
case MSG_CTL_BIND: {
uint64_t peer_id = hdr->id;
if (peer_id == 0) {
send_error(client, MSG_ERR_INVALID_MSG, "BIND with zero peer_id");
break;
}
/* Проверить конфликт: этот peer_id уже занят другим клиентом */
struct msg_client* other = t->clients;
while (other) {
if (other != client && other->bound_peer_id == peer_id) {
send_error(client, MSG_ERR_ID_TAKEN, "peer_id already bound by another client");
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "BIND peer=%016llx rejected: already bound",
(unsigned long long)peer_id);
break;
}
other = other->next;
}
if (other) break; /* конфликт */
client->bound_peer_id = peer_id;
send_response(client, MSG_RSP_BIND_OK, peer_id, NULL, 0);
t->counters.bind_count++;
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "BIND peer=%016llx", (unsigned long long)peer_id);
break;
}
case MSG_CTL_UNBIND:
client->bound_peer_id = 0;
t->counters.unbind_count++;
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "UNBIND");
break;
case MSG_CTL_GET_PUBKEY:
send_response(client, MSG_RSP_PUBKEY, 0,
t->instance->my_keys.public_key, SC_PUBKEY_SIZE);
break;
case MSG_CTL_GET_PRIVKEY:
if (!client->is_localhost) {
send_error(client, MSG_ERR_ACCESS_DENIED, "private key only available from localhost");
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "GET_PRIVKEY denied: not localhost");
break;
}
send_response(client, MSG_RSP_PRIVKEY, 0,
t->instance->my_keys.private_key, SC_PRIVKEY_SIZE);
break;
case MSG_CTL_GET_PEER_INFO: {
uint64_t peer_id = hdr->id;
struct ROUTE_BGP* bgp = t->instance->bgp;
if (!bgp) {
send_error(client, MSG_ERR_PEER_NOT_FOUND, "BGP not initialized");
break;
}
struct NODEINFO_Q* nq = nodeinfo_find_by_id(bgp, peer_id);
if (!nq) {
send_error(client, MSG_ERR_PEER_NOT_FOUND, "peer not found");
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "GET_PEER_INFO: peer=%016llx not found",
(unsigned long long)peer_id);
break;
}
uint8_t buf[SC_PUBKEY_SIZE * 2];
memcpy(buf, nq->node.public_key, SC_PUBKEY_SIZE);
memcpy(buf + SC_PUBKEY_SIZE, nq->node.ed25519_public_key, SC_PUBKEY_SIZE);
send_response(client, MSG_RSP_PEER_INFO, peer_id, buf, SC_PUBKEY_SIZE * 2);
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "GET_PEER_INFO: peer=%016llx sent pubkey+edpub",
(unsigned long long)peer_id);
break;
}
case MSG_DATA_SEND: {
uint64_t dst = hdr->id ? hdr->id : client->bound_peer_id;
if (dst == 0) {
send_error(client, MSG_ERR_NOT_BOUND, "Not bound to any peer");
break;
}
if (payload_size == 0) break;
int is_signed = (hdr->options & MSG_OPT_SIGNED) ? 1 : 0;
if (forward_to_peer(t, dst, payload, payload_size, is_signed) != 0) {
send_error(client, MSG_ERR_SEND_FAILED, "Failed to forward message to peer");
t->counters.drops_no_route++;
}
break;
}
default:
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "unknown message type 0x%02x from client", hdr->type);
send_error(client, MSG_ERR_INVALID_MSG, "Unknown message type");
break;
}
uint16_t msg_size = hdr->size;
if (client->recv_len > msg_size)
memmove(client->recv_buffer, client->recv_buffer + msg_size, client->recv_len - msg_size);
client->recv_len -= msg_size;
}
}
/* ============================================================================
* Отправка пиру через etcp_router
* ============================================================================ */
static int forward_to_peer(struct msg_transport* t, uint64_t dst_peer_id,
const uint8_t* data, size_t data_len, int is_signed) {
struct ETCP_ROUTER_CONN* rconn = etcp_router_conn_get(t->instance, dst_peer_id, t->svc_id);
if (!rconn) {
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "forward_to_peer: no route to %016llx", (unsigned long long)dst_peer_id);
return -1;
}
int ret;
if (is_signed) {
ret = etcp_router_conn_send_signed(rconn, data, data_len);
} else {
ret = etcp_router_conn_send(rconn, data, data_len);
}
if (ret != 0) {
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "forward_to_peer: send failed to %016llx (signed=%d)",
(unsigned long long)dst_peer_id, is_signed);
return -1;
}
t->counters.msg_sent_to_peer++;
return 0;
}
/* ============================================================================
* Приём от пира через etcp_router → доставка TCP-клиенту
* ============================================================================ */
static void on_etcp_recv(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (!conn || !entry) return;
struct UTUN_INSTANCE* inst = conn->instance;
if (!inst) { queue_dgram_free(entry); queue_entry_free(entry); return; }
struct msg_transport* t = inst->msg_t;
if (!t) {
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "on_etcp_recv: msg_transport not initialized on instance");
queue_dgram_free(entry); queue_entry_free(entry); return;
}
uint8_t* payload = entry->dgram + 1;
size_t payload_len = entry->len - 1;
uint64_t src_node_id = conn->peer_node_id;
DEBUG_TRACE(DEBUG_CATEGORY_MSGTRANSPORT, "on_etcp_recv: from %016llx len=%zu",
(unsigned long long)src_node_id, payload_len);
/* Ищем клиента привязанного к этому пиру */
struct msg_client* client = t->clients;
while (client) {
if (client->bound_peer_id == src_node_id) break;
client = client->next;
}
if (!client) {
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "on_etcp_recv: no client bound to %016llx, dropping",
(unsigned long long)src_node_id);
t->counters.drops_no_client++;
queue_dgram_free(entry);
queue_entry_free(entry);
return;
}
t->counters.msg_recv_from_peer++;
/* Формируем MSG_DATA_RECV */
size_t total = MSG_HDR_SIZE + payload_len;
uint8_t* buf = u_malloc(total);
if (!buf) {
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "on_etcp_recv: u_malloc failed");
queue_dgram_free(entry); queue_entry_free(entry); return;
}
struct msg_header* hdr = (struct msg_header*)buf;
msg_build_header(hdr, (uint16_t)payload_len, MSG_DATA_RECV, src_node_id, 0);
memcpy(buf + MSG_HDR_SIZE, payload, payload_len);
#ifdef _WIN32
send(client->fd, (const char*)buf, (int)total, 0);
#else
send(client->fd, buf, total, 0);
#endif
u_free(buf);
t->counters.msg_sent_to_client++;
queue_dgram_free(entry);
queue_entry_free(entry);
}
/* ============================================================================
* Помощники отправки ответов TCP-клиенту
* ============================================================================ */
static void send_response(struct msg_client* client, uint8_t type, uint64_t id,
const uint8_t* data, size_t data_len) {
size_t total = MSG_HDR_SIZE + data_len;
uint8_t* buf = u_malloc(total);
if (!buf) return;
struct msg_header* hdr = (struct msg_header*)buf;
msg_build_header(hdr, (uint16_t)data_len, type, id, 0);
if (data_len > 0) memcpy(buf + MSG_HDR_SIZE, data, data_len);
#ifdef _WIN32
send(client->fd, (const char*)buf, (int)total, 0);
#else
send(client->fd, buf, total, 0);
#endif
u_free(buf);
}
static void send_error(struct msg_client* client, uint8_t error_code, const char* msg) {
size_t msg_len = strlen(msg);
size_t total = MSG_HDR_SIZE + 1 + msg_len;
uint8_t* buf = u_malloc(total);
if (!buf) return;
struct msg_header* hdr = (struct msg_header*)buf;
msg_build_header(hdr, (uint16_t)(1 + msg_len), MSG_RSP_ERROR, 0, 0);
buf[MSG_HDR_SIZE] = error_code;
memcpy(buf + MSG_HDR_SIZE + 1, msg, msg_len);
#ifdef _WIN32
send(client->fd, (const char*)buf, (int)total, 0);
#else
send(client->fd, buf, total, 0);
#endif
u_free(buf);
}
+126
View File
@@ -0,0 +1,126 @@
/*
* msg_transport.h — TCP-сервер транспорта сообщений (local IPC → ETCP)
*
* Принимает TCP-соединения от локальных приложений.
* Каждое соединение привязывается (BIND) к конкретному remote peer node_id.
* Сообщения от локального клиента пересылаются пиру через etcp_router,
* сообщения от пира доставляются локальному клиенту.
*
* Формат фрейма на TCP:
* [size:2 (uint16_t, native endian)] [type:1] [id:8 (uint64_t)] [options:1] [data...]
* size включает само поле size.
*/
#ifndef MSG_TRANSPORT_H
#define MSG_TRANSPORT_H
#include <stdint.h>
#include "../lib/socket_compat.h"
#ifdef __cplusplus
extern "C" {
#endif
/* ============================================================================
* Протокол
* ============================================================================ */
#define MSG_MAX_MSG_SIZE 8192
#define MSG_SIGN_SIZE 64 /* Ed25519 */
/* Options byte bits */
#define MSG_OPT_SIGNED 0x01
/* Типы сообщений */
#define MSG_CTL_BIND 0x01 /* GUI→utun: привязать TCP-соединение к пиру id */
#define MSG_CTL_UNBIND 0x02 /* GUI→utun: отвязать */
#define MSG_CTL_GET_PUBKEY 0x03 /* GUI→utun: запросить публичный ключ */
#define MSG_CTL_GET_PRIVKEY 0x04 /* GUI→utun: запросить приватный ключ (только localhost) */
#define MSG_CTL_GET_PEER_INFO 0x05 /* GUI→utun: запросить ключи пира id (pubkey + ed25519_pubkey) */
#define MSG_DATA_SEND 0x10 /* GUI→utun: отправить сообщение пиру id */
#define MSG_DATA_RECV 0x11 /* utun→GUI: получено сообщение от пира id */
#define MSG_RSP_BIND_OK 0xE1 /* utun→GUI: BIND успешен */
#define MSG_RSP_PUBKEY 0xE2 /* utun→GUI: публичный ключ (32 байта) */
#define MSG_RSP_PRIVKEY 0xE3 /* utun→GUI: приватный ключ (32 байта) */
#define MSG_RSP_PEER_INFO 0xE4 /* utun→GUI: ключи пира (pubkey+edpub=64 байта) */
#define MSG_RSP_ERROR 0xFF /* utun→GUI: ошибка */
/* Коды ошибок для MSG_RSP_ERROR */
#define MSG_ERR_INVALID_MSG 0x01
#define MSG_ERR_NOT_BOUND 0x02
#define MSG_ERR_NO_ROUTE 0x03
#define MSG_ERR_SEND_FAILED 0x04
#define MSG_ERR_ACCESS_DENIED 0x07
#define MSG_ERR_PEER_NOT_FOUND 0x08
#define MSG_ERR_ID_TAKEN 0x06
#pragma pack(push, 1)
struct msg_header {
uint16_t size; /* полный размер сообщения, включая это поле */
uint8_t type; /* тип (MSG_CTL_* / MSG_DATA_* / MSG_RSP_*) */
uint64_t id; /* peer node_id */
uint8_t options; /* битовые флаги (MSG_OPT_*) */
};
#pragma pack(pop)
#define MSG_HDR_SIZE sizeof(struct msg_header) /* 12 */
static inline void msg_build_header(struct msg_header* hdr,
uint16_t payload_size,
uint8_t msg_type,
uint64_t id,
uint8_t options) {
hdr->size = MSG_HDR_SIZE + payload_size;
hdr->type = msg_type;
hdr->id = id;
hdr->options = options;
}
static inline int msg_validate_header(const struct msg_header* hdr) {
if (hdr->size < MSG_HDR_SIZE || hdr->size > MSG_MAX_MSG_SIZE) return -1;
return 0;
}
/* ============================================================================
* Счётчики (диагностика)
* ============================================================================ */
struct UTUN_INSTANCE;
struct UASYNC;
struct msg_transport;
struct msg_transport_counters {
uint64_t accepted;
uint64_t closed;
uint64_t bind_count;
uint64_t unbind_count;
uint64_t msg_sent_to_peer;
uint64_t msg_recv_from_peer;
uint64_t msg_sent_to_client;
uint64_t errors;
uint64_t drops_no_client;
uint64_t drops_no_route;
};
/* ============================================================================
* API
* ============================================================================ */
int msg_transport_init(struct msg_transport** out,
struct UTUN_INSTANCE* instance,
struct UASYNC* ua,
struct sockaddr_storage* bind_addr,
uint32_t max_clients,
uint8_t svc_id);
void msg_transport_shutdown(struct msg_transport* t);
void msg_transport_get_counters(struct msg_transport* t,
struct msg_transport_counters* c);
#ifdef __cplusplus
}
#endif
#endif /* MSG_TRANSPORT_H */
+37 -13
View File
@@ -15,10 +15,12 @@
#include "etcp_debug.h"
#include "config_parser.h"
#include "route_node.h"
#include "route_node_lmdb.h"
#include "route_lib.h"
#include "route_bgp.h"
#include "route_ping.h"
#include "route_connectivity.h"
#include "control_server.h"
// ============================================================================
@@ -244,6 +246,16 @@ struct ROUTE_BGP* route_bgp_init(struct UTUN_INSTANCE* instance) {
bgp->instance = instance;
bgp->ping_pending = NULL;
bgp->next_ping_req_id = 1;
bgp->nodeinfo_env = NULL;
bgp->nodeinfo_dbi = 0;
sc_derive_ed25519_pubkey(instance->my_keys.private_key, bgp->ed25519_public_key);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Ed25519 pubkey derived from X25519 private key");
// Создаём/открываем LMDB базу nodeinfo если задан db_path в конфиге
if (instance->config && instance->config->global.db_path[0]) {
route_node_lmdb_init(bgp, instance->config->global.db_path);
}
// Create nodes queue with hash support for fast lookup by node_id
bgp->nodes = queue_new(instance->ua, BGP_NODES_HASH_SIZE, offsetof(struct NODEINFO_Q, node.node_id)-sizeof(struct ll_entry), 8, "bgp_nodes");
@@ -324,6 +336,8 @@ void route_bgp_destroy(struct UTUN_INSTANCE* instance) {
u_free(instance->bgp->local_node);
}
route_node_lmdb_destroy(instance->bgp);
if (instance->bgp->nodes) {
queue_free(instance->bgp->nodes);
}
@@ -356,9 +370,16 @@ void route_bgp_new_conn(struct ETCP_CONN* conn) {
}
struct ROUTE_BGP* bgp = conn->instance->bgp;
struct NODEINFO_Q* peer_nq = nodeinfo_find_by_id(bgp, conn->peer_node_id);
route_bgp_add_to_senders(bgp, conn);
if (peer_nq && peer_nq->alien) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_bgp_new_conn: peer 0x%016llx is alien, skipping route exchange",
(unsigned long long)conn->peer_node_id);
return;
}
// Scan ALL connections and ALL links in instance - start NAT check if not started
struct ETCP_CONN* c = conn->instance->connections;
while (c) {
@@ -429,6 +450,8 @@ void route_bgp_remove_conn(struct ETCP_CONN* conn) {
if (route_bgp_remove_path(nq, conn) == 1) {
need_withdraw = true;
if (rt) route_delete(rt, nq);
if (conn->instance && conn->instance->control_srv)
control_server_notify_node_removed(conn->instance->control_srv, nq->node.node_id);
nq->dirty = 1;
route_connectivity_cancel_node(conn->instance, nq);
if (nq->paths) { queue_free(nq->paths); nq->paths = NULL; }
@@ -467,19 +490,9 @@ void route_bgp_remove_conn(struct ETCP_CONN* conn) {
* NEW NODEINFO BASED IMPLEMENTATION
* ================================================ */
struct NODEINFO_Q* route_bgp_get_node(struct ROUTE_BGP* bgp, uint64_t node_id) {
if (!bgp || !bgp->nodes) {
return NULL;
}
uint64_t key = node_id;
struct ll_entry* e = queue_find_data_by_index(bgp->nodes, &key);
return e ? (struct NODEINFO_Q*)e : NULL;
}
struct ETCP_CONN* route_bgp_find_conn_for_node(struct ROUTE_BGP* bgp, uint64_t node_id) {
if (!bgp) return NULL;
struct NODEINFO_Q* nq = route_bgp_get_node(bgp, node_id);
struct NODEINFO_Q* nq = nodeinfo_find_by_id(bgp, node_id);
if (!nq || !nq->paths || !nq->paths->head) return NULL;
struct ll_entry* e = nq->paths->head;
struct NODEINFO_PATH* best = NULL;
@@ -650,6 +663,7 @@ static void route_bgp_extract_nat_addr(struct ETCP_CONN* conn, uint32_t* nat_ip,
void route_bgp_start_link_nat_check(struct ROUTE_BGP* bgp, struct ETCP_LINK* link) {// проверяем тип нат для линка link
if (!bgp || !link || !link->conn || !link->etcp) return;
if (link->nat_type == NAT_TYPE_DIRECT) return;
if (link->nat_check_status == NAT_CHECK_IN_PROGRESS) return;
// Skip NAT check for private sockets - they are not reachable from outside
if (link->conn->type == CFG_SERVER_TYPE_PRIVATE) {
@@ -728,7 +742,7 @@ int route_bgp_process_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, co
return -1;
}
struct NODEINFO_Q* nodeinfo1 = route_bgp_get_node(bgp, node_id);
struct NODEINFO_Q* nodeinfo1 = nodeinfo_find_by_id(bgp, node_id);
uint8_t new_ver = ni->ver;
if (nodeinfo1 && (int8_t)(nodeinfo1->last_ver-new_ver)>=0) {
@@ -771,6 +785,8 @@ int route_bgp_process_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, co
nodeinfo1->connectivity.interface_status = PROBE_RESULT_UNKNOWN;
nodeinfo1->connectivity.nat_status = PROBE_RESULT_UNKNOWN;
nodeinfo1->connectivity.real_status = PROBE_RESULT_UNKNOWN;
nodeinfo1->connectivity.ping_req_time = 0;
nodeinfo1->alien = 0;
queue_data_put_with_index(bgp->nodes, &nodeinfo1->ll);
} else {
socks_changed = (nodeinfo1->node.local_v4_sockets != ni->local_v4_sockets) ||
@@ -813,6 +829,12 @@ int route_bgp_process_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, co
route_insert(bgp->instance->rt, nodeinfo1);
}
if (node_id != bgp->instance->node_id) {
route_node_lmdb_put(bgp, nodeinfo1);
}
if (bgp->instance->control_srv) control_server_notify_node_change(bgp->instance->control_srv, nodeinfo1);
int hop_count=nodeinfo1->node.hop_count;
// рассылаем по узлам которых нет в hop_list этой ноды (loop prevention)
@@ -850,7 +872,7 @@ int route_bgp_process_withdraw(struct ROUTE_BGP* bgp, struct ETCP_CONN* sender,
uint64_t node_id = wp->node_id;
uint64_t wd_source = wp->wd_source;
struct NODEINFO_Q* nq = route_bgp_get_node(bgp, node_id);
struct NODEINFO_Q* nq = nodeinfo_find_by_id(bgp, node_id);
if (!nq) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "node not found");
return 0;
@@ -858,6 +880,7 @@ int route_bgp_process_withdraw(struct ROUTE_BGP* bgp, struct ETCP_CONN* sender,
int ret=route_bgp_remove_path_by_hop(nq, wd_source);
if (ret>0 || !nq->paths || (nq->paths && queue_entry_count(nq->paths) == 0)) {
if (bgp->instance && bgp->instance->rt) route_delete(bgp->instance->rt, nq);
if (bgp->instance->control_srv) control_server_notify_node_removed(bgp->instance->control_srv, node_id);
nq->dirty = 1;
route_connectivity_cancel_node(bgp->instance, nq);
if (nq->paths) { queue_free(nq->paths); nq->paths = NULL; }
@@ -990,6 +1013,7 @@ static void route_bgp_handle_nat_info(struct ROUTE_BGP* bgp, struct ETCP_CONN* f
uint8_t verified_type;
if (info->nat_type == NAT_TYPE_EIM) verified_type = NAT_VERIFIED_EIM;
else if (info->nat_type == NAT_TYPE_STRICT) verified_type = NAT_VERIFIED_STRICT;
else if (info->nat_type == NAT_TYPE_DIRECT) verified_type = NAT_VERIFIED_DIRECT;
else verified_type = NAT_VERIFIED_UNKNOWN;
int data_changed = 0;
+5 -6
View File
@@ -4,6 +4,7 @@
#include <stdint.h>
#include <stddef.h>
#include "../lib/ll_queue.h"
#include "../lib/liblmdb/lmdb.h"
#include "route_lib.h"
#include "route_node.h"
#include "secure_channel.h"
@@ -88,6 +89,9 @@ struct ROUTE_BGP {
struct route_ping_pending* ping_pending;
uint32_t next_ping_req_id;
uint8_t allow_nat_check_local; // 1 = разрешить NAT check для локальных подсетей (для тестов)
uint8_t ed25519_public_key[SC_PUBKEY_SIZE]; // свой Ed25519 pubkey (для подписи сообщений)
MDB_env* nodeinfo_env; // LMDB environment handle
MDB_dbi nodeinfo_dbi; // LMDB database handle for nodeinfo table
};
/**
@@ -151,12 +155,7 @@ void route_bgp_send_nodeinfo(struct NODEINFO_Q* node, struct ETCP_CONN* conn);
*/
void route_bgp_send_withdraw(struct ROUTE_BGP* bgp, uint64_t node_id);
/**
* @brief Поиск NODEINFO_Q по node_id через hash в nodes queue.
*
* @return node или NULL
*/
struct NODEINFO_Q* route_bgp_get_node(struct ROUTE_BGP* bgp, uint64_t node_id);
int nodeinfo_dyn_size(struct NODEINFO* node);
/**
* @brief Поиск оптимального ETCP соединения для указанного node_id.
+12 -1
View File
@@ -121,6 +121,13 @@ int get_node_v6_routes(struct NODEINFO_Q *node, const struct NODEINFO_IPV6_SUBNE
return (int)info->local_v6_subnets;
}
struct NODEINFO_Q* nodeinfo_find_by_id(struct ROUTE_BGP* bgp, uint64_t node_id) {
if (!bgp || !bgp->nodes) return NULL;
uint64_t key = node_id;
struct ll_entry* e = queue_find_data_by_index(bgp->nodes, &key);
return e ? (struct NODEINFO_Q*)e : NULL;
}
int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BGP* bgp) {
if (!instance || !bgp) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_update_my_nodeinfo: invalid args");
@@ -176,9 +183,12 @@ int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BG
|| (vc6 != (int)bgp->local_node->node.local_v6_subnets)
|| (sock6_count != (int)bgp->local_node->node.local_v6_sockets)
|| (addr6_count != (int)bgp->local_node->node.local_v6_addrs);
if (bgp->local_node && bgp->instance && bgp->instance->bgp) {
if (memcmp(bgp->local_node->node.ed25519_public_key, bgp->instance->bgp->ed25519_public_key, SC_PUBKEY_SIZE) != 0) changed = 1;
}
}
if (changed) {
if (changed) {
if (bgp->local_node) u_free(bgp->local_node);
bgp->local_node = u_calloc(1, sizeof(struct NODEINFO_Q) + dyn);
if (!bgp->local_node) return -1;
@@ -193,6 +203,7 @@ int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BG
bgp->local_node->node.local_v6_subnets = vc6;
bgp->local_node->node.node_name_len = name_len;
memcpy(bgp->local_node->node.public_key, instance->my_keys.public_key, SC_PUBKEY_SIZE);
memcpy(bgp->local_node->node.ed25519_public_key, bgp->ed25519_public_key, SC_PUBKEY_SIZE);
bgp->local_node->dirty = 1;
bgp->local_node->last_ver = bgp->local_node->node.ver;
+15 -1
View File
@@ -33,6 +33,12 @@ struct UTUN_INSTANCE;
#define CONN_PROBE_COUNT 3
#define CONN_PROBE_TIMEOUT_MS 1000
#define CONN_TYPE_NONE 0
#define CONN_TYPE_DIRECT 1
#define CONN_TYPE_REVERSE 2
#define CONN_TYPE_INDIRECT 3
#define CONN_MGR_MAX_INTERMEDIARIES 3
// ---- состояние связности с удалённым узлом (локальное, не передаётся по BGP) ----
struct NODE_CONNECTIVITY {
@@ -49,6 +55,7 @@ struct NODE_CONNECTIVITY {
uint64_t interface_probe_time;
uint64_t nat_probe_time;
uint64_t real_probe_time;
uint64_t ping_req_time; // время последнего полученного ping request от этого узла (0.1ms tb)
};
/**
@@ -57,7 +64,8 @@ struct NODE_CONNECTIVITY {
struct NODEINFO {
uint64_t node_id; // (big-endian)
uint8_t ver; // версия пакета (циклический счетчик чтобы быстро сравнивать с локальной копией - были ли обновления)
uint8_t public_key[SC_PUBKEY_SIZE]; // node pubkey
uint8_t public_key[SC_PUBKEY_SIZE]; // X25519 pubkey
uint8_t ed25519_public_key[SC_PUBKEY_SIZE]; // Ed25519 pubkey for signature verification
uint8_t node_name_len; // размер в байтах (без null терминации)
uint8_t local_v4_sockets; // NODEINFO_IPV4_SOCKET_META число метаданных ipv4 сокетов
uint8_t local_v4_addrs; // NODEINFO_IPV4_ADDR число типизированных ipv4 адресов узла (плоский список)
@@ -132,6 +140,10 @@ struct NODEINFO_Q {
struct ll_queue* paths; // сюда помещаем struct NODEINFO_PATH
uint8_t dirty;
uint8_t last_ver;
uint8_t alien; // 1 = чужой узел (не запрашиваем роутинг при connect)
uint8_t conn_mgr_type; // CONN_TYPE_* — тип managed-подключения
uint64_t conn_mgr_intermediaries[CONN_MGR_MAX_INTERMEDIARIES]; // посредники для INDIRECT
uint8_t conn_mgr_intermediariy_count;
struct NODE_CONNECTIVITY connectivity; // состояние связности (локальное)
struct ETCP_SOCKET* best_socket;
struct NODEINFO node; // Всегда в конце структуры - динамически расширяемый блок
@@ -173,4 +185,6 @@ int get_node_v6_sockets_meta(struct NODEINFO_Q *node, const struct NODEINFO_IPV6
*/
int get_node_v6_addrs(struct NODEINFO_Q *node, const struct NODEINFO_IPV6_ADDR **out_addrs);
struct NODEINFO_Q* nodeinfo_find_by_id(struct ROUTE_BGP* bgp, uint64_t node_id);
#endif // ROUTE_NODE_H
+150
View File
@@ -0,0 +1,150 @@
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/stat.h>
#include "route_node_lmdb.h"
#include "route_bgp.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#define NODEINFO_DB_MAPSIZE (10UL * 1024 * 1024)
int route_node_lmdb_init(struct ROUTE_BGP* bgp, const char* db_path) {
if (!bgp || !db_path || !db_path[0]) return -1;
int rc = mdb_env_create(&bgp->nodeinfo_env);
if (rc != MDB_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_env_create failed: %s", mdb_strerror(rc));
return -1;
}
rc = mdb_env_set_mapsize(bgp->nodeinfo_env, NODEINFO_DB_MAPSIZE);
if (rc != MDB_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_env_set_mapsize failed: %s", mdb_strerror(rc));
mdb_env_close(bgp->nodeinfo_env);
bgp->nodeinfo_env = NULL;
return -1;
}
rc = mdb_env_set_maxdbs(bgp->nodeinfo_env, 2);
if (rc != MDB_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_env_set_maxdbs failed: %s", mdb_strerror(rc));
mdb_env_close(bgp->nodeinfo_env);
bgp->nodeinfo_env = NULL;
return -1;
}
rc = mdb_env_open(bgp->nodeinfo_env, db_path, 0, 0644);
if (rc != MDB_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_env_open(%s) failed: %s", db_path, mdb_strerror(rc));
mdb_env_close(bgp->nodeinfo_env);
bgp->nodeinfo_env = NULL;
return -1;
}
MDB_txn* txn = NULL;
rc = mdb_txn_begin(bgp->nodeinfo_env, NULL, 0, &txn);
if (rc != MDB_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_txn_begin failed: %s", mdb_strerror(rc));
mdb_env_close(bgp->nodeinfo_env);
bgp->nodeinfo_env = NULL;
return -1;
}
rc = mdb_dbi_open(txn, "nodeinfo", MDB_CREATE, &bgp->nodeinfo_dbi);
if (rc != MDB_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_dbi_open(nodeinfo) failed: %s", mdb_strerror(rc));
mdb_txn_abort(txn);
mdb_env_close(bgp->nodeinfo_env);
bgp->nodeinfo_env = NULL;
return -1;
}
rc = mdb_txn_commit(txn);
if (rc != MDB_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_txn_commit failed: %s", mdb_strerror(rc));
mdb_env_close(bgp->nodeinfo_env);
bgp->nodeinfo_env = NULL;
return -1;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "LMDB nodeinfo database opened: %s", db_path);
return 0;
}
void route_node_lmdb_destroy(struct ROUTE_BGP* bgp) {
if (!bgp) return;
if (bgp->nodeinfo_env) {
mdb_env_close(bgp->nodeinfo_env);
bgp->nodeinfo_env = NULL;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "LMDB nodeinfo database closed");
}
}
int route_node_lmdb_put(struct ROUTE_BGP* bgp, struct NODEINFO_Q* nq) {
if (!bgp || !bgp->nodeinfo_env || !nq) return -1;
int dyn_size = nodeinfo_dyn_size(&nq->node);
size_t val_size = sizeof(struct NODEINFO) + dyn_size;
MDB_val key, data;
key.mv_size = 8;
key.mv_data = &nq->node.node_id;
data.mv_size = val_size;
data.mv_data = (void*)&nq->node;
MDB_txn* txn = NULL;
int rc = mdb_txn_begin(bgp->nodeinfo_env, NULL, 0, &txn);
if (rc != MDB_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB put mdb_txn_begin failed: %s", mdb_strerror(rc));
return -1;
}
rc = mdb_put(txn, bgp->nodeinfo_dbi, &key, &data, 0);
if (rc != MDB_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_put node %016llx failed: %s",
(unsigned long long)nq->node.node_id, mdb_strerror(rc));
mdb_txn_abort(txn);
return -1;
}
rc = mdb_txn_commit(txn);
if (rc != MDB_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB put mdb_txn_commit failed: %s", mdb_strerror(rc));
return -1;
}
return 0;
}
int route_node_lmdb_del(struct ROUTE_BGP* bgp, uint64_t node_id) {
if (!bgp || !bgp->nodeinfo_env) return -1;
MDB_val key;
key.mv_size = 8;
key.mv_data = &node_id;
MDB_txn* txn = NULL;
int rc = mdb_txn_begin(bgp->nodeinfo_env, NULL, 0, &txn);
if (rc != MDB_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB del mdb_txn_begin failed: %s", mdb_strerror(rc));
return -1;
}
rc = mdb_del(txn, bgp->nodeinfo_dbi, &key, NULL);
if (rc != MDB_SUCCESS && rc != MDB_NOTFOUND) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_del node %016llx failed: %s",
(unsigned long long)node_id, mdb_strerror(rc));
mdb_txn_abort(txn);
return -1;
}
rc = mdb_txn_commit(txn);
if (rc != MDB_SUCCESS) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB del mdb_txn_commit failed: %s", mdb_strerror(rc));
return -1;
}
return 0;
}
+14
View File
@@ -0,0 +1,14 @@
#ifndef ROUTE_NODE_LMDB_H
#define ROUTE_NODE_LMDB_H
#include <stdint.h>
#include "route_node.h"
struct ROUTE_BGP;
int route_node_lmdb_init(struct ROUTE_BGP* bgp, const char* db_path);
void route_node_lmdb_destroy(struct ROUTE_BGP* bgp);
int route_node_lmdb_put(struct ROUTE_BGP* bgp, struct NODEINFO_Q* nq);
int route_node_lmdb_del(struct ROUTE_BGP* bgp, uint64_t node_id);
#endif
+6 -1
View File
@@ -325,6 +325,11 @@ void route_ping_handle_req(struct ROUTE_BGP* bgp,
ctx->request_id = req_pkt->request_id;
ctx->count_total = req_pkt->count;
ctx->timeout_ms = req_pkt->timeout_ms;
{
struct NODEINFO_Q* req_nq = nodeinfo_find_by_id(bgp, from_conn->peer_node_id);
if (req_nq) req_nq->connectivity.ping_req_time = get_time_tb();
}
if (len >= sizeof(struct BGP_PING_REQUEST)) {
memcpy(ctx->pubkey, req_pkt->pubkey, SC_PUBKEY_SIZE);
}
@@ -338,7 +343,7 @@ void route_ping_handle_req(struct ROUTE_BGP* bgp,
/* Если target не указан — можно разрешить из nodeinfo */
/*
if (sin->sin_addr.s_addr == 0 && sin->sin_port == 0) {
struct NODEINFO_Q* nq = route_bgp_get_node(bgp, req_pkt->node_id);
struct NODEINFO_Q* nq = nodeinfo_find_by_id(bgp, req_pkt->node_id);
if (nq) {
const struct NODEINFO_IPV4_ADDR* addrs;
int count = get_node_v4_addrs(nq, &addrs);
+204 -632
View File
File diff suppressed because it is too large. Load diff
+26 -14
View File
@@ -7,7 +7,7 @@
// Размеры ключей
#define SC_PRIVKEY_SIZE 32
#define SC_PUBKEY_SIZE 64
#define SC_PUBKEY_SIZE 32
#define SC_HASH_SIZE 32
#define SC_NONCE_SIZE 13 // CCM requires exactly 13 bytes
#define SC_SHARED_SECRET_SIZE SC_HASH_SIZE
@@ -15,7 +15,7 @@
#define SC_TAG_SIZE 16
#define SC_CRC32_SIZE 4
// Шифрование pubkey при передаче (salt + double SHA256 XOR)
// Обфускация pubkey при передаче (salt + double SHA256 XOR)
#define SC_PUBKEY_ENC_SALT_SIZE 8
#define SC_PUBKEY_ENC_SIZE (SC_PUBKEY_SIZE + SC_PUBKEY_ENC_SALT_SIZE)
@@ -43,14 +43,10 @@ struct SC_MYKEYS {
// Контекст защищенного канала
struct secure_channel {
struct SC_MYKEYS* pk;
/* Ключи пира (после key exchange) */
/* Ключ пира (после key exchange) */
uint8_t peer_public_key[SC_PUBKEY_SIZE];
uint8_t session_key[SC_SESSION_KEY_SIZE]; /* Derived session key */
/* Nonces для отправки и приема (теперь генерируются динамически, не используются counters для nonce) */
uint8_t send_nonce[SC_NONCE_SIZE];
uint8_t recv_nonce[SC_NONCE_SIZE];
uint8_t initialized;
uint8_t peer_key_set;
uint8_t session_ready;
@@ -78,15 +74,9 @@ sc_status_t sc_obfuscate_pubkey(const uint8_t *salt, const uint8_t *peer_pubkey,
// --- Streaming cipher (AES-128-CTR, confidentiality only) ---
#define SC_STREAM_NONCE_SIZE 12
#define SC_STREAM_AES_SCHED_SIZE 176 // sizeof(struct tc_aes_key_sched_struct) = Nb*(Nr+1)*4 = 4*11*4
struct sc_stream_state {
#ifdef USE_OPENSSL
void *ectx; // EVP_CIPHER_CTX* (opaque, created/destroyed in .c)
#else
uint8_t sched_buf[SC_STREAM_AES_SCHED_SIZE]; // AES-128 key schedule
uint8_t ctr_block[16]; // nonce(12) + counter(4, BE)
#endif
void *ectx; // EVP_CIPHER_CTX*
uint8_t initialized;
};
@@ -94,4 +84,26 @@ sc_status_t sc_stream_init(sc_context_t *ctx, struct sc_stream_state *state, uin
sc_status_t sc_stream_xor(struct sc_stream_state *state, uint8_t *data, size_t data_len);
void sc_stream_cleanup(struct sc_stream_state *state);
// --- Streaming Ed25519 signature ---
// SHA-512 инкрементально (EVP_DigestUpdate) + Ed25519 подпись хеша (one-shot EVP_DigestSign).
// Память O(1), не зависит от размера данных.
#define SC_SIGN_SIZE 64 // Ed25519 signature size
struct sc_stream_sign_state {
void *md_ctx; // EVP_MD_CTX* — SHA-512 аккумулятор
void *pkey; // EVP_PKEY* — Ed25519 ключ
uint8_t initialized;
uint8_t is_sign; // 1=sign, 0=verify
};
sc_status_t sc_stream_sign_init(sc_context_t *ctx, struct sc_stream_sign_state *state);
sc_status_t sc_stream_sign_verify_init(struct sc_stream_sign_state *state, const uint8_t *ed25519_pubkey);
sc_status_t sc_stream_sign_update(struct sc_stream_sign_state *state, const uint8_t *data, size_t len);
sc_status_t sc_stream_sign_final(struct sc_stream_sign_state *state, uint8_t *sig_out, size_t *sig_len);
sc_status_t sc_stream_sign_verify(struct sc_stream_sign_state *state, const uint8_t *sig, size_t sig_len);
void sc_stream_sign_cleanup(struct sc_stream_sign_state *state);
sc_status_t sc_derive_ed25519_pubkey(const uint8_t *x25519_privkey, uint8_t *ed25519_pubkey_out);
#endif // SECURE_CHANNEL_H
+29
View File
@@ -11,7 +11,9 @@
#include "route_bgp.h"
#include "etcp_connections.h"
#include "etcp.h"
#include "conn_mgr.h"
#include "control_server.h"
#include "msg_transport.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include <stdlib.h>
@@ -128,6 +130,9 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
if (route_insert(instance->rt,instance->bgp->local_node)) DEBUG_INFO(DEBUG_CATEGORY_ROUTING,"Added local routes"); else DEBUG_WARN(DEBUG_CATEGORY_ROUTING,"Failed to add local routes");
}
}
instance->conn_mgr = conn_mgr_init(instance);
if (!instance->conn_mgr) DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "Failed to initialize Connection Manager (non-fatal)");
// Initialize firewall
fw_init(&instance->fw);
@@ -289,6 +294,13 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
// Stop running if not already
instance->running = 0;
// Shutdown message transport server
if (instance->msg_t) {
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "Shutting down message transport server");
msg_transport_shutdown(instance->msg_t);
instance->msg_t = NULL;
}
// Shutdown control server first
if (instance->control_srv) {
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Shutting down control server");
@@ -365,6 +377,8 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
// Cleanup etcp_router
etcp_router_destroy(instance);
if (instance->conn_mgr) { conn_mgr_destroy(instance->conn_mgr); instance->conn_mgr = NULL; }
// Cleanup BGP module
if (instance->bgp) {
@@ -482,6 +496,21 @@ int utun_instance_init(struct UTUN_INSTANCE *instance) {
}
}
// Initialize message transport server if configured
if (instance->config->global.msg_transport_sock.ss_family != 0) {
int mt_ret = msg_transport_init(&instance->msg_t, instance, instance->ua,
&instance->config->global.msg_transport_sock,
8, ETCP_ID_MSG_TRANSPORT);
if (mt_ret != 0) {
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "Failed to initialize msg_transport, continuing without IPC transport");
instance->msg_t = NULL;
} else {
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "Message transport server initialized");
}
} else {
instance->msg_t = NULL;
}
// Start the main loop
instance->running = 1;
+6
View File
@@ -31,7 +31,9 @@ struct ETCP_BINDINGS;
struct ETCP_ROUTER_BINDINGS;
struct ROUTE_BGP;
struct control_server;
struct msg_transport;
struct PING_CONTEXT;
struct CONN_MGR;
// uTun instance configuration
struct UTUN_INSTANCE {
@@ -92,6 +94,9 @@ struct UTUN_INSTANCE {
// Control server for monitoring
struct control_server* control_srv;
// Message transport server (local IPC → ETCP)
struct msg_transport* msg_t;
// Firewall
struct firewall_ctx fw;
@@ -108,6 +113,7 @@ struct UTUN_INSTANCE {
// etcp_router bindings и seq-connections (per-instance service routing)
struct ETCP_ROUTER_BINDINGS router_bindings;
struct ll_queue* router_conns;
struct CONN_MGR* conn_mgr; // Connection Manager (может быть NULL)
// TCP proxy server (exit node)
struct tcp_proxy_server tcp_proxy_server;
+37 -105
View File
@@ -5,6 +5,7 @@ check_PROGRAMS = \
test_ll_queue \
test_serialize \
test_stream_cipher \
test_stream_sign \
test_etcp_transport \
test_transport \
test_stcp \
@@ -43,42 +44,18 @@ check_PROGRAMS = \
test_icmp_proxy \
test_tcp_proxy_client \
test_bgp_route_exchange \
test_conn_mgr \
test_bbr_integration \
test_intensive_memory_pool \
test_tcp_io \
bench_timeout_heap \
bench_uasync_timeouts
# Долгие тесты: запускаются только вручную, не включаются в make check
# test_etcp_congestion — DISABLED: старый congestion control удалён, ждёт новых BBR тестов
# test_tcp_proxy_remote — 2-node TCP через etcp, требует fix tcp_proxy_client singleton
# Долгие тесты: запускаются только вручную
noinst_PROGRAMS =
# test_crypto and test_ecc_encrypt only needed for TinyCrypt (not when using OpenSSL)
if USE_OPENSSL
else
check_PROGRAMS += test_crypto test_ecc_encrypt
endif
# Basic includes
AM_CFLAGS = -g -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
# TinyCrypt source files
TINYCRYPT_SRCDIR = $(top_srcdir)/tinycrypt/lib/source
# TinyCrypt object files (built by src/Makefile.am with utun- prefix)
TINYCRYPT_OBJS = \
$(top_builddir)/src/utun-aes_encrypt.o \
$(top_builddir)/src/utun-aes_decrypt.o \
$(top_builddir)/src/utun-ccm_mode.o \
$(top_builddir)/src/utun-cmac_mode.o \
$(top_builddir)/src/utun-ctr_mode.o \
$(top_builddir)/src/utun-ecc.o \
$(top_builddir)/src/utun-ecc_dh.o \
$(top_builddir)/src/utun-ecc_dsa.o \
$(top_builddir)/src/utun-sha256.o \
$(top_builddir)/src/utun-ecc_platform_specific.o \
$(top_builddir)/src/utun-utils.o
AM_CFLAGS = -g -I$(top_srcdir)/src -I$(top_srcdir)/lib
# Secure channel and CRC objects (built in src directory)
SECURE_CHANNEL_OBJS = $(top_builddir)/src/utun-secure_channel.o $(top_builddir)/src/utun-crc32.o
@@ -116,7 +93,9 @@ ETCP_FULL_OBJS = \
$(top_builddir)/src/utun-route_bgp.o \
$(top_builddir)/src/utun-route_ping.o \
$(top_builddir)/src/utun-route_node.o \
$(top_builddir)/src/utun-route_node_lmdb.o \
$(top_builddir)/src/utun-route_connectivity.o \
$(top_builddir)/src/utun-conn_mgr.o \
$(top_builddir)/src/utun-routing.o \
$(top_builddir)/src/utun-tun_if.o \
$(top_builddir)/src/utun-tun_route.o \
@@ -125,6 +104,7 @@ ETCP_FULL_OBJS = \
$(top_builddir)/src/utun-eim_nat.o \
$(top_builddir)/src/utun-nat_transport.o \
$(top_builddir)/src/utun-control_server.o \
$(top_builddir)/src/utun-msg_transport.o \
$(TUN_PLATFORM_OBJ) \
$(top_builddir)/src/utun-utun_instance.o \
$(top_builddir)/src/proxy/utun-tcp_proxy_client.o \
@@ -139,41 +119,18 @@ ETCP_FULL_OBJS = \
$(STCP_LINK_OBJS) \
$(ETCP_CORE_OBJS)
# Windows-specific libraries (advapi32 for CryptGenRandom, ws2_32 for sockets)
# Windows-specific libraries
if OS_WINDOWS
WIN_LIBS = -lws2_32 -liphlpapi -ladvapi32 -lbcrypt -ldbghelp
else
WIN_LIBS =
endif
# Common libraries (libuasync.a from lib directory)
# Common libraries
COMMON_LIBS = $(top_builddir)/lib/libuasync.a -lpthread $(WIN_LIBS)
# Crypto libraries (conditional)
if USE_OPENSSL
# Crypto always uses OpenSSL
CRYPTO_LIBS = -lcrypto
TINYCRYPT_BUILT =
else
CRYPTO_LIBS = $(TINYCRYPT_OBJS)
TINYCRYPT_BUILT = tinycrypt-objects
endif
# Tests run via check-local (with timing and colored output), not TESTS
# Build TinyCrypt objects as a group (only when not using OpenSSL)
if USE_OPENSSL
else
tinycrypt-objects: $(TINYCRYPT_OBJS)
endif
# Ensure TinyCrypt objects are built before tests that need them
if USE_OPENSSL
BUILT_SOURCES =
else
BUILT_SOURCES = $(TINYCRYPT_OBJS)
$(TINYCRYPT_OBJS): $(top_builddir)/src/utun
@$(MAKE) -C $(top_builddir)/src tinycrypt-objects
endif
# Test definitions
test_etcp_bbr_SOURCES = test_etcp_bbr.c
@@ -181,54 +138,41 @@ test_etcp_bbr_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_etcp_bbr_LDADD = $(top_builddir)/src/utun-etcp_bbr.o $(COMMON_LIBS)
test_etcp_crypto_SOURCES = test_etcp_crypto.c
test_etcp_crypto_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_etcp_crypto_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_etcp_crypto_LDADD = $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_stream_cipher_SOURCES = test_stream_cipher.c
test_stream_cipher_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_stream_cipher_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_stream_cipher_LDADD = $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_stream_sign_SOURCES = test_stream_sign.c
test_stream_sign_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_stream_sign_LDADD = $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_transport_SOURCES = test_stcp_link.c
test_transport_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_transport_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_transport_LDADD = $(STCP_LINK_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_etcp_transport_SOURCES = test_etcp_stcp.c
test_etcp_transport_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_etcp_transport_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_etcp_transport_LDADD = $(STCP_LINK_OBJS) $(top_builddir)/src/utun-etcp_api.o $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_stcp_SOURCES = test_stcp.c
test_stcp_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_stcp_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_stcp_LDADD = $(top_builddir)/src/utun-stcp.o $(top_builddir)/src/utun-stcp_server.o $(top_builddir)/src/utun-stcp_client.o $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
if USE_OPENSSL
else
test_crypto_SOURCES = test_crypto.c
test_crypto_CFLAGS = -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source -I$(top_srcdir)/lib
test_crypto_LDADD = $(TINYCRYPT_OBJS) $(COMMON_LIBS)
endif
test_etcp_two_instances_SOURCES = test_etcp_two_instances.c
test_etcp_two_instances_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_etcp_two_instances_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_etcp_two_instances_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_etcp_simple_traffic_SOURCES = test_etcp_simple_traffic.c
test_etcp_simple_traffic_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_etcp_simple_traffic_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_etcp_simple_traffic_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_ipv6_sockets_SOURCES = test_ipv6_sockets.c
test_ipv6_sockets_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_ipv6_sockets_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_ipv6_sockets_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
# test_etcp_congestion — отключён (старый congestion control удалён)
#test_etcp_congestion_SOURCES = test_etcp_congestion.c
#test_etcp_congestion_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
#test_etcp_congestion_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
# test_etcp_reinit_inflight — отключён (старый congestion control удалён)
#test_etcp_reinit_inflight_SOURCES = test_etcp_reinit_inflight.c
#test_etcp_reinit_inflight_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
#test_etcp_reinit_inflight_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_tcp_proxy_client_SOURCES = test_tcp_proxy_client.c
test_tcp_proxy_client_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
@@ -271,35 +215,35 @@ test_route6_lib_LDADD = $(top_builddir)/src/utun-route6_lib.o \
test_etcp_minimal_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_etcp_100_packets_SOURCES = test_etcp_100_packets.c
test_etcp_100_packets_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_etcp_100_packets_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_etcp_100_packets_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_etcp_reconnect_SOURCES = test_etcp_reconnect.c
test_etcp_reconnect_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_etcp_reconnect_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_etcp_reconnect_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_pkt_normalizer_etcp_SOURCES = test_pkt_normalizer_etcp.c
test_pkt_normalizer_etcp_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_pkt_normalizer_etcp_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_pkt_normalizer_etcp_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_pkt_normalizer_standalone_SOURCES = test_pkt_normalizer_standalone.c
test_pkt_normalizer_standalone_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_pkt_normalizer_standalone_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_pkt_normalizer_standalone_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_etcp_api_SOURCES = test_etcp_api.c
test_etcp_api_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_etcp_api_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_etcp_api_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_etcp_ping_SOURCES = test_etcp_ping.c
test_etcp_ping_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_etcp_ping_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_etcp_ping_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_route_ping_SOURCES = test_route_ping.c
test_route_ping_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_route_ping_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_route_ping_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_nat_detection_SOURCES = test_nat_detection.c
test_nat_detection_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_nat_detection_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_nat_detection_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_nat_engine_SOURCES = test_nat_engine.c
@@ -307,7 +251,7 @@ test_nat_engine_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_nat_engine_LDADD = $(top_builddir)/src/utun-eim_nat.o $(top_builddir)/src/utun-config_parser.o $(COMMON_LIBS)
test_nat_transport_SOURCES = test_nat_transport.c
test_nat_transport_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_nat_transport_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_nat_transport_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_nat_stress_SOURCES = test_nat_stress.c
@@ -322,13 +266,6 @@ test_serialize_SOURCES = test_serialize.c
test_serialize_CFLAGS = -I$(top_srcdir)/lib
test_serialize_LDADD = $(COMMON_LIBS)
if USE_OPENSSL
else
test_ecc_encrypt_SOURCES = test_ecc_encrypt.c
test_ecc_encrypt_CFLAGS = -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source -I$(top_srcdir)/lib
test_ecc_encrypt_LDADD = $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS) -lcrypto
endif
test_intensive_memory_pool_SOURCES = test_intensive_memory_pool.c
test_intensive_memory_pool_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_intensive_memory_pool_LDADD = $(COMMON_LIBS)
@@ -366,21 +303,19 @@ test_config_debug_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_config_debug_LDADD = $(top_builddir)/src/utun-config_parser.o $(COMMON_LIBS)
test_route_lib_SOURCES = test_route_lib.c
test_route_lib_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/src -I$(top_srcdir)/tinycrypt/lib/include
test_route_lib_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_route_lib_LDADD = $(top_builddir)/src/utun-route_lib.o $(top_builddir)/src/utun-route_node.o $(top_builddir)/src/utun-etcp_debug.o $(COMMON_LIBS)
test_bgp_route_exchange_SOURCES = test_bgp_route_exchange.c
test_bgp_route_exchange_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_bgp_route_exchange_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_bgp_route_exchange_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
# test_dummynet временно исключен (медленный)
# test_dummynet_SOURCES = test_dummynet.c
# test_dummynet_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
# test_dummynet_LDADD = $(top_builddir)/src/utun-dummynet.o $(COMMON_LIBS)
test_conn_mgr_SOURCES = test_conn_mgr.c
test_conn_mgr_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_conn_mgr_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_bbr_integration_SOURCES = bbr_integration/test_bbr_integration.c
test_bbr_integration_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_bbr_integration_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_bbr_integration_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
bench_timeout_heap_SOURCES = bench_timeout_heap.c
@@ -391,9 +326,6 @@ bench_uasync_timeouts_SOURCES = bench_uasync_timeouts.c
bench_uasync_timeouts_CFLAGS = -I$(top_srcdir)/lib
bench_uasync_timeouts_LDADD = $(COMMON_LIBS)
# Build tinycrypt objects before tests that need them
BUILT_SOURCES = $(TINYCRYPT_BUILT)
# Copy test configs to build directory (tests run from build/tests/)
all-local: copy-test-configs
+4 -8
View File
@@ -334,15 +334,11 @@ int main(void) {
if (!ctx.ua) { printf("ERROR: uasync_create failed\n"); return 1; }
const char* s_priv =
"67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb";
const char* s_pub =
"1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17"
"c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9";
"38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68";
const char* s_pub = "ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a";
const char* c_priv =
"4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2";
const char* c_pub =
"c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3"
"c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71";
"704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f";
const char* c_pub = "b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01";
printf("Creating instances...\n");
ctx.sender = create_instance(ctx.ua, 0x1111111111111111ULL, c_priv, c_pub);
+3 -3
View File
@@ -1,7 +1,7 @@
[global]
my_node_id=0xAAAA000000000002
my_private_key=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2
my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71
my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f
my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01
tun_ip=10.200.20.1/24
tun_ifname=tun_test_cli
debug_level=error
@@ -13,7 +13,7 @@ type=public
[client: c1]
keepalive=1
link=s1:127.0.0.1:15001
peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9
peer_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a
[tcp_proxy_client]
enabled=yes
+2 -2
View File
@@ -1,7 +1,7 @@
[global]
my_node_id=0xAAAA000000000001
my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb
my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9
my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68
my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a
tun_ip=10.200.10.1/24
tun_ifname=tun_test_exit
debug_level=error
+2 -2
View File
@@ -26,8 +26,8 @@ static char config_path[256];
static const char* config_content =
"[global]\n"
"my_node_id=0x1111111111111111\n"
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n"
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"tun_ip=10.99.0.1/24\n"
"tun_ifname=tun99\n"
"# Debug and logging configuration\n"
+136
View File
@@ -0,0 +1,136 @@
/**
* @file test_conn_mgr.c
* @brief Connection Manager — smoke test
*
* Test 1: Direct via existing ETCP conn → CONN_TYPE_DIRECT
* Test 2: Status check → state/type
* Test 3: Alien node → NODEINFO_Q.alien=1
*
* Pending:
* Test 4: New link UP callback (INIT to new peer via conn_mgr)
* Test 5: Indirect through intermediary (needs full Phase 3 exchange)
* Test 6: Idle timeout
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include "../lib/platform_compat.h"
#include "test_utils.h"
#ifndef _WIN32
#include <unistd.h>
#endif
#include "../src/etcp.h"
#include "../src/etcp_connections.h"
#include "../src/config_parser.h"
#include "../src/config_updater.h"
#include "../src/utun_instance.h"
#include "../src/route_bgp.h"
#include "../src/conn_mgr.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#define TIMEOUT_TB 300000
#define POLL_MS 5
#define NID_A 0xAAAA000000000001ULL
#define NID_B 0xBBBB000000000002ULL
static struct UTUN_INSTANCE* g_a = NULL, *g_b = NULL;
static struct UASYNC* ua = NULL;
static volatile int result = 0;
static void* ttimer = NULL;
static char tdir[] = "/tmp/utun_cm_XXXXXX";
static char ca[256], cb[256];
static int pa = 0, pb = 0;
static volatile int cdone = 0, cresult = 0;
static int wf(const char* p, const char* f, ...) {
va_list ap; FILE* fp = fopen(p, "w"); if (!fp) return -1;
va_start(ap, f); vfprintf(fp, f, ap); va_end(ap); fclose(fp); return 0;
}
static char* gv(const char* p, const char* k) {
struct utun_config* c = parse_config(p); if (!c) return NULL;
char* r = (strcmp(k, "pub") == 0) ? u_strdup(c->global.my_public_key_hex) : u_strdup(c->global.my_private_key_hex);
free_config(c); return r;
}
static int lks(struct UTUN_INSTANCE* i) {
int n = 0; struct ETCP_CONN* c = i->connections;
while (c) { struct ETCP_LINK* l = c->links; while (l) { if (l->initialized && l->link_status) n++; l = l->next; } c = c->next; }
return n;
}
static void fail(const char* msg) { fprintf(stderr, "FAIL: %s\n", msg); fflush(stderr); result = 2; }
static void test1(void* arg);
static void test2(void* arg);
static void test3(void* arg);
static void to_cb(void* arg) { (void)arg; fprintf(stderr, "TIMEOUT\n"); result = 2; }
static void ccb(int r, uint64_t id, void* arg) {
(void)arg; fprintf(stderr, "connect_cb: result=%d node=0x%llx\n", r, (unsigned long long)id); fflush(stderr);
cdone = 1; cresult = r;
}
static void test1(void* arg) {
(void)arg; if (result) return;
if (!nodeinfo_find_by_id(g_a->bgp, NID_B)) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test1, "t1"); return; }
fprintf(stderr, "Test 1: direct — conn_mgr_connect_node(B)\n"); fflush(stderr);
conn_mgr_connect_node(g_a->conn_mgr, NID_B, 0, ccb, NULL);
uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test2, "t2a");
}
static void test2(void* arg) {
(void)arg; if (result) return;
if (!cdone) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test2, "t2b"); return; }
if (cresult != CONN_MGR_OK) { fail("connect failed"); return; }
uint8_t st, ty; conn_mgr_get_status(g_a->conn_mgr, NID_B, &st, &ty);
fprintf(stderr, "Test 2: status state=%d type=%d\n", st, ty); fflush(stderr);
if (st == CONN_MGR_STATE_CONNECTED && ty == CONN_TYPE_DIRECT) fprintf(stderr, " OK: DIRECT\n");
else { fail("status mismatch"); return; }
test3(NULL);
}
static void test3(void* arg) {
(void)arg;
uint64_t aid = 0xEEEE000000000001ULL;
struct NODEINFO ni; memset(&ni, 0, sizeof(ni)); ni.node_id = aid; ni.ver = 1;
conn_mgr_add_alien_node(g_a->conn_mgr, (uint8_t*)&ni, sizeof(ni));
struct NODEINFO_Q* nq = nodeinfo_find_by_id(g_a->bgp, aid);
fprintf(stderr, "Test 3: alien alien=%d\n", nq ? nq->alien : -1); fflush(stderr);
if (nq && nq->alien == 1) fprintf(stderr, " OK: alien flag set\n");
else { fail("alien flag not set"); return; }
fprintf(stderr, "=== ALL DONE ===\n"); fflush(stderr);
result = (result == 0) ? 1 : 2;
}
static void setup(void) {
test_mkdtemp(tdir);
int base = 47000 + (getpid() % 15000); pa = base; pb = base + 1;
snprintf(ca, sizeof(ca), "%s/a.conf", tdir); snprintf(cb, sizeof(cb), "%s/b.conf", tdir);
wf(ca, "[global]\nmy_node_id=0x%llx\ntun_ip=10.99.0.1/24\ntun_ifname=tun99\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n", NID_A, pa);
wf(cb, "[global]\nmy_node_id=0x%llx\ntun_ip=10.99.0.2/24\ntun_ifname=tun98\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n", NID_B, pb);
config_ensure_keys_and_node_id(ca); config_ensure_keys_and_node_id(cb);
char *p0 = gv(ca,"pub"), *r0 = gv(ca,"priv"), *p1 = gv(cb,"pub"), *r1 = gv(cb,"priv");
wf(ca, "[global]\nmy_node_id=0x%llx\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.1/24\ntun_ifname=tun99\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[client: to_b]\nkeepalive=1\npeer_public_key=%s\nlink=s1:127.0.0.1:%d\n[allowed_keys]\nallow_all=1\n", NID_A, r0, p0, pa, p1, pb);
wf(cb, "[global]\nmy_node_id=0x%llx\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.2/24\ntun_ifname=tun98\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n", NID_B, r1, p1, pb);
u_free(p0); u_free(r0); u_free(p1); u_free(r1);
}
static void cleanup(void) { test_unlink(ca); test_unlink(cb); test_rmdir(tdir); }
int main(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN);
utun_instance_set_tun_init_enabled(0); setup();
ua = uasync_create();
g_a = utun_instance_create(ua, ca); g_b = utun_instance_create(ua, cb);
if (!g_a || !g_b) goto done;
utun_instance_init(g_a); utun_instance_init(g_b);
uasync_call_soon(ua, NULL, test1);
ttimer = uasync_set_timeout(ua, TIMEOUT_TB, NULL, to_cb, "to");
{ int el = 0; while (!result && el < TIMEOUT_TB + 5000) { uasync_poll(ua, POLL_MS); el += POLL_MS; } }
fprintf(stderr, "final result=%d\n", result); fflush(stderr);
done:
if (ttimer) uasync_cancel_timeout(ua, ttimer);
if (g_a) { g_a->running = 0; utun_instance_destroy(g_a); }
if (g_b) { g_b->running = 0; utun_instance_destroy(g_b); }
if (ua) { uasync_destroy(ua, 0); ua = NULL; }
cleanup();
return (result == 1) ? 0 : 1;
}
+5 -5
View File
@@ -41,8 +41,8 @@ static char client_config_path[256];
static const char* server_config_content =
"[global]\n"
"my_node_id=0x1111111111111111\n"
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n"
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"tun_ip=10.99.0.1/24\n"
"tun_ifname=tun99\n"
"\n"
@@ -57,8 +57,8 @@ static const char* server_config_content =
static const char* client_config_content =
"[global]\n"
"my_node_id=0x2222222222222222\n"
"my_private_key=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2\n"
"my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n"
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
"tun_ip=10.99.0.2/24\n"
"tun_ifname=tun98\n"
"\n"
@@ -68,7 +68,7 @@ static const char* client_config_content =
"\n"
"[client: test_client]\n"
"keepalive=1\n"
"peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"peer_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"link=test:127.0.0.1:9021\n";
// Create temp config files
+5 -5
View File
@@ -48,8 +48,8 @@ static char client_config_path[256];
static const char* server_config_content =
"[global]\n"
"my_node_id=0x1111111111111111\n"
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n"
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"tun_ip=10.99.0.1/24\n"
"tun_ifname=tun99\n"
"\n"
@@ -64,8 +64,8 @@ static const char* server_config_content =
static const char* client_config_content =
"[global]\n"
"my_node_id=0x2222222222222222\n"
"my_private_key=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2\n"
"my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n"
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
"tun_ip=10.99.0.2/24\n"
"tun_ifname=tun98\n"
"\n"
@@ -75,7 +75,7 @@ static const char* client_config_content =
"\n"
"[client: test_client]\n"
"keepalive=1\n"
"peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"peer_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"link=test:127.0.0.1:9031\n";
// Create temp config files
+4 -4
View File
@@ -298,10 +298,10 @@ int main(void) {
if (!ctx.ua) { printf("uasync failed\n"); return 1; }
/* Ключи */
const char* s_priv = "67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb";
const char* s_pub = "1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9";
const char* c_priv = "4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2";
const char* c_pub = "c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71";
const char* s_priv = "38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68";
const char* s_pub = "ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a";
const char* c_priv = "704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f";
const char* c_pub = "b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01";
printf("Creating instances...\n");
ctx.sender = create_instance(ctx.ua, 0x1111111111111111ULL, c_priv, c_pub);
+4 -4
View File
@@ -186,10 +186,10 @@ int main(void) {
printf("Using fixed keys...\n");
/* These keys are copied from test_etcp_two_instances.c and are known to work */
const char* s_priv = "67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb";
const char* s_pub = "1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9";
const char* c_priv = "4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2";
const char* c_pub = "c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71";
const char* s_priv = "38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68";
const char* s_pub = "ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a";
const char* c_priv = "704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f";
const char* c_pub = "b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01";
printf("Creating instances...\n");
server = create_instance(ua, 0x1111111111111111ULL, s_priv, s_pub);
+5 -5
View File
@@ -32,8 +32,8 @@ static int pong_received = 0;
static const char* server_config_content =
"[global]\n"
"my_node_id=0x1111111111111111\n"
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n"
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"tun_ip=10.99.0.1/24\n"
"tun_ifname=tun99\n"
"\n"
@@ -47,8 +47,8 @@ static const char* server_config_content =
static const char* client_config_content =
"[global]\n"
"my_node_id=0x2222222222222222\n"
"my_private_key=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2\n"
"my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n"
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
"tun_ip=10.99.0.2/24\n"
"tun_ifname=tun98\n"
"\n"
@@ -58,7 +58,7 @@ static const char* client_config_content =
"\n"
"[client:test_client]\n"
"keepalive=1\n"
"peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"peer_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"link=test:127.0.0.1:9011\n";
static int create_temp_configs(void) {
+5 -5
View File
@@ -70,8 +70,8 @@ static int create_temp_configs(void) {
fprintf(f,
"[global]\n"
"my_node_id=0x1111111111111111\n"
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n"
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"tun_ip=10.99.0.1/24\n"
"tun_ifname=tun99\n"
"\n"
@@ -89,8 +89,8 @@ static int create_temp_configs(void) {
fprintf(f,
"[global]\n"
"my_node_id=0x2222222222222222\n"
"my_private_key=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2\n"
"my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n"
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
"tun_ip=10.99.0.2/24\n"
"tun_ifname=tun98\n"
"\n"
@@ -100,7 +100,7 @@ static int create_temp_configs(void) {
"\n"
"[client: test_client]\n"
"keepalive=1\n"
"peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"peer_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"link=test:127.0.0.1:%d\n",
client_port, server_port);
fclose(f);
+10 -9
View File
@@ -144,7 +144,7 @@ static int buffers_full(struct test_ctx* ctx) {
struct ETCP_CONN* conn = ctx->sender->connections;
if (!conn) return 0;
struct ETCP_LINK* link = conn->links;
return link && link->send_blocked_inflight
return link && link->inflight_bytes >= link->inflight_lim_bytes
&& conn->input_wait_ack->count > 5
&& ctx->total_packets_sent >= TOTAL_PACKETS;
}
@@ -190,8 +190,9 @@ static void monitor(void* arg) {
break;
case 2: // fill buffers
if (buffers_full(ctx)) {
printf(" buffers full: inflight_blocked=%d wait_ack=%d sent=%d\n",
ctx->sender->connections->links->send_blocked_inflight,
printf(" buffers full: inflight=%u/%u wait_ack=%d sent=%d\n",
ctx->sender->connections->links->inflight_bytes,
ctx->sender->connections->links->inflight_lim_bytes,
ctx->sender->connections->input_wait_ack->count,
ctx->total_packets_sent);
ctx->phase = 3;
@@ -203,8 +204,8 @@ static void monitor(void* arg) {
utun_instance_destroy(ctx->receiver);
ctx->receiver = NULL;
{
const char* s_priv = "67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb";
const char* s_pub = "1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9";
const char* s_priv = "38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68";
const char* s_pub = "ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a";
ctx->receiver = create_instance(ctx->ua, 0x2222222222222222ULL, s_priv, s_pub);
add_server(ctx->receiver, "srv1", SRV_PORT);
utun_instance_init(ctx->receiver);
@@ -274,10 +275,10 @@ int main(void) {
ctx.ua = uasync_create();
if (!ctx.ua) { printf("uasync_create failed\n"); return 1; }
const char* s_priv = "67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb";
const char* s_pub = "1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9";
const char* c_priv = "4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2";
const char* c_pub = "c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71";
const char* s_priv = "38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68";
const char* s_pub = "ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a";
const char* c_priv = "704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f";
const char* c_pub = "b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01";
ctx.sender = create_instance(ctx.ua, 0x1111111111111111ULL, c_priv, c_pub);
ctx.receiver = create_instance(ctx.ua, 0x2222222222222222ULL, s_priv, s_pub);
+5 -5
View File
@@ -49,8 +49,8 @@ static void* g_mon_id = NULL;
static const char* srv_cfg =
"[global]\n"
"my_node_id=0x1111111111111111\n"
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n"
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"tun_ip=10.99.0.1/24\n"
"tun_ifname=tun99\n"
"[server: s1]\n"
@@ -63,8 +63,8 @@ static const char* srv_cfg =
static const char* cli_cfg =
"[global]\n"
"my_node_id=0x2222222222222222\n"
"my_private_key=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2\n"
"my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n"
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
"tun_ip=10.99.0.2/24\n"
"tun_ifname=tun98\n"
"[server: s1]\n"
@@ -72,7 +72,7 @@ static const char* cli_cfg =
"type=public\n"
"[client: c1]\n"
"keepalive=1\n"
"peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"peer_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"link=s1:127.0.0.1:9041\n";
// ======================== Test state ========================
+201
View File
@@ -11,6 +11,7 @@
#include "../src/etcp.h"
#include "../src/etcp_api.h"
#include "../src/etcp_router.h"
#include "../src/route_bgp.h"
#include "../src/utun_instance.h"
#include "../lib/u_async.h"
#include "../lib/ll_queue.h"
@@ -122,6 +123,85 @@ static int test_total = 0, test_passed = 0, test_failed = 0;
#define PASS() do { puts("PASS"); test_passed++; } while(0)
#define FAIL(msg) do { printf("FAIL: %s\n", msg); test_failed++; return -1; } while(0)
// ======================== Sign test infrastructure ========================
static int g_sign_ready = 0;
static struct SC_MYKEYS g_sign_keys;
static sc_context_t g_sign_ctx;
static uint8_t g_sign_ed25519_pubkey[SC_PUBKEY_SIZE];
static const uint64_t SIGNER_NODE = 0xCCCC000000000001ULL;
static int setup_sign_keys(void) {
if (g_sign_ready) return 0;
if (sc_generate_keypair(&g_sign_keys) != SC_OK) { printf(" setup_sign_keys: generate_keypair failed\n"); return -1; }
sc_init_ctx(&g_sign_ctx, &g_sign_keys);
if (sc_derive_ed25519_pubkey(g_sign_keys.private_key, g_sign_ed25519_pubkey) != SC_OK) { printf(" setup_sign_keys: derive_ed25519_pubkey failed\n"); return -1; }
g_sign_ready = 1;
return 0;
}
static void setup_bgp_for_sign_test(struct UTUN_INSTANCE* inst, int with_ed25519_key) {
struct ROUTE_BGP* bgp = u_calloc(1, sizeof(struct ROUTE_BGP));
if (!bgp) { printf(" setup_bgp: u_calloc failed\n"); return; }
bgp->instance = inst;
bgp->nodes = queue_new(inst->ua, BGP_NODES_HASH_SIZE,
offsetof(struct NODEINFO_Q, node.node_id) - sizeof(struct ll_entry), 8, "bgp_nodes_test");
if (!bgp->nodes) { printf(" setup_bgp: queue_new failed\n"); u_free(bgp); return; }
inst->bgp = bgp;
size_t dsize = sizeof(struct NODEINFO_Q) - sizeof(struct ll_entry);
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)queue_entry_new(dsize);
if (!nq) { printf(" setup_bgp: queue_entry_new failed\n"); return; }
memset(&nq->node, 0, sizeof(nq->node));
nq->node.node_id = SIGNER_NODE;
if (with_ed25519_key) memcpy(nq->node.ed25519_public_key, g_sign_ed25519_pubkey, SC_PUBKEY_SIZE);
queue_data_put_with_index(bgp->nodes, &nq->ll);
}
static void teardown_bgp_for_sign_test(struct UTUN_INSTANCE* inst) {
if (!inst || !inst->bgp) return;
if (inst->bgp->nodes) {
struct ll_entry* e;
while ((e = queue_data_get(inst->bgp->nodes)) != NULL) queue_entry_free(e);
queue_free(inst->bgp->nodes);
}
u_free(inst->bgp);
inst->bgp = NULL;
}
static void inject_signed(etcp_recv_fn recv_cb, struct UTUN_INSTANCE* inst,
uint64_t src, uint8_t svc_id, uint32_t seq,
const uint8_t* pl, size_t pl_len, uint8_t flags,
int tamper_sig) {
size_t hdr_pl = SVC_ROUTE_HDR_SIZE + pl_len;
size_t total = hdr_pl + SC_SIGN_SIZE;
uint8_t* dgram = u_malloc(total);
if (!dgram) { printf(" inject_signed: u_malloc failed\n"); return; }
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)dgram;
memset(hdr, 0, SVC_ROUTE_HDR_SIZE);
hdr->cmd = ETCP_ID_SVC_ROUTE;
hdr->dst_node_id = inst->node_id;
hdr->src_node_id = src;
hdr->seq = seq;
hdr->svc_id = svc_id;
hdr->flags = flags | ROUTER_FLAG_SIGNED;
if (pl_len > 0) memcpy(dgram + SVC_ROUTE_HDR_SIZE, pl, pl_len);
struct sc_stream_sign_state sign_state;
if (sc_stream_sign_init(&g_sign_ctx, &sign_state) != SC_OK) { u_free(dgram); return; }
if (sc_stream_sign_update(&sign_state, dgram, hdr_pl) != SC_OK) { sc_stream_sign_cleanup(&sign_state); u_free(dgram); return; }
size_t sig_len = SC_SIGN_SIZE;
if (sc_stream_sign_final(&sign_state, dgram + hdr_pl, &sig_len) != SC_OK) { u_free(dgram); return; }
if (tamper_sig) dgram[hdr_pl] ^= 0x01;
struct ll_entry* e = queue_entry_new(0);
if (!e) { u_free(dgram); return; }
e->dgram = dgram;
e->len = total;
recv_cb(&fake_conn, e);
}
// ======================== Tests ========================
static int test_init_destroy(void) {
@@ -688,6 +768,118 @@ static int test_server_reinit(void) {
return 0;
}
// ======================== Signed message tests ========================
static int test_sign_verify_ok(void) {
TEST("signed packet — verify OK, delivered to handler");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
setup_bgp_for_sign_test(&inst, 1);
rx_reset(0xF1);
uint8_t data[] = { TEST_SVC_ID, 0xF1, 0 };
inject_signed(recv_cb, &inst, SIGNER_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 0);
if (rx.delivered != 1) FAIL("signed packet not delivered");
if (rx.errors != 0) FAIL("data mismatch on signed packet");
teardown_bgp_for_sign_test(&inst);
TEARDOWN();
PASS();
return 0;
}
static int test_sign_bad_signature(void) {
TEST("signed packet — tampered signature → dropped");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
setup_bgp_for_sign_test(&inst, 1);
rx_reset(0xF2);
uint8_t data[] = { TEST_SVC_ID, 0xF2, 0 };
inject_signed(recv_cb, &inst, SIGNER_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 1 /*tamper*/);
if (rx.delivered != 0) FAIL("tampered signed packet should be dropped");
if (rx.errors != 0) FAIL("handler should not be called for bad signature");
teardown_bgp_for_sign_test(&inst);
TEARDOWN();
PASS();
return 0;
}
static int test_sign_unknown_node(void) {
TEST("signed packet — sender not in routing table → dropped");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
setup_bgp_for_sign_test(&inst, 1);
rx_reset(0xF3);
uint8_t data[] = { TEST_SVC_ID, 0xF3, 0 };
uint64_t unknown = 0xDEAD000000000001ULL;
inject_signed(recv_cb, &inst, unknown, TEST_SVC_ID, 0, data + 1, 2, 0, 0);
if (rx.delivered != 0) FAIL("packet from unknown node should be dropped");
if (rx.errors != 0) FAIL("handler not called for unknown node");
teardown_bgp_for_sign_test(&inst);
TEARDOWN();
PASS();
return 0;
}
static int test_sign_no_ed25519_key(void) {
TEST("signed packet — node in table but no Ed25519 key → dropped");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
setup_bgp_for_sign_test(&inst, 0 /*zero ed25519 key*/);
rx_reset(0xF4);
uint8_t data[] = { TEST_SVC_ID, 0xF4, 0 };
inject_signed(recv_cb, &inst, SIGNER_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 0);
if (rx.delivered != 0) FAIL("packet with zero ed25519 key should be dropped");
teardown_bgp_for_sign_test(&inst);
TEARDOWN();
PASS();
return 0;
}
static int test_sign_too_short(void) {
TEST("signed packet — SIGNED flag but no signature bytes → dropped");
struct UASYNC* ua; struct UTUN_INSTANCE inst; etcp_recv_fn recv_cb;
SETUP();
setup_bgp_for_sign_test(&inst, 1);
rx_reset(0xF5);
struct SVC_ROUTE_HDR hdr;
memset(&hdr, 0, sizeof(hdr));
hdr.cmd = ETCP_ID_SVC_ROUTE;
hdr.dst_node_id = inst.node_id;
hdr.src_node_id = SIGNER_NODE;
hdr.seq = 0;
hdr.svc_id = TEST_SVC_ID;
hdr.flags = ROUTER_FLAG_SIGNED;
uint8_t payload[] = { TEST_SVC_ID, 0xF5, 0 };
size_t total = SVC_ROUTE_HDR_SIZE + sizeof(payload);
struct ll_entry* e = queue_entry_new(0);
if (!e) FAIL("queue_entry_new");
e->dgram = u_malloc(total);
if (!e->dgram) { queue_entry_free(e); FAIL("u_malloc"); }
memcpy(e->dgram, &hdr, SVC_ROUTE_HDR_SIZE);
memcpy(e->dgram + SVC_ROUTE_HDR_SIZE, payload, sizeof(payload));
e->len = total;
recv_cb(&fake_conn, e);
if (rx.delivered != 0) FAIL("too-short signed packet should be dropped");
teardown_bgp_for_sign_test(&inst);
TEARDOWN();
PASS();
return 0;
}
// ======================== Main ========================
int main(void) {
// Run all tests
@@ -715,6 +907,15 @@ int main(void) {
test_sess_id_wrap();
test_server_reinit();
if (setup_sign_keys() != 0) { printf(" SKIP signed tests — crypto init failed\n"); }
else {
test_sign_verify_ok();
test_sign_bad_signature();
test_sign_unknown_node();
test_sign_no_ed25519_key();
test_sign_too_short();
}
printf("\n=== Results: %d/%d passed, %d failed ===\n", test_passed, test_total, test_failed);
return test_failed > 0 ? 1 : 0;
}
+5 -5
View File
@@ -63,8 +63,8 @@ static int create_temp_configs(void) {
fprintf(f,
"[global]\n"
"my_node_id=0x1111111111111111\n"
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n"
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"tun_ip=10.99.0.1/24\n"
"tun_ifname=tun99\n"
"\n"
@@ -82,8 +82,8 @@ static int create_temp_configs(void) {
fprintf(f,
"[global]\n"
"my_node_id=0x2222222222222222\n"
"my_private_key=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2\n"
"my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n"
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
"tun_ip=10.99.0.2/24\n"
"tun_ifname=tun98\n"
"\n"
@@ -93,7 +93,7 @@ static int create_temp_configs(void) {
"\n"
"[client: test_client]\n"
"keepalive=1\n"
"peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"peer_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"link=test:127.0.0.1:%d\n",
client_port, server_port);
fclose(f);
+1 -1
View File
@@ -18,7 +18,7 @@ static int test_failed = 0;
static struct SC_MYKEYS s_keys, c_keys;
#define TASSERT(cond) do { \
if (!(cond)) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, " FAIL: %s", #cond); test_failed = 1; return; } \
if (!(cond)) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, " FAIL: %s", #cond); test_failed = 1; return test_failed; } \
} while(0)
static int g_srv_recv_count = 0;
+5 -5
View File
@@ -41,8 +41,8 @@ static char client_config_path[256];
static const char* server_config_content =
"[global]\n"
"my_node_id=0x1111111111111111\n"
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n"
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"tun_ip=10.99.0.1/24\n"
"tun_ifname=tun99\n"
"\n"
@@ -57,8 +57,8 @@ static const char* server_config_content =
static const char* client_config_content =
"[global]\n"
"my_node_id=0x2222222222222222\n"
"my_private_key=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2\n"
"my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n"
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
"tun_ip=10.99.0.2/24\n"
"tun_ifname=tun98\n"
"\n"
@@ -68,7 +68,7 @@ static const char* client_config_content =
"\n"
"[client: test_client]\n"
"keepalive=1\n"
"peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"peer_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"link=test:127.0.0.1:9011\n";
// Create temp config files
+5 -5
View File
@@ -41,13 +41,13 @@ static const char* cfg_node_client(void) {
snprintf(buf, sizeof(buf),
"[global]\n"
"my_node_id=0xEEEE000000000001\n"
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n"
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"tun_ip=10.99.0.1/24\n"
"tun_ifname=tun99\n"
"[server: s1]\naddr=127.0.0.1:9081\ntype=public\n"
"[client: c1]\nkeepalive=1\nlink=s1:127.0.0.1:9082\n"
"peer_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n"
"peer_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
"[tcp_proxy_client]\n"
"enabled=yes\n"
"tun_name=tun_tcp\n"
@@ -61,8 +61,8 @@ static const char* cfg_node_exit(void) {
snprintf(buf, sizeof(buf),
"[global]\n"
"my_node_id=0xEEEE000000000002\n"
"my_private_key=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2\n"
"my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n"
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
"tun_ip=10.99.0.2/24\n"
"tun_ifname=tun98\n"
"[server: s1]\naddr=127.0.0.1:9082\ntype=public\n"
+6 -6
View File
@@ -56,8 +56,8 @@ static const char* server_config =
"[global]\n"
"my_node_name=server_v6\n"
"my_node_id=0xAAAA000000000001\n"
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n"
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"tun_ip=10.99.0.1/24\n"
"tun_ifname=tun99\n"
"tun_test_mode=1\n"
@@ -76,8 +76,8 @@ static const char* client_config =
"[global]\n"
"my_node_name=client_v6\n"
"my_node_id=0xBBBB000000000002\n"
"my_private_key=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2\n"
"my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n"
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
"tun_ip=10.99.0.2/24\n"
"tun_ifname=tun98\n"
"tun_test_mode=1\n"
@@ -88,7 +88,7 @@ static const char* client_config =
"\n"
"[client: v6client]\n"
"keepalive=1\n"
"peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"peer_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"link=v6srv:[::1]:40111\n";
static int create_temp_configs(void) {
@@ -221,7 +221,7 @@ static int verify_remote_v6_nodeinfo(const char* name, struct UTUN_INSTANCE* ins
printf("FAIL [%s]: no bgp\n", name);
return 0;
}
struct NODEINFO_Q* nq = route_bgp_get_node(inst->bgp, peer_node_id);
struct NODEINFO_Q* nq = nodeinfo_find_by_id(inst->bgp, peer_node_id);
if (!nq) {
printf("FAIL [%s]: remote node %016llx not found\n", name, (unsigned long long)peer_node_id);
return 0;
+8 -8
View File
@@ -244,16 +244,16 @@ int main(void) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Waiting for BGP exchange (S learns C1 and C2)...");
int bgp_wait_cycles = 0;
while (!test_timed_out && bgp_wait_cycles < 500) {
if (inst_s->bgp && route_bgp_get_node(inst_s->bgp, NODE_ID_C1) != NULL &&
route_bgp_get_node(inst_s->bgp, NODE_ID_C2) != NULL) {
if (inst_s->bgp && nodeinfo_find_by_id(inst_s->bgp, NODE_ID_C1) != NULL &&
nodeinfo_find_by_id(inst_s->bgp, NODE_ID_C2) != NULL) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "S learned about C1 and C2");
break;
}
uasync_poll(ua, 10);
bgp_wait_cycles++;
}
if (!inst_s->bgp || route_bgp_get_node(inst_s->bgp, NODE_ID_C1) == NULL ||
route_bgp_get_node(inst_s->bgp, NODE_ID_C2) == NULL) {
if (!inst_s->bgp || nodeinfo_find_by_id(inst_s->bgp, NODE_ID_C1) == NULL ||
nodeinfo_find_by_id(inst_s->bgp, NODE_ID_C2) == NULL) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "S did not learn about C1/C2 in time");
goto cleanup;
}
@@ -313,7 +313,7 @@ int main(void) {
}
// 5. Verify all NAT fields on server
struct NODEINFO_Q* node_c1 = route_bgp_get_node(inst_s->bgp, NODE_ID_C1);
struct NODEINFO_Q* node_c1 = nodeinfo_find_by_id(inst_s->bgp, NODE_ID_C1);
if (!node_c1) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "NODEINFO_Q for C1 disappeared");
goto cleanup;
@@ -408,7 +408,7 @@ int main(void) {
bgp_wait_cycles = 0;
int c2_verified_nat = 0;
while (!test_timed_out && bgp_wait_cycles < 500) {
struct NODEINFO_Q* node_c1_on_c2 = inst_c2->bgp ? route_bgp_get_node(inst_c2->bgp, NODE_ID_C1) : NULL;
struct NODEINFO_Q* node_c1_on_c2 = inst_c2->bgp ? nodeinfo_find_by_id(inst_c2->bgp, NODE_ID_C1) : NULL;
if (node_c1_on_c2) {
const struct NODEINFO_IPV4_SOCKET_META* meta = NULL;
int meta_count = get_node_v4_sockets_meta(node_c1_on_c2, &meta);
@@ -477,14 +477,14 @@ int main(void) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Waiting for C2 to learn C1 nodeinfo...");
bgp_wait_cycles = 0;
while (!test_timed_out && bgp_wait_cycles < 500) {
if (inst_c2->bgp && route_bgp_get_node(inst_c2->bgp, NODE_ID_C1) != NULL) {
if (inst_c2->bgp && nodeinfo_find_by_id(inst_c2->bgp, NODE_ID_C1) != NULL) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "C2 learned C1 nodeinfo");
break;
}
uasync_poll(ua, 10);
bgp_wait_cycles++;
}
if (!inst_c2->bgp || route_bgp_get_node(inst_c2->bgp, NODE_ID_C1) == NULL) {
if (!inst_c2->bgp || nodeinfo_find_by_id(inst_c2->bgp, NODE_ID_C1) == NULL) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: C2 did not learn C1 nodeinfo in time");
goto cleanup;
}
+2 -2
View File
@@ -565,8 +565,8 @@ int main(void) {
DEBUG_INFO(DEBUG_CATEGORY_NAT, "Waiting for BGP...");
int bgp_cycles = 0;
while (!test_timed_out && bgp_cycles < 500) {
if (inst_provider->bgp && route_bgp_get_node(inst_provider->bgp, NODE_ID_CLIENT) &&
inst_client->bgp && route_bgp_get_node(inst_client->bgp, NODE_ID_PROVIDER)) {
if (inst_provider->bgp && nodeinfo_find_by_id(inst_provider->bgp, NODE_ID_CLIENT) &&
inst_client->bgp && nodeinfo_find_by_id(inst_client->bgp, NODE_ID_PROVIDER)) {
DEBUG_INFO(DEBUG_CATEGORY_NAT, "BGP exchanged");
break;
}
+5 -5
View File
@@ -40,8 +40,8 @@ static char client_config_path[256];
static const char* server_config_content =
"[global]\n"
"my_node_id=0x1111111111111111\n"
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n"
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"tun_ip=10.99.0.1/24\n"
"tun_ifname=tun99\n"
"\n"
@@ -55,8 +55,8 @@ static const char* server_config_content =
static const char* client_config_content =
"[global]\n"
"my_node_id=0x2222222222222222\n"
"my_private_key=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2\n"
"my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n"
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
"tun_ip=10.99.0.2/24\n"
"tun_ifname=tun98\n"
"\n"
@@ -66,7 +66,7 @@ static const char* client_config_content =
"\n"
"[client: test_client]\n"
"keepalive=1\n"
"peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"peer_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"link=test:127.0.0.1:9041\n";
static struct UTUN_INSTANCE* server_instance = NULL;
+3 -3
View File
@@ -248,14 +248,14 @@ int main(void) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Waiting for BGP exchange (B learns C)...");
int bgp_wait_cycles = 0;
while (!test_timed_out && bgp_wait_cycles < 500) {
if (inst_b->bgp && route_bgp_get_node(inst_b->bgp, NODE_ID_C) != NULL) {
if (inst_b->bgp && nodeinfo_find_by_id(inst_b->bgp, NODE_ID_C) != NULL) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "B learned about C");
break;
}
uasync_poll(ua, 10);
bgp_wait_cycles++;
}
if (!inst_b->bgp || route_bgp_get_node(inst_b->bgp, NODE_ID_C) == NULL) {
if (!inst_b->bgp || nodeinfo_find_by_id(inst_b->bgp, NODE_ID_C) == NULL) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "B did not learn about C in time");
goto cleanup;
}
@@ -271,7 +271,7 @@ int main(void) {
/* addr[] в network byte order, port в host byte order */
uint32_t target_ip = 0;
uint16_t target_port = 0;
struct NODEINFO_Q* nq = inst_b->bgp ? route_bgp_get_node(inst_b->bgp, NODE_ID_C) : NULL;
struct NODEINFO_Q* nq = inst_b->bgp ? nodeinfo_find_by_id(inst_b->bgp, NODE_ID_C) : NULL;
if (nq) {
const struct NODEINFO_IPV4_ADDR* addrs;
int sc = get_node_v4_addrs(nq, &addrs);
+1 -1
View File
@@ -15,7 +15,7 @@ static int test_failed = 0;
#define BASE_PORT 24567
#define TASSERT(cond) do { \
if (!(cond)) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, " FAIL: %s", #cond); test_failed = 1; return; } \
if (!(cond)) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, " FAIL: %s", #cond); test_failed = 1; return test_failed; } \
} while(0)
struct recv_ctx { uint8_t *data; size_t len, cap; int count; };
+230
View File
@@ -0,0 +1,230 @@
// test_stream_sign.c — tests for streaming Ed25519 signature
#include "../src/secure_channel.h"
#include "../lib/debug_config.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
static int test_failed = 0;
#define CHECK(expr, msg) do { \
if (!(expr)) { \
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "FAIL: %s", msg); \
test_failed = 1; \
} else { \
DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "PASS: %s", msg); \
} \
} while(0)
int main(void) {
debug_config_init();
debug_set_level(DEBUG_LEVEL_INFO);
debug_set_categories(DEBUG_CATEGORY_CRYPTO);
DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "=== Streaming Sign Test ===");
struct SC_MYKEYS keys_a;
CHECK(sc_generate_keypair(&keys_a) == SC_OK, "generate keypair A");
sc_context_t ctx_a;
CHECK(sc_init_ctx(&ctx_a, &keys_a) == SC_OK, "init ctx A");
uint8_t ed25519_pubkey_a[SC_PUBKEY_SIZE];
CHECK(sc_derive_ed25519_pubkey(keys_a.private_key, ed25519_pubkey_a) == SC_OK, "derive ed25519 pubkey from x25519");
// ==========================================
// Test 1: basic sign/verify — single chunk
// ==========================================
{
uint8_t data[1024];
for (int i = 0; i < (int)sizeof(data); i++) data[i] = (uint8_t)(i * 7 + 31);
struct sc_stream_sign_state signer, verifier;
CHECK(sc_stream_sign_init(&ctx_a, &signer) == SC_OK, "sign_init");
CHECK(sc_stream_sign_verify_init(&verifier, ed25519_pubkey_a) == SC_OK, "verify_init");
CHECK(sc_stream_sign_update(&signer, data, sizeof(data)) == SC_OK, "sign_update 1024B");
CHECK(sc_stream_sign_update(&verifier, data, sizeof(data)) == SC_OK, "verify_update 1024B");
uint8_t sig[SC_SIGN_SIZE];
size_t sig_len = sizeof(sig);
CHECK(sc_stream_sign_final(&signer, sig, &sig_len) == SC_OK, "sign_final");
CHECK(sig_len == SC_SIGN_SIZE, "signature size 64");
CHECK(sc_stream_sign_verify(&verifier, sig, sig_len) == SC_OK, "verify OK");
}
// ==========================================
// Test 2: incremental update — many small chunks
// ==========================================
{
uint8_t data[2048];
for (int i = 0; i < (int)sizeof(data); i++) data[i] = (uint8_t)(i * 3 + 17);
struct sc_stream_sign_state signer, verifier;
CHECK(sc_stream_sign_init(&ctx_a, &signer) == SC_OK, "inc: sign_init");
CHECK(sc_stream_sign_verify_init(&verifier, ed25519_pubkey_a) == SC_OK, "inc: verify_init");
for (int i = 0; i < (int)sizeof(data); i += 7) {
int chunk = ((int)sizeof(data) - i < 7) ? ((int)sizeof(data) - i) : 7;
CHECK(sc_stream_sign_update(&signer, data + i, chunk) == SC_OK, "inc: sign_update");
CHECK(sc_stream_sign_update(&verifier, data + i, chunk) == SC_OK, "inc: verify_update");
}
uint8_t sig[SC_SIGN_SIZE];
size_t sig_len = sizeof(sig);
CHECK(sc_stream_sign_final(&signer, sig, &sig_len) == SC_OK, "inc: sign_final");
CHECK(sc_stream_sign_verify(&verifier, sig, sig_len) == SC_OK, "inc: verify OK");
}
// ==========================================
// Test 3: tampered data — verify MUST fail
// ==========================================
{
uint8_t data[512];
for (int i = 0; i < (int)sizeof(data); i++) data[i] = (uint8_t)(i * 5 + 7);
struct sc_stream_sign_state signer, verifier;
CHECK(sc_stream_sign_init(&ctx_a, &signer) == SC_OK, "tamper: sign_init");
CHECK(sc_stream_sign_verify_init(&verifier, ed25519_pubkey_a) == SC_OK, "tamper: verify_init");
CHECK(sc_stream_sign_update(&signer, data, sizeof(data)) == SC_OK, "tamper: sign_update");
uint8_t sig[SC_SIGN_SIZE];
size_t sig_len = sizeof(sig);
CHECK(sc_stream_sign_final(&signer, sig, &sig_len) == SC_OK, "tamper: sign_final");
data[0] ^= 0x01;
CHECK(sc_stream_sign_update(&verifier, data, sizeof(data)) == SC_OK, "tamper: verify_update (modified)");
CHECK(sc_stream_sign_verify(&verifier, sig, sig_len) == SC_ERR_AUTH_FAILED, "tamper: verify FAIL (expected)");
}
// ==========================================
// Test 4: wrong public key — verify MUST fail
// ==========================================
{
struct SC_MYKEYS keys_b;
CHECK(sc_generate_keypair(&keys_b) == SC_OK, "generate keypair B");
uint8_t pubkey_b[SC_PUBKEY_SIZE];
CHECK(sc_derive_ed25519_pubkey(keys_b.private_key, pubkey_b) == SC_OK, "derive pubkey B");
uint8_t data[256];
for (int i = 0; i < (int)sizeof(data); i++) data[i] = (uint8_t)i;
struct sc_stream_sign_state signer, verifier;
CHECK(sc_stream_sign_init(&ctx_a, &signer) == SC_OK, "wrongkey: sign_init (A)");
CHECK(sc_stream_sign_verify_init(&verifier, pubkey_b) == SC_OK, "wrongkey: verify_init (B's key)");
CHECK(sc_stream_sign_update(&signer, data, sizeof(data)) == SC_OK, "wrongkey: sign_update");
CHECK(sc_stream_sign_update(&verifier, data, sizeof(data)) == SC_OK, "wrongkey: verify_update");
uint8_t sig[SC_SIGN_SIZE];
size_t sig_len = sizeof(sig);
CHECK(sc_stream_sign_final(&signer, sig, &sig_len) == SC_OK, "wrongkey: sign_final");
CHECK(sc_stream_sign_verify(&verifier, sig, sig_len) == SC_ERR_AUTH_FAILED, "wrongkey: verify FAIL (expected)");
}
// ==========================================
// Test 5: empty data — sign/verify empty message
// ==========================================
{
struct sc_stream_sign_state signer, verifier;
CHECK(sc_stream_sign_init(&ctx_a, &signer) == SC_OK, "empty: sign_init");
CHECK(sc_stream_sign_verify_init(&verifier, ed25519_pubkey_a) == SC_OK, "empty: verify_init");
uint8_t sig[SC_SIGN_SIZE];
size_t sig_len = sizeof(sig);
CHECK(sc_stream_sign_final(&signer, sig, &sig_len) == SC_OK, "empty: sign_final (no data)");
CHECK(sc_stream_sign_verify(&verifier, sig, sig_len) == SC_OK, "empty: verify OK");
}
// ==========================================
// Test 6: update with len=0 — should be no-op
// ==========================================
{
uint8_t data[64];
for (int i = 0; i < (int)sizeof(data); i++) data[i] = (uint8_t)(i * 11);
struct sc_stream_sign_state signer;
CHECK(sc_stream_sign_init(&ctx_a, &signer) == SC_OK, "zerolen: sign_init");
CHECK(sc_stream_sign_update(&signer, NULL, 0) == SC_OK, "zerolen: NULL+0 OK");
CHECK(sc_stream_sign_update(&signer, data, 0) == SC_OK, "zerolen: data+0 OK");
CHECK(sc_stream_sign_update(&signer, data, sizeof(data)) == SC_OK, "zerolen: sign_update 64B");
uint8_t sig[SC_SIGN_SIZE];
size_t sig_len = sizeof(sig);
CHECK(sc_stream_sign_final(&signer, sig, &sig_len) == SC_OK, "zerolen: sign_final");
}
// ==========================================
// Test 7: error handling — init
// ==========================================
{
struct sc_stream_sign_state state;
memset(&state, 0, sizeof(state));
CHECK(sc_stream_sign_init(NULL, &state) == SC_ERR_INVALID_ARG, "err: init NULL ctx");
CHECK(sc_stream_sign_init(&ctx_a, NULL) == SC_ERR_INVALID_ARG, "err: init NULL state");
sc_context_t no_pk_ctx;
memset(&no_pk_ctx, 0, sizeof(no_pk_ctx));
CHECK(sc_stream_sign_init(&no_pk_ctx, &state) == SC_ERR_NOT_INITIALIZED, "err: init uninitialized ctx");
CHECK(sc_stream_sign_verify_init(NULL, ed25519_pubkey_a) == SC_ERR_INVALID_ARG, "err: verify_init NULL state");
CHECK(sc_stream_sign_verify_init(&state, NULL) == SC_ERR_INVALID_ARG, "err: verify_init NULL pubkey");
}
// ==========================================
// Test 8: error handling — update
// ==========================================
{
struct sc_stream_sign_state state;
memset(&state, 0, sizeof(state));
CHECK(sc_stream_sign_update(NULL, (uint8_t*)"x", 1) == SC_ERR_INVALID_ARG, "err: update NULL state");
CHECK(sc_stream_sign_update(&state, NULL, 1) == SC_ERR_INVALID_ARG, "err: update NULL data");
CHECK(sc_stream_sign_update(&state, (uint8_t*)"x", 1) == SC_ERR_NOT_INITIALIZED, "err: update not init");
}
// ==========================================
// Test 9: error handling — final/verify on wrong init
// ==========================================
{
struct sc_stream_sign_state verifier;
CHECK(sc_stream_sign_verify_init(&verifier, ed25519_pubkey_a) == SC_OK, "err: verify_init for final test");
uint8_t sig[SC_SIGN_SIZE] = {0};
size_t sig_len = sizeof(sig);
CHECK(sc_stream_sign_final(&verifier, sig, &sig_len) == SC_ERR_NOT_INITIALIZED, "err: final on verify state");
sc_stream_sign_cleanup(&verifier);
}
{
struct sc_stream_sign_state signer;
CHECK(sc_stream_sign_init(&ctx_a, &signer) == SC_OK, "err: sign_init for verify test");
uint8_t sig[SC_SIGN_SIZE] = {0};
CHECK(sc_stream_sign_verify(&signer, sig, SC_SIGN_SIZE) == SC_ERR_NOT_INITIALIZED, "err: verify on sign state");
sc_stream_sign_cleanup(&signer);
}
// ==========================================
// Test 10: cleanup
// ==========================================
{
struct sc_stream_sign_state state;
memset(&state, 0, sizeof(state));
sc_stream_sign_cleanup(&state); // uninitialized — no crash
sc_stream_sign_cleanup(NULL); // NULL — no crash
CHECK(sc_stream_sign_init(&ctx_a, &state) == SC_OK, "cleanup: init");
sc_stream_sign_cleanup(&state);
CHECK(state.initialized == 0, "cleanup: initialized cleared");
}
if (test_failed) {
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "=== Streaming Sign Test: FAILED ===");
return 1;
}
DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "=== Streaming Sign Test: PASSED ===");
return 0;
}
+5 -5
View File
@@ -135,18 +135,18 @@ static void test_timeout(void* arg) {
static const char* cfg_exit(int srv_port) { static char b[1024]; snprintf(b,sizeof(b),
"[global]\nmy_node_id=0xCCCC000000000001\n"
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n"
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"tun_ip=10.99.0.1/24\ntun_ifname=tun99\n"
"[server:s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n[tcp_proxy_server]\nenabled=yes\n", srv_port); return b; }
static const char* cfg_b(int srv_port, int cli_port) { static char b[1024]; snprintf(b,sizeof(b),
"[global]\nmy_node_id=0xCCCC000000000002\n"
"my_private_key=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2\n"
"my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n"
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
"tun_ip=10.99.0.2/24\ntun_ifname=tun98\n"
"[server:s1]\naddr=127.0.0.1:%d\ntype=public\n[client:c1]\nkeepalive=1\nlink=s1:127.0.0.1:%d\n"
"peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"peer_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"[tcp_proxy_server]\nenabled=yes\n", srv_port, cli_port); return b; }
int main(void) {
+5 -5
View File
@@ -40,13 +40,13 @@ static const char* cfg_node_client(void) {
snprintf(buf, sizeof(buf),
"[global]\n"
"my_node_id=0xDDDD000000000001\n"
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n"
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"tun_ip=10.99.0.1/24\n"
"tun_ifname=tun99\n"
"[server: s1]\naddr=127.0.0.1:9071\ntype=public\n"
"[client: c1]\nkeepalive=1\nlink=s1:127.0.0.1:9072\n"
"peer_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n"
"peer_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
"[tcp_proxy_client]\n"
"enabled=yes\n"
"tun_name=tun_tcp\n"
@@ -60,8 +60,8 @@ static const char* cfg_node_exit(void) {
snprintf(buf, sizeof(buf),
"[global]\n"
"my_node_id=0xDDDD000000000002\n"
"my_private_key=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2\n"
"my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n"
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
"tun_ip=10.99.0.2/24\n"
"tun_ifname=tun98\n"
"[server: s1]\naddr=127.0.0.1:9072\ntype=public\n"
+83
View File
@@ -0,0 +1,83 @@
cmake_minimum_required(VERSION 3.16)
project(chatgui VERSION 0.1.0 LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
find_package(Qt5 COMPONENTS Widgets Network Test QUIET)
if(NOT Qt5_FOUND)
find_package(Qt6 COMPONENTS Widgets Network Test REQUIRED)
set(QT_LIBS Qt6::Widgets Qt6::Network)
set(QT_TEST_LIB Qt6::Test)
else()
set(QT_LIBS Qt5::Widgets Qt5::Network)
set(QT_TEST_LIB Qt5::Test)
endif()
find_package(rlottie REQUIRED)
find_package(ZLIB REQUIRED)
find_package(OpenSSL REQUIRED)
set(ZXING_READERS ON CACHE BOOL "")
set(ZXING_WRITERS "OLD" CACHE STRING "")
set(ZXING_ENABLE_QRCODE ON CACHE BOOL "")
set(ZXING_ENABLE_AZTEC OFF CACHE BOOL "")
set(ZXING_ENABLE_DATAMATRIX OFF CACHE BOOL "")
set(ZXING_ENABLE_MAXICODE OFF CACHE BOOL "")
set(ZXING_ENABLE_PDF417 OFF CACHE BOOL "")
set(ZXING_ENABLE_1D OFF CACHE BOOL "")
set(ZXING_C_API OFF CACHE BOOL "")
set(ZXING_EXAMPLES OFF CACHE BOOL "")
set(ZXING_EXAMPLES_QT OFF CACHE BOOL "")
set(ZXING_BLACKBOX_TESTS OFF CACHE BOOL "")
set(ZXING_UNIT_TESTS OFF CACHE BOOL "")
set(BUILD_SHARED_LIBS OFF CACHE BOOL "")
add_subdirectory(zxing-cpp)
set_target_properties(ZXing PROPERTIES AUTOMOC OFF)
add_executable(chatgui
src/main.cpp
src/mainwindow.cpp
src/channellist.cpp
src/channeldelegate.cpp
src/chatview.cpp
src/messagedelegate.cpp
src/messagelist.cpp
src/accountlist.cpp
src/emoji.cpp
src/emojipanel.cpp
src/emojitabbar.cpp
src/inputbar.cpp
src/lottieicon.cpp
src/animtimer.cpp
src/qrcode_utils.cpp
src/invite_link.cpp
src/invitedialog.cpp
transport/msg_client.cpp
transport/crypto.cpp
transport/chat_propagator.cpp
db/db_manager.cpp
db/sqlite3.c
resources/chatgui.qrc
)
target_include_directories(chatgui PRIVATE ${CMAKE_SOURCE_DIR}/db)
target_compile_definitions(chatgui PRIVATE SQLITE_THREADSAFE=1)
set_source_files_properties(db/sqlite3.c PROPERTIES LANGUAGE C)
target_link_libraries(chatgui PRIVATE ${QT_LIBS} rlottie::rlottie ZLIB::ZLIB OpenSSL::Crypto ZXing::ZXing pthread dl)
# ============================================================================
# Tests
# ============================================================================
enable_testing()
add_executable(test_crypto
tests/test_crypto.cpp
transport/crypto.cpp
)
target_include_directories(test_crypto PRIVATE ${CMAKE_SOURCE_DIR})
target_link_libraries(test_crypto PRIVATE Qt5::Core ${QT_TEST_LIB} OpenSSL::Crypto pthread)
add_test(NAME test_crypto COMMAND test_crypto)
+15
View File
@@ -0,0 +1,15 @@
BUILDDIR := build
all:
@[ -f $(BUILDDIR)/Makefile ] || cmake -S . -B $(BUILDDIR)
cmake --build $(BUILDDIR)
check:
@[ -f $(BUILDDIR)/Makefile ] || cmake -S . -B $(BUILDDIR)
cmake --build $(BUILDDIR) --target test_crypto
cd $(BUILDDIR) && ctest --output-on-failure
clean:
rm -rf $(BUILDDIR)
.PHONY: all check clean
+42
View File
@@ -0,0 +1,42 @@
@echo off
pushd "%~dp0\..\.."
echo ChatGUI Build Script for MSYS2 UCRT64
echo ===========================================
echo.
set MSYS2_PATH=C:\msys64
if not "%MSYS2_ROOT%"=="" set MSYS2_PATH=%MSYS2_ROOT%
if not "%MSYS2%"=="" set MSYS2_PATH=%MSYS2%
if not exist "%MSYS2_PATH%\msys2_shell.cmd" (
echo [ERROR] MSYS2 not found at %MSYS2_PATH%
echo Please install MSYS2 from https://www.msys2.org/
echo or set MSYS2_ROOT environment variable.
pause
exit /b 1
)
echo [INFO] MSYS2 found at: %MSYS2_PATH%
echo [INFO] Starting build...
echo.
set "CURRENT_DIR=%CD%"
call "%MSYS2_PATH%\msys2_shell.cmd" -ucrt64 -here -defterm -no-start -c "cd \"%CURRENT_DIR%/tools/chatgui\" && bash build.sh 2>&1 | tee build_win.log"
set BUILD_RESULT=%ERRORLEVEL%
echo.
echo ===========================================
if %BUILD_RESULT% == 0 (
echo [SUCCESS] Build completed!
echo [INFO] Executable: tools\chatgui\build\chatgui.exe
) else (
echo [FAILED] Build failed with error code: %BUILD_RESULT%
echo [INFO] Check tools\chatgui\build_win.log for details
)
echo ===========================================
echo.
pause
+44
View File
@@ -0,0 +1,44 @@
#!/bin/bash
set -eo pipefail
LOG_FILE="build_win.log"
> "$LOG_FILE"
log() { echo "$1"; echo "$1" >> "$LOG_FILE"; }
log "================================"
log "ChatGUI Build Script (MSYS2 UCRT64)"
log "================================"
log ""
SRC_DIR="$(cd "$(dirname "$0")" && pwd)"
BUILD_DIR="$SRC_DIR/build"
log "Source: $SRC_DIR"
log "Build: $BUILD_DIR"
log ""
log "[1/3] Configuring CMake..."
cmake -S "$SRC_DIR" -B "$BUILD_DIR" -G "MSYS Makefiles" 2>&1 | tee -a "$LOG_FILE"
test ${PIPESTATUS[0]} -eq 0 || { log "CMake configure FAILED"; exit 1; }
log ""
log "[2/3] Building..."
cmake --build "$BUILD_DIR" 2>&1 | tee -a "$LOG_FILE"
test ${PIPESTATUS[0]} -eq 0 || { log "Build FAILED"; exit 1; }
log ""
log "[3/3] Verifying..."
EXE="$BUILD_DIR/chatgui.exe"
if [ -f "$EXE" ]; then
SIZE=$(du -h "$EXE" | cut -f1)
log " OK: chatgui.exe ($SIZE)"
log ""
log "================================"
log "Build SUCCESS"
log "================================"
log "Executable: $EXE"
else
log " FAIL: chatgui.exe not found"
exit 1
fi
+201
View File
@@ -0,0 +1,201 @@
Архитектура чата (группового)
База данных: SQLite3 (WAL mode), chats.db
================================================================================
Схема БД (10 таблиц)
================================================================================
-- 1. local_identity — текущий локальный узел (одна строка, приватный ключ здесь)
-- приватный ключ получаем от utun через MSG_RSP_PRIVKEY (только localhost)
CREATE TABLE local_identity (
id INTEGER PRIMARY KEY CHECK (id = 1),
node_id INTEGER NOT NULL UNIQUE,
name TEXT NOT NULL,
x25519_pubkey BLOB NOT NULL, -- 32 bytes
x25519_privkey BLOB, -- 32 bytes (только на этом устройстве)
ed25519_pubkey BLOB, -- 32 bytes (derive из x25519 privkey)
created_at INTEGER DEFAULT (unixepoch()),
updated_at INTEGER DEFAULT (unixepoch())
);
-- 2. my_nodes — другие наши ноды (другие устройства, нет приватного ключа)
CREATE TABLE my_nodes (
node_id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
x25519_pubkey BLOB NOT NULL, -- 32 bytes
ed25519_pubkey BLOB, -- 32 bytes
created_at INTEGER DEFAULT (unixepoch()),
updated_at INTEGER DEFAULT (unixepoch())
);
-- 3. nodes — известные удалённые узлы (получаем через msg_transport от utun)
CREATE TABLE nodes (
node_id INTEGER PRIMARY KEY,
name TEXT,
x25519_pubkey BLOB NOT NULL, -- 32 bytes
ed25519_pubkey BLOB, -- 32 bytes (для проверки подписи пира)
is_online INTEGER DEFAULT 0,
last_seen_at INTEGER DEFAULT (unixepoch()),
created_at INTEGER DEFAULT (unixepoch()),
updated_at INTEGER DEFAULT (unixepoch())
);
-- 3a. accounts — UI-информация об узлах для отображения сообщений
CREATE TABLE accounts (
node_id INTEGER PRIMARY KEY REFERENCES nodes(node_id),
display_name TEXT NOT NULL, -- "Alice"
avatar_color TEXT DEFAULT '#4A90E2', -- hex RGB
avatar_letter TEXT NOT NULL, -- "A"
is_contact INTEGER DEFAULT 1, -- показывать в боковой панели
created_at INTEGER DEFAULT (unixepoch())
);
-- 4. channels — каналы (DM и групповые)
CREATE TABLE channels (
id INTEGER PRIMARY KEY AUTOINCREMENT,
channel_id TEXT NOT NULL UNIQUE, -- "dm:nodeA_nodeB" (A<B) или "group:hash"
name TEXT NOT NULL,
owner_node_id INTEGER, -- создатель (NULL для DM)
is_dm INTEGER DEFAULT 0,
last_message TEXT,
last_msg_at INTEGER, -- unix timestamp ms
created_at INTEGER DEFAULT (unixepoch())
);
CREATE UNIQUE INDEX idx_channel_cid ON channels(channel_id);
-- 5. channel_members — участники канала
CREATE TABLE channel_members (
channel_id TEXT NOT NULL REFERENCES channels(channel_id) ON DELETE CASCADE,
node_id INTEGER NOT NULL,
joined_at INTEGER DEFAULT (unixepoch()),
PRIMARY KEY (channel_id, node_id)
);
CREATE INDEX idx_cm_node ON channel_members(node_id);
-- 6. messages — сообщения чата
-- signature = Ed25519 отправителя над (channel_id, author_node_id, content, timestamp)
CREATE TABLE messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
channel_id TEXT NOT NULL REFERENCES channels(channel_id) ON DELETE CASCADE,
author_node_id INTEGER NOT NULL,
content TEXT NOT NULL CHECK(length(content) <= 4096),
content_type TEXT DEFAULT 'text/plain',
timestamp INTEGER NOT NULL, -- unix milliseconds
signature BLOB NOT NULL CHECK(length(signature) == 64),
is_outgoing INTEGER DEFAULT 0,
is_read INTEGER DEFAULT 0,
created_at INTEGER DEFAULT (unixepoch())
);
CREATE INDEX idx_msg_channel_time ON messages(channel_id, timestamp);
CREATE INDEX idx_msg_author ON messages(author_node_id);
-- дедупликация: один автор не может отправить два сообщения в один ms в один канал
CREATE UNIQUE INDEX idx_msg_dedup ON messages(channel_id, author_node_id, timestamp);
-- 7. reactions — реакции на сообщения
-- signature = Ed25519 отправителя над (message_id, node_id, emoji)
CREATE TABLE reactions (
message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
node_id INTEGER NOT NULL,
emoji TEXT NOT NULL, -- unicode codepoint или :shortcode:
signature BLOB NOT NULL CHECK(length(signature) == 64),
created_at INTEGER DEFAULT (unixepoch()),
PRIMARY KEY (message_id, node_id, emoji)
);
-- 8. attachments — вложения к сообщениям
-- signature = Ed25519 отправителя над (message_id, filename, content_type, size, sha256)
CREATE TABLE attachments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
filename TEXT NOT NULL,
content_type TEXT NOT NULL, -- MIME type
size INTEGER NOT NULL, -- bytes
sha256 BLOB NOT NULL, -- 32 bytes
file_path TEXT, -- локальный путь к сохранённому файлу
signature BLOB NOT NULL CHECK(length(signature) == 64),
is_upload INTEGER DEFAULT 0, -- 0=скачано, 1=наше
created_at INTEGER DEFAULT (unixepoch())
);
CREATE INDEX idx_att_msg ON attachments(message_id);
-- 9. ui_state — сохранение состояния UI (последний канал, позиция скролла и т.д.)
CREATE TABLE ui_state (
key TEXT PRIMARY KEY,
value TEXT
);================================================================================
Основные запросы
================================================================================
-- Текущая нода (приватный ключ — только на этом устройстве)
SELECT node_id, name, x25519_pubkey, x25519_privkey, ed25519_pubkey
FROM local_identity WHERE id = 1;
-- Другие наши ноды (без приватных ключей)
SELECT node_id, name, x25519_pubkey, ed25519_pubkey FROM my_nodes;
-- Список каналов с последним сообщением
SELECT c.channel_id, c.name, c.is_dm, c.last_message, c.last_msg_at
FROM channels c
JOIN channel_members cm ON c.channel_id = cm.channel_id
WHERE cm.node_id = (SELECT node_id FROM local_identity WHERE id = 1)
ORDER BY c.last_msg_at DESC;
-- Сообщения канала (последние 50)
SELECT id, author_node_id, content, content_type, timestamp,
signature, is_outgoing, is_read
FROM messages
WHERE channel_id = ?
ORDER BY timestamp DESC
LIMIT 50;
-- Кто в канале
SELECT cm.node_id, n.name
FROM channel_members cm LEFT JOIN nodes n ON cm.node_id = n.node_id
WHERE cm.channel_id = ?;
-- DM между двумя узлами
-- channel_id = "dm:" || min(a,b) || "_" || max(a,b)
-- Реакции сообщения
SELECT node_id, emoji, signature FROM reactions WHERE message_id = ? ORDER BY created_at;
-- Вложения сообщения
SELECT id, filename, content_type, size, sha256, file_path
FROM attachments WHERE message_id = ?;
================================================================================
Примечания
================================================================================
- local_identity — одна строка (id=1), содержит x25519_privkey.
Приватный ключ получаем от utun через MSG_RSP_PRIVKEY (доступен только с localhost).
- my_nodes — другие наши устройства, только pubkey-информация (нет приватных ключей).
- Адреса и подсети (node_addresses, node_subnets) — зона ответственности utun.
Чат не занимается сетевым уровнем.
- Все timestamp: INTEGER (unix epoch). messages.timestamp — миллисекунды,
created_at/updated_at/joined_at — секунды.
- signature везде NOT NULL Ed25519 (64 bytes) — подпись автора контента:
messages : sign(edPriv, channel_id || author_node_id || content || timestamp)
reactions : sign(edPriv, message_id || node_id || emoji)
attachments: sign(edPriv, message_id || filename || content_type || size || sha256)
- channel_id для DM: "dm:nodeA_nodeB" где A < B (канонический порядок).
- channel_id для групп: "group:" + SHA256(owner_node_id || name || timestamp).
- deleted_at / soft delete не добавлены — можно добавить позже при необходимости.
+167
View File
@@ -0,0 +1,167 @@
Архитектура чата (группового)
База данных: SQLite3 (WAL mode), chats.db
================================================================================
Схема БД (7 таблиц)
================================================================================
-- 1. my_nodes — наши собственные ноды (разные устройства / инстансы utun)
CREATE TABLE my_nodes (
node_id INTEGER PRIMARY KEY,
name TEXT NOT NULL, -- human-readable имя устройства
x25519_pubkey BLOB NOT NULL, -- 32 bytes
ed25519_pubkey BLOB, -- 32 bytes (derive из x25519 privkey)
is_current INTEGER DEFAULT 0, -- 1 = текущий инстанс (к которому подключён GUI)
created_at INTEGER DEFAULT (unixepoch()),
updated_at INTEGER DEFAULT (unixepoch())
);
-- 2. nodes — известные удалённые узлы (получаем через msg_transport от utun)
CREATE TABLE nodes (
node_id INTEGER PRIMARY KEY,
name TEXT,
x25519_pubkey BLOB NOT NULL, -- 32 bytes
ed25519_pubkey BLOB, -- 32 bytes (для проверки подписи пира)
is_online INTEGER DEFAULT 0,
last_seen_at INTEGER DEFAULT (unixepoch()),
created_at INTEGER DEFAULT (unixepoch()),
updated_at INTEGER DEFAULT (unixepoch())
);
-- 3. channels — каналы (DM и групповые)
CREATE TABLE channels (
id INTEGER PRIMARY KEY AUTOINCREMENT,
channel_id TEXT NOT NULL UNIQUE, -- "dm:nodeA_nodeB" (A<B) или "group:hash"
name TEXT NOT NULL,
owner_node_id INTEGER, -- создатель (NULL для DM)
is_dm INTEGER DEFAULT 0,
last_message TEXT,
last_msg_at INTEGER, -- unix timestamp ms
created_at INTEGER DEFAULT (unixepoch())
);
CREATE UNIQUE INDEX idx_channel_cid ON channels(channel_id);
-- 4. channel_members — участники канала
CREATE TABLE channel_members (
channel_id TEXT NOT NULL REFERENCES channels(channel_id) ON DELETE CASCADE,
node_id INTEGER NOT NULL,
joined_at INTEGER DEFAULT (unixepoch()),
PRIMARY KEY (channel_id, node_id)
);
CREATE INDEX idx_cm_node ON channel_members(node_id);
-- 5. messages — сообщения чата
CREATE TABLE messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
channel_id TEXT NOT NULL REFERENCES channels(channel_id) ON DELETE CASCADE,
author_node_id INTEGER NOT NULL,
content TEXT NOT NULL CHECK(length(content) <= 4096),
content_type TEXT DEFAULT 'text/plain',
timestamp INTEGER NOT NULL, -- unix milliseconds
signature BLOB CHECK(signature IS NULL OR length(signature) == 64),
is_outgoing INTEGER DEFAULT 0,
is_read INTEGER DEFAULT 0,
created_at INTEGER DEFAULT (unixepoch())
);
CREATE INDEX idx_msg_channel_time ON messages(channel_id, timestamp);
CREATE INDEX idx_msg_author ON messages(author_node_id);
-- дедупликация: один автор не может отправить два сообщения в один ms в один канал
CREATE UNIQUE INDEX idx_msg_dedup ON messages(channel_id, author_node_id, timestamp);
-- 6. reactions — реакции на сообщения
CREATE TABLE reactions (
message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
node_id INTEGER NOT NULL,
emoji TEXT NOT NULL, -- unicode codepoint или :shortcode:
signature BLOB, -- Ed25519 (64 bytes)
created_at INTEGER DEFAULT (unixepoch()),
PRIMARY KEY (message_id, node_id, emoji)
);
-- 7. attachments — вложения к сообщениям
CREATE TABLE attachments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
filename TEXT NOT NULL,
content_type TEXT NOT NULL, -- MIME type
size INTEGER NOT NULL, -- bytes
sha256 BLOB NOT NULL, -- 32 bytes
file_path TEXT, -- локальный путь к сохранённому файлу
signature BLOB, -- Ed25519 (64 bytes)
is_upload INTEGER DEFAULT 0, -- 0=скачано, 1=наше
created_at INTEGER DEFAULT (unixepoch())
);
CREATE INDEX idx_att_msg ON attachments(message_id);
================================================================================
Основные запросы
================================================================================
-- Текущая нода
SELECT node_id, name, x25519_pubkey, ed25519_pubkey
FROM my_nodes WHERE is_current = 1;
-- Все свои ноды
SELECT node_id, name, is_current FROM my_nodes ORDER BY created_at;
-- Список каналов с последним сообщением (для текущего пользователя)
SELECT c.channel_id, c.name, c.is_dm, c.last_message, c.last_msg_at
FROM channels c
JOIN channel_members cm ON c.channel_id = cm.channel_id
JOIN my_nodes mn ON cm.node_id = mn.node_id
WHERE mn.is_current = 1
ORDER BY c.last_msg_at DESC;
-- Сообщения канала (последние 50)
SELECT id, author_node_id, content, content_type, timestamp,
signature, is_outgoing, is_read
FROM messages
WHERE channel_id = ?
ORDER BY timestamp DESC
LIMIT 50;
-- Кто в канале
SELECT cm.node_id, n.name
FROM channel_members cm LEFT JOIN nodes n ON cm.node_id = n.node_id
WHERE cm.channel_id = ?;
-- DM между двумя узлами
-- channel_id = "dm:" || min(a,b) || "_" || max(a,b)
-- Реакции сообщения
SELECT node_id, emoji, signature FROM reactions WHERE message_id = ? ORDER BY created_at;
-- Вложения сообщения
SELECT id, filename, content_type, size, sha256, file_path
FROM attachments WHERE message_id = ?;
================================================================================
Примечания
================================================================================
- Адреса и подсети (node_addresses, node_subnets) — зона ответственности utun.
Чат не занимается сетевым уровнем. При необходимости utun отдаёт эту информацию
через control_server (etcpmon протокол).
- Все timestamp: INTEGER (unix epoch). messages.timestamp — миллисекунды,
created_at/updated_at/joined_at — секунды.
- signature везде Ed25519 (64 bytes) — NULL если не подписано.
- channel_id для DM: "dm:nodeA_nodeB" где A < B (канонический порядок).
- channel_id для групп: "group:" + SHA256(owner_node_id || name || timestamp).
- local_seq (из предыдущей версии) убран — дедупликация через UNIQUE индекс
(channel_id, author_node_id, timestamp) достаточна.
- deleted_at / soft delete не добавлены — можно добавить позже при необходимости.
+645
View File
@@ -0,0 +1,645 @@
#include "db_manager.h"
#include "../src/debug_ui.h"
#include <openssl/sha.h>
#include <openssl/evp.h>
#include <cstring>
DbManager::DbManager(const QString& path, QObject* parent)
: QObject(parent)
{
int rc = sqlite3_open(path.toUtf8().constData(), &m_db);
if (rc != SQLITE_OK) {
GUI_ERROR("Cannot open database: %s", sqlite3_errmsg(m_db));
sqlite3_close(m_db);
m_db = nullptr;
return;
}
sqlite3_exec(m_db, "PRAGMA journal_mode=WAL", nullptr, nullptr, nullptr);
sqlite3_exec(m_db, "PRAGMA foreign_keys=ON", nullptr, nullptr, nullptr);
m_myNodeId = 100;
initTables();
if (getChannels().isEmpty()) seedStubData();
GUI_INFO("Database opened: %s (my_node_id=%llu)", path.toUtf8().constData(),
static_cast<unsigned long long>(m_myNodeId));
}
DbManager::~DbManager() {
if (m_db) { sqlite3_close(m_db); m_db = nullptr; }
}
/* ======================================================================== */
sqlite3_stmt* DbManager::prepareOrNull(const char* sql) const {
sqlite3_stmt* stmt = nullptr;
if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) {
GUI_ERROR("prepare failed: %s — %s", sql, sqlite3_errmsg(m_db));
return nullptr;
}
return stmt;
}
QString DbManager::colText(sqlite3_stmt* stmt, int col) {
return QString::fromUtf8(
reinterpret_cast<const char*>(sqlite3_column_text(stmt, col)));
}
QByteArray DbManager::colBlob(sqlite3_stmt* stmt, int col) {
int len = sqlite3_column_bytes(stmt, col);
if (len <= 0) return {};
return QByteArray(static_cast<const char*>(sqlite3_column_blob(stmt, col)), len);
}
/* ======================================================================== */
void DbManager::initTables() {
const char* sql =
"CREATE TABLE IF NOT EXISTS local_identity ("
" id INTEGER PRIMARY KEY CHECK (id = 1),"
" node_id INTEGER NOT NULL UNIQUE,"
" name TEXT NOT NULL,"
" x25519_pubkey BLOB NOT NULL,"
" x25519_privkey BLOB,"
" ed25519_pubkey BLOB,"
" created_at INTEGER DEFAULT (unixepoch()),"
" updated_at INTEGER DEFAULT (unixepoch())"
");"
"CREATE TABLE IF NOT EXISTS my_nodes ("
" node_id INTEGER PRIMARY KEY,"
" name TEXT NOT NULL,"
" x25519_pubkey BLOB NOT NULL,"
" ed25519_pubkey BLOB,"
" created_at INTEGER DEFAULT (unixepoch()),"
" updated_at INTEGER DEFAULT (unixepoch())"
");"
"CREATE TABLE IF NOT EXISTS nodes ("
" node_id INTEGER PRIMARY KEY,"
" name TEXT,"
" x25519_pubkey BLOB NOT NULL,"
" ed25519_pubkey BLOB,"
" is_online INTEGER DEFAULT 0,"
" last_seen_at INTEGER DEFAULT (unixepoch()),"
" created_at INTEGER DEFAULT (unixepoch()),"
" updated_at INTEGER DEFAULT (unixepoch())"
");"
"CREATE TABLE IF NOT EXISTS accounts ("
" node_id INTEGER PRIMARY KEY REFERENCES nodes(node_id),"
" display_name TEXT NOT NULL,"
" avatar_color TEXT DEFAULT '#4A90E2',"
" avatar_letter TEXT NOT NULL,"
" is_contact INTEGER DEFAULT 1,"
" created_at INTEGER DEFAULT (unixepoch())"
");"
"CREATE TABLE IF NOT EXISTS channels ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" channel_id TEXT NOT NULL UNIQUE,"
" name TEXT NOT NULL,"
" owner_node_id INTEGER,"
" is_dm INTEGER DEFAULT 0,"
" last_message TEXT,"
" last_msg_at INTEGER,"
" created_at INTEGER DEFAULT (unixepoch())"
");"
"CREATE UNIQUE INDEX IF NOT EXISTS idx_channel_cid ON channels(channel_id);"
"CREATE TABLE IF NOT EXISTS channel_members ("
" channel_id TEXT NOT NULL REFERENCES channels(channel_id) ON DELETE CASCADE,"
" node_id INTEGER NOT NULL,"
" joined_at INTEGER DEFAULT (unixepoch()),"
" PRIMARY KEY (channel_id, node_id)"
");"
"CREATE INDEX IF NOT EXISTS idx_cm_node ON channel_members(node_id);"
"CREATE TABLE IF NOT EXISTS messages ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" channel_id TEXT NOT NULL REFERENCES channels(channel_id) ON DELETE CASCADE,"
" author_node_id INTEGER NOT NULL,"
" content TEXT NOT NULL CHECK(length(content) <= 4096),"
" content_type TEXT DEFAULT 'text/plain',"
" timestamp INTEGER NOT NULL,"
" signature BLOB NOT NULL CHECK(length(signature) == 64),"
" is_outgoing INTEGER DEFAULT 0,"
" is_read INTEGER DEFAULT 0,"
" created_at INTEGER DEFAULT (unixepoch())"
");"
"CREATE INDEX IF NOT EXISTS idx_msg_channel_time ON messages(channel_id, timestamp);"
"CREATE INDEX IF NOT EXISTS idx_msg_author ON messages(author_node_id);"
"CREATE UNIQUE INDEX IF NOT EXISTS idx_msg_dedup "
" ON messages(channel_id, author_node_id, timestamp);"
"CREATE TABLE IF NOT EXISTS reactions ("
" message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,"
" node_id INTEGER NOT NULL,"
" emoji TEXT NOT NULL,"
" signature BLOB NOT NULL CHECK(length(signature) == 64),"
" created_at INTEGER DEFAULT (unixepoch()),"
" PRIMARY KEY (message_id, node_id, emoji)"
");"
"CREATE TABLE IF NOT EXISTS attachments ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,"
" filename TEXT NOT NULL,"
" content_type TEXT NOT NULL,"
" size INTEGER NOT NULL,"
" sha256 BLOB NOT NULL,"
" file_path TEXT,"
" signature BLOB NOT NULL CHECK(length(signature) == 64),"
" is_upload INTEGER DEFAULT 0,"
" created_at INTEGER DEFAULT (unixepoch())"
");"
"CREATE INDEX IF NOT EXISTS idx_att_msg ON attachments(message_id);"
"CREATE TABLE IF NOT EXISTS ui_state ("
" key TEXT PRIMARY KEY,"
" value TEXT"
");"
"CREATE TABLE IF NOT EXISTS bucket_meta ("
" channel_id TEXT NOT NULL,"
" day_unix INTEGER NOT NULL,"
" hour INTEGER NOT NULL,"
" msg_count INTEGER DEFAULT 0,"
" sig_hash BLOB DEFAULT '',"
" PRIMARY KEY (channel_id, day_unix, hour)"
");"
"CREATE TABLE IF NOT EXISTS node_addresses ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" node_id INTEGER NOT NULL REFERENCES nodes(node_id) ON DELETE CASCADE,"
" family INTEGER NOT NULL CHECK(family IN (4, 6)),"
" address BLOB NOT NULL,"
" port INTEGER NOT NULL CHECK(port > 0 AND port <= 65535),"
" is_nat INTEGER DEFAULT 0,"
" created_at INTEGER DEFAULT (unixepoch()),"
" updated_at INTEGER DEFAULT (unixepoch())"
");"
"CREATE INDEX IF NOT EXISTS idx_na_node ON node_addresses(node_id);";
char* err = nullptr;
if (sqlite3_exec(m_db, sql, nullptr, nullptr, &err) != SQLITE_OK) {
GUI_ERROR("initTables: %s", err);
sqlite3_free(err);
} else {
GUI_INFO("Database tables initialized (11 tables)");
}
}
/* ======================================================================== */
QList<ChannelRow> DbManager::getChannels() const {
QList<ChannelRow> list;
sqlite3_stmt* stmt = prepareOrNull(
"SELECT c.channel_id, c.name, c.is_dm, c.last_message, c.last_msg_at"
" FROM channels c JOIN channel_members cm ON c.channel_id = cm.channel_id"
" WHERE cm.node_id = ? ORDER BY c.last_msg_at DESC");
if (!stmt) return list;
sqlite3_bind_int64(stmt, 1, static_cast<sqlite3_int64>(m_myNodeId));
while (sqlite3_step(stmt) == SQLITE_ROW) {
ChannelRow r;
r.channelId = colText(stmt, 0);
r.name = colText(stmt, 1);
r.isDm = sqlite3_column_int(stmt, 2) != 0;
r.lastMessage = colText(stmt, 3);
r.lastMsgAt = sqlite3_column_int64(stmt, 4);
list.append(r);
}
sqlite3_finalize(stmt);
return list;
}
QList<MessageRow> DbManager::getMessages(const QString& channelId, int limit) const {
QList<MessageRow> list;
sqlite3_stmt* stmt = prepareOrNull(
"SELECT id, author_node_id, content, content_type, timestamp,"
" signature, is_outgoing, is_read"
" FROM messages WHERE channel_id = ? ORDER BY timestamp ASC LIMIT ?");
if (!stmt) return list;
sqlite3_bind_text(stmt, 1, channelId.toUtf8().constData(), -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 2, limit);
while (sqlite3_step(stmt) == SQLITE_ROW) {
MessageRow m;
m.id = sqlite3_column_int64(stmt, 0);
m.channelId = channelId;
m.authorNodeId = static_cast<quint64>(sqlite3_column_int64(stmt, 1));
m.content = colText(stmt, 2);
m.contentType = colText(stmt, 3);
m.timestamp = sqlite3_column_int64(stmt, 4);
m.signature = colBlob(stmt, 5);
m.isOutgoing = sqlite3_column_int(stmt, 6) != 0;
m.isRead = sqlite3_column_int(stmt, 7) != 0;
list.append(m);
}
sqlite3_finalize(stmt);
return list;
}
QList<AccountRow> DbManager::getAccounts(bool contactsOnly) const {
QList<AccountRow> list;
const char* sql = contactsOnly
? "SELECT node_id, display_name, avatar_color, avatar_letter, is_contact"
" FROM accounts WHERE is_contact = 1 ORDER BY display_name"
: "SELECT node_id, display_name, avatar_color, avatar_letter, is_contact"
" FROM accounts ORDER BY display_name";
sqlite3_stmt* stmt = prepareOrNull(sql);
if (!stmt) return list;
while (sqlite3_step(stmt) == SQLITE_ROW) {
AccountRow a;
a.nodeId = static_cast<quint64>(sqlite3_column_int64(stmt, 0));
a.displayName = colText(stmt, 1);
a.avatarColor = colText(stmt, 2);
a.avatarLetter = colText(stmt, 3);
a.isContact = sqlite3_column_int(stmt, 4) != 0;
list.append(a);
}
sqlite3_finalize(stmt);
return list;
}
AccountRow DbManager::getAccount(quint64 nodeId) const {
AccountRow a;
sqlite3_stmt* stmt = prepareOrNull(
"SELECT node_id, display_name, avatar_color, avatar_letter, is_contact"
" FROM accounts WHERE node_id = ?");
if (!stmt) return a;
sqlite3_bind_int64(stmt, 1, static_cast<sqlite3_int64>(nodeId));
if (sqlite3_step(stmt) == SQLITE_ROW) {
a.nodeId = static_cast<quint64>(sqlite3_column_int64(stmt, 0));
a.displayName = colText(stmt, 1);
a.avatarColor = colText(stmt, 2);
a.avatarLetter = colText(stmt, 3);
a.isContact = sqlite3_column_int(stmt, 4) != 0;
}
sqlite3_finalize(stmt);
return a;
}
QByteArray DbManager::getNodeEdPub(quint64 nodeId) const {
sqlite3_stmt* stmt = prepareOrNull(
"SELECT ed25519_pubkey FROM nodes WHERE node_id=?");
if (!stmt) return {};
sqlite3_bind_int64(stmt, 1, static_cast<sqlite3_int64>(nodeId));
QByteArray r;
if (sqlite3_step(stmt) == SQLITE_ROW)
r = colBlob(stmt, 0);
sqlite3_finalize(stmt);
return r;
}
QList<DbManager::BucketMeta> DbManager::getBucketMeta(const QString& channelId) const {
QList<BucketMeta> list;
sqlite3_stmt* stmt = prepareOrNull(
"SELECT day_unix, hour, msg_count, sig_hash"
" FROM bucket_meta WHERE channel_id=? ORDER BY day_unix, hour");
if (!stmt) return list;
sqlite3_bind_text(stmt, 1, channelId.toUtf8().constData(), -1, SQLITE_STATIC);
while (sqlite3_step(stmt) == SQLITE_ROW) {
BucketMeta b;
b.day = sqlite3_column_int64(stmt, 0);
b.hour = sqlite3_column_int(stmt, 1);
b.count = sqlite3_column_int(stmt, 2);
b.hash = colBlob(stmt, 3);
list.append(b);
}
sqlite3_finalize(stmt);
return list;
}
void DbManager::setBucketMeta(const QString& channelId, qint64 day, int hour,
int count, const QByteArray& hash) {
sqlite3_stmt* stmt = prepareOrNull(
"INSERT OR REPLACE INTO bucket_meta(channel_id,day_unix,hour,msg_count,sig_hash)"
" VALUES(?,?,?,?,?)");
if (!stmt) return;
sqlite3_bind_text(stmt, 1, channelId.toUtf8().constData(), -1, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 2, day);
sqlite3_bind_int(stmt, 3, hour);
sqlite3_bind_int(stmt, 4, count);
sqlite3_bind_blob(stmt, 5, hash.constData(), hash.size(), SQLITE_STATIC);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
}
QList<QByteArray> DbManager::getMessageSigs(const QString& channelId,
qint64 day, int hour) const {
QList<QByteArray> sigs;
qint64 dayStart = day + hour * 3600;
qint64 dayEnd = dayStart + 3600;
sqlite3_stmt* stmt = prepareOrNull(
"SELECT signature FROM messages WHERE channel_id=?"
" AND timestamp/1000 >= ? AND timestamp/1000 < ? ORDER BY timestamp");
if (!stmt) return sigs;
sqlite3_bind_text(stmt, 1, channelId.toUtf8().constData(), -1, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 2, dayStart);
sqlite3_bind_int64(stmt, 3, dayEnd);
while (sqlite3_step(stmt) == SQLITE_ROW)
sigs.append(colBlob(stmt, 0));
sqlite3_finalize(stmt);
return sigs;
}
QList<MessageRow> DbManager::getMessagesBySigs(const QList<QByteArray>& sigs) const {
QList<MessageRow> msgs;
if (sigs.isEmpty()) return msgs;
QString sql = "SELECT id, channel_id, author_node_id, content, content_type,"
" timestamp, signature, is_outgoing, is_read FROM messages"
" WHERE signature IN (";
for (int i = 0; i < sigs.size(); i++)
sql += (i > 0 ? ",?" : "?");
sql += ") ORDER BY timestamp";
sqlite3_stmt* stmt = prepareOrNull(sql.toUtf8().constData());
if (!stmt) return msgs;
for (int i = 0; i < sigs.size(); i++)
sqlite3_bind_blob(stmt, i + 1, sigs[i].constData(), sigs[i].size(), SQLITE_STATIC);
while (sqlite3_step(stmt) == SQLITE_ROW) {
MessageRow m;
m.id = sqlite3_column_int64(stmt, 0);
m.channelId = colText(stmt, 1);
m.authorNodeId = static_cast<quint64>(sqlite3_column_int64(stmt, 2));
m.content = colText(stmt, 3);
m.contentType = colText(stmt, 4);
m.timestamp = sqlite3_column_int64(stmt, 5);
m.signature = colBlob(stmt, 6);
m.isOutgoing = sqlite3_column_int(stmt, 7) != 0;
m.isRead = sqlite3_column_int(stmt, 8) != 0;
msgs.append(m);
}
sqlite3_finalize(stmt);
return msgs;
}
QList<quint64> DbManager::getOnlinePeers() const {
QList<quint64> peers;
sqlite3_stmt* stmt = prepareOrNull(
"SELECT node_id FROM nodes WHERE is_online=1 AND node_id != ?");
if (!stmt) return peers;
sqlite3_bind_int64(stmt, 1, static_cast<sqlite3_int64>(m_myNodeId));
while (sqlite3_step(stmt) == SQLITE_ROW)
peers.append(static_cast<quint64>(sqlite3_column_int64(stmt, 0)));
sqlite3_finalize(stmt);
return peers;
}
bool DbManager::insertMessage(const QString& channelId, quint64 authorNodeId,
const QString& content, qint64 timestamp, int isOutgoing) {
static const unsigned char sig64[64] = {};
sqlite3_stmt* stmt = prepareOrNull(
"INSERT INTO messages"
" (channel_id,author_node_id,content,timestamp,signature,is_outgoing,is_read)"
" VALUES(?,?,?,?,?,?,1)");
if (!stmt) return false;
sqlite3_bind_text(stmt, 1, channelId.toUtf8().constData(), -1, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 2, static_cast<sqlite3_int64>(authorNodeId));
sqlite3_bind_text(stmt, 3, content.toUtf8().constData(), -1, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 4, timestamp);
sqlite3_bind_blob(stmt, 5, sig64, 64, SQLITE_STATIC);
sqlite3_bind_int(stmt, 6, isOutgoing);
bool ok = (sqlite3_step(stmt) == SQLITE_DONE);
sqlite3_finalize(stmt);
/* update bucket_meta */
qint64 day = timestamp / 86400000; /* ms per day */
int hour = static_cast<int>((timestamp % 86400000) / 3600000);
auto sigs = getMessageSigs(channelId, day, hour);
int cnt = sigs.size();
QByteArray hash(32, '\0');
if (cnt > 0) {
EVP_MD_CTX* md_ctx = EVP_MD_CTX_new();
EVP_DigestInit_ex(md_ctx, EVP_sha256(), NULL);
for (auto& s : sigs)
EVP_DigestUpdate(md_ctx, s.constData(), s.size());
unsigned int len = SHA256_DIGEST_LENGTH;
uint8_t buf[SHA256_DIGEST_LENGTH];
EVP_DigestFinal_ex(md_ctx, buf, &len);
EVP_MD_CTX_free(md_ctx);
hash = QByteArray(reinterpret_cast<const char*>(buf), static_cast<int>(len));
}
setBucketMeta(channelId, day, hour, cnt, hash);
return ok;
}
QByteArray DbManager::getLocalPubkey() const {
sqlite3_stmt* stmt = prepareOrNull(
"SELECT x25519_pubkey FROM local_identity WHERE id=1");
if (!stmt) return {};
QByteArray r;
if (sqlite3_step(stmt) == SQLITE_ROW)
r = colBlob(stmt, 0);
sqlite3_finalize(stmt);
return r;
}
QList<NodeAddr> DbManager::getOnlinePeerAddresses(quint64 excludeNodeId) const {
QList<NodeAddr> list;
sqlite3_stmt* stmt = prepareOrNull(
"SELECT na.node_id, na.family, na.address, na.port"
" FROM node_addresses na JOIN nodes n ON na.node_id = n.node_id"
" WHERE n.is_online=1 AND na.is_nat=0 AND na.node_id != ?"
" ORDER BY RANDOM()");
if (!stmt) return list;
sqlite3_bind_int64(stmt, 1, static_cast<sqlite3_int64>(excludeNodeId));
while (sqlite3_step(stmt) == SQLITE_ROW) {
NodeAddr a;
a.nodeId = static_cast<quint64>(sqlite3_column_int64(stmt, 0));
a.family = sqlite3_column_int(stmt, 1);
a.address = colBlob(stmt, 2);
a.port = static_cast<quint16>(sqlite3_column_int(stmt, 3));
list.append(a);
}
sqlite3_finalize(stmt);
return list;
}
/* ======================================================================== */
static constexpr unsigned char kDummySig[64] = {
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,
0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,
0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,
0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F
};
void DbManager::seedStubData() {
GUI_INFO("Seeding stub data...");
sqlite3_exec(m_db, "BEGIN", nullptr, nullptr, nullptr);
/* my_node dummy pubkey */
unsigned char pub_dummy[32] = {};
sqlite3_stmt* ins = prepareOrNull(
"INSERT OR IGNORE INTO local_identity(id,node_id,name,x25519_pubkey,ed25519_pubkey)"
" VALUES(1,?,'MyDevice',?,?)");
sqlite3_bind_int64(ins, 1, m_myNodeId);
sqlite3_bind_blob(ins, 2, pub_dummy, 32, SQLITE_STATIC);
sqlite3_bind_blob(ins, 3, pub_dummy, 32, SQLITE_STATIC);
sqlite3_step(ins); sqlite3_finalize(ins);
/* nodes + accounts */
struct { quint64 id; const char* name; const char* letter; const char* color; } accs[] = {
{1, "Alice", "A", "#4A90E2"}, {2, "Bob", "B", "#7ED321"},
{3, "Carol", "C", "#F5A623"}, {4, "Dave", "D", "#9013FE"},
{5, "Eve", "E", "#00B894"},
};
for (auto& a : accs) {
ins = prepareOrNull("INSERT OR IGNORE INTO nodes(node_id,name,x25519_pubkey,ed25519_pubkey)"
" VALUES(?,?,?,?)");
sqlite3_bind_int64(ins, 1, static_cast<sqlite3_int64>(a.id));
sqlite3_bind_text(ins, 2, a.name, -1, SQLITE_STATIC);
sqlite3_bind_blob(ins, 3, pub_dummy, 32, SQLITE_STATIC);
sqlite3_bind_blob(ins, 4, pub_dummy, 32, SQLITE_STATIC);
sqlite3_step(ins); sqlite3_finalize(ins);
ins = prepareOrNull("INSERT OR IGNORE INTO accounts(node_id,display_name,avatar_color,avatar_letter)"
" VALUES(?,?,?,?)");
sqlite3_bind_int64(ins, 1, static_cast<sqlite3_int64>(a.id));
sqlite3_bind_text(ins, 2, a.name, -1, SQLITE_STATIC);
sqlite3_bind_text(ins, 3, a.color, -1, SQLITE_STATIC);
sqlite3_bind_text(ins, 4, a.letter, -1, SQLITE_STATIC);
sqlite3_step(ins); sqlite3_finalize(ins);
}
/* set some nodes online for demo */
sqlite3_exec(m_db,
"UPDATE nodes SET is_online=1 WHERE node_id IN (1,2,3)", nullptr, nullptr, nullptr);
/* node addresses for online peers (stub, for invite link demo) */
struct { quint64 id; int fam; unsigned char addr[16]; int port; int nat; } addrs[] = {
{1, 4, {192,168,1,10}, 8001, 0},
{1, 6, {0xfd,0x12,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, 8001, 0},
{2, 4, {10,0,0,5}, 9001, 0},
{2, 4, {172,16,0,100}, 9001, 0},
{3, 4, {192,168,5,20}, 10001, 1}, /* Carol behind NAT */
};
for (auto& ad : addrs) {
ins = prepareOrNull("INSERT OR IGNORE INTO node_addresses(node_id,family,address,port,is_nat)"
" VALUES(?,?,?,?,?)");
sqlite3_bind_int64(ins, 1, static_cast<sqlite3_int64>(ad.id));
sqlite3_bind_int(ins, 2, ad.fam);
sqlite3_bind_blob(ins, 3, ad.addr, ad.fam == 4 ? 4 : 16, SQLITE_STATIC);
sqlite3_bind_int(ins, 4, ad.port);
sqlite3_bind_int(ins, 5, ad.nat);
sqlite3_step(ins); sqlite3_finalize(ins);
}
/* channels (5) */
struct { const char* cid; const char* name; } chs[] = {
{"ch:general", "#general"},
{"ch:random", "#random"},
{"ch:dev-team", "#dev-team"},
{"ch:announce", "#announcements"},
{"ch:offtopic", "#offtopic"},
};
for (auto& ch : chs) {
ins = prepareOrNull("INSERT OR IGNORE INTO channels(channel_id,name) VALUES(?,?)");
sqlite3_bind_text(ins, 1, ch.cid, -1, SQLITE_STATIC);
sqlite3_bind_text(ins, 2, ch.name, -1, SQLITE_STATIC);
sqlite3_step(ins); sqlite3_finalize(ins);
/* my node member */
ins = prepareOrNull("INSERT OR IGNORE INTO channel_members(channel_id,node_id) VALUES(?,?)");
sqlite3_bind_text(ins, 1, ch.cid, -1, SQLITE_STATIC);
sqlite3_bind_int64(ins, 2, static_cast<sqlite3_int64>(m_myNodeId));
sqlite3_step(ins); sqlite3_finalize(ins);
/* all 5 accounts as members (everyone in every channel for demo) */
for (auto& a : accs) {
ins = prepareOrNull("INSERT OR IGNORE INTO channel_members(channel_id,node_id) VALUES(?,?)");
sqlite3_bind_text(ins, 1, ch.cid, -1, SQLITE_STATIC);
sqlite3_bind_int64(ins, 2, static_cast<sqlite3_int64>(a.id));
sqlite3_step(ins); sqlite3_finalize(ins);
}
}
/* messages (8) */
struct { const char* cid; quint64 author; qint64 ts; const char* text; quint8 o; } msgs[] = {
{"ch:general", 1, 1719200100000, "Hey team! How's the deployment going?", 0},
{"ch:general", 2, 1719200220000, "All green on staging. Running the final integration tests now.", 0},
{"ch:general", 3, 1719200520000, "I've pushed the ETCP-42 fix to staging. BGP handshake should be stable now. Please review the patch when you get a chance.", 0},
{"ch:general", 4, 1719200700000, "Wait — the BGP session is still flapping after the handshake. I'm seeing the keepalive timer fire but no route updates come through. Logs attached.", 0},
{"ch:general", 1, 1719200880000, "Reviewed the patch. LGTM. Let's merge and deploy to production tomorrow morning.", 0},
{"ch:general", 2, 1719201000000, "Can you share the full logs for the BGP session? I'll dig into the keepalive timer logic.", 0},
{"ch:general", 3, 1719201180000, "Sounds good. Let's coordinate in #dev-team. I'll prepare the release notes.", 0},
{"ch:general", 1, 1719201300000, "Deploy scheduled for tomorrow 10:00 UTC. All features are frozen — no more merges to main.", 0},
};
for (auto& m : msgs) {
ins = prepareOrNull(
"INSERT OR IGNORE INTO messages"
" (channel_id,author_node_id,content,timestamp,signature,is_outgoing,is_read)"
" VALUES(?,?,?,?,?,?,1)");
sqlite3_bind_text(ins, 1, m.cid, -1, SQLITE_STATIC);
sqlite3_bind_int64(ins, 2, static_cast<sqlite3_int64>(m.author));
sqlite3_bind_text(ins, 3, m.text, -1, SQLITE_STATIC);
sqlite3_bind_int64(ins, 4, m.ts);
sqlite3_bind_blob(ins, 5, kDummySig, 64, SQLITE_STATIC);
sqlite3_bind_int(ins, 6, m.o);
sqlite3_step(ins); sqlite3_finalize(ins);
}
/* reactions */
struct { qint64 msgRowId; quint64 node; const char* emoji; } reacts[] = {
{2, 1, "\xF0\x9F\x91\x8D"}, {2, 3, "\xF0\x9F\x91\x8D"},
{2, 1, "\xF0\x9F\x91\x8B"}, {2, 3, "\xF0\x9F\x91\x8B"}, {2, 4, "\xF0\x9F\x91\x8B"},
{3, 1, "\xF0\x9F\x91\x8D"}, {3, 2, "\xF0\x9F\x91\x8D"}, {3, 4, "\xF0\x9F\x91\x8D"},
{3, 1, "\xE2\x9D\xA4"},
{4, 2, "\xF0\x9F\x98\xAE"},
{5, 1, "\xF0\x9F\x91\x8D"}, {5, 2, "\xF0\x9F\x91\x8D"}, {5, 3, "\xF0\x9F\x91\x8D"},
{5, 4, "\xF0\x9F\x91\x8D"}, {5, 5, "\xF0\x9F\x91\x8D"},
{5, 2, "\xF0\x9F\x8E\x89"}, {5, 5, "\xF0\x9F\x8E\x89"},
{7, 1, "\xF0\x9F\xA4\x96"}, {7, 2, "\xF0\x9F\xA4\x96"},
{8, 2, "\xF0\x9F\x9A\x80"}, {8, 3, "\xF0\x9F\x9A\x80"}, {8, 4, "\xF0\x9F\x9A\x80"},
{8, 1, "\xE2\x9D\xA4"}, {8, 2, "\xE2\x9D\xA4"}, {8, 3, "\xE2\x9D\xA4"},
{8, 4, "\xE2\x9D\xA4"}, {8, 5, "\xE2\x9D\xA4"}, {8, 1, "\xE2\x9D\xA4"}, {8, 1, "\xE2\x9D\xA4"},
};
std::string lastMsg;
qint64 lastTs = 0;
for (auto& r : reacts) {
ins = prepareOrNull(
"INSERT OR IGNORE INTO reactions(message_id,node_id,emoji,signature)"
" VALUES(?,?,?,?)");
sqlite3_bind_int64(ins, 1, r.msgRowId);
sqlite3_bind_int64(ins, 2, static_cast<sqlite3_int64>(r.node));
sqlite3_bind_text(ins, 3, r.emoji, -1, SQLITE_STATIC);
sqlite3_bind_blob(ins, 4, kDummySig, 64, SQLITE_STATIC);
sqlite3_step(ins); sqlite3_finalize(ins);
}
lastMsg = "Deploy scheduled for tomorrow 10:00 UTC. All features are frozen — no more merges to main.";
lastTs = 1719201300000;
/* update channel last_msg */
ins = prepareOrNull("UPDATE channels SET last_message=?, last_msg_at=? WHERE channel_id='ch:general'");
sqlite3_bind_text(ins, 1, lastMsg.c_str(), -1, SQLITE_STATIC);
sqlite3_bind_int64(ins, 2, lastTs);
sqlite3_step(ins); sqlite3_finalize(ins);
sqlite3_exec(m_db, "COMMIT", nullptr, nullptr, nullptr);
GUI_INFO("Seed data inserted (5 accounts, 5 channels, 8 messages, %d reactions, %d addresses)",
static_cast<int>(sizeof(reacts)/sizeof(reacts[0])),
static_cast<int>(sizeof(addrs)/sizeof(addrs[0])));
}
QString DbManager::getUiState(const QString& key) const {
sqlite3_stmt* stmt = prepareOrNull("SELECT value FROM ui_state WHERE key=?");
if (!stmt) return {};
sqlite3_bind_text(stmt, 1, key.toUtf8().constData(), -1, SQLITE_STATIC);
QString val;
if (sqlite3_step(stmt) == SQLITE_ROW)
val = colText(stmt, 0);
sqlite3_finalize(stmt);
return val;
}
void DbManager::setUiState(const QString& key, const QString& value) {
sqlite3_stmt* stmt = prepareOrNull(
"INSERT OR REPLACE INTO ui_state(key,value) VALUES(?,?)");
if (!stmt) return;
sqlite3_bind_text(stmt, 1, key.toUtf8().constData(), -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, value.toUtf8().constData(), -1, SQLITE_STATIC);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
}
+88
View File
@@ -0,0 +1,88 @@
#ifndef DB_MANAGER_H
#define DB_MANAGER_H
#include <sqlite3.h>
#include <QString>
#include <QList>
#include <QObject>
#include <cstdint>
struct ChannelRow {
QString channelId;
QString name;
bool isDm = false;
QString lastMessage;
qint64 lastMsgAt = 0;
};
struct MessageRow {
qint64 id = 0;
QString channelId;
quint64 authorNodeId = 0;
QString content;
QString contentType;
qint64 timestamp = 0;
QByteArray signature;
bool isOutgoing = false;
bool isRead = false;
};
struct AccountRow {
quint64 nodeId = 0;
QString displayName;
QString avatarColor;
QString avatarLetter;
bool isContact = true;
};
struct NodeAddr {
quint64 nodeId = 0;
int family = 0; // 4=IPv4, 6=IPv6
QByteArray address;
quint16 port = 0;
};
class DbManager : public QObject {
Q_OBJECT
public:
explicit DbManager(const QString& path, QObject* parent = nullptr);
~DbManager() override;
bool isOpen() const { return m_db != nullptr; }
quint64 myNodeId() const { return m_myNodeId; }
QList<ChannelRow> getChannels() const;
QList<MessageRow> getMessages(const QString& channelId, int limit = 50) const;
QList<AccountRow> getAccounts(bool contactsOnly = false) const;
AccountRow getAccount(quint64 nodeId) const;
QByteArray getNodeEdPub(quint64 nodeId) const;
struct BucketMeta { qint64 day; int hour; int count; QByteArray hash; };
QList<BucketMeta> getBucketMeta(const QString& channelId) const;
void setBucketMeta(const QString& channelId, qint64 day, int hour,
int count, const QByteArray& hash);
QList<QByteArray> getMessageSigs(const QString& channelId, qint64 day, int hour) const;
QList<MessageRow> getMessagesBySigs(const QList<QByteArray>& sigs) const;
QList<quint64> getOnlinePeers() const;
QByteArray getLocalPubkey() const;
QList<NodeAddr> getOnlinePeerAddresses(quint64 excludeNodeId) const;
void seedStubData();
bool insertMessage(const QString& channelId, quint64 authorNodeId,
const QString& content, qint64 timestamp, int isOutgoing);
QString getUiState(const QString& key) const;
void setUiState(const QString& key, const QString& value);
private:
void initTables();
sqlite3_stmt* prepareOrNull(const char* sql) const;
static QString colText(sqlite3_stmt* stmt, int col);
static QByteArray colBlob(sqlite3_stmt* stmt, int col);
sqlite3* m_db = nullptr;
quint64 m_myNodeId = 0;
};
#endif
Loaded 100 of 495 files, more files were not shown because too many files have changed in this diff. Show more