Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80177c9b3e | ||
|
|
8f13f0ec4d | ||
|
|
5420e414ce | ||
|
|
69112ffde1 | ||
|
|
89d675f15d | ||
|
|
914f252b12 | ||
|
|
a7b20f268f | ||
|
|
c0207ad41d | ||
|
|
8051dfe4e5 | ||
|
|
22c24d135e | ||
|
|
d8c8c1c5b0 | ||
|
|
231081dd2f | ||
|
|
62b3695a77 | ||
|
|
0e2b0e28d8 | ||
|
|
e50ca9978e | ||
|
|
b667d6f352 | ||
|
|
6717c863e7 | ||
|
|
2bbdf8195f | ||
|
|
73f707ef9b |
No files matched your search
+5
-1
@@ -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
|
||||
|
||||
@@ -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 архитектура
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
mtest
|
||||
mtest[23456]
|
||||
testdb
|
||||
mdb_copy
|
||||
mdb_stat
|
||||
mdb_dump
|
||||
mdb_load
|
||||
*.lo
|
||||
*.[ao]
|
||||
*.so
|
||||
*.exe
|
||||
*[~#]
|
||||
*.bak
|
||||
*.orig
|
||||
*.rej
|
||||
*.gcov
|
||||
*.gcda
|
||||
*.gcno
|
||||
core
|
||||
core.*
|
||||
valgrind.*
|
||||
man/
|
||||
html/
|
||||
@@ -0,0 +1,317 @@
|
||||
LMDB 0.9 Change Log
|
||||
|
||||
LMDB 0.9.36 Engineering
|
||||
ITS#7772 - limit sub-page growth when re-adding existing data
|
||||
ITS#8335 - more input validation for put(MDB_MULTIPLE)
|
||||
ITS#8386 - be a bit more precise that mdb_get retrieves data in intro.doc
|
||||
ITS#8590 - suppress SIGPIPE on Mac OSX for mdb_copy/compact
|
||||
ITS#8739 - don't use fdatasync on FreeBSD 11.0 and older
|
||||
ITS#8824 - mdb_dump: cleanup check for MDB_SUCCESS
|
||||
ITS#9388 - fix mdb_stat page counts for dupsort DBs
|
||||
ITS#10054 - fix write of 2GB values
|
||||
ITS#10108 - mdb_dump: fix comment
|
||||
ITS#10454 - fix metapage sync on Mac OSX
|
||||
ITS#10504 - silence linker waarning on Mac OSX
|
||||
ITS#10522 - reject 0-length values in DUPSORT DBs
|
||||
|
||||
LMDB 0.9.35 Release (2026/01/27)
|
||||
ITS#10434 - Fix typo affecting Mac OSX
|
||||
|
||||
LMDB 0.9.34 Release (2026/01/26)
|
||||
ITS#9564 - fix race condition freeing spilled pages at end of transaction
|
||||
ITS#10222 - Update mdb_dump(1) and mdb_load(1) man pages for append (-a) option
|
||||
ITS#10275 - mdb_load: add -Q option to use NOSYNC
|
||||
ITS#10296 - fix fdatasync on MacOS
|
||||
ITS#10342 - fix memleak in mdb_txn_begin for nested txns
|
||||
ITS#10346 - fix mdb_env_copy2 with values > (2GB-16)
|
||||
ITS#10355 - fix mplay build on musl
|
||||
ITS#10396 - fix mdb_cursor_del0 with multiple DUPSORT cursors
|
||||
ITS#10419 - add support for NetBSD
|
||||
ITS#10420 - add support for Haiku
|
||||
ITS#10421 - mdb_load: check for malicious input
|
||||
|
||||
LMDB 0.9.33 Release (2024/05/21)
|
||||
ITS#9037 mdb_page_search: fix error code when DBI record is missing
|
||||
ITS#10198 For win32, stop passing ignored parameter
|
||||
ITS#10212 Fix meta page usage by read only tools
|
||||
|
||||
LMDB 0.9.32 Release (2024/01/29)
|
||||
ITS#9378 - Add ability to replay log and replay log tool
|
||||
ITS#10095 - partial revert of ITS#9278. The patch was incorrect and introduced numerous race conditions.
|
||||
ITS#10125 - mdb_load: fix cursor reinit in Append mode
|
||||
ITS#10137 - Allow users to define MDB_IDL_LOGN
|
||||
|
||||
LMDB 0.9.31 Release (2023/07/10)
|
||||
ITS#8447 - Fix cursor_put(MDB_CURRENT) on DUPSORT DB with different sized data
|
||||
|
||||
LMDB 0.9.30 Release (2023/02/08)
|
||||
ITS#9806 - LMDB page_split: key threshold depends on page size
|
||||
ITS#9916 - avoid gcc optimization bug on sparc64 linux
|
||||
ITS#9919 - Mark infrequently used functions as cold
|
||||
ITS#9723 - clear C_EOF on cursor with MDB_FIRST_DUP
|
||||
ITS#9030 - Use sys/cachectl.h rather than asm/cachectl.h on mips
|
||||
|
||||
LMDB 0.9.29 Release (2021/03/16)
|
||||
ITS#9461 refix ITS#9376
|
||||
ITS#9500 fix regression from ITS#8662
|
||||
|
||||
LMDB 0.9.28 Release (2021/02/04)
|
||||
ITS#8662 add -a append option to mdb_load
|
||||
|
||||
LMDB 0.9.27 Release (2020/10/26)
|
||||
ITS#9376 fix repeated DUPSORT cursor deletes
|
||||
|
||||
LMDB 0.9.26 Release (2020/08/11)
|
||||
ITS#9278 fix robust mutex cleanup for FreeBSD
|
||||
|
||||
LMDB 0.9.25 Release (2020/01/30)
|
||||
ITS#9068 fix mdb_dump/load backslashes in printable content
|
||||
ITS#9118 add MAP_NOSYNC for FreeBSD
|
||||
ITS#9155 free mt_spill_pgs in non-nested txn on end
|
||||
|
||||
LMDB 0.9.24 Release (2019/07/24)
|
||||
ITS#8969 Tweak mdb_page_split
|
||||
ITS#8975 WIN32 fix writemap set_mapsize crash
|
||||
ITS#9007 Fix loose pages in WRITEMAP
|
||||
|
||||
LMDB 0.9.23 Release (2018/12/19)
|
||||
ITS#8756 Fix loose pages in dirty list
|
||||
ITS#8831 Fix mdb_load flag init
|
||||
ITS#8844 Fix mdb_env_close in forked process
|
||||
Documentation
|
||||
ITS#8857 mdb_cursor_del doesn't invalidate cursor
|
||||
ITS#8908 GET_MULTIPLE etc don't change passed in key
|
||||
|
||||
LMDB 0.9.22 Release (2018/03/22)
|
||||
Fix MDB_DUPSORT alignment bug (ITS#8819)
|
||||
Fix regression with new db from 0.9.19 (ITS#8760)
|
||||
Fix liblmdb to build on Solaris (ITS#8612)
|
||||
Fix delete behavior with DUPSORT DB (ITS#8622)
|
||||
Fix mdb_cursor_get/mdb_cursor_del behavior (ITS#8722)
|
||||
|
||||
LMDB 0.9.21 Release (2017/06/01)
|
||||
Fix xcursor after cursor_del (ITS#8622)
|
||||
|
||||
LMDB 0.9.20 (Withdrawn)
|
||||
Fix mdb_load with escaped plaintext (ITS#8558)
|
||||
Fix mdb_cursor_last / mdb_put interaction (ITS#8557)
|
||||
|
||||
LMDB 0.9.19 Release (2016/12/28)
|
||||
Fix mdb_env_cwalk cursor init (ITS#8424)
|
||||
Fix robust mutexes on Solaris 10/11 (ITS#8339)
|
||||
Tweak Win32 error message buffer
|
||||
Fix MDB_GET_BOTH on non-dup record (ITS#8393)
|
||||
Optimize mdb_drop
|
||||
Fix xcursors after mdb_cursor_del (ITS#8406)
|
||||
Fix MDB_NEXT_DUP after mdb_cursor_del (ITS#8412)
|
||||
Fix mdb_cursor_put resetting C_EOF (ITS#8489)
|
||||
Fix mdb_env_copyfd2 to return EPIPE on SIGPIPE (ITS#8504)
|
||||
Fix mdb_env_copy with empty DB (ITS#8209)
|
||||
Fix behaviors with fork (ITS#8505)
|
||||
Fix mdb_dbi_open with mainDB cursors (ITS#8542)
|
||||
Fix robust mutexes on kFreeBSD (ITS#8554)
|
||||
Fix utf8_to_utf16 error checks (ITS#7992)
|
||||
Fix F_NOCACHE on MacOS, error is non-fatal (ITS#7682)
|
||||
Build
|
||||
Make shared lib suffix overridable (ITS#8481)
|
||||
Documentation
|
||||
Cleanup doxygen nits
|
||||
Note reserved vs actual mem/disk usage
|
||||
|
||||
|
||||
LMDB 0.9.18 Release (2016/02/05)
|
||||
Fix robust mutex detection on glibc 2.10-11 (ITS#8330)
|
||||
Fix page_search_root assert on FreeDB (ITS#8336)
|
||||
Fix MDB_APPENDDUP vs. rewrite(single item) (ITS#8334)
|
||||
Fix mdb_copy of large files on Windows
|
||||
Fix subcursor move after delete (ITS#8355)
|
||||
Fix mdb_midl_shirnk off-by-one (ITS#8363)
|
||||
Check for utf8_to_utf16 failures (ITS#7992)
|
||||
Catch strdup failure in mdb_dbi_open
|
||||
Build
|
||||
Additional makefile var tweaks (ITS#8169)
|
||||
Documentation
|
||||
Add Getting Started page
|
||||
Update WRITEMAP description
|
||||
|
||||
|
||||
LMDB 0.9.17 Release (2015/11/30)
|
||||
Fix ITS#7377 catch calloc failure
|
||||
Fix ITS#8237 regression from ITS#7589
|
||||
Fix ITS#8238 page_split for DUPFIXED pages
|
||||
Fix ITS#8221 MDB_PAGE_FULL on delete/rebalance
|
||||
Fix ITS#8258 rebalance/split assert
|
||||
Fix ITS#8263 cursor_put cursor tracking
|
||||
Fix ITS#8264 cursor_del cursor tracking
|
||||
Fix ITS#8310 cursor_del cursor tracking
|
||||
Fix ITS#8299 mdb_del cursor tracking
|
||||
Fix ITS#8300 mdb_del cursor tracking
|
||||
Fix ITS#8304 mdb_del cursor tracking
|
||||
Fix ITS#7771 fakepage cursor tracking
|
||||
Fix ITS#7789 ensure mapsize >= pages in use
|
||||
Fix ITS#7971 mdb_txn_renew0() new reader slots
|
||||
Fix ITS#7969 use __sync_synchronize on non-x86
|
||||
Fix ITS#8311 page_split from update_key
|
||||
Fix ITS#8312 loose pages in nested txn
|
||||
Fix ITS#8313 mdb_rebalance dummy cursor
|
||||
Fix ITS#8315 dirty_room in nested txn
|
||||
Fix ITS#8323 dirty_list in nested txn
|
||||
Fix ITS#8316 page_merge cursor tracking
|
||||
Fix ITS#8321 cursor tracking
|
||||
Fix ITS#8319 mdb_load error messages
|
||||
Fix ITS#8320 mdb_load plaintext input
|
||||
Added mdb_txn_id() (ITS#7994)
|
||||
Added robust mutex support
|
||||
Miscellaneous cleanup/simplification
|
||||
Build
|
||||
Create install dirs if needed (ITS#8256)
|
||||
Fix ThreadProc decl on Win32/MSVC (ITS#8270)
|
||||
Added ssize_t typedef for MSVC (ITS#8067)
|
||||
Use ANSI apis on Windows (ITS#8069)
|
||||
Use O_SYNC if O_DSYNC,MDB_DSYNC are not defined (ITS#7209)
|
||||
Allow passing AR to make (ITS#8168)
|
||||
Allow passing mandir to make install (ITS#8169)
|
||||
|
||||
LMDB 0.9.16 Release (2015/08/14)
|
||||
Fix cursor EOF bug (ITS#8190)
|
||||
Fix handling of subDB records (ITS#8181)
|
||||
Fix mdb_midl_shrink() usage (ITS#8200)
|
||||
|
||||
LMDB 0.9.15 Release (2015/06/19)
|
||||
Fix txn init (ITS#7961,#7987)
|
||||
Fix MDB_PREV_DUP (ITS#7955,#7671)
|
||||
Fix compact of empty env (ITS#7956)
|
||||
Fix mdb_copy file mode
|
||||
Fix mdb_env_close() after failed mdb_env_open()
|
||||
Fix mdb_rebalance collapsing root (ITS#8062)
|
||||
Fix mdb_load with large values (ITS#8066)
|
||||
Fix to retry writes on EINTR (ITS#8106)
|
||||
Fix mdb_cursor_del on empty DB (ITS#8109)
|
||||
Fix MDB_INTEGERDUP key compare (ITS#8117)
|
||||
Fix error handling (ITS#7959,#8157,etc.)
|
||||
Fix race conditions (ITS#7969,7970)
|
||||
Added workaround for fdatasync bug in ext3fs
|
||||
Build
|
||||
Don't use -fPIC for static lib
|
||||
Update .gitignore (ITS#7952,#7953)
|
||||
Cleanup for "make test" (ITS#7841), "make clean", mtest*.c
|
||||
Misc. Android/Windows cleanup
|
||||
Documentation
|
||||
Fix MDB_APPEND doc
|
||||
Fix MDB_MAXKEYSIZE doc (ITS#8156)
|
||||
Fix mdb_cursor_put,mdb_cursor_del EACCES description
|
||||
Fix mdb_env_sync(MDB_RDONLY env) doc (ITS#8021)
|
||||
Clarify MDB_WRITEMAP doc (ITS#8021)
|
||||
Clarify mdb_env_open doc
|
||||
Clarify mdb_dbi_open doc
|
||||
|
||||
LMDB 0.9.14 Release (2014/09/20)
|
||||
Fix to support 64K page size (ITS#7713)
|
||||
Fix to persist decreased as well as increased mapsizes (ITS#7789)
|
||||
Fix cursor bug when deleting last node of a DUPSORT key
|
||||
Fix mdb_env_info to return FIXEDMAP address
|
||||
Fix ambiguous error code from writing to closed DBI (ITS#7825)
|
||||
Fix mdb_copy copying past end of file (ITS#7886)
|
||||
Fix cursor bugs from page_merge/rebalance
|
||||
Fix to dirty fewer pages in deletes (mdb_page_loose())
|
||||
Fix mdb_dbi_open creating subDBs (ITS#7917)
|
||||
Fix mdb_cursor_get(_DUP) with single value (ITS#7913)
|
||||
Fix Windows compat issues in mtests (ITS#7879)
|
||||
Add compacting variant of mdb_copy
|
||||
Add BigEndian integer key compare code
|
||||
Add mdb_dump/mdb_load utilities
|
||||
|
||||
LMDB 0.9.13 Release (2014/06/18)
|
||||
Fix mdb_page_alloc unlimited overflow page search
|
||||
Documentation
|
||||
Re-fix MDB_CURRENT doc (ITS#7793)
|
||||
Fix MDB_GET_MULTIPLE/MDB_NEXT_MULTIPLE doc
|
||||
|
||||
LMDB 0.9.12 Release (2014/06/13)
|
||||
Fix MDB_GET_BOTH regression (ITS#7875,#7681)
|
||||
Fix MDB_MULTIPLE writing multiple keys (ITS#7834)
|
||||
Fix mdb_rebalance (ITS#7829)
|
||||
Fix mdb_page_split (ITS#7815)
|
||||
Fix md_entries count (ITS#7861,#7828,#7793)
|
||||
Fix MDB_CURRENT (ITS#7793)
|
||||
Fix possible crash on Windows DLL detach
|
||||
Misc code cleanup
|
||||
Documentation
|
||||
mdb_cursor_put: cursor moves on error (ITS#7771)
|
||||
|
||||
|
||||
LMDB 0.9.11 Release (2014/01/15)
|
||||
Add mdb_env_set_assert() (ITS#7775)
|
||||
Fix: invalidate txn on page allocation errors (ITS#7377)
|
||||
Fix xcursor tracking in mdb_cursor_del0() (ITS#7771)
|
||||
Fix corruption from deletes (ITS#7756)
|
||||
Fix Windows/MSVC build issues
|
||||
Raise safe limit of max MDB_MAXKEYSIZE
|
||||
Misc code cleanup
|
||||
Documentation
|
||||
Remove spurious note about non-overlapping flags (ITS#7665)
|
||||
|
||||
LMDB 0.9.10 Release (2013/11/12)
|
||||
Add MDB_NOMEMINIT option
|
||||
Fix mdb_page_split() again (ITS#7589)
|
||||
Fix MDB_NORDAHEAD definition (ITS#7734)
|
||||
Fix mdb_cursor_del() positioning (ITS#7733)
|
||||
Partial fix for larger page sizes (ITS#7713)
|
||||
Fix Windows64/MSVC build issues
|
||||
|
||||
LMDB 0.9.9 Release (2013/10/24)
|
||||
Add mdb_env_get_fd()
|
||||
Add MDB_NORDAHEAD option
|
||||
Add MDB_NOLOCK option
|
||||
Avoid wasting space in mdb_page_split() (ITS#7589)
|
||||
Fix mdb_page_merge() cursor fixup (ITS#7722)
|
||||
Fix mdb_cursor_del() on last delete (ITS#7718)
|
||||
Fix adding WRITEMAP on existing env (ITS#7715)
|
||||
Fix nested txns (ITS#7515)
|
||||
Fix mdb_env_copy() O_DIRECT bug (ITS#7682)
|
||||
Fix mdb_cursor_set(SET_RANGE) return code (ITS#7681)
|
||||
Fix mdb_rebalance() cursor fixup (ITS#7701)
|
||||
Misc code cleanup
|
||||
Documentation
|
||||
Note that by default, readers need write access
|
||||
|
||||
|
||||
LMDB 0.9.8 Release (2013/09/09)
|
||||
Allow mdb_env_set_mapsize() on an open environment
|
||||
Fix mdb_dbi_flags() (ITS#7672)
|
||||
Fix mdb_page_unspill() in nested txns
|
||||
Fix mdb_cursor_get(CURRENT|NEXT) after a delete
|
||||
Fix mdb_cursor_get(DUP) to always return key (ITS#7671)
|
||||
Fix mdb_cursor_del() to always advance to next item (ITS#7670)
|
||||
Fix mdb_cursor_set(SET_RANGE) for tree with single page (ITS#7681)
|
||||
Fix mdb_env_copy() retry open if O_DIRECT fails (ITS#7682)
|
||||
Tweak mdb_page_spill() to be less aggressive
|
||||
Documentation
|
||||
Update caveats since mdb_reader_check() added in 0.9.7
|
||||
|
||||
LMDB 0.9.7 Release (2013/08/17)
|
||||
Don't leave stale lockfile on failed RDONLY open (ITS#7664)
|
||||
Fix mdb_page_split() ref beyond cursor depth
|
||||
Fix read txn data race (ITS#7635)
|
||||
Fix mdb_rebalance (ITS#7536, #7538)
|
||||
Fix mdb_drop() (ITS#7561)
|
||||
Misc DEBUG macro fixes
|
||||
Add MDB_NOTLS envflag
|
||||
Add mdb_env_copyfd()
|
||||
Add mdb_txn_env() (ITS#7660)
|
||||
Add mdb_dbi_flags() (ITS#7661)
|
||||
Add mdb_env_get_maxkeysize()
|
||||
Add mdb_env_reader_list()/mdb_env_reader_check()
|
||||
Add mdb_page_spill/unspill, remove hard txn size limit
|
||||
Use shorter names for semaphores (ITS#7615)
|
||||
Build
|
||||
Fix install target (ITS#7656)
|
||||
Documentation
|
||||
Misc updates for cursors, DB handles, data lifetime
|
||||
|
||||
LMDB 0.9.6 Release (2013/02/25)
|
||||
Many fixes/enhancements
|
||||
|
||||
LMDB 0.9.5 Release (2012/11/30)
|
||||
Renamed from libmdb to liblmdb
|
||||
Many fixes/enhancements
|
||||
@@ -0,0 +1,20 @@
|
||||
Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted only as authorized by the OpenLDAP
|
||||
Public License.
|
||||
|
||||
A copy of this license is available in the file LICENSE in the
|
||||
top-level directory of the distribution or, alternatively, at
|
||||
<http://www.OpenLDAP.org/license.html>.
|
||||
|
||||
OpenLDAP is a registered trademark of the OpenLDAP Foundation.
|
||||
|
||||
Individual files and/or contributed packages may be copyright by
|
||||
other parties and/or subject to additional restrictions.
|
||||
|
||||
This work also contains materials derived from public sources.
|
||||
|
||||
Additional information about OpenLDAP can be obtained at
|
||||
<http://www.openldap.org/>.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,47 @@
|
||||
The OpenLDAP Public License
|
||||
Version 2.8, 17 August 2003
|
||||
|
||||
Redistribution and use of this software and associated documentation
|
||||
("Software"), with or without modification, are permitted provided
|
||||
that the following conditions are met:
|
||||
|
||||
1. Redistributions in source form must retain copyright statements
|
||||
and notices,
|
||||
|
||||
2. Redistributions in binary form must reproduce applicable copyright
|
||||
statements and notices, this list of conditions, and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution, and
|
||||
|
||||
3. Redistributions must contain a verbatim copy of this document.
|
||||
|
||||
The OpenLDAP Foundation may revise this license from time to time.
|
||||
Each revision is distinguished by a version number. You may use
|
||||
this Software under terms of this license revision or under the
|
||||
terms of any subsequent revision of the license.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE OPENLDAP FOUNDATION AND ITS
|
||||
CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
||||
SHALL THE OPENLDAP FOUNDATION, ITS CONTRIBUTORS, OR THE AUTHOR(S)
|
||||
OR OWNER(S) OF THE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The names of the authors and copyright holders must not be used in
|
||||
advertising or otherwise to promote the sale, use or other dealing
|
||||
in this Software without specific, written prior permission. Title
|
||||
to copyright in this Software shall at all times remain with copyright
|
||||
holders.
|
||||
|
||||
OpenLDAP is a registered trademark of the OpenLDAP Foundation.
|
||||
|
||||
Copyright 1999-2003 The OpenLDAP Foundation, Redwood City,
|
||||
California, USA. All Rights Reserved. Permission to copy and
|
||||
distribute verbatim copies of this document is granted.
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright 2015-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
/** @page starting Getting Started
|
||||
|
||||
LMDB is compact, fast, powerful, and robust and implements a simplified
|
||||
variant of the BerkeleyDB (BDB) API. (BDB is also very powerful, and verbosely
|
||||
documented in its own right.) After reading this page, the main
|
||||
\ref mdb documentation should make sense. Thanks to Bert Hubert
|
||||
for creating the
|
||||
<a href="https://github.com/ahupowerdns/ahutils/blob/master/lmdb-semantics.md">
|
||||
initial version</a> of this writeup.
|
||||
|
||||
Everything starts with an environment, created by #mdb_env_create().
|
||||
Once created, this environment must also be opened with #mdb_env_open().
|
||||
|
||||
#mdb_env_open() gets passed a name which is interpreted as a directory
|
||||
path. Note that this directory must exist already, it is not created
|
||||
for you. Within that directory, a lock file and a storage file will be
|
||||
generated. If you don't want to use a directory, you can pass the
|
||||
#MDB_NOSUBDIR option, in which case the path you provided is used
|
||||
directly as the data file, and another file with a "-lock" suffix
|
||||
added will be used for the lock file.
|
||||
|
||||
Once the environment is open, a transaction can be created within it
|
||||
using #mdb_txn_begin(). Transactions may be read-write or read-only,
|
||||
and read-write transactions may be nested. A transaction must only
|
||||
be used by one thread at a time. Transactions are always required,
|
||||
even for read-only access. The transaction provides a consistent
|
||||
view of the data.
|
||||
|
||||
Once a transaction has been created, a database can be opened within it
|
||||
using #mdb_dbi_open(). If only one database will ever be used in the
|
||||
environment, a NULL can be passed as the database name. For named
|
||||
databases, the #MDB_CREATE flag must be used to create the database
|
||||
if it doesn't already exist. Also, #mdb_env_set_maxdbs() must be
|
||||
called after #mdb_env_create() and before #mdb_env_open() to set the
|
||||
maximum number of named databases you want to support.
|
||||
|
||||
Note: a single transaction can open multiple databases. Generally
|
||||
databases should only be opened once, by the first transaction in
|
||||
the process. After the first transaction completes, the database
|
||||
handles can freely be used by all subsequent transactions.
|
||||
|
||||
Within a transaction, #mdb_get() can retrieve and #mdb_put() can store single
|
||||
key/value pairs if that is all you need to do (but see \ref Cursors
|
||||
below if you want to do more).
|
||||
|
||||
A key/value pair is expressed as two #MDB_val structures. This struct
|
||||
has two fields, \c mv_size and \c mv_data. The data is a \c void pointer to
|
||||
an array of \c mv_size bytes.
|
||||
|
||||
Because LMDB is very efficient (and usually zero-copy), the data returned
|
||||
in an #MDB_val structure may be memory-mapped straight from disk. In
|
||||
other words <b>look but do not touch</b> (or free() for that matter).
|
||||
Once a transaction is closed, the values can no longer be used, so
|
||||
make a copy if you need to keep them after that.
|
||||
|
||||
@section Cursors Cursors
|
||||
|
||||
To do more powerful things, we must use a cursor.
|
||||
|
||||
Within the transaction, a cursor can be created with #mdb_cursor_open().
|
||||
With this cursor we can store/retrieve/delete (multiple) values using
|
||||
#mdb_cursor_get(), #mdb_cursor_put(), and #mdb_cursor_del().
|
||||
|
||||
#mdb_cursor_get() positions itself depending on the cursor operation
|
||||
requested, and for some operations, on the supplied key. For example,
|
||||
to list all key/value pairs in a database, use operation #MDB_FIRST for
|
||||
the first call to #mdb_cursor_get(), and #MDB_NEXT on subsequent calls,
|
||||
until the end is hit.
|
||||
|
||||
To retrieve all keys starting from a specified key value, use #MDB_SET.
|
||||
For more cursor operations, see the \ref mdb docs.
|
||||
|
||||
When using #mdb_cursor_put(), either the function will position the
|
||||
cursor for you based on the \b key, or you can use operation
|
||||
#MDB_CURRENT to use the current position of the cursor. Note that
|
||||
\b key must then match the current position's key.
|
||||
|
||||
@subsection summary Summarizing the Opening
|
||||
|
||||
So we have a cursor in a transaction which opened a database in an
|
||||
environment which is opened from a filesystem after it was
|
||||
separately created.
|
||||
|
||||
Or, we create an environment, open it from a filesystem, create a
|
||||
transaction within it, open a database within that transaction,
|
||||
and create a cursor within all of the above.
|
||||
|
||||
Got it?
|
||||
|
||||
@section thrproc Threads and Processes
|
||||
|
||||
LMDB uses POSIX locks on files, and these locks have issues if one
|
||||
process opens a file multiple times. Because of this, do not
|
||||
#mdb_env_open() a file multiple times from a single process. Instead,
|
||||
share the LMDB environment that has opened the file across all threads.
|
||||
Otherwise, if a single process opens the same environment multiple times,
|
||||
closing it once will remove all the locks held on it, and the other
|
||||
instances will be vulnerable to corruption from other processes.
|
||||
|
||||
Also note that a transaction is tied to one thread by default using
|
||||
Thread Local Storage. If you want to pass read-only transactions across
|
||||
threads, you can use the #MDB_NOTLS option on the environment.
|
||||
|
||||
@section txns Transactions, Rollbacks, etc.
|
||||
|
||||
To actually get anything done, a transaction must be committed using
|
||||
#mdb_txn_commit(). Alternatively, all of a transaction's operations
|
||||
can be discarded using #mdb_txn_abort(). In a read-only transaction,
|
||||
any cursors will \b not automatically be freed. In a read-write
|
||||
transaction, all cursors will be freed and must not be used again.
|
||||
|
||||
For read-only transactions, obviously there is nothing to commit to
|
||||
storage. The transaction still must eventually be aborted to close
|
||||
any database handle(s) opened in it, or committed to keep the
|
||||
database handles around for reuse in new transactions.
|
||||
|
||||
In addition, as long as a transaction is open, a consistent view of
|
||||
the database is kept alive, which requires storage. A read-only
|
||||
transaction that no longer requires this consistent view should
|
||||
be terminated (committed or aborted) when the view is no longer
|
||||
needed (but see below for an optimization).
|
||||
|
||||
There can be multiple simultaneously active read-only transactions
|
||||
but only one that can write. Once a single read-write transaction
|
||||
is opened, all further attempts to begin one will block until the
|
||||
first one is committed or aborted. This has no effect on read-only
|
||||
transactions, however, and they may continue to be opened at any time.
|
||||
|
||||
@section dupkeys Duplicate Keys
|
||||
|
||||
#mdb_get() and #mdb_put() respectively have no and only some support
|
||||
for multiple key/value pairs with identical keys. If there are multiple
|
||||
values for a key, #mdb_get() will only return the first value.
|
||||
|
||||
When multiple values for one key are required, pass the #MDB_DUPSORT
|
||||
flag to #mdb_dbi_open(). In an #MDB_DUPSORT database, by default
|
||||
#mdb_put() will not replace the value for a key if the key existed
|
||||
already. Instead it will add the new value to the key. In addition,
|
||||
#mdb_del() will pay attention to the value field too, allowing for
|
||||
specific values of a key to be deleted.
|
||||
|
||||
Finally, additional cursor operations become available for
|
||||
traversing through and retrieving duplicate values.
|
||||
|
||||
@section optim Some Optimization
|
||||
|
||||
If you frequently begin and abort read-only transactions, as an
|
||||
optimization, it is possible to only reset and renew a transaction.
|
||||
|
||||
#mdb_txn_reset() releases any old copies of data kept around for
|
||||
a read-only transaction. To reuse this reset transaction, call
|
||||
#mdb_txn_renew() on it. Any cursors in this transaction must also
|
||||
be renewed using #mdb_cursor_renew().
|
||||
|
||||
Note that #mdb_txn_reset() is similar to #mdb_txn_abort() and will
|
||||
close any databases you opened within the transaction.
|
||||
|
||||
To permanently free a transaction, reset or not, use #mdb_txn_abort().
|
||||
|
||||
@section cleanup Cleaning Up
|
||||
|
||||
For read-only transactions, any cursors created within it must
|
||||
be closed using #mdb_cursor_close().
|
||||
|
||||
It is very rarely necessary to close a database handle, and in
|
||||
general they should just be left open.
|
||||
|
||||
@section onward The Full API
|
||||
|
||||
The full \ref mdb documentation lists further details, like how to:
|
||||
|
||||
\li size a database (the default limits are intentionally small)
|
||||
\li drop and clean a database
|
||||
\li detect and report errors
|
||||
\li optimize (bulk) loading speed
|
||||
\li (temporarily) reduce robustness to gain even more speed
|
||||
\li gather statistics about the database
|
||||
\li define custom sort orders
|
||||
|
||||
*/
|
||||
+1608
File diff suppressed because it is too large.
Load diff
+10504
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,55 @@
|
||||
.TH MDB_COPY 1 "2014/07/01" "LMDB 0.9.14"
|
||||
.\" Copyright 2012-2021 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.SH NAME
|
||||
mdb_copy \- LMDB environment copy tool
|
||||
.SH SYNOPSIS
|
||||
.B mdb_copy
|
||||
[\c
|
||||
.BR \-V ]
|
||||
[\c
|
||||
.BR \-c ]
|
||||
[\c
|
||||
.BR \-n ]
|
||||
.B srcpath
|
||||
[\c
|
||||
.BR dstpath ]
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mdb_copy
|
||||
utility copies an LMDB environment. The environment can
|
||||
be copied regardless of whether it is currently in use.
|
||||
No lockfile is created, since it gets recreated at need.
|
||||
|
||||
If
|
||||
.I dstpath
|
||||
is specified it must be the path of an empty directory
|
||||
for storing the backup. Otherwise, the backup will be
|
||||
written to stdout.
|
||||
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-V
|
||||
Write the library version number to the standard output, and exit.
|
||||
.TP
|
||||
.BR \-c
|
||||
Compact while copying. Only current data pages will be copied; freed
|
||||
or unused pages will be omitted from the copy. This option will
|
||||
slow down the backup process as it is more CPU-intensive.
|
||||
Currently it fails if the environment has suffered a page leak.
|
||||
.TP
|
||||
.BR \-n
|
||||
Open LDMB environment(s) which do not use subdirectories.
|
||||
|
||||
.SH DIAGNOSTICS
|
||||
Exit status is zero if no errors occur.
|
||||
Errors result in a non-zero exit status and
|
||||
a diagnostic message being written to standard error.
|
||||
.SH CAVEATS
|
||||
This utility can trigger significant file size growth if run
|
||||
in parallel with write transactions, because pages which they
|
||||
free during copying cannot be reused until the copy is done.
|
||||
.SH "SEE ALSO"
|
||||
.BR mdb_stat (1)
|
||||
.SH AUTHOR
|
||||
Howard Chu of Symas Corporation <http://www.symas.com>
|
||||
@@ -0,0 +1,82 @@
|
||||
/* mdb_copy.c - memory-mapped database backup tool */
|
||||
/*
|
||||
* Copyright 2012-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#define MDB_STDOUT GetStdHandle(STD_OUTPUT_HANDLE)
|
||||
#else
|
||||
#define MDB_STDOUT 1
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
static void
|
||||
sighandle(int sig)
|
||||
{
|
||||
}
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int rc;
|
||||
MDB_env *env;
|
||||
const char *progname = argv[0], *act;
|
||||
unsigned flags = MDB_RDONLY;
|
||||
unsigned cpflags = 0;
|
||||
|
||||
for (; argc > 1 && argv[1][0] == '-'; argc--, argv++) {
|
||||
if (argv[1][1] == 'n' && argv[1][2] == '\0')
|
||||
flags |= MDB_NOSUBDIR;
|
||||
else if (argv[1][1] == 'c' && argv[1][2] == '\0')
|
||||
cpflags |= MDB_CP_COMPACT;
|
||||
else if (argv[1][1] == 'V' && argv[1][2] == '\0') {
|
||||
printf("%s\n", MDB_VERSION_STRING);
|
||||
exit(0);
|
||||
} else
|
||||
argc = 0;
|
||||
}
|
||||
|
||||
if (argc<2 || argc>3) {
|
||||
fprintf(stderr, "usage: %s [-V] [-c] [-n] srcpath [dstpath]\n", progname);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
#ifdef SIGPIPE
|
||||
signal(SIGPIPE, sighandle);
|
||||
#endif
|
||||
#ifdef SIGHUP
|
||||
signal(SIGHUP, sighandle);
|
||||
#endif
|
||||
signal(SIGINT, sighandle);
|
||||
signal(SIGTERM, sighandle);
|
||||
|
||||
act = "opening environment";
|
||||
rc = mdb_env_create(&env);
|
||||
if (rc == MDB_SUCCESS) {
|
||||
rc = mdb_env_open(env, argv[1], flags, 0600);
|
||||
}
|
||||
if (rc == MDB_SUCCESS) {
|
||||
act = "copying";
|
||||
if (argc == 2)
|
||||
rc = mdb_env_copyfd2(env, MDB_STDOUT, cpflags);
|
||||
else
|
||||
rc = mdb_env_copy2(env, argv[2], cpflags);
|
||||
}
|
||||
if (rc)
|
||||
fprintf(stderr, "%s: %s failed, error %d (%s)\n",
|
||||
progname, act, rc, mdb_strerror(rc));
|
||||
mdb_env_close(env);
|
||||
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
.TH MDB_DUMP 1 "2015/09/30" "LMDB 0.9.17"
|
||||
.\" Copyright 2014-2021 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.SH NAME
|
||||
mdb_dump \- LMDB environment export tool
|
||||
.SH SYNOPSIS
|
||||
.B mdb_dump
|
||||
[\c
|
||||
.BR \-V ]
|
||||
[\c
|
||||
.BI \-f \ file\fR]
|
||||
[\c
|
||||
.BR \-l ]
|
||||
[\c
|
||||
.BR \-n ]
|
||||
[\c
|
||||
.BR \-p ]
|
||||
[\c
|
||||
.BR \-a \ |
|
||||
.BI \-s \ subdb\fR]
|
||||
.BR \ envpath
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mdb_dump
|
||||
utility reads a database and writes its contents to the
|
||||
standard output using a portable flat-text format
|
||||
understood by the
|
||||
.BR mdb_load (1)
|
||||
utility.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-V
|
||||
Write the library version number to the standard output, and exit.
|
||||
.TP
|
||||
.BR \-f \ file
|
||||
Write to the specified file instead of to the standard output.
|
||||
.TP
|
||||
.BR \-l
|
||||
List the databases stored in the environment. Just the
|
||||
names will be listed, no data will be output.
|
||||
.TP
|
||||
.BR \-n
|
||||
Dump an LMDB database which does not use subdirectories.
|
||||
.TP
|
||||
.BR \-p
|
||||
If characters in either the key or data items are printing characters (as
|
||||
defined by isprint(3)), output them directly. This option permits users to
|
||||
use standard text editors and tools to modify the contents of databases.
|
||||
|
||||
Note: different systems may have different notions about what characters
|
||||
are considered printing characters, and databases dumped in this manner may
|
||||
be less portable to external systems.
|
||||
.TP
|
||||
.BR \-a
|
||||
Dump all of the subdatabases in the environment.
|
||||
.TP
|
||||
.BR \-s \ subdb
|
||||
Dump a specific subdatabase. If no database is specified, only the main database is dumped.
|
||||
.SH DIAGNOSTICS
|
||||
Exit status is zero if no errors occur.
|
||||
Errors result in a non-zero exit status and
|
||||
a diagnostic message being written to standard error.
|
||||
|
||||
Dumping databases that use user-defined comparison functions will output
|
||||
records with the ordering imposed by those comparison functions. If
|
||||
.B mdb_load
|
||||
is invoked without including the
|
||||
.B -a
|
||||
option when reloading those records, the new databases will likely be
|
||||
damaged beyond repair, permitting neither record storage nor retrieval.
|
||||
|
||||
.SH "SEE ALSO"
|
||||
.BR mdb_load (1)
|
||||
.SH AUTHOR
|
||||
Howard Chu of Symas Corporation <http://www.symas.com>
|
||||
@@ -0,0 +1,319 @@
|
||||
/* mdb_dump.c - memory-mapped database dump tool */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define Z "I"
|
||||
#else
|
||||
#define Z "z"
|
||||
#endif
|
||||
|
||||
#define PRINT 1
|
||||
static int mode;
|
||||
|
||||
typedef struct flagbit {
|
||||
int bit;
|
||||
char *name;
|
||||
} flagbit;
|
||||
|
||||
flagbit dbflags[] = {
|
||||
{ MDB_REVERSEKEY, "reversekey" },
|
||||
{ MDB_DUPSORT, "dupsort" },
|
||||
{ MDB_INTEGERKEY, "integerkey" },
|
||||
{ MDB_DUPFIXED, "dupfixed" },
|
||||
{ MDB_INTEGERDUP, "integerdup" },
|
||||
{ MDB_REVERSEDUP, "reversedup" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
static volatile sig_atomic_t gotsig;
|
||||
|
||||
static void dumpsig( int sig )
|
||||
{
|
||||
gotsig=1;
|
||||
}
|
||||
|
||||
static const char hexc[] = "0123456789abcdef";
|
||||
|
||||
static void hex(unsigned char c)
|
||||
{
|
||||
putchar(hexc[c >> 4]);
|
||||
putchar(hexc[c & 0xf]);
|
||||
}
|
||||
|
||||
static void text(MDB_val *v)
|
||||
{
|
||||
unsigned char *c, *end;
|
||||
|
||||
putchar(' ');
|
||||
c = v->mv_data;
|
||||
end = c + v->mv_size;
|
||||
while (c < end) {
|
||||
if (isprint(*c)) {
|
||||
if (*c == '\\')
|
||||
putchar('\\');
|
||||
putchar(*c);
|
||||
} else {
|
||||
putchar('\\');
|
||||
hex(*c);
|
||||
}
|
||||
c++;
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
static void byte(MDB_val *v)
|
||||
{
|
||||
unsigned char *c, *end;
|
||||
|
||||
putchar(' ');
|
||||
c = v->mv_data;
|
||||
end = c + v->mv_size;
|
||||
while (c < end) {
|
||||
hex(*c++);
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
/* Dump in BDB-compatible format */
|
||||
static int dumpit(MDB_txn *txn, MDB_dbi dbi, char *name)
|
||||
{
|
||||
MDB_cursor *mc;
|
||||
MDB_stat ms;
|
||||
MDB_val key, data;
|
||||
MDB_envinfo info;
|
||||
unsigned int flags;
|
||||
int rc, i;
|
||||
|
||||
rc = mdb_dbi_flags(txn, dbi, &flags);
|
||||
if (rc) return rc;
|
||||
|
||||
rc = mdb_stat(txn, dbi, &ms);
|
||||
if (rc) return rc;
|
||||
|
||||
rc = mdb_env_info(mdb_txn_env(txn), &info);
|
||||
if (rc) return rc;
|
||||
|
||||
printf("VERSION=3\n");
|
||||
printf("format=%s\n", mode & PRINT ? "print" : "bytevalue");
|
||||
if (name)
|
||||
printf("database=%s\n", name);
|
||||
printf("type=btree\n");
|
||||
printf("mapsize=%" Z "u\n", info.me_mapsize);
|
||||
if (info.me_mapaddr)
|
||||
printf("mapaddr=%p\n", info.me_mapaddr);
|
||||
printf("maxreaders=%u\n", info.me_maxreaders);
|
||||
|
||||
if (flags & MDB_DUPSORT)
|
||||
printf("duplicates=1\n");
|
||||
|
||||
for (i=0; dbflags[i].bit; i++)
|
||||
if (flags & dbflags[i].bit)
|
||||
printf("%s=1\n", dbflags[i].name);
|
||||
|
||||
printf("db_pagesize=%d\n", ms.ms_psize);
|
||||
printf("HEADER=END\n");
|
||||
|
||||
rc = mdb_cursor_open(txn, dbi, &mc);
|
||||
if (rc) return rc;
|
||||
|
||||
while ((rc = mdb_cursor_get(mc, &key, &data, MDB_NEXT)) == MDB_SUCCESS) {
|
||||
if (gotsig) {
|
||||
rc = EINTR;
|
||||
break;
|
||||
}
|
||||
if (mode & PRINT) {
|
||||
text(&key);
|
||||
text(&data);
|
||||
} else {
|
||||
byte(&key);
|
||||
byte(&data);
|
||||
}
|
||||
}
|
||||
printf("DATA=END\n");
|
||||
if (rc == MDB_NOTFOUND)
|
||||
rc = MDB_SUCCESS;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void usage(char *prog)
|
||||
{
|
||||
fprintf(stderr, "usage: %s [-V] [-f output] [-l] [-n] [-p] [-a|-s subdb] dbpath\n", prog);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int i, rc;
|
||||
MDB_env *env;
|
||||
MDB_txn *txn;
|
||||
MDB_dbi dbi;
|
||||
char *prog = argv[0];
|
||||
char *envname;
|
||||
char *subname = NULL;
|
||||
int alldbs = 0, envflags = 0, list = 0;
|
||||
|
||||
if (argc < 2) {
|
||||
usage(prog);
|
||||
}
|
||||
|
||||
/* -a: dump all subDBs
|
||||
* -s: dump only the named subDB
|
||||
* -n: use NOSUBDIR flag on env_open
|
||||
* -p: use printable characters
|
||||
* -f: write to file instead of stdout
|
||||
* -V: print version and exit
|
||||
* (default) dump only the main DB
|
||||
*/
|
||||
while ((i = getopt(argc, argv, "af:lnps:V")) != EOF) {
|
||||
switch(i) {
|
||||
case 'V':
|
||||
printf("%s\n", MDB_VERSION_STRING);
|
||||
exit(0);
|
||||
break;
|
||||
case 'l':
|
||||
list = 1;
|
||||
/*FALLTHROUGH*/
|
||||
case 'a':
|
||||
if (subname)
|
||||
usage(prog);
|
||||
alldbs++;
|
||||
break;
|
||||
case 'f':
|
||||
if (freopen(optarg, "w", stdout) == NULL) {
|
||||
fprintf(stderr, "%s: %s: reopen: %s\n",
|
||||
prog, optarg, strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
case 'n':
|
||||
envflags |= MDB_NOSUBDIR;
|
||||
break;
|
||||
case 'p':
|
||||
mode |= PRINT;
|
||||
break;
|
||||
case 's':
|
||||
if (alldbs)
|
||||
usage(prog);
|
||||
subname = optarg;
|
||||
break;
|
||||
default:
|
||||
usage(prog);
|
||||
}
|
||||
}
|
||||
|
||||
if (optind != argc - 1)
|
||||
usage(prog);
|
||||
|
||||
#ifdef SIGPIPE
|
||||
signal(SIGPIPE, dumpsig);
|
||||
#endif
|
||||
#ifdef SIGHUP
|
||||
signal(SIGHUP, dumpsig);
|
||||
#endif
|
||||
signal(SIGINT, dumpsig);
|
||||
signal(SIGTERM, dumpsig);
|
||||
|
||||
envname = argv[optind];
|
||||
rc = mdb_env_create(&env);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (alldbs || subname) {
|
||||
mdb_env_set_maxdbs(env, 2);
|
||||
}
|
||||
|
||||
rc = mdb_env_open(env, envname, envflags | MDB_RDONLY, 0664);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
rc = mdb_open(txn, subname, 0, &dbi);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
|
||||
if (alldbs) {
|
||||
MDB_cursor *cursor;
|
||||
MDB_val key;
|
||||
int count = 0;
|
||||
|
||||
rc = mdb_cursor_open(txn, dbi, &cursor);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
while ((rc = mdb_cursor_get(cursor, &key, NULL, MDB_NEXT_NODUP)) == 0) {
|
||||
char *str;
|
||||
MDB_dbi db2;
|
||||
if (memchr(key.mv_data, '\0', key.mv_size))
|
||||
continue;
|
||||
count++;
|
||||
str = malloc(key.mv_size+1);
|
||||
memcpy(str, key.mv_data, key.mv_size);
|
||||
str[key.mv_size] = '\0';
|
||||
rc = mdb_open(txn, str, 0, &db2);
|
||||
if (rc == MDB_SUCCESS) {
|
||||
if (list) {
|
||||
printf("%s\n", str);
|
||||
list++;
|
||||
} else {
|
||||
rc = dumpit(txn, db2, str);
|
||||
if (rc)
|
||||
break;
|
||||
}
|
||||
mdb_close(env, db2);
|
||||
}
|
||||
free(str);
|
||||
if (rc) continue;
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
if (!count) {
|
||||
fprintf(stderr, "%s: %s does not contain multiple databases\n", prog, envname);
|
||||
rc = MDB_NOTFOUND;
|
||||
} else if (rc == MDB_NOTFOUND) {
|
||||
rc = MDB_SUCCESS;
|
||||
}
|
||||
} else {
|
||||
rc = dumpit(txn, dbi, subname);
|
||||
}
|
||||
if (rc && rc != MDB_NOTFOUND)
|
||||
fprintf(stderr, "%s: %s: %s\n", prog, envname, mdb_strerror(rc));
|
||||
|
||||
mdb_close(env, dbi);
|
||||
txn_abort:
|
||||
mdb_txn_abort(txn);
|
||||
env_close:
|
||||
mdb_env_close(env);
|
||||
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
.TH MDB_LOAD 1 "2015/09/30" "LMDB 0.9.17"
|
||||
.\" Copyright 2014-2021 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.SH NAME
|
||||
mdb_load \- LMDB environment import tool
|
||||
.SH SYNOPSIS
|
||||
.B mdb_load
|
||||
[\c
|
||||
.BR \-V ]
|
||||
[\c
|
||||
.BR \-a ]
|
||||
[\c
|
||||
.BI \-f \ file\fR]
|
||||
[\c
|
||||
.BR \-n ]
|
||||
[\c
|
||||
.BI \-s \ subdb\fR]
|
||||
[\c
|
||||
.BR \-N ]
|
||||
[\c
|
||||
.BR \-Q ]
|
||||
[\c
|
||||
.BR \-T ]
|
||||
.BR \ envpath
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mdb_load
|
||||
utility reads from the standard input and loads it into the
|
||||
LMDB environment
|
||||
.BR envpath .
|
||||
|
||||
The input to
|
||||
.B mdb_load
|
||||
must be in the output format specified by the
|
||||
.BR mdb_dump (1)
|
||||
utility or as specified by the
|
||||
.B -T
|
||||
option below.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-V
|
||||
Write the library version number to the standard output, and exit.
|
||||
.TP
|
||||
.BR \-a
|
||||
Append all records in the order they appear in the input. The input is assumed to already be
|
||||
in correctly sorted order and no sorting or checking for redundant values will be performed.
|
||||
This option must be used to reload data that was produced by running
|
||||
.B mdb_dump
|
||||
on a database that uses custom compare functions.
|
||||
.TP
|
||||
.BR \-f \ file
|
||||
Read from the specified file instead of from the standard input.
|
||||
.TP
|
||||
.BR \-n
|
||||
Load an LMDB database which does not use subdirectories.
|
||||
.TP
|
||||
.BR \-s \ subdb
|
||||
Load a specific subdatabase. If no database is specified, data is loaded into the main database.
|
||||
.TP
|
||||
.BR \-N
|
||||
Don't overwrite existing records when loading into an already existing database; just skip them.
|
||||
.TP
|
||||
.BR \-Q
|
||||
Quick mode, uses MDB_NOSYNC for faster loading. Forces sync with mdb_env_sync() before exiting.
|
||||
.TP
|
||||
.BR \-T
|
||||
Load data from simple text files. The input must be paired lines of text, where the first
|
||||
line of the pair is the key item, and the second line of the pair is its corresponding
|
||||
data item.
|
||||
|
||||
A simple escape mechanism, where newline and backslash (\\) characters are special, is
|
||||
applied to the text input. Newline characters are interpreted as record separators.
|
||||
Backslash characters in the text will be interpreted in one of two ways: If the backslash
|
||||
character precedes another backslash character, the pair will be interpreted as a literal
|
||||
backslash. If the backslash character precedes any other character, the two characters
|
||||
following the backslash will be interpreted as a hexadecimal specification of a single
|
||||
character; for example, \\0a is a newline character in the ASCII character set.
|
||||
|
||||
For this reason, any backslash or newline characters that naturally occur in the text
|
||||
input must be escaped to avoid misinterpretation by
|
||||
.BR mdb_load .
|
||||
|
||||
.SH DIAGNOSTICS
|
||||
Exit status is zero if no errors occur.
|
||||
Errors result in a non-zero exit status and
|
||||
a diagnostic message being written to standard error.
|
||||
|
||||
.SH "SEE ALSO"
|
||||
.BR mdb_dump (1)
|
||||
.SH AUTHOR
|
||||
Howard Chu of Symas Corporation <http://www.symas.com>
|
||||
@@ -0,0 +1,515 @@
|
||||
/* mdb_load.c - memory-mapped database load tool */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <unistd.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define PRINT 1
|
||||
#define NOHDR 2
|
||||
static int mode;
|
||||
|
||||
static char *subname = NULL;
|
||||
|
||||
static size_t lineno;
|
||||
static int version;
|
||||
|
||||
static int flags;
|
||||
|
||||
static char *prog;
|
||||
|
||||
static int Eof;
|
||||
|
||||
static MDB_envinfo info;
|
||||
|
||||
static MDB_val kbuf, dbuf;
|
||||
static MDB_val k0buf;
|
||||
|
||||
#ifdef _WIN32
|
||||
#define Z "I"
|
||||
#else
|
||||
#define Z "z"
|
||||
#endif
|
||||
|
||||
#define STRLENOF(s) (sizeof(s)-1)
|
||||
|
||||
typedef struct flagbit {
|
||||
int bit;
|
||||
char *name;
|
||||
int len;
|
||||
} flagbit;
|
||||
|
||||
#define S(s) s, STRLENOF(s)
|
||||
|
||||
flagbit dbflags[] = {
|
||||
{ MDB_REVERSEKEY, S("reversekey") },
|
||||
{ MDB_DUPSORT, S("dupsort") },
|
||||
{ MDB_INTEGERKEY, S("integerkey") },
|
||||
{ MDB_DUPFIXED, S("dupfixed") },
|
||||
{ MDB_INTEGERDUP, S("integerdup") },
|
||||
{ MDB_REVERSEDUP, S("reversedup") },
|
||||
{ 0, NULL, 0 }
|
||||
};
|
||||
|
||||
static void readhdr(void)
|
||||
{
|
||||
char *ptr;
|
||||
|
||||
flags = 0;
|
||||
while (fgets(dbuf.mv_data, dbuf.mv_size, stdin) != NULL) {
|
||||
lineno++;
|
||||
if (!strncmp(dbuf.mv_data, "VERSION=", STRLENOF("VERSION="))) {
|
||||
version=atoi((char *)dbuf.mv_data+STRLENOF("VERSION="));
|
||||
if (version > 3) {
|
||||
fprintf(stderr, "%s: line %" Z "d: unsupported VERSION %d\n",
|
||||
prog, lineno, version);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "HEADER=END", STRLENOF("HEADER=END"))) {
|
||||
break;
|
||||
} else if (!strncmp(dbuf.mv_data, "format=", STRLENOF("format="))) {
|
||||
if (!strncmp((char *)dbuf.mv_data+STRLENOF("FORMAT="), "print", STRLENOF("print")))
|
||||
mode |= PRINT;
|
||||
else if (strncmp((char *)dbuf.mv_data+STRLENOF("FORMAT="), "bytevalue", STRLENOF("bytevalue"))) {
|
||||
fprintf(stderr, "%s: line %" Z "d: unsupported FORMAT %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("FORMAT="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "database=", STRLENOF("database="))) {
|
||||
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
|
||||
if (ptr) *ptr = '\0';
|
||||
if (subname) free(subname);
|
||||
subname = strdup((char *)dbuf.mv_data+STRLENOF("database="));
|
||||
} else if (!strncmp(dbuf.mv_data, "type=", STRLENOF("type="))) {
|
||||
if (strncmp((char *)dbuf.mv_data+STRLENOF("type="), "btree", STRLENOF("btree"))) {
|
||||
fprintf(stderr, "%s: line %" Z "d: unsupported type %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("type="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "mapaddr=", STRLENOF("mapaddr="))) {
|
||||
int i;
|
||||
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
|
||||
if (ptr) *ptr = '\0';
|
||||
i = sscanf((char *)dbuf.mv_data+STRLENOF("mapaddr="), "%p", &info.me_mapaddr);
|
||||
if (i != 1) {
|
||||
fprintf(stderr, "%s: line %" Z "d: invalid mapaddr %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("mapaddr="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "mapsize=", STRLENOF("mapsize="))) {
|
||||
int i;
|
||||
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
|
||||
if (ptr) *ptr = '\0';
|
||||
i = sscanf((char *)dbuf.mv_data+STRLENOF("mapsize="), "%" Z "u", &info.me_mapsize);
|
||||
if (i != 1) {
|
||||
fprintf(stderr, "%s: line %" Z "d: invalid mapsize %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("mapsize="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "maxreaders=", STRLENOF("maxreaders="))) {
|
||||
int i;
|
||||
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
|
||||
if (ptr) *ptr = '\0';
|
||||
i = sscanf((char *)dbuf.mv_data+STRLENOF("maxreaders="), "%u", &info.me_maxreaders);
|
||||
if (i != 1) {
|
||||
fprintf(stderr, "%s: line %" Z "d: invalid maxreaders %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("maxreaders="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else {
|
||||
int i;
|
||||
for (i=0; dbflags[i].bit; i++) {
|
||||
if (!strncmp(dbuf.mv_data, dbflags[i].name, dbflags[i].len) &&
|
||||
((char *)dbuf.mv_data)[dbflags[i].len] == '=') {
|
||||
flags |= dbflags[i].bit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dbflags[i].bit) {
|
||||
ptr = memchr(dbuf.mv_data, '=', dbuf.mv_size);
|
||||
if (!ptr) {
|
||||
fprintf(stderr, "%s: line %" Z "d: unexpected format\n",
|
||||
prog, lineno);
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
*ptr = '\0';
|
||||
fprintf(stderr, "%s: line %" Z "d: unrecognized keyword ignored: %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void badend(void)
|
||||
{
|
||||
fprintf(stderr, "%s: line %" Z "d: unexpected end of input\n",
|
||||
prog, lineno);
|
||||
}
|
||||
|
||||
static int unhex(unsigned char *c2)
|
||||
{
|
||||
int x, c;
|
||||
x = *c2++ & 0x4f;
|
||||
if (x & 0x40)
|
||||
x -= 55;
|
||||
c = x << 4;
|
||||
x = *c2 & 0x4f;
|
||||
if (x & 0x40)
|
||||
x -= 55;
|
||||
c |= x;
|
||||
return c;
|
||||
}
|
||||
|
||||
static int readline(MDB_val *out, MDB_val *buf)
|
||||
{
|
||||
unsigned char *c1, *c2, *end;
|
||||
size_t len, l2;
|
||||
int c;
|
||||
|
||||
if (!(mode & NOHDR)) {
|
||||
c = fgetc(stdin);
|
||||
if (c == EOF) {
|
||||
Eof = 1;
|
||||
return EOF;
|
||||
}
|
||||
if (c != ' ') {
|
||||
lineno++;
|
||||
if (fgets(buf->mv_data, buf->mv_size, stdin) == NULL) {
|
||||
badend:
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
if (c == 'D' && !strncmp(buf->mv_data, "ATA=END", STRLENOF("ATA=END")))
|
||||
return EOF;
|
||||
goto badend;
|
||||
}
|
||||
}
|
||||
if (fgets(buf->mv_data, buf->mv_size, stdin) == NULL) {
|
||||
Eof = 1;
|
||||
return EOF;
|
||||
}
|
||||
lineno++;
|
||||
|
||||
c1 = buf->mv_data;
|
||||
len = strlen((char *)c1);
|
||||
if (!len) {
|
||||
/* This can only happen with an intentionally invalid input
|
||||
* with a NUL byte after the leading SPACE
|
||||
*/
|
||||
goto badend;
|
||||
}
|
||||
l2 = len;
|
||||
|
||||
/* Is buffer too short? */
|
||||
while (c1[len-1] != '\n') {
|
||||
buf->mv_data = realloc(buf->mv_data, buf->mv_size*2);
|
||||
if (!buf->mv_data) {
|
||||
Eof = 1;
|
||||
fprintf(stderr, "%s: line %" Z "d: out of memory, line too long\n",
|
||||
prog, lineno);
|
||||
return EOF;
|
||||
}
|
||||
c1 = buf->mv_data;
|
||||
c1 += l2;
|
||||
if (fgets((char *)c1, buf->mv_size+1, stdin) == NULL) {
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
buf->mv_size *= 2;
|
||||
len = strlen((char *)c1);
|
||||
l2 += len;
|
||||
}
|
||||
c1 = c2 = buf->mv_data;
|
||||
len = l2;
|
||||
c1[--len] = '\0';
|
||||
end = c1 + len;
|
||||
|
||||
if (mode & PRINT) {
|
||||
while (c2 < end) {
|
||||
if (*c2 == '\\') {
|
||||
if (c2[1] == '\\') {
|
||||
*c1++ = *c2;
|
||||
} else {
|
||||
if (c2+3 > end || !isxdigit(c2[1]) || !isxdigit(c2[2])) {
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
*c1++ = unhex(++c2);
|
||||
}
|
||||
c2 += 2;
|
||||
} else {
|
||||
/* copies are redundant when no escapes were used */
|
||||
*c1++ = *c2++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* odd length not allowed */
|
||||
if (len & 1) {
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
while (c2 < end) {
|
||||
if (!isxdigit(*c2) || !isxdigit(c2[1])) {
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
*c1++ = unhex(c2);
|
||||
c2 += 2;
|
||||
}
|
||||
}
|
||||
c2 = out->mv_data = buf->mv_data;
|
||||
out->mv_size = c1 - c2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void usage(void)
|
||||
{
|
||||
fprintf(stderr, "usage: %s [-V] [-a] [-f input] [-n] [-s name] [-N] [-T] dbpath\n", prog);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static int greater(const MDB_val *a, const MDB_val *b)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int i, rc;
|
||||
MDB_env *env;
|
||||
MDB_txn *txn;
|
||||
MDB_cursor *mc;
|
||||
MDB_dbi dbi;
|
||||
char *envname;
|
||||
int envflags = MDB_NOSYNC, putflags = 0;
|
||||
int dohdr = 0, append = 0;
|
||||
MDB_val prevk;
|
||||
|
||||
prog = argv[0];
|
||||
|
||||
if (argc < 2) {
|
||||
usage();
|
||||
}
|
||||
|
||||
/* -a: append records in input order
|
||||
* -f: load file instead of stdin
|
||||
* -n: use NOSUBDIR flag on env_open
|
||||
* -s: load into named subDB
|
||||
* -N: use NOOVERWRITE on puts
|
||||
* -Q: quick mode using NOSYNC
|
||||
* -T: read plaintext
|
||||
* -V: print version and exit
|
||||
*/
|
||||
while ((i = getopt(argc, argv, "af:ns:NQTV")) != EOF) {
|
||||
switch(i) {
|
||||
case 'V':
|
||||
printf("%s\n", MDB_VERSION_STRING);
|
||||
exit(0);
|
||||
break;
|
||||
case 'a':
|
||||
append = 1;
|
||||
break;
|
||||
case 'f':
|
||||
if (freopen(optarg, "r", stdin) == NULL) {
|
||||
fprintf(stderr, "%s: %s: reopen: %s\n",
|
||||
prog, optarg, strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
case 'n':
|
||||
envflags |= MDB_NOSUBDIR;
|
||||
break;
|
||||
case 's':
|
||||
subname = strdup(optarg);
|
||||
break;
|
||||
case 'N':
|
||||
putflags = MDB_NOOVERWRITE|MDB_NODUPDATA;
|
||||
break;
|
||||
case 'Q':
|
||||
envflags |= MDB_NOSYNC;
|
||||
break;
|
||||
case 'T':
|
||||
mode |= NOHDR | PRINT;
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
}
|
||||
}
|
||||
|
||||
if (optind != argc - 1)
|
||||
usage();
|
||||
|
||||
dbuf.mv_size = 4096;
|
||||
dbuf.mv_data = malloc(dbuf.mv_size);
|
||||
|
||||
if (!(mode & NOHDR))
|
||||
readhdr();
|
||||
|
||||
envname = argv[optind];
|
||||
rc = mdb_env_create(&env);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
mdb_env_set_maxdbs(env, 2);
|
||||
|
||||
if (info.me_maxreaders)
|
||||
mdb_env_set_maxreaders(env, info.me_maxreaders);
|
||||
|
||||
if (info.me_mapsize)
|
||||
mdb_env_set_mapsize(env, info.me_mapsize);
|
||||
|
||||
if (info.me_mapaddr)
|
||||
envflags |= MDB_FIXEDMAP;
|
||||
|
||||
rc = mdb_env_open(env, envname, envflags, 0664);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
kbuf.mv_size = mdb_env_get_maxkeysize(env) * 2 + 2;
|
||||
kbuf.mv_data = malloc(kbuf.mv_size * 2);
|
||||
k0buf.mv_size = kbuf.mv_size;
|
||||
k0buf.mv_data = (char *)kbuf.mv_data + kbuf.mv_size;
|
||||
prevk.mv_data = k0buf.mv_data;
|
||||
|
||||
while(!Eof) {
|
||||
MDB_val key, data;
|
||||
int batch = 0;
|
||||
flags = 0;
|
||||
int appflag;
|
||||
|
||||
if (!dohdr) {
|
||||
dohdr = 1;
|
||||
} else if (!(mode & NOHDR))
|
||||
readhdr();
|
||||
|
||||
rc = mdb_txn_begin(env, NULL, 0, &txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
rc = mdb_dbi_open(txn, subname, flags|MDB_CREATE, &dbi);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_dbi_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
prevk.mv_size = 0;
|
||||
if (append) {
|
||||
mdb_set_compare(txn, dbi, greater);
|
||||
if (flags & MDB_DUPSORT)
|
||||
mdb_set_dupsort(txn, dbi, greater);
|
||||
}
|
||||
|
||||
rc = mdb_cursor_open(txn, dbi, &mc);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
|
||||
while(1) {
|
||||
rc = readline(&key, &kbuf);
|
||||
if (rc) /* rc == EOF */
|
||||
break;
|
||||
|
||||
rc = readline(&data, &dbuf);
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: line %" Z "d: failed to read key value\n", prog, lineno);
|
||||
goto txn_abort;
|
||||
}
|
||||
|
||||
if (append) {
|
||||
appflag = MDB_APPEND;
|
||||
if (flags & MDB_DUPSORT) {
|
||||
if (prevk.mv_size == key.mv_size && !memcmp(prevk.mv_data, key.mv_data, key.mv_size))
|
||||
appflag = MDB_CURRENT|MDB_APPENDDUP;
|
||||
else {
|
||||
memcpy(prevk.mv_data, key.mv_data, key.mv_size);
|
||||
prevk.mv_size = key.mv_size;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
appflag = 0;
|
||||
}
|
||||
rc = mdb_cursor_put(mc, &key, &data, putflags|appflag);
|
||||
if (rc == MDB_KEYEXIST && putflags)
|
||||
continue;
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: line %" Z "d: mdb_cursor_put failed, error %d %s\n", prog, lineno, rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
batch++;
|
||||
if (batch == 100) {
|
||||
rc = mdb_txn_commit(txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: line %" Z "d: txn_commit: %s\n",
|
||||
prog, lineno, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
rc = mdb_txn_begin(env, NULL, 0, &txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
rc = mdb_cursor_open(txn, dbi, &mc);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
if (append) {
|
||||
MDB_val k, d;
|
||||
mdb_cursor_get(mc, &k, &d, MDB_LAST);
|
||||
memcpy(prevk.mv_data, k.mv_data, k.mv_size);
|
||||
prevk.mv_size = k.mv_size;
|
||||
}
|
||||
batch = 0;
|
||||
}
|
||||
}
|
||||
rc = mdb_txn_commit(txn);
|
||||
txn = NULL;
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: line %" Z "d: txn_commit: %s\n",
|
||||
prog, lineno, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
if (envflags & MDB_NOSYNC) {
|
||||
rc = mdb_env_sync(env, 1);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_sync failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
}
|
||||
mdb_dbi_close(env, dbi);
|
||||
}
|
||||
|
||||
txn_abort:
|
||||
mdb_txn_abort(txn);
|
||||
env_close:
|
||||
mdb_env_close(env);
|
||||
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
.TH MDB_STAT 1 "2015/09/30" "LMDB 0.9.17"
|
||||
.\" Copyright 2012-2021 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.SH NAME
|
||||
mdb_stat \- LMDB environment status tool
|
||||
.SH SYNOPSIS
|
||||
.B mdb_stat
|
||||
[\c
|
||||
.BR \-V ]
|
||||
[\c
|
||||
.BR \-e ]
|
||||
[\c
|
||||
.BR \-f [ f [ f ]]]
|
||||
[\c
|
||||
.BR \-n ]
|
||||
[\c
|
||||
.BR \-r [ r ]]
|
||||
[\c
|
||||
.BR \-a \ |
|
||||
.BI \-s \ subdb\fR]
|
||||
.BR \ envpath
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mdb_stat
|
||||
utility displays the status of an LMDB environment.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-V
|
||||
Write the library version number to the standard output, and exit.
|
||||
.TP
|
||||
.BR \-e
|
||||
Display information about the database environment.
|
||||
.TP
|
||||
.BR \-f
|
||||
Display information about the environment freelist.
|
||||
If \fB\-ff\fP is given, summarize each freelist entry.
|
||||
If \fB\-fff\fP is given, display the full list of page IDs in the freelist.
|
||||
.TP
|
||||
.BR \-n
|
||||
Display the status of an LMDB database which does not use subdirectories.
|
||||
.TP
|
||||
.BR \-r
|
||||
Display information about the environment reader table.
|
||||
Shows the process ID, thread ID, and transaction ID for each active
|
||||
reader slot. The process ID and transaction ID are in decimal, the
|
||||
thread ID is in hexadecimal. The transaction ID is displayed as "-"
|
||||
if the reader does not currently have a read transaction open.
|
||||
If \fB\-rr\fP is given, check for stale entries in the reader
|
||||
table and clear them. The reader table will be printed again
|
||||
after the check is performed.
|
||||
.TP
|
||||
.BR \-a
|
||||
Display the status of all of the subdatabases in the environment.
|
||||
.TP
|
||||
.BR \-s \ subdb
|
||||
Display the status of a specific subdatabase.
|
||||
.SH DIAGNOSTICS
|
||||
Exit status is zero if no errors occur.
|
||||
Errors result in a non-zero exit status and
|
||||
a diagnostic message being written to standard error.
|
||||
.SH "SEE ALSO"
|
||||
.BR mdb_copy (1)
|
||||
.SH AUTHOR
|
||||
Howard Chu of Symas Corporation <http://www.symas.com>
|
||||
@@ -0,0 +1,263 @@
|
||||
/* mdb_stat.c - memory-mapped database status tool */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define Z "I"
|
||||
#else
|
||||
#define Z "z"
|
||||
#endif
|
||||
|
||||
static void prstat(MDB_stat *ms)
|
||||
{
|
||||
#if 0
|
||||
printf(" Page size: %u\n", ms->ms_psize);
|
||||
#endif
|
||||
printf(" Tree depth: %u\n", ms->ms_depth);
|
||||
printf(" Branch pages: %"Z"u\n", ms->ms_branch_pages);
|
||||
printf(" Leaf pages: %"Z"u\n", ms->ms_leaf_pages);
|
||||
printf(" Overflow pages: %"Z"u\n", ms->ms_overflow_pages);
|
||||
printf(" Entries: %"Z"u\n", ms->ms_entries);
|
||||
}
|
||||
|
||||
static void usage(char *prog)
|
||||
{
|
||||
fprintf(stderr, "usage: %s [-V] [-n] [-e] [-r[r]] [-f[f[f]]] [-a|-s subdb] dbpath\n", prog);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int i, rc;
|
||||
MDB_env *env;
|
||||
MDB_txn *txn;
|
||||
MDB_dbi dbi;
|
||||
MDB_stat mst;
|
||||
MDB_envinfo mei;
|
||||
char *prog = argv[0];
|
||||
char *envname;
|
||||
char *subname = NULL;
|
||||
int alldbs = 0, envinfo = 0, envflags = 0, freinfo = 0, rdrinfo = 0;
|
||||
|
||||
if (argc < 2) {
|
||||
usage(prog);
|
||||
}
|
||||
|
||||
/* -a: print stat of main DB and all subDBs
|
||||
* -s: print stat of only the named subDB
|
||||
* -e: print env info
|
||||
* -f: print freelist info
|
||||
* -r: print reader info
|
||||
* -n: use NOSUBDIR flag on env_open
|
||||
* -V: print version and exit
|
||||
* (default) print stat of only the main DB
|
||||
*/
|
||||
while ((i = getopt(argc, argv, "Vaefnrs:")) != EOF) {
|
||||
switch(i) {
|
||||
case 'V':
|
||||
printf("%s\n", MDB_VERSION_STRING);
|
||||
exit(0);
|
||||
break;
|
||||
case 'a':
|
||||
if (subname)
|
||||
usage(prog);
|
||||
alldbs++;
|
||||
break;
|
||||
case 'e':
|
||||
envinfo++;
|
||||
break;
|
||||
case 'f':
|
||||
freinfo++;
|
||||
break;
|
||||
case 'n':
|
||||
envflags |= MDB_NOSUBDIR;
|
||||
break;
|
||||
case 'r':
|
||||
rdrinfo++;
|
||||
break;
|
||||
case 's':
|
||||
if (alldbs)
|
||||
usage(prog);
|
||||
subname = optarg;
|
||||
break;
|
||||
default:
|
||||
usage(prog);
|
||||
}
|
||||
}
|
||||
|
||||
if (optind != argc - 1)
|
||||
usage(prog);
|
||||
|
||||
envname = argv[optind];
|
||||
rc = mdb_env_create(&env);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (alldbs || subname) {
|
||||
mdb_env_set_maxdbs(env, 4);
|
||||
}
|
||||
|
||||
rc = mdb_env_open(env, envname, envflags | MDB_RDONLY, 0664);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
if (envinfo) {
|
||||
(void)mdb_env_stat(env, &mst);
|
||||
(void)mdb_env_info(env, &mei);
|
||||
printf("Environment Info\n");
|
||||
printf(" Map address: %p\n", mei.me_mapaddr);
|
||||
printf(" Map size: %"Z"u\n", mei.me_mapsize);
|
||||
printf(" Page size: %u\n", mst.ms_psize);
|
||||
printf(" Max pages: %"Z"u\n", mei.me_mapsize / mst.ms_psize);
|
||||
printf(" Number of pages used: %"Z"u\n", mei.me_last_pgno+1);
|
||||
printf(" Last transaction ID: %"Z"u\n", mei.me_last_txnid);
|
||||
printf(" Max readers: %u\n", mei.me_maxreaders);
|
||||
printf(" Number of readers used: %u\n", mei.me_numreaders);
|
||||
}
|
||||
|
||||
if (rdrinfo) {
|
||||
printf("Reader Table Status\n");
|
||||
rc = mdb_reader_list(env, (MDB_msg_func *)fputs, stdout);
|
||||
if (rdrinfo > 1) {
|
||||
int dead;
|
||||
mdb_reader_check(env, &dead);
|
||||
printf(" %d stale readers cleared.\n", dead);
|
||||
rc = mdb_reader_list(env, (MDB_msg_func *)fputs, stdout);
|
||||
}
|
||||
if (!(subname || alldbs || freinfo))
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
if (freinfo) {
|
||||
MDB_cursor *cursor;
|
||||
MDB_val key, data;
|
||||
size_t pages = 0, *iptr;
|
||||
|
||||
printf("Freelist Status\n");
|
||||
dbi = 0;
|
||||
rc = mdb_cursor_open(txn, dbi, &cursor);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
rc = mdb_stat(txn, dbi, &mst);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
prstat(&mst);
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
iptr = data.mv_data;
|
||||
pages += *iptr;
|
||||
if (freinfo > 1) {
|
||||
char *bad = "";
|
||||
size_t pg, prev;
|
||||
ssize_t i, j, span = 0;
|
||||
j = *iptr++;
|
||||
for (i = j, prev = 1; --i >= 0; ) {
|
||||
pg = iptr[i];
|
||||
if (pg <= prev)
|
||||
bad = " [bad sequence]";
|
||||
prev = pg;
|
||||
pg += span;
|
||||
for (; i >= span && iptr[i-span] == pg; span++, pg++) ;
|
||||
}
|
||||
printf(" Transaction %"Z"u, %"Z"d pages, maxspan %"Z"d%s\n",
|
||||
*(size_t *)key.mv_data, j, span, bad);
|
||||
if (freinfo > 2) {
|
||||
for (--j; j >= 0; ) {
|
||||
pg = iptr[j];
|
||||
for (span=1; --j >= 0 && iptr[j] == pg+span; span++) ;
|
||||
printf(span>1 ? " %9"Z"u[%"Z"d]\n" : " %9"Z"u\n",
|
||||
pg, span);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
printf(" Free pages: %"Z"u\n", pages);
|
||||
}
|
||||
|
||||
rc = mdb_open(txn, subname, 0, &dbi);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
|
||||
rc = mdb_stat(txn, dbi, &mst);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
printf("Status of %s\n", subname ? subname : "Main DB");
|
||||
prstat(&mst);
|
||||
|
||||
if (alldbs) {
|
||||
MDB_cursor *cursor;
|
||||
MDB_val key;
|
||||
|
||||
rc = mdb_cursor_open(txn, dbi, &cursor);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
while ((rc = mdb_cursor_get(cursor, &key, NULL, MDB_NEXT_NODUP)) == 0) {
|
||||
char *str;
|
||||
MDB_dbi db2;
|
||||
if (memchr(key.mv_data, '\0', key.mv_size))
|
||||
continue;
|
||||
str = malloc(key.mv_size+1);
|
||||
memcpy(str, key.mv_data, key.mv_size);
|
||||
str[key.mv_size] = '\0';
|
||||
rc = mdb_open(txn, str, 0, &db2);
|
||||
if (rc == MDB_SUCCESS)
|
||||
printf("Status of %s\n", str);
|
||||
free(str);
|
||||
if (rc) continue;
|
||||
rc = mdb_stat(txn, db2, &mst);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
prstat(&mst);
|
||||
mdb_close(env, db2);
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
}
|
||||
|
||||
if (rc == MDB_NOTFOUND)
|
||||
rc = MDB_SUCCESS;
|
||||
|
||||
mdb_close(env, dbi);
|
||||
txn_abort:
|
||||
mdb_txn_abort(txn);
|
||||
env_close:
|
||||
mdb_env_close(env);
|
||||
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
/** @file midl.c
|
||||
* @brief ldap bdb back-end ID List functions */
|
||||
/* $OpenLDAP$ */
|
||||
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
|
||||
*
|
||||
* Copyright 2000-2021 The OpenLDAP Foundation.
|
||||
* Portions Copyright 2001-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include "midl.h"
|
||||
|
||||
/** @defgroup internal LMDB Internals
|
||||
* @{
|
||||
*/
|
||||
/** @defgroup idls ID List Management
|
||||
* @{
|
||||
*/
|
||||
#define CMP(x,y) ( (x) < (y) ? -1 : (x) > (y) )
|
||||
|
||||
unsigned mdb_midl_search( MDB_IDL ids, MDB_ID id )
|
||||
{
|
||||
/*
|
||||
* binary search of id in ids
|
||||
* if found, returns position of id
|
||||
* if not found, returns first position greater than id
|
||||
*/
|
||||
unsigned base = 0;
|
||||
unsigned cursor = 1;
|
||||
int val = 0;
|
||||
unsigned n = ids[0];
|
||||
|
||||
while( 0 < n ) {
|
||||
unsigned pivot = n >> 1;
|
||||
cursor = base + pivot + 1;
|
||||
val = CMP( ids[cursor], id );
|
||||
|
||||
if( val < 0 ) {
|
||||
n = pivot;
|
||||
|
||||
} else if ( val > 0 ) {
|
||||
base = cursor;
|
||||
n -= pivot + 1;
|
||||
|
||||
} else {
|
||||
return cursor;
|
||||
}
|
||||
}
|
||||
|
||||
if( val > 0 ) {
|
||||
++cursor;
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
#if 0 /* superseded by append/sort */
|
||||
int mdb_midl_insert( MDB_IDL ids, MDB_ID id )
|
||||
{
|
||||
unsigned x, i;
|
||||
|
||||
x = mdb_midl_search( ids, id );
|
||||
assert( x > 0 );
|
||||
|
||||
if( x < 1 ) {
|
||||
/* internal error */
|
||||
return -2;
|
||||
}
|
||||
|
||||
if ( x <= ids[0] && ids[x] == id ) {
|
||||
/* duplicate */
|
||||
assert(0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( ++ids[0] >= MDB_IDL_DB_MAX ) {
|
||||
/* no room */
|
||||
--ids[0];
|
||||
return -2;
|
||||
|
||||
} else {
|
||||
/* insert id */
|
||||
for (i=ids[0]; i>x; i--)
|
||||
ids[i] = ids[i-1];
|
||||
ids[x] = id;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
MDB_IDL mdb_midl_alloc(int num)
|
||||
{
|
||||
MDB_IDL ids = malloc((num+2) * sizeof(MDB_ID));
|
||||
if (ids) {
|
||||
*ids++ = num;
|
||||
*ids = 0;
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
void mdb_midl_free(MDB_IDL ids)
|
||||
{
|
||||
if (ids)
|
||||
free(ids-1);
|
||||
}
|
||||
|
||||
void mdb_midl_shrink( MDB_IDL *idp )
|
||||
{
|
||||
MDB_IDL ids = *idp;
|
||||
if (*(--ids) > MDB_IDL_UM_MAX &&
|
||||
(ids = realloc(ids, (MDB_IDL_UM_MAX+2) * sizeof(MDB_ID))))
|
||||
{
|
||||
*ids++ = MDB_IDL_UM_MAX;
|
||||
*idp = ids;
|
||||
}
|
||||
}
|
||||
|
||||
static int mdb_midl_grow( MDB_IDL *idp, int num )
|
||||
{
|
||||
MDB_IDL idn = *idp-1;
|
||||
/* grow it */
|
||||
idn = realloc(idn, (*idn + num + 2) * sizeof(MDB_ID));
|
||||
if (!idn)
|
||||
return ENOMEM;
|
||||
*idn++ += num;
|
||||
*idp = idn;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_midl_need( MDB_IDL *idp, unsigned num )
|
||||
{
|
||||
MDB_IDL ids = *idp;
|
||||
num += ids[0];
|
||||
if (num > ids[-1]) {
|
||||
num = (num + num/4 + (256 + 2)) & -256;
|
||||
if (!(ids = realloc(ids-1, num * sizeof(MDB_ID))))
|
||||
return ENOMEM;
|
||||
*ids++ = num - 2;
|
||||
*idp = ids;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_midl_append( MDB_IDL *idp, MDB_ID id )
|
||||
{
|
||||
MDB_IDL ids = *idp;
|
||||
/* Too big? */
|
||||
if (ids[0] >= ids[-1]) {
|
||||
if (mdb_midl_grow(idp, MDB_IDL_UM_MAX))
|
||||
return ENOMEM;
|
||||
ids = *idp;
|
||||
}
|
||||
ids[0]++;
|
||||
ids[ids[0]] = id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app )
|
||||
{
|
||||
MDB_IDL ids = *idp;
|
||||
/* Too big? */
|
||||
if (ids[0] + app[0] >= ids[-1]) {
|
||||
if (mdb_midl_grow(idp, app[0]))
|
||||
return ENOMEM;
|
||||
ids = *idp;
|
||||
}
|
||||
memcpy(&ids[ids[0]+1], &app[1], app[0] * sizeof(MDB_ID));
|
||||
ids[0] += app[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_midl_append_range( MDB_IDL *idp, MDB_ID id, unsigned n )
|
||||
{
|
||||
MDB_ID *ids = *idp, len = ids[0];
|
||||
/* Too big? */
|
||||
if (len + n > ids[-1]) {
|
||||
if (mdb_midl_grow(idp, n | MDB_IDL_UM_MAX))
|
||||
return ENOMEM;
|
||||
ids = *idp;
|
||||
}
|
||||
ids[0] = len + n;
|
||||
ids += len;
|
||||
while (n)
|
||||
ids[n--] = id++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mdb_midl_xmerge( MDB_IDL idl, MDB_IDL merge )
|
||||
{
|
||||
MDB_ID old_id, merge_id, i = merge[0], j = idl[0], k = i+j, total = k;
|
||||
idl[0] = (MDB_ID)-1; /* delimiter for idl scan below */
|
||||
old_id = idl[j];
|
||||
while (i) {
|
||||
merge_id = merge[i--];
|
||||
for (; old_id < merge_id; old_id = idl[--j])
|
||||
idl[k--] = old_id;
|
||||
idl[k--] = merge_id;
|
||||
}
|
||||
idl[0] = total;
|
||||
}
|
||||
|
||||
/* Quicksort + Insertion sort for small arrays */
|
||||
|
||||
#define SMALL 8
|
||||
#define MIDL_SWAP(a,b) { itmp=(a); (a)=(b); (b)=itmp; }
|
||||
|
||||
void
|
||||
mdb_midl_sort( MDB_IDL ids )
|
||||
{
|
||||
/* Max possible depth of int-indexed tree * 2 items/level */
|
||||
int istack[sizeof(int)*CHAR_BIT * 2];
|
||||
int i,j,k,l,ir,jstack;
|
||||
MDB_ID a, itmp;
|
||||
|
||||
ir = (int)ids[0];
|
||||
l = 1;
|
||||
jstack = 0;
|
||||
for(;;) {
|
||||
if (ir - l < SMALL) { /* Insertion sort */
|
||||
for (j=l+1;j<=ir;j++) {
|
||||
a = ids[j];
|
||||
for (i=j-1;i>=1;i--) {
|
||||
if (ids[i] >= a) break;
|
||||
ids[i+1] = ids[i];
|
||||
}
|
||||
ids[i+1] = a;
|
||||
}
|
||||
if (jstack == 0) break;
|
||||
ir = istack[jstack--];
|
||||
l = istack[jstack--];
|
||||
} else {
|
||||
k = (l + ir) >> 1; /* Choose median of left, center, right */
|
||||
MIDL_SWAP(ids[k], ids[l+1]);
|
||||
if (ids[l] < ids[ir]) {
|
||||
MIDL_SWAP(ids[l], ids[ir]);
|
||||
}
|
||||
if (ids[l+1] < ids[ir]) {
|
||||
MIDL_SWAP(ids[l+1], ids[ir]);
|
||||
}
|
||||
if (ids[l] < ids[l+1]) {
|
||||
MIDL_SWAP(ids[l], ids[l+1]);
|
||||
}
|
||||
i = l+1;
|
||||
j = ir;
|
||||
a = ids[l+1];
|
||||
for(;;) {
|
||||
do i++; while(ids[i] > a);
|
||||
do j--; while(ids[j] < a);
|
||||
if (j < i) break;
|
||||
MIDL_SWAP(ids[i],ids[j]);
|
||||
}
|
||||
ids[l+1] = ids[j];
|
||||
ids[j] = a;
|
||||
jstack += 2;
|
||||
if (ir-i+1 >= j-l) {
|
||||
istack[jstack] = ir;
|
||||
istack[jstack-1] = i;
|
||||
ir = j-1;
|
||||
} else {
|
||||
istack[jstack] = j-1;
|
||||
istack[jstack-1] = l;
|
||||
l = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id )
|
||||
{
|
||||
/*
|
||||
* binary search of id in ids
|
||||
* if found, returns position of id
|
||||
* if not found, returns first position greater than id
|
||||
*/
|
||||
unsigned base = 0;
|
||||
unsigned cursor = 1;
|
||||
int val = 0;
|
||||
unsigned n = (unsigned)ids[0].mid;
|
||||
|
||||
while( 0 < n ) {
|
||||
unsigned pivot = n >> 1;
|
||||
cursor = base + pivot + 1;
|
||||
val = CMP( id, ids[cursor].mid );
|
||||
|
||||
if( val < 0 ) {
|
||||
n = pivot;
|
||||
|
||||
} else if ( val > 0 ) {
|
||||
base = cursor;
|
||||
n -= pivot + 1;
|
||||
|
||||
} else {
|
||||
return cursor;
|
||||
}
|
||||
}
|
||||
|
||||
if( val > 0 ) {
|
||||
++cursor;
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id )
|
||||
{
|
||||
unsigned x, i;
|
||||
|
||||
x = mdb_mid2l_search( ids, id->mid );
|
||||
|
||||
if( x < 1 ) {
|
||||
/* internal error */
|
||||
return -2;
|
||||
}
|
||||
|
||||
if ( x <= ids[0].mid && ids[x].mid == id->mid ) {
|
||||
/* duplicate */
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( ids[0].mid >= MDB_IDL_UM_MAX ) {
|
||||
/* too big */
|
||||
return -2;
|
||||
|
||||
} else {
|
||||
/* insert id */
|
||||
ids[0].mid++;
|
||||
for (i=(unsigned)ids[0].mid; i>x; i--)
|
||||
ids[i] = ids[i-1];
|
||||
ids[x] = *id;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id )
|
||||
{
|
||||
/* Too big? */
|
||||
if (ids[0].mid >= MDB_IDL_UM_MAX) {
|
||||
return -2;
|
||||
}
|
||||
ids[0].mid++;
|
||||
ids[ids[0].mid] = *id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
/** @} */
|
||||
@@ -0,0 +1,188 @@
|
||||
/** @file midl.h
|
||||
* @brief LMDB ID List header file.
|
||||
*
|
||||
* This file was originally part of back-bdb but has been
|
||||
* modified for use in libmdb. Most of the macros defined
|
||||
* in this file are unused, just left over from the original.
|
||||
*
|
||||
* This file is only used internally in libmdb and its definitions
|
||||
* are not exposed publicly.
|
||||
*/
|
||||
/* $OpenLDAP$ */
|
||||
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
|
||||
*
|
||||
* Copyright 2000-2021 The OpenLDAP Foundation.
|
||||
* Portions Copyright 2001-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
#ifndef _MDB_MIDL_H_
|
||||
#define _MDB_MIDL_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** @defgroup internal LMDB Internals
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** @defgroup idls ID List Management
|
||||
* @{
|
||||
*/
|
||||
/** A generic unsigned ID number. These were entryIDs in back-bdb.
|
||||
* Preferably it should have the same size as a pointer.
|
||||
*/
|
||||
typedef size_t MDB_ID;
|
||||
|
||||
/** An IDL is an ID List, a sorted array of IDs. The first
|
||||
* element of the array is a counter for how many actual
|
||||
* IDs are in the list. In the original back-bdb code, IDLs are
|
||||
* sorted in ascending order. For libmdb IDLs are sorted in
|
||||
* descending order.
|
||||
*/
|
||||
typedef MDB_ID *MDB_IDL;
|
||||
|
||||
/* IDL sizes - likely should be even bigger
|
||||
* limiting factors: sizeof(ID), thread stack size
|
||||
*/
|
||||
#ifndef MDB_IDL_LOGN
|
||||
#define MDB_IDL_LOGN 16 /* DB_SIZE is 2^16, UM_SIZE is 2^17 */
|
||||
#endif
|
||||
#define MDB_IDL_DB_SIZE (1<<MDB_IDL_LOGN)
|
||||
#define MDB_IDL_UM_SIZE (1<<(MDB_IDL_LOGN+1))
|
||||
|
||||
#define MDB_IDL_DB_MAX (MDB_IDL_DB_SIZE-1)
|
||||
#define MDB_IDL_UM_MAX (MDB_IDL_UM_SIZE-1)
|
||||
|
||||
#define MDB_IDL_SIZEOF(ids) (((ids)[0]+1) * sizeof(MDB_ID))
|
||||
#define MDB_IDL_IS_ZERO(ids) ( (ids)[0] == 0 )
|
||||
#define MDB_IDL_CPY( dst, src ) (memcpy( dst, src, MDB_IDL_SIZEOF( src ) ))
|
||||
#define MDB_IDL_FIRST( ids ) ( (ids)[1] )
|
||||
#define MDB_IDL_LAST( ids ) ( (ids)[(ids)[0]] )
|
||||
|
||||
/** Current max length of an #mdb_midl_alloc()ed IDL */
|
||||
#define MDB_IDL_ALLOCLEN( ids ) ( (ids)[-1] )
|
||||
|
||||
/** Append ID to IDL. The IDL must be big enough. */
|
||||
#define mdb_midl_xappend(idl, id) do { \
|
||||
MDB_ID *xidl = (idl), xlen = ++(xidl[0]); \
|
||||
xidl[xlen] = (id); \
|
||||
} while (0)
|
||||
|
||||
/** Search for an ID in an IDL.
|
||||
* @param[in] ids The IDL to search.
|
||||
* @param[in] id The ID to search for.
|
||||
* @return The index of the first ID greater than or equal to \b id.
|
||||
*/
|
||||
unsigned mdb_midl_search( MDB_IDL ids, MDB_ID id );
|
||||
|
||||
/** Allocate an IDL.
|
||||
* Allocates memory for an IDL of the given size.
|
||||
* @return IDL on success, NULL on failure.
|
||||
*/
|
||||
MDB_IDL mdb_midl_alloc(int num);
|
||||
|
||||
/** Free an IDL.
|
||||
* @param[in] ids The IDL to free.
|
||||
*/
|
||||
void mdb_midl_free(MDB_IDL ids);
|
||||
|
||||
/** Shrink an IDL.
|
||||
* Return the IDL to the default size if it has grown larger.
|
||||
* @param[in,out] idp Address of the IDL to shrink.
|
||||
*/
|
||||
void mdb_midl_shrink(MDB_IDL *idp);
|
||||
|
||||
/** Make room for num additional elements in an IDL.
|
||||
* @param[in,out] idp Address of the IDL.
|
||||
* @param[in] num Number of elements to make room for.
|
||||
* @return 0 on success, ENOMEM on failure.
|
||||
*/
|
||||
int mdb_midl_need(MDB_IDL *idp, unsigned num);
|
||||
|
||||
/** Append an ID onto an IDL.
|
||||
* @param[in,out] idp Address of the IDL to append to.
|
||||
* @param[in] id The ID to append.
|
||||
* @return 0 on success, ENOMEM if the IDL is too large.
|
||||
*/
|
||||
int mdb_midl_append( MDB_IDL *idp, MDB_ID id );
|
||||
|
||||
/** Append an IDL onto an IDL.
|
||||
* @param[in,out] idp Address of the IDL to append to.
|
||||
* @param[in] app The IDL to append.
|
||||
* @return 0 on success, ENOMEM if the IDL is too large.
|
||||
*/
|
||||
int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app );
|
||||
|
||||
/** Append an ID range onto an IDL.
|
||||
* @param[in,out] idp Address of the IDL to append to.
|
||||
* @param[in] id The lowest ID to append.
|
||||
* @param[in] n Number of IDs to append.
|
||||
* @return 0 on success, ENOMEM if the IDL is too large.
|
||||
*/
|
||||
int mdb_midl_append_range( MDB_IDL *idp, MDB_ID id, unsigned n );
|
||||
|
||||
/** Merge an IDL onto an IDL. The destination IDL must be big enough.
|
||||
* @param[in] idl The IDL to merge into.
|
||||
* @param[in] merge The IDL to merge.
|
||||
*/
|
||||
void mdb_midl_xmerge( MDB_IDL idl, MDB_IDL merge );
|
||||
|
||||
/** Sort an IDL.
|
||||
* @param[in,out] ids The IDL to sort.
|
||||
*/
|
||||
void mdb_midl_sort( MDB_IDL ids );
|
||||
|
||||
/** An ID2 is an ID/pointer pair.
|
||||
*/
|
||||
typedef struct MDB_ID2 {
|
||||
MDB_ID mid; /**< The ID */
|
||||
void *mptr; /**< The pointer */
|
||||
} MDB_ID2;
|
||||
|
||||
/** An ID2L is an ID2 List, a sorted array of ID2s.
|
||||
* The first element's \b mid member is a count of how many actual
|
||||
* elements are in the array. The \b mptr member of the first element is unused.
|
||||
* The array is sorted in ascending order by \b mid.
|
||||
*/
|
||||
typedef MDB_ID2 *MDB_ID2L;
|
||||
|
||||
/** Search for an ID in an ID2L.
|
||||
* @param[in] ids The ID2L to search.
|
||||
* @param[in] id The ID to search for.
|
||||
* @return The index of the first ID2 whose \b mid member is greater than or equal to \b id.
|
||||
*/
|
||||
unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id );
|
||||
|
||||
|
||||
/** Insert an ID2 into a ID2L.
|
||||
* @param[in,out] ids The ID2L to insert into.
|
||||
* @param[in] id The ID2 to insert.
|
||||
* @return 0 on success, -1 if the ID was already present in the ID2L.
|
||||
*/
|
||||
int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id );
|
||||
|
||||
/** Append an ID2 into a ID2L.
|
||||
* @param[in,out] ids The ID2L to append into.
|
||||
* @param[in] id The ID2 to append.
|
||||
* @return 0 on success, -2 if the ID2L is too big.
|
||||
*/
|
||||
int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id );
|
||||
|
||||
/** @} */
|
||||
/** @} */
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* _MDB_MIDL_H_ */
|
||||
@@ -0,0 +1,580 @@
|
||||
/* mplay.c - memory-mapped database log replay */
|
||||
/*
|
||||
* Copyright 2011-2023 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
#define MDB_SCNy(t) "z" #t
|
||||
|
||||
#define SCMP(s) s, (sizeof(s)-1)
|
||||
char inbuf[8192];
|
||||
char *dbuf, *kbuf;
|
||||
size_t dbufsize;
|
||||
int maxkey;
|
||||
|
||||
#define SOFF(s) (sizeof(s)+1)
|
||||
|
||||
#define MAXENVS 16
|
||||
#define MAXTXNS 16
|
||||
#define MAXCRSS 16
|
||||
|
||||
#define MAXPIDS 16
|
||||
|
||||
typedef struct crspair {
|
||||
void *tcrs; /* scanned text pointer */
|
||||
MDB_cursor *rcrs;
|
||||
} crspair;
|
||||
|
||||
typedef struct txnpair {
|
||||
void *ttxn; /* scanned text pointer */
|
||||
MDB_txn *rtxn;
|
||||
crspair cursors[MAXCRSS];
|
||||
int ncursors;
|
||||
} txnpair;
|
||||
|
||||
typedef struct envpair {
|
||||
void *tenv;
|
||||
MDB_env *renv;
|
||||
txnpair txns[MAXTXNS];
|
||||
int ntxns;
|
||||
} envpair;
|
||||
|
||||
envpair envs[MAXENVS];
|
||||
int nenvs;
|
||||
|
||||
envpair *lastenv;
|
||||
txnpair *lasttxn;
|
||||
crspair *lastcrs;
|
||||
|
||||
typedef struct pidpair {
|
||||
int tpid;
|
||||
pid_t rpid;
|
||||
int fdout, fdin;
|
||||
} pidpair;
|
||||
|
||||
pidpair *lastpid;
|
||||
|
||||
pidpair pids[MAXPIDS];
|
||||
int npids;
|
||||
|
||||
unsigned long lcount;
|
||||
|
||||
static int unhex(unsigned char *c2)
|
||||
{
|
||||
int x, c;
|
||||
x = *c2++ & 0x4f;
|
||||
if (x & 0x40)
|
||||
x -= 55;
|
||||
c = x << 4;
|
||||
x = *c2 & 0x4f;
|
||||
if (x & 0x40)
|
||||
x -= 55;
|
||||
c |= x;
|
||||
return c;
|
||||
}
|
||||
|
||||
int inhex(char *in, char *out)
|
||||
{
|
||||
char *c2 = out;
|
||||
while (isxdigit(*in)) {
|
||||
*c2++ = unhex((unsigned char *)in);
|
||||
in += 2;
|
||||
}
|
||||
return c2 - out;
|
||||
}
|
||||
|
||||
static void addenv(void *tenv, MDB_env *renv)
|
||||
{
|
||||
assert(nenvs < MAXENVS);
|
||||
envs[nenvs].tenv = tenv;
|
||||
envs[nenvs].renv = renv;
|
||||
envs[nenvs].ntxns = 0;
|
||||
lastenv = envs+nenvs;
|
||||
nenvs++;
|
||||
}
|
||||
|
||||
static envpair *findenv(void *tenv)
|
||||
{
|
||||
int i;
|
||||
if (!lastenv || lastenv->tenv != tenv) {
|
||||
for (i=0; i<nenvs; i++)
|
||||
if (envs[i].tenv == tenv)
|
||||
break;
|
||||
assert(i < nenvs);
|
||||
lastenv = &envs[i];
|
||||
}
|
||||
return lastenv;
|
||||
}
|
||||
|
||||
static void delenv(envpair *ep)
|
||||
{
|
||||
int i = ep - envs;
|
||||
for (; i<nenvs-1; i++)
|
||||
envs[i] = envs[i+1];
|
||||
nenvs--;
|
||||
lastenv = NULL;
|
||||
}
|
||||
|
||||
static void addtxn(void *tenv, void *ttxn, MDB_txn *rtxn)
|
||||
{
|
||||
envpair *ep;
|
||||
txnpair *tp;
|
||||
|
||||
ep = findenv(tenv);
|
||||
assert(ep->ntxns < MAXTXNS);
|
||||
tp = ep->txns+ep->ntxns;
|
||||
tp->ttxn = ttxn;
|
||||
tp->rtxn = rtxn;
|
||||
tp->ncursors = 0;
|
||||
ep->ntxns++;
|
||||
lasttxn = tp;
|
||||
}
|
||||
|
||||
static txnpair *findtxn(void *ttxn)
|
||||
{
|
||||
int i, j;
|
||||
if (lasttxn && lasttxn->ttxn == ttxn)
|
||||
return lasttxn;
|
||||
if (lastenv) {
|
||||
for (i=0; i<lastenv->ntxns; i++) {
|
||||
if (lastenv->txns[i].ttxn == ttxn) {
|
||||
lasttxn = lastenv->txns+i;
|
||||
return lasttxn;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i=0; i<nenvs; i++) {
|
||||
if (envs+i == lastenv) continue;
|
||||
for (j=0; j<envs[i].ntxns; j++) {
|
||||
if (envs[i].txns[j].ttxn == ttxn) {
|
||||
lastenv = envs+i;
|
||||
lasttxn = envs[i].txns+j;
|
||||
return lasttxn;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(0); /* should have found it */
|
||||
}
|
||||
|
||||
static void deltxn(txnpair *tp)
|
||||
{
|
||||
int i = tp - lastenv->txns;
|
||||
for (; i<lastenv->ntxns-1; i++)
|
||||
lastenv->txns[i] = lastenv->txns[i+1];
|
||||
lastenv->ntxns--;
|
||||
lasttxn = NULL;
|
||||
}
|
||||
|
||||
static void addcrs(txnpair *tp, void *tcrs, MDB_cursor *rcrs)
|
||||
{
|
||||
int j = tp->ncursors;
|
||||
assert(tp->ncursors < MAXCRSS);
|
||||
|
||||
tp->cursors[j].tcrs = tcrs;
|
||||
tp->cursors[j].rcrs = rcrs;
|
||||
tp->ncursors++;
|
||||
lastcrs = tp->cursors+j;
|
||||
}
|
||||
|
||||
static crspair *findcrs(void *tcrs)
|
||||
{
|
||||
int i, j, k;
|
||||
envpair *ep;
|
||||
txnpair *tp;
|
||||
crspair *cp;
|
||||
if (lastcrs && lastcrs->tcrs == tcrs)
|
||||
return lastcrs;
|
||||
if (lasttxn) {
|
||||
for (k=0, cp=lasttxn->cursors; k<lasttxn->ncursors; k++, cp++) {
|
||||
if (cp->tcrs == tcrs) {
|
||||
lastcrs = cp;
|
||||
return lastcrs;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastenv) {
|
||||
for (j=0, tp=lastenv->txns; j<lastenv->ntxns; j++, tp++){
|
||||
if (tp == lasttxn)
|
||||
continue;
|
||||
for (k=0, cp = tp->cursors; k<tp->ncursors; k++, cp++) {
|
||||
if (cp->tcrs == tcrs) {
|
||||
lastcrs = cp;
|
||||
lasttxn = tp;
|
||||
return lastcrs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i=0, ep=envs; i<nenvs; i++, ep++) {
|
||||
for (j=0, tp=ep->txns; j<ep->ntxns; j++, tp++) {
|
||||
if (tp == lasttxn)
|
||||
continue;
|
||||
for (k=0, cp = tp->cursors; k<tp->ncursors; k++, cp++) {
|
||||
if (cp->tcrs == tcrs) {
|
||||
lastcrs = cp;
|
||||
lasttxn = tp;
|
||||
lastenv = ep;
|
||||
return lastcrs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(0); /* should have found it already */
|
||||
}
|
||||
|
||||
static void delcrs(void *tcrs)
|
||||
{
|
||||
int i;
|
||||
crspair *cp = findcrs(tcrs);
|
||||
mdb_cursor_close(cp->rcrs);
|
||||
for (i = cp - lasttxn->cursors; i<lasttxn->ncursors-1; i++)
|
||||
lasttxn->cursors[i] = lasttxn->cursors[i+1];
|
||||
lasttxn->ncursors--;
|
||||
lastcrs = NULL;
|
||||
}
|
||||
|
||||
void child()
|
||||
{
|
||||
int rc;
|
||||
MDB_val key, data;
|
||||
char *ptr;
|
||||
|
||||
while (fgets(inbuf, sizeof(inbuf), stdin)) {
|
||||
ptr = inbuf;
|
||||
if (!strncmp(ptr, SCMP("exit")))
|
||||
break;
|
||||
|
||||
if (!strncmp(ptr, SCMP("mdb_env_create"))) {
|
||||
void *tenv;
|
||||
MDB_env *renv;
|
||||
sscanf(ptr+SOFF("mdb_env_create"), "%p", &tenv);
|
||||
E(mdb_env_create(&renv));
|
||||
addenv(tenv, renv);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_env_set_maxdbs"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
unsigned int maxdbs;
|
||||
sscanf(ptr+SOFF("mdb_env_set_maxdbs"), "%p, %u", &tenv, &maxdbs);
|
||||
ep = findenv(tenv);
|
||||
E(mdb_env_set_maxdbs(ep->renv, maxdbs));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_env_set_mapsize"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
size_t mapsize;
|
||||
sscanf(ptr+SOFF("mdb_env_set_mapsize"), "%p, %"MDB_SCNy(u), &tenv, &mapsize);
|
||||
ep = findenv(tenv);
|
||||
E(mdb_env_set_mapsize(ep->renv, mapsize));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_env_open"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
char *path;
|
||||
int len;
|
||||
unsigned int flags, mode;
|
||||
sscanf(ptr+SOFF("mdb_env_open"), "%p, %n", &tenv, &len);
|
||||
path = ptr+SOFF("mdb_env_open")+len;
|
||||
ptr = strchr(path, ',');
|
||||
*ptr++ = '\0';
|
||||
sscanf(ptr, "%u, %o", &flags, &mode);
|
||||
ep = findenv(tenv);
|
||||
E(mdb_env_open(ep->renv, path, flags, mode));
|
||||
if (!maxkey) {
|
||||
maxkey = mdb_env_get_maxkeysize(ep->renv);
|
||||
kbuf = malloc(maxkey+2);
|
||||
dbuf = malloc(maxkey+2);
|
||||
dbufsize = maxkey;
|
||||
}
|
||||
} else if (!strncmp(ptr, SCMP("mdb_env_close"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
sscanf(ptr+SOFF("mdb_env_close"), "%p", &tenv);
|
||||
ep = findenv(tenv);
|
||||
mdb_env_close(ep->renv);
|
||||
delenv(ep);
|
||||
if (!nenvs) /* if no other envs left, this process is done */
|
||||
break;
|
||||
} else if (!strncmp(ptr, SCMP("mdb_txn_begin"))) {
|
||||
unsigned int flags;
|
||||
void *tenv, *ttxn;
|
||||
envpair *ep;
|
||||
MDB_txn *rtxn;
|
||||
sscanf(ptr+SOFF("mdb_txn_begin"), "%p, %*p, %u = %p", &tenv, &flags, &ttxn);
|
||||
ep = findenv(tenv);
|
||||
E(mdb_txn_begin(ep->renv, NULL, flags, &rtxn));
|
||||
addtxn(tenv, ttxn, rtxn);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_txn_commit"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
sscanf(ptr+SOFF("mdb_txn_commit"), "%p", &ttxn);
|
||||
tp = findtxn(ttxn);
|
||||
E(mdb_txn_commit(tp->rtxn));
|
||||
deltxn(tp);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_txn_abort"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
sscanf(ptr+SOFF("mdb_txn_abort"), "%p", &ttxn);
|
||||
tp = findtxn(ttxn);
|
||||
mdb_txn_abort(tp->rtxn);
|
||||
deltxn(tp);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_dbi_open"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
char *dbname;
|
||||
unsigned int flags;
|
||||
unsigned int tdbi;
|
||||
MDB_dbi dbi;
|
||||
sscanf(ptr+SOFF("mdb_dbi_open"), "%p, ", &ttxn);
|
||||
dbname = strchr(ptr+SOFF("mdb_dbi_open"), ',');
|
||||
dbname += 2;
|
||||
ptr = strchr(dbname, ',');
|
||||
*ptr++ = '\0';
|
||||
if (!strcmp(dbname, "(null)"))
|
||||
dbname = NULL;
|
||||
sscanf(ptr, "%u = %u", &flags, &tdbi);
|
||||
tp = findtxn(ttxn);
|
||||
E(mdb_dbi_open(tp->rtxn, dbname, flags, &dbi));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_dbi_close"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
unsigned int tdbi;
|
||||
sscanf(ptr+SOFF("mdb_dbi_close"), "%p, %u", &tenv, &tdbi);
|
||||
ep = findenv(tenv);
|
||||
mdb_dbi_close(ep->renv, tdbi);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_cursor_open"))) {
|
||||
void *ttxn, *tcrs;
|
||||
txnpair *tp;
|
||||
MDB_cursor *rcrs;
|
||||
unsigned int tdbi;
|
||||
sscanf(ptr+SOFF("mdb_cursor_open"), "%p, %u = %p", &ttxn, &tdbi, &tcrs);
|
||||
tp = findtxn(ttxn);
|
||||
E(mdb_cursor_open(tp->rtxn, tdbi, &rcrs));
|
||||
addcrs(tp, tcrs, rcrs);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_cursor_close"))) {
|
||||
void *tcrs;
|
||||
sscanf(ptr+SOFF("mdb_cursor_close"), "%p", &tcrs);
|
||||
delcrs(tcrs);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_cursor_put"))) {
|
||||
void *tcrs;
|
||||
crspair *cp;
|
||||
unsigned int flags;
|
||||
int len;
|
||||
sscanf(ptr+SOFF("mdb_cursor_put"), "%p, ", &tcrs);
|
||||
cp = findcrs(tcrs);
|
||||
ptr = strchr(ptr+SOFF("mdb_cursor_put"), ',');
|
||||
sscanf(ptr+1, "%"MDB_SCNy(u)",", &key.mv_size);
|
||||
if (key.mv_size) {
|
||||
ptr = strchr(ptr, '[');
|
||||
inhex(ptr+1, kbuf);
|
||||
key.mv_data = kbuf;
|
||||
ptr += key.mv_size * 2 + 2;
|
||||
}
|
||||
ptr = strchr(ptr+1, ',');
|
||||
sscanf(ptr+1, "%"MDB_SCNy(u)"%n", &data.mv_size, &len);
|
||||
if (data.mv_size > dbufsize) {
|
||||
dbuf = realloc(dbuf, data.mv_size+2);
|
||||
assert(dbuf != NULL);
|
||||
dbufsize = data.mv_size;
|
||||
}
|
||||
ptr += len+1;
|
||||
if (*ptr == '[') {
|
||||
inhex(ptr+1, dbuf);
|
||||
data.mv_data = dbuf;
|
||||
ptr += data.mv_size * 2 + 2;
|
||||
} else {
|
||||
sprintf(dbuf, "%09ld", (long)mdb_txn_id(lasttxn->rtxn));
|
||||
}
|
||||
sscanf(ptr+1, "%u", &flags);
|
||||
E(mdb_cursor_put(cp->rcrs, &key, &data, flags));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_cursor_del"))) {
|
||||
void *tcrs;
|
||||
crspair *cp;
|
||||
unsigned int flags;
|
||||
sscanf(ptr+SOFF("mdb_cursor_del"), "%p, %u", &tcrs, &flags);
|
||||
cp = findcrs(tcrs);
|
||||
E(mdb_cursor_del(cp->rcrs, flags));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_put"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
unsigned int tdbi, flags;
|
||||
int len;
|
||||
sscanf(ptr+SOFF("mdb_put"),"%p, %u, %"MDB_SCNy(u), &ttxn, &tdbi, &key.mv_size);
|
||||
tp = findtxn(ttxn);
|
||||
ptr = strchr(ptr+SOFF("mdb_put"), '[');
|
||||
inhex(ptr+1, kbuf);
|
||||
key.mv_data = kbuf;
|
||||
ptr += key.mv_size * 2 + 2;
|
||||
sscanf(ptr+1, "%"MDB_SCNy(u)"%n", &data.mv_size, &len);
|
||||
if (data.mv_size > dbufsize) {
|
||||
dbuf = realloc(dbuf, data.mv_size+2);
|
||||
assert(dbuf != NULL);
|
||||
dbufsize = data.mv_size;
|
||||
}
|
||||
ptr += len+1;
|
||||
if (*ptr == '[') {
|
||||
inhex(ptr+1, dbuf);
|
||||
ptr += data.mv_size * 2 + 2;
|
||||
} else {
|
||||
sprintf(dbuf, "%09ld", (long)mdb_txn_id(tp->rtxn));
|
||||
}
|
||||
data.mv_data = dbuf;
|
||||
sscanf(ptr+1, "%u", &flags);
|
||||
RES(MDB_KEYEXIST,mdb_put(tp->rtxn, tdbi, &key, &data, flags));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_del"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
unsigned int tdbi;
|
||||
int len;
|
||||
sscanf(ptr+SOFF("mdb_del"),"%p, %u, %"MDB_SCNy(u), &ttxn, &tdbi, &key.mv_size);
|
||||
tp = findtxn(ttxn);
|
||||
ptr = strchr(ptr+SOFF("mdb_del"), '[');
|
||||
inhex(ptr+1, kbuf);
|
||||
key.mv_data = kbuf;
|
||||
ptr += key.mv_size * 2 + 2;
|
||||
sscanf(ptr+1, "%"MDB_SCNy(u)"%n", &data.mv_size, &len);
|
||||
if (data.mv_size > dbufsize) {
|
||||
dbuf = realloc(dbuf, data.mv_size+2);
|
||||
assert(dbuf != NULL);
|
||||
dbufsize = data.mv_size;
|
||||
}
|
||||
ptr += len+1;
|
||||
if (*ptr == '[') {
|
||||
inhex(ptr+1, dbuf);
|
||||
} else {
|
||||
sprintf(dbuf, "%09ld", (long)mdb_txn_id(tp->rtxn));
|
||||
}
|
||||
data.mv_data = dbuf;
|
||||
RES(MDB_NOTFOUND,mdb_del(tp->rtxn, tdbi, &key, &data));
|
||||
}
|
||||
write(1, "\n", 1);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static pidpair *addpid(int tpid)
|
||||
{
|
||||
int fdout[2], fdin[2];
|
||||
pid_t pid;
|
||||
assert(npids < MAXPIDS);
|
||||
pids[npids].tpid = tpid;
|
||||
pipe(fdout);
|
||||
pipe(fdin);
|
||||
if ((pid = fork()) == 0) {
|
||||
/* child */
|
||||
fflush(stdin);
|
||||
fflush(stdout);
|
||||
dup2(fdout[0], 0);
|
||||
dup2(fdin[1], 1);
|
||||
child();
|
||||
return 0; /* NOTREACHED */
|
||||
} else {
|
||||
pids[npids].rpid = pid;
|
||||
pids[npids].fdout = fdout[1];
|
||||
pids[npids].fdin = fdin[0];
|
||||
lastpid = pids+npids;
|
||||
npids++;
|
||||
return lastpid;
|
||||
}
|
||||
}
|
||||
|
||||
static pidpair *findpid(int tpid)
|
||||
{
|
||||
int i;
|
||||
if (!lastpid || lastpid->tpid != tpid) {
|
||||
for (i=0; i<npids; i++)
|
||||
if (pids[i].tpid == tpid)
|
||||
break;
|
||||
if (i == npids)
|
||||
return NULL;
|
||||
lastpid = &pids[i];
|
||||
}
|
||||
return lastpid;
|
||||
}
|
||||
|
||||
volatile pid_t killpid;
|
||||
|
||||
static void delpid(int tpid)
|
||||
{
|
||||
pidpair *pp = findpid(tpid);
|
||||
if (pp) {
|
||||
pid_t kpid = pp->rpid;
|
||||
killpid = kpid;
|
||||
write(pp->fdout, "exit\n", sizeof("exit"));
|
||||
while (killpid == kpid)
|
||||
usleep(10000);
|
||||
}
|
||||
}
|
||||
|
||||
static void reaper(int sig)
|
||||
{
|
||||
int status, i;
|
||||
pid_t pid = waitpid(-1, &status, 0);
|
||||
if (pid > 0) {
|
||||
fprintf(stderr, "# %s %d\n", WIFEXITED(status) ? "exited" : "killed", pid);
|
||||
for (i=0; i<npids; i++)
|
||||
if (pids[i].rpid == pid)
|
||||
break;
|
||||
assert(i < npids);
|
||||
close(pids[i].fdout);
|
||||
close(pids[i].fdin);
|
||||
for (;i<npids-1; i++)
|
||||
pids[i] = pids[i+1];
|
||||
npids--;
|
||||
killpid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
signal(SIGCHLD, reaper);
|
||||
|
||||
while (fgets(inbuf, sizeof(inbuf), stdin)) {
|
||||
pidpair *pp;
|
||||
int tpid, len;
|
||||
char c, *ptr;
|
||||
lcount++;
|
||||
|
||||
if (inbuf[0] == '#' && !strncmp(inbuf+1, SCMP(" killed"))) {
|
||||
sscanf(inbuf+SOFF("killed"),"%d", &tpid);
|
||||
delpid(tpid);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inbuf[0] != '>')
|
||||
continue;
|
||||
ptr = inbuf+1;
|
||||
sscanf(ptr, "%d:%n", &tpid, &len);
|
||||
pp = findpid(tpid);
|
||||
if (!pp)
|
||||
pp = addpid(tpid); /* new process */
|
||||
|
||||
ptr = inbuf+len+1;
|
||||
len = strlen(ptr);
|
||||
write(pp->fdout, ptr, len); /* send command and wait for ack */
|
||||
read(pp->fdin, &c, 1);
|
||||
}
|
||||
while (npids)
|
||||
delpid(pids[0].tpid);
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/* mtest.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor, *cur2;
|
||||
MDB_cursor_op op;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[32] = "";
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
count = (rand()%384) + 64;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = rand()%1024;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_maxreaders(env, 1));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP /*|MDB_NOSYNC*/, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, NULL, 0, &dbi));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
for (i=0;i<count;i++) {
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
/* Set <data> in each iteration, since MDB_NOOVERWRITE may modify it */
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NOOVERWRITE))) {
|
||||
j++;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
}
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
j=0;
|
||||
key.mv_data = sval;
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(sval, "%03x ", values[i]);
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, NULL))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor last\n");
|
||||
E(mdb_cursor_get(cursor, &key, &data, MDB_LAST));
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor last/prev\n");
|
||||
E(mdb_cursor_get(cursor, &key, &data, MDB_LAST));
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
E(mdb_cursor_get(cursor, &key, &data, MDB_PREV));
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
printf("Deleting with cursor\n");
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cur2));
|
||||
for (i=0; i<50; i++) {
|
||||
if (RES(MDB_NOTFOUND, mdb_cursor_get(cur2, &key, &data, MDB_NEXT)))
|
||||
break;
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
E(mdb_del(txn, dbi, &key, NULL));
|
||||
}
|
||||
|
||||
printf("Restarting cursor in txn\n");
|
||||
for (op=MDB_FIRST, i=0; i<=32; op=MDB_NEXT, i++) {
|
||||
if (RES(MDB_NOTFOUND, mdb_cursor_get(cur2, &key, &data, op)))
|
||||
break;
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
mdb_cursor_close(cur2);
|
||||
E(mdb_txn_commit(txn));
|
||||
|
||||
printf("Restarting cursor outside txn\n");
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
for (op=MDB_FIRST, i=0; i<=32; op=MDB_NEXT, i++) {
|
||||
if (RES(MDB_NOTFOUND, mdb_cursor_get(cursor, &key, &data, op)))
|
||||
break;
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/* mtest2.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Just like mtest.c, but using a subDB instead of the main DB */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[32] = "";
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
count = (rand()%384) + 64;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = rand()%1024;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_maxreaders(env, 1));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id1", MDB_CREATE, &dbi));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
for (i=0;i<count;i++) {
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NOOVERWRITE)))
|
||||
j++;
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
j=0;
|
||||
key.mv_data = sval;
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(sval, "%03x ", values[i]);
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, NULL))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/* mtest3.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Tests for sorted duplicate DBs */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[32];
|
||||
char kval[sizeof(int)];
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
memset(sval, 0, sizeof(sval));
|
||||
|
||||
count = (rand()%384) + 64;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = rand()%1024;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id2", MDB_CREATE|MDB_DUPSORT, &dbi));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
for (i=0;i<count;i++) {
|
||||
if (!(i & 0x0f))
|
||||
sprintf(kval, "%03x", values[i]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA)))
|
||||
j++;
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
j=0;
|
||||
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(kval, "%03x", values[i & ~0x0f]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/* mtest4.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Tests for sorted duplicate DBs with fixed-size keys */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[8];
|
||||
char kval[sizeof(int)];
|
||||
|
||||
memset(sval, 0, sizeof(sval));
|
||||
|
||||
count = 510;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = i*5;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id4", MDB_CREATE|MDB_DUPSORT|MDB_DUPFIXED, &dbi));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
strcpy(kval, "001");
|
||||
for (i=0;i<count;i++) {
|
||||
sprintf(sval, "%07x", values[i]);
|
||||
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA)))
|
||||
j++;
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
/* there should be one full page of dups now.
|
||||
*/
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
/* test all 3 branches of split code:
|
||||
* 1: new key in lower half
|
||||
* 2: new key at split point
|
||||
* 3: new key in upper half
|
||||
*/
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
sprintf(sval, "%07x", values[3]+1);
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
(void)RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA));
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
sprintf(sval, "%07x", values[255]+1);
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
(void)RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA));
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
sprintf(sval, "%07x", values[500]+1);
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
(void)RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA));
|
||||
E(mdb_txn_commit(txn));
|
||||
|
||||
/* Try MDB_NEXT_MULTIPLE */
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT_MULTIPLE)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
j=0;
|
||||
|
||||
for (i= count - 1; i > -1; i-= (rand()%3)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(sval, "%07x", values[i]);
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/* mtest5.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Tests for sorted duplicate DBs using cursor_put */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[32];
|
||||
char kval[sizeof(int)];
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
memset(sval, 0, sizeof(sval));
|
||||
|
||||
count = (rand()%384) + 64;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = rand()%1024;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id2", MDB_CREATE|MDB_DUPSORT, &dbi));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
for (i=0;i<count;i++) {
|
||||
if (!(i & 0x0f))
|
||||
sprintf(kval, "%03x", values[i]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
if (RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NODUPDATA)))
|
||||
j++;
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
mdb_cursor_close(cursor);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
j=0;
|
||||
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(kval, "%03x", values[i & ~0x0f]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/* mtest6.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Tests for DB splits and merges */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
char dkbuf[1024];
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data, sdata;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
long kval;
|
||||
char *sval;
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id6", MDB_CREATE|MDB_INTEGERKEY, &dbi));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
E(mdb_stat(txn, dbi, &mst));
|
||||
|
||||
sval = calloc(1, mst.ms_psize / 4);
|
||||
key.mv_size = sizeof(long);
|
||||
key.mv_data = &kval;
|
||||
sdata.mv_size = mst.ms_psize / 4 - 30;
|
||||
sdata.mv_data = sval;
|
||||
|
||||
printf("Adding 12 values, should yield 3 splits\n");
|
||||
for (i=0;i<12;i++) {
|
||||
kval = i*5;
|
||||
sprintf(sval, "%08x", kval);
|
||||
data = sdata;
|
||||
(void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE));
|
||||
}
|
||||
printf("Adding 12 more values, should yield 3 splits\n");
|
||||
for (i=0;i<12;i++) {
|
||||
kval = i*5+4;
|
||||
sprintf(sval, "%08x", kval);
|
||||
data = sdata;
|
||||
(void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE));
|
||||
}
|
||||
printf("Adding 12 more values, should yield 3 splits\n");
|
||||
for (i=0;i<12;i++) {
|
||||
kval = i*5+1;
|
||||
sprintf(sval, "%08x", kval);
|
||||
data = sdata;
|
||||
(void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE));
|
||||
}
|
||||
E(mdb_cursor_get(cursor, &key, &data, MDB_FIRST));
|
||||
|
||||
do {
|
||||
printf("key: %p %s, data: %p %.*s\n",
|
||||
key.mv_data, mdb_dkey(&key, dkbuf),
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
} while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0);
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_commit(txn);
|
||||
|
||||
#if 0
|
||||
j=0;
|
||||
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(kval, "%03x", values[i & ~0x0f]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
#endif
|
||||
mdb_env_close(env);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/* sample-bdb.txt - BerkeleyDB toy/sample
|
||||
*
|
||||
* Do a line-by-line comparison of this and sample-mdb.txt
|
||||
*/
|
||||
/*
|
||||
* Copyright 2012-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <db.h>
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int rc;
|
||||
DB_ENV *env;
|
||||
DB *dbi;
|
||||
DBT key, data;
|
||||
DB_TXN *txn;
|
||||
DBC *cursor;
|
||||
char sval[32], kval[32];
|
||||
|
||||
/* Note: Most error checking omitted for simplicity */
|
||||
|
||||
#define FLAGS (DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_TXN|DB_INIT_MPOOL|DB_CREATE|DB_THREAD)
|
||||
rc = db_env_create(&env, 0);
|
||||
rc = env->open(env, "./testdb", FLAGS, 0664);
|
||||
rc = db_create(&dbi, env, 0);
|
||||
rc = env->txn_begin(env, NULL, &txn, 0);
|
||||
rc = dbi->open(dbi, txn, "test.bdb", NULL, DB_BTREE, DB_CREATE, 0664);
|
||||
|
||||
memset(&key, 0, sizeof(DBT));
|
||||
memset(&data, 0, sizeof(DBT));
|
||||
key.size = sizeof(int);
|
||||
key.data = sval;
|
||||
data.size = sizeof(sval);
|
||||
data.data = sval;
|
||||
|
||||
sprintf(sval, "%03x %d foo bar", 32, 3141592);
|
||||
rc = dbi->put(dbi, txn, &key, &data, 0);
|
||||
rc = txn->commit(txn, 0);
|
||||
if (rc) {
|
||||
fprintf(stderr, "txn->commit: (%d) %s\n", rc, db_strerror(rc));
|
||||
goto leave;
|
||||
}
|
||||
rc = env->txn_begin(env, NULL, &txn, 0);
|
||||
rc = dbi->cursor(dbi, txn, &cursor, 0);
|
||||
key.flags = DB_DBT_USERMEM;
|
||||
key.data = kval;
|
||||
key.ulen = sizeof(kval);
|
||||
data.flags = DB_DBT_USERMEM;
|
||||
data.data = sval;
|
||||
data.ulen = sizeof(sval);
|
||||
while ((rc = cursor->c_get(cursor, &key, &data, DB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.data, (int) key.size, (char *) key.data,
|
||||
data.data, (int) data.size, (char *) data.data);
|
||||
}
|
||||
rc = cursor->c_close(cursor);
|
||||
rc = txn->abort(txn);
|
||||
leave:
|
||||
rc = dbi->close(dbi, 0);
|
||||
rc = env->close(env, 0);
|
||||
return rc;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/* sample-mdb.txt - MDB toy/sample
|
||||
*
|
||||
* Do a line-by-line comparison of this and sample-bdb.txt
|
||||
*/
|
||||
/*
|
||||
* Copyright 2012-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_cursor *cursor;
|
||||
char sval[32];
|
||||
|
||||
/* Note: Most error checking omitted for simplicity */
|
||||
|
||||
rc = mdb_env_create(&env);
|
||||
rc = mdb_env_open(env, "./testdb", 0, 0664);
|
||||
rc = mdb_txn_begin(env, NULL, 0, &txn);
|
||||
rc = mdb_dbi_open(txn, NULL, 0, &dbi);
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = sval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
sprintf(sval, "%03x %d foo bar", 32, 3141592);
|
||||
rc = mdb_put(txn, dbi, &key, &data, 0);
|
||||
rc = mdb_txn_commit(txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_commit: (%d) %s\n", rc, mdb_strerror(rc));
|
||||
goto leave;
|
||||
}
|
||||
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
|
||||
rc = mdb_cursor_open(txn, dbi, &cursor);
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
leave:
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<tagfile>
|
||||
<compound kind="page">
|
||||
<name>mdb_copy_1</name>
|
||||
<title>mdb_copy - environment copy tool</title>
|
||||
<filename>mdb_copy.1</filename>
|
||||
</compound>
|
||||
<compound kind="page">
|
||||
<name>mdb_dump_1</name>
|
||||
<title>mdb_dump - environment export tool</title>
|
||||
<filename>mdb_dump.1</filename>
|
||||
</compound>
|
||||
<compound kind="page">
|
||||
<name>mdb_load_1</name>
|
||||
<title>mdb_load - environment import tool</title>
|
||||
<filename>mdb_load.1</filename>
|
||||
</compound>
|
||||
<compound kind="page">
|
||||
<name>mdb_stat_1</name>
|
||||
<title>mdb_stat - environment status tool</title>
|
||||
<filename>mdb_stat.1</filename>
|
||||
</compound>
|
||||
</tagfile>
|
||||
+8
-92
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
* ============================================================================ */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large.
Load diff
+26
-14
@@ -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
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 ========================
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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; };
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
Executable
+44
@@ -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
|
||||
@@ -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 не добавлены — можно добавить позже при необходимости.
|
||||
@@ -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 не добавлены — можно добавить позже при необходимости.
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
Reference in new issue
Block a user