Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f651f1df0 | ||
|
|
cff81a70da | ||
|
|
4cfaaec3b0 | ||
|
|
b5d49926a9 | ||
|
|
d7c84a94e2 | ||
|
|
9af10a208f | ||
|
|
677a5cdcf5 | ||
|
|
54c92f423f | ||
|
|
b37e7001b0 | ||
|
|
ce37db137b |
No files matched your search
+1
-6
@@ -1,6 +1,5 @@
|
||||
# Build directory (out-of-tree build)
|
||||
/build/
|
||||
tools/chatgui/build/
|
||||
|
||||
# Build outputs
|
||||
*.o
|
||||
@@ -60,7 +59,7 @@ Makefile.in
|
||||
.cache/
|
||||
|
||||
# Debug outputs
|
||||
/core
|
||||
core
|
||||
vgcore.*
|
||||
callgrind.out.*
|
||||
|
||||
@@ -97,9 +96,6 @@ utun.conf
|
||||
tools/bping/bping
|
||||
tools/proxy/udp_proxy
|
||||
|
||||
# Reference projects (not part of utun)
|
||||
tools/tdesktop-dev/
|
||||
|
||||
# Debug/temp artifacts
|
||||
*.bak
|
||||
*.debug
|
||||
@@ -112,4 +108,3 @@ cl
|
||||
build_win.log
|
||||
/wintun.dll
|
||||
src/lwip_orig/
|
||||
lib/timeout/
|
||||
@@ -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:** OpenSSL (AES-CCM, X25519, SHA256)
|
||||
**Cryptography:** TinyCrypt + OpenSSL (AES-CCM, ECC, 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 | X25519 private key |
|
||||
| `SC_PUBKEY_SIZE` | 32 | X25519 public key |
|
||||
| `SC_PRIVKEY_SIZE` | 32 | ECC private key |
|
||||
| `SC_PUBKEY_SIZE` | 64 | ECC 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` | 40 | Total pubkey+salt block sent unencrypted |
|
||||
| `SC_PUBKEY_ENC_SIZE` | 72 | Total pubkey+salt block sent unencrypted |
|
||||
|
||||
### Using Secure Channel (secure_channel.h)
|
||||
```c
|
||||
@@ -211,8 +211,6 @@ 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)
|
||||
@@ -248,7 +246,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 X25519 key exchange, pubkey obfuscation
|
||||
- `secure_channel.c/h` - AES-CCM encryption with ECC key exchange, pubkey obfuscation
|
||||
- `crc32.c/h` - CRC32 checksums
|
||||
|
||||
**NAT (src/)**
|
||||
@@ -287,7 +285,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 + X25519 key exchange, nonce-based encryption, pubkey obfuscation
|
||||
- **Secure Channel:** AES-CCM + ECC key exchange, nonce-based encryption, pubkey obfuscation
|
||||
|
||||
## Queue Usage Rules
|
||||
|
||||
@@ -339,141 +337,6 @@ 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 архитектура
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# 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
|
||||
+4
-9
@@ -7,8 +7,8 @@ libuasync_a_SOURCES = \
|
||||
ll_queue.c \
|
||||
debug_config.c \
|
||||
debug_config.h \
|
||||
timeout_heap.c \
|
||||
timeout_heap.h \
|
||||
twheel.c \
|
||||
twheel.h \
|
||||
memory_pool.c \
|
||||
memory_pool.h \
|
||||
sha256.c \
|
||||
@@ -28,19 +28,14 @@ libuasync_a_SOURCES = \
|
||||
serialize.c \
|
||||
serialize.h \
|
||||
tcp_io.c \
|
||||
tcp_io.h \
|
||||
liblmdb/mdb.c \
|
||||
liblmdb/midl.c \
|
||||
liblmdb/lmdb.h \
|
||||
liblmdb/midl.h
|
||||
tcp_io.h
|
||||
|
||||
libuasync_a_CFLAGS = \
|
||||
-D_ISOC99_SOURCE \
|
||||
-DDEBUG_OUTPUT_STDERR \
|
||||
-g \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/lib \
|
||||
-I$(srcdir)/liblmdb
|
||||
-I$(top_srcdir)/lib
|
||||
|
||||
# Clean build directory
|
||||
clean-local:
|
||||
|
||||
+26
-4
@@ -127,7 +127,6 @@ static const struct {
|
||||
{"nat", DEBUG_CATEGORY_NAT},
|
||||
{"keepalive", DEBUG_CATEGORY_KEEPALIVE},
|
||||
{"etcp_route", DEBUG_CATEGORY_ETCPROUTE},
|
||||
{"bbr", DEBUG_CATEGORY_BBR},
|
||||
{"all", DEBUG_CATEGORY_ALL},
|
||||
{NULL, DEBUG_CATEGORY_NONE}
|
||||
};
|
||||
@@ -199,9 +198,32 @@ void debug_apply_global_level(const char* level_name) {
|
||||
|
||||
/* Get category name for logging */
|
||||
const char* debug_get_category_name(debug_category_t category_idx) {
|
||||
for (int i = 0; g_debug_categories[i].name; i++)
|
||||
if (g_debug_categories[i].category == category_idx) return g_debug_categories[i].name;
|
||||
return "UNKNOWN";
|
||||
switch (category_idx) {
|
||||
case DEBUG_CATEGORY_UASYNC: return "UASYNC";
|
||||
case DEBUG_CATEGORY_LL_QUEUE: return "LL_QUEUE";
|
||||
case DEBUG_CATEGORY_CONNECTION: return "CONNECTION";
|
||||
case DEBUG_CATEGORY_ETCP: return "ETCP";
|
||||
case DEBUG_CATEGORY_CRYPTO: return "CRYPTO";
|
||||
case DEBUG_CATEGORY_MEMORY: return "MEMORY";
|
||||
case DEBUG_CATEGORY_TIMING: return "TIMING";
|
||||
case DEBUG_CATEGORY_CONFIG: return "CONFIG";
|
||||
case DEBUG_CATEGORY_TUN: return "TUN";
|
||||
case DEBUG_CATEGORY_ROUTING: return "ROUTING";
|
||||
case DEBUG_CATEGORY_TIMERS: return "TIMERS";
|
||||
case DEBUG_CATEGORY_NORMALIZER: return "NORMALIZER";
|
||||
case DEBUG_CATEGORY_BGP: return "BGP";
|
||||
case DEBUG_CATEGORY_SOCKET: return "SOCKET";
|
||||
case DEBUG_CATEGORY_CONTROL: return "CONTROL";
|
||||
case DEBUG_CATEGORY_DUMP: return "DUMP";
|
||||
case DEBUG_CATEGORY_TRAFFIC: return "TRAFFIC";
|
||||
case DEBUG_CATEGORY_DEBUG: return "DEBUG";
|
||||
case DEBUG_CATEGORY_GENERAL: return "GENERAL";
|
||||
case DEBUG_CATEGORY_NAT: return "NAT";
|
||||
case DEBUG_CATEGORY_KEEPALIVE: return "KEEPALIVE";
|
||||
case DEBUG_CATEGORY_ETCPROUTE: return "ETCPROUTE";
|
||||
case DEBUG_CATEGORY_NONE: return "NONE";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
/* Global debug configuration */
|
||||
|
||||
+1
-2
@@ -60,8 +60,7 @@ typedef int debug_category_t;
|
||||
#define DEBUG_CATEGORY_NAT 20 // EIM NAT module
|
||||
#define DEBUG_CATEGORY_KEEPALIVE 21 // Link keepalive logic
|
||||
#define DEBUG_CATEGORY_ETCPROUTE 22 // ETCP routing
|
||||
#define DEBUG_CATEGORY_BBR 23 // BBR congestion control
|
||||
#define DEBUG_CATEGORY_COUNT 24 // Total number of categories
|
||||
#define DEBUG_CATEGORY_COUNT 23 // Total number of categories
|
||||
#define DEBUG_CATEGORY_ALL (-1) // special value for all categories
|
||||
|
||||
/* Debug configuration structure */
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
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/
|
||||
@@ -1,317 +0,0 @@
|
||||
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
|
||||
@@ -1,20 +0,0 @@
|
||||
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
@@ -1,47 +0,0 @@
|
||||
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.
|
||||
@@ -1,192 +0,0 @@
|
||||
/*
|
||||
* 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
@@ -1,55 +0,0 @@
|
||||
.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>
|
||||
@@ -1,82 +0,0 @@
|
||||
/* 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;
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
.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>
|
||||
@@ -1,319 +0,0 @@
|
||||
/* 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;
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
.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>
|
||||
@@ -1,515 +0,0 @@
|
||||
/* 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;
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
.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>
|
||||
@@ -1,263 +0,0 @@
|
||||
/* 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;
|
||||
}
|
||||
@@ -1,359 +0,0 @@
|
||||
/** @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;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
/** @} */
|
||||
@@ -1,188 +0,0 @@
|
||||
/** @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_ */
|
||||
@@ -1,580 +0,0 @@
|
||||
/* 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);
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
/* 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;
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
/* 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;
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
/* 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;
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
/* 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;
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
/* 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;
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
/* 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;
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/* 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;
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/* 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;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<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>
|
||||
@@ -135,14 +135,20 @@ void memory_pool_destroy(struct memory_pool* pool) {
|
||||
return;
|
||||
}
|
||||
|
||||
int cnt=0;
|
||||
// Освободить все объекты в пуле
|
||||
void* obj = pool->free_head;
|
||||
while (obj) {
|
||||
void* next = *(void**)obj;
|
||||
g_total_free--;
|
||||
u_free(obj);
|
||||
cnt++;
|
||||
obj = next;
|
||||
}
|
||||
|
||||
// if (cnt>MEMORY_POOL_MAX_FREE)
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "pool_destroy: allocations=%zu, reuse=%zu, current pool size=%d", pool->allocations, pool->reuse_count, cnt);
|
||||
|
||||
pool->free_head = NULL;
|
||||
pool->free_count = 0;
|
||||
u_free(pool);
|
||||
|
||||
+32
-75
@@ -20,14 +20,13 @@ static void error_cb(socket_t sock, void* arg);
|
||||
static void resume_read_cb(struct ll_queue* q, void* arg);
|
||||
static void fin_deferred_cb(struct ll_queue* q, void* arg);
|
||||
static void write_queue_fetch_cb(struct ll_queue* q, void* arg);
|
||||
static int flush_write_buf(struct tcp_conn* tc);
|
||||
static void tcp_conn_handle_error(struct tcp_conn* tc, int err);
|
||||
static void flush_write_buf(struct tcp_conn* tc);
|
||||
static const uint8_t tcp_fin_sentinel;
|
||||
|
||||
struct tcp_conn* tcp_conn_create(
|
||||
struct UASYNC* ua, socket_t sock,
|
||||
size_t entry_data_size, size_t write_chunk_size,
|
||||
int read_high_water, int read_low_water, int rcvbuf_size,
|
||||
int read_high_water, int read_low_water,
|
||||
void (*on_fin)(struct tcp_conn* tc, void* arg),
|
||||
void (*on_error)(struct tcp_conn* tc, int err, void* arg),
|
||||
void* arg)
|
||||
@@ -43,7 +42,6 @@ struct tcp_conn* tcp_conn_create(
|
||||
tc->write_chunk_size = write_chunk_size;
|
||||
tc->read_high_water = read_high_water;
|
||||
tc->read_low_water = read_low_water;
|
||||
tc->rcvbuf_size = rcvbuf_size;
|
||||
tc->on_fin = on_fin;
|
||||
tc->on_error = on_error;
|
||||
tc->arg = arg;
|
||||
@@ -86,27 +84,20 @@ struct tcp_conn* tcp_conn_create(
|
||||
}
|
||||
tc->write_monitor = 1;
|
||||
|
||||
if (rcvbuf_size > 0) {
|
||||
if (socket_set_buffers(sock, 0, rcvbuf_size) < 0)
|
||||
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: socket_set_buffers rcvbuf=%d failed fd=%d", rcvbuf_size, (int)sock);
|
||||
else
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: socket_set_buffers rcvbuf=%d ok fd=%d", rcvbuf_size, (int)sock);
|
||||
}
|
||||
|
||||
{
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t alen = sizeof(addr);
|
||||
if (getpeername(sock, (struct sockaddr*)&addr, &alen) == 0) tc->connected = 1;
|
||||
}
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: fd=%d entry=%zu chunk=%zu hw=%d lw=%d rcvbuf=%d connected=%d",
|
||||
(int)sock, entry_data_size, write_chunk_size, read_high_water, read_low_water, rcvbuf_size, tc->connected);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: fd=%d entry=%zu chunk=%zu hw=%d lw=%d connected=%d",
|
||||
(int)sock, entry_data_size, write_chunk_size, read_high_water, read_low_water, tc->connected);
|
||||
return tc;
|
||||
}
|
||||
|
||||
void tcp_conn_destroy(struct tcp_conn* tc) {
|
||||
if (!tc) return;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_destroy: fd=%d connected=%d error=%d fin_remote=%d fin_local=%d closed=%d",
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_conn_destroy: fd=%d connected=%d error=%d fin_remote=%d fin_local=%d closed=%d",
|
||||
(int)tc->sock, tc->connected, tc->error, tc->fin_remote, tc->fin_local, tc->closed);
|
||||
|
||||
if (tc->socket_id) {
|
||||
@@ -137,45 +128,6 @@ void tcp_conn_destroy(struct tcp_conn* tc) {
|
||||
u_free(tc);
|
||||
}
|
||||
|
||||
static void tcp_conn_handle_error(struct tcp_conn* tc, int err)
|
||||
{
|
||||
tc->error = 1;
|
||||
|
||||
queue_waiter_cancel(tc->read_queue, &tc->read_waiter);
|
||||
queue_set_empty_callback(tc->read_queue, NULL, NULL);
|
||||
queue_set_callback(tc->read_queue, NULL, NULL);
|
||||
|
||||
if (tc->sock != SOCKET_INVALID) {
|
||||
while (1) {
|
||||
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
|
||||
if (!e) break;
|
||||
uint8_t* buf = memory_pool_alloc(tc->data_pool);
|
||||
if (!buf) { queue_entry_free(e); break; }
|
||||
ssize_t n = recv(tc->sock, buf, tc->entry_data_size, 0);
|
||||
if (n > 0) { e->dgram = buf; e->len = (uint16_t)n; queue_data_put(tc->read_queue, e); }
|
||||
else { memory_pool_free(tc->data_pool, buf); queue_entry_free(e); break; }
|
||||
}
|
||||
}
|
||||
|
||||
if (tc->socket_id) {
|
||||
uasync_remove_socket_t(tc->ua, tc->sock);
|
||||
tc->socket_id = NULL;
|
||||
}
|
||||
if (tc->sock != SOCKET_INVALID) {
|
||||
socket_close_wrapper(tc->sock);
|
||||
tc->sock = SOCKET_INVALID;
|
||||
}
|
||||
|
||||
queue_set_callback(tc->write_queue, NULL, NULL);
|
||||
|
||||
if (tc->on_error) {
|
||||
void (*on_err)(struct tcp_conn*, int, void*) = tc->on_error;
|
||||
void* arg = tc->arg;
|
||||
tc->on_error = NULL;
|
||||
on_err(tc, err, arg);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Чтение из сокета
|
||||
// ====================================================================
|
||||
@@ -210,7 +162,7 @@ static void read_cb(socket_t sock, void* arg) {
|
||||
memory_pool_free(tc->data_pool, buf);
|
||||
queue_entry_free(e);
|
||||
tc->fin_remote = 1;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: FIN fd=%d", (int)tc->sock);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_io: FIN fd=%d", (int)tc->sock);
|
||||
uasync_set_socket_read(tc->ua, tc->socket_id, 0);
|
||||
queue_waiter_cancel(tc->read_queue, &tc->read_waiter);
|
||||
if (tc->read_queue->count == 0) {
|
||||
@@ -222,8 +174,9 @@ static void read_cb(socket_t sock, void* arg) {
|
||||
memory_pool_free(tc->data_pool, buf);
|
||||
queue_entry_free(e);
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return;
|
||||
tc->error = 1;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: recv error fd=%d errno=%d", (int)tc->sock, errno);
|
||||
tcp_conn_handle_error(tc, errno);
|
||||
if (tc->on_error) tc->on_error(tc, errno, tc->arg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +198,7 @@ static void fin_deferred_cb(struct ll_queue* q, void* arg) {
|
||||
// Запись в сокет
|
||||
// ====================================================================
|
||||
|
||||
static int flush_write_buf(struct tcp_conn* tc) {
|
||||
static void flush_write_buf(struct tcp_conn* tc) {
|
||||
while (tc->write_buf && tc->write_offset < tc->write_len) {
|
||||
ssize_t n = send(tc->sock, tc->write_buf + tc->write_offset, tc->write_len - tc->write_offset, MSG_NOSIGNAL);
|
||||
if (n > 0) {
|
||||
@@ -256,16 +209,16 @@ static int flush_write_buf(struct tcp_conn* tc) {
|
||||
tc->write_len = 0;
|
||||
tc->write_offset = 0;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: flush_wbuf done fd=%d", (int)tc->sock);
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) return 0;
|
||||
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) return;
|
||||
tc->error = 1;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: send error fd=%d errno=%d", (int)tc->sock, errno);
|
||||
tcp_conn_handle_error(tc, errno);
|
||||
return 1;
|
||||
if (tc->on_error) tc->on_error(tc, errno, tc->arg);
|
||||
return;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
|
||||
@@ -274,7 +227,7 @@ static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
|
||||
if (tc->error || tc->closed) { DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch skip fd=%d error=%d closed=%d", (int)tc->sock, tc->error, tc->closed); return; }
|
||||
|
||||
if (tc->write_buf) {
|
||||
if (flush_write_buf(tc)) return;
|
||||
flush_write_buf(tc);
|
||||
if (tc->write_buf) return; // остался остаток, ждём write_cb
|
||||
}
|
||||
|
||||
@@ -296,14 +249,14 @@ static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
|
||||
|
||||
if (e->len == 0) {
|
||||
if (!e->dgram) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: CLOSE fd=%d", (int)tc->sock);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_io: CLOSE fd=%d", (int)tc->sock);
|
||||
queue_entry_free(e); queue_resume_callback(q);
|
||||
uasync_remove_socket_t(tc->ua, tc->sock);
|
||||
socket_close_wrapper(tc->sock);
|
||||
tc->socket_id = NULL; tc->sock = SOCKET_INVALID; tc->closed = 1;
|
||||
if (tc->on_closed) tc->on_closed(tc, tc->arg);
|
||||
} else if (e->dgram == &tcp_fin_sentinel) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: FIN fd=%d", (int)tc->sock);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_io: FIN fd=%d", (int)tc->sock);
|
||||
queue_entry_free(e); queue_resume_callback(q);
|
||||
shutdown(tc->sock, SHUT_WR);
|
||||
tc->fin_local = 1;
|
||||
@@ -325,9 +278,9 @@ static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
|
||||
tc->write_buf = memory_pool_alloc(tc->data_pool);
|
||||
if (!tc->write_buf) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: write_buf alloc failed fd=%d", (int)tc->sock);
|
||||
tc->error = 1;
|
||||
memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
tcp_conn_handle_error(tc, ENOMEM);
|
||||
return;
|
||||
}
|
||||
memcpy(tc->write_buf, e->dgram + n, rem);
|
||||
@@ -342,9 +295,9 @@ static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
|
||||
tc->write_buf = memory_pool_alloc(tc->data_pool);
|
||||
if (!tc->write_buf) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: write_buf alloc failed fd=%d", (int)tc->sock);
|
||||
tc->error = 1;
|
||||
memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
tcp_conn_handle_error(tc, ENOMEM);
|
||||
return;
|
||||
}
|
||||
memcpy(tc->write_buf, e->dgram, e->len);
|
||||
@@ -356,10 +309,11 @@ static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
|
||||
tc->write_monitor = 1;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch POLLOUT ON fd=%d (EAGAIN, wbuf=%zu bytes)", (int)tc->sock, tc->write_len);
|
||||
} else {
|
||||
tc->error = 1;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: send error fd=%d errno=%d", (int)tc->sock, errno);
|
||||
if (tc->on_error) tc->on_error(tc, errno, tc->arg);
|
||||
memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
tcp_conn_handle_error(tc, errno);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,15 +327,17 @@ static void write_cb(socket_t sock, void* arg) {
|
||||
socklen_t len = sizeof(err);
|
||||
if (getsockopt(tc->sock, SOL_SOCKET, SO_ERROR, &err, &len) == 0 && err == 0) {
|
||||
tc->connected = 1;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: connect ok fd=%d", (int)tc->sock);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_io: connect ok fd=%d", (int)tc->sock);
|
||||
} else {
|
||||
tc->error = 1;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: write_cb connect fail fd=%d err=%d wmon=%d (POLLOUT still active)", (int)tc->sock, err, tc->write_monitor);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: connect fail fd=%d err=%d", (int)tc->sock, err);
|
||||
tcp_conn_handle_error(tc, err ? err : -1);
|
||||
if (tc->on_error) tc->on_error(tc, err ? err : -1, tc->arg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (flush_write_buf(tc)) return;
|
||||
flush_write_buf(tc);
|
||||
if (!tc->write_buf) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: write_cb flushed fd=%d wmon=%d (resume WQ cb)", (int)tc->sock, tc->write_monitor);
|
||||
queue_resume_callback(tc->write_queue);
|
||||
@@ -396,9 +352,10 @@ static void error_cb(socket_t sock, void* arg) {
|
||||
(void)sock;
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
if (!tc || tc->sock == SOCKET_INVALID) return;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: async error fd=%d connected=%d err=%d fin_r=%d fin_l=%d closed=%d wmon=%d",
|
||||
(int)tc->sock, tc->connected, tc->error, tc->fin_remote, tc->fin_local, tc->closed, tc->write_monitor);
|
||||
tcp_conn_handle_error(tc, -1);
|
||||
tc->error = 1;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: error_cb fd=%d connected=%d fin_remote=%d wmon=%d", (int)tc->sock, tc->connected, tc->fin_remote, tc->write_monitor);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: async error fd=%d", (int)tc->sock);
|
||||
if (tc->on_error) tc->on_error(tc, -1, tc->arg);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
@@ -412,7 +369,7 @@ int tcp_conn_push_fin(struct tcp_conn* tc) {
|
||||
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
|
||||
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin entry_pool exhausted fd=%d", (int)tc->sock); return -1; }
|
||||
e->dgram = (uint8_t*)&tcp_fin_sentinel; e->len = 0;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin fd=%d wq=%d", (int)tc->sock, tc->write_queue->count + 1);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin fd=%d wq=%d", (int)tc->sock, tc->write_queue->count + 1);
|
||||
queue_data_put(tc->write_queue, e);
|
||||
return 0;
|
||||
}
|
||||
@@ -423,7 +380,7 @@ int tcp_conn_push_close(struct tcp_conn* tc) {
|
||||
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
|
||||
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close entry_pool exhausted fd=%d", (int)tc->sock); return -1; }
|
||||
e->dgram = NULL; e->len = 0;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close fd=%d wq=%d", (int)tc->sock, tc->write_queue->count + 1);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close fd=%d wq=%d", (int)tc->sock, tc->write_queue->count + 1);
|
||||
queue_data_put(tc->write_queue, e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
+38
-23
@@ -1,23 +1,40 @@
|
||||
// tcp_io.h — управление TCP-соединением через uasync + ll_queue
|
||||
//
|
||||
// Один tcp_conn = одно TCP-соединение. Два пула:
|
||||
// entry_pool — struct ll_entry (без inline data)
|
||||
// data_pool — буферы (max(entry_data_size, write_chunk_size))
|
||||
// Один tcp_conn = одно TCP-соединение. Две очереди: read_queue (сокет → данные) и
|
||||
// write_queue (данные → сокет). Обе работают через автозабор ll_queue (deferred).
|
||||
//
|
||||
// Чтение: read_cb → recv() в data_pool → entry в read_queue → callback внешнего кода.
|
||||
// high_water/low_water управляют backpressure (EPOLLIN on/off).
|
||||
// FIN (recv==0) → on_fin (сразу или через empty_callback после дренажа очереди).
|
||||
// Пул-аллокация:
|
||||
// entry_pool — только struct ll_entry (без inline data)
|
||||
// data_pool — буферы данных чтения/записи (max(entry_data_size, write_chunk_size))
|
||||
// Данные и структуры аллоцируются раздельно — не копируются при recv/send.
|
||||
//
|
||||
// Запись: внешний код кладёт entry в write_queue через queue_data_put().
|
||||
// write_queue_fetch_cb (deferred) → send(), при EAGAIN → write_buf + EPOLLOUT.
|
||||
// Всё отправлено → on_flushed.
|
||||
// Чтение:
|
||||
// read_cb → memory_pool_alloc(read_pool) → recv() прямо в буфер → entry в read_queue
|
||||
// read_queue автозабор(deferred) → внешний коллбэк (например, отправка в ETCP)
|
||||
// high_water → пауза чтения (убираем EPOLLIN), low_water/waiter → возобновление
|
||||
// FIN: если read_queue не пуст → empty_callback откладывает on_fin; иначе сразу
|
||||
//
|
||||
// FIN/Close: tcp_conn_push_fin/close ставят сентинел в write_queue.
|
||||
// FIN → shutdown(SHUT_WR) → on_fin_sent
|
||||
// CLOSE → close сокета → on_closed
|
||||
// Запись:
|
||||
// Внешний код: tcp_conn_push_write(data, len) → аллокация через write_pool → entry в write_queue
|
||||
// write_queue автозабор(deferred): fetch_cb → send() в сокет
|
||||
// EAGAIN → write_buf (из data_pool) + EPOLLOUT ON → write_cb досылает → resume автозабора
|
||||
// Всё отправлено → EPOLLOUT OFF + on_flushed (если установлен)
|
||||
//
|
||||
// Ошибка: любая ошибка сокета → tcp_conn_handle_error → close + null коллбэков → on_error.
|
||||
// Реализация on_error обязана вызвать tcp_conn_destroy. После возврата tc недействителен.
|
||||
// FIN / Close через очередь отправки:
|
||||
// tcp_conn_push_fin(tc) — ставит FIN в write_queue (dgram=&sentinel, len=0)
|
||||
// tcp_conn_push_close(tc) — ставит CLOSE в write_queue (dgram=NULL, len=0)
|
||||
// Оба сигнала обрабатываются после всех предшествующих данных в очереди:
|
||||
// FIN → shutdown(SHUT_WR), стоп чтения, on_fin_sent
|
||||
// CLOSE → close сокета, on_closed
|
||||
// После FIN входящие данные отбрасываются (read_cb → discard).
|
||||
// После CLOSE сокет закрыт, tc->closed=1, дальнейшая отправка невозможна.
|
||||
//
|
||||
// Connect:
|
||||
// tcp_conn_create регистрирует сокет с read_cb + write_cb в uasync
|
||||
// После create вызывается connect() (неблокирующий, EINPROGRESS)
|
||||
// write_cb детектит завершение connect через getsockopt(SO_ERROR)
|
||||
// getpeername() сразу после create проверяет pre-connected сокеты (socketpair)
|
||||
// До connect данные копятся в write_queue (автозабор не пытается send на unconnected сокет)
|
||||
|
||||
#ifndef TCP_IO_H
|
||||
#define TCP_IO_H
|
||||
@@ -37,7 +54,6 @@ struct tcp_conn {
|
||||
|
||||
int read_high_water;
|
||||
int read_low_water;
|
||||
int rcvbuf_size;
|
||||
uint8_t read_paused;
|
||||
|
||||
uint8_t write_monitor; // 1 = EPOLLOUT активен
|
||||
@@ -47,7 +63,7 @@ struct tcp_conn {
|
||||
uint8_t fin_local; // FIN отправлен удалённой стороне (shutdown SHUT_WR)
|
||||
uint8_t closed; // сокет полностью закрыт (close)
|
||||
|
||||
// Частичная отправка (из data_pool, не в очереди — досылается первой)
|
||||
// Частичная отправка (из write_pool, не в очереди — досылается первой)
|
||||
uint8_t* write_buf;
|
||||
size_t write_len;
|
||||
size_t write_offset;
|
||||
@@ -63,7 +79,7 @@ struct tcp_conn {
|
||||
// Коллбэки
|
||||
void (*on_fin)(struct tcp_conn* tc, void* arg); // FIN получен от удалённой стороны (fin_remote=1)
|
||||
void (*on_fin_sent)(struct tcp_conn* tc, void* arg); // FIN отправлен удалённой стороне (fin_local=1)
|
||||
void (*on_error)(struct tcp_conn* tc, int err, void* arg); // фатальная ошибка (вызывается после close сокета; обязан вызвать tcp_conn_destroy)
|
||||
void (*on_error)(struct tcp_conn* tc, int err, void* arg); // ошибка сокета
|
||||
void (*on_flushed)(struct tcp_conn* tc, void* arg); // все данные записи отправлены (write_queue + write_buf пусты)
|
||||
void (*on_closed)(struct tcp_conn* tc, void* arg); // сокет закрыт через очередь (после close)
|
||||
void* arg;
|
||||
@@ -76,22 +92,21 @@ struct tcp_conn* tcp_conn_create(
|
||||
size_t write_chunk_size,
|
||||
int read_high_water,
|
||||
int read_low_water,
|
||||
int rcvbuf_size,
|
||||
void (*on_fin)(struct tcp_conn* tc, void* arg),
|
||||
void (*on_error)(struct tcp_conn* tc, int err, void* arg),
|
||||
void* arg);
|
||||
|
||||
// Дренирует очереди, закрывает сокет, уничтожает пулы, освобождает tc.
|
||||
void tcp_conn_destroy(struct tcp_conn* tc);
|
||||
|
||||
// Запись: queue_data_put(tc->write_queue, entry) напрямую.
|
||||
// Автозабор (deferred) сам шлёт когда сокет готов.
|
||||
// Порог: 32 записи, при заполнении queue_waiter_wait.
|
||||
// Внешний код пишет данные в tc->write_queue напрямую (queue_data_put).
|
||||
// Автозабор write_queue (deferred) сам отправляет когда сокет готов.
|
||||
// Перед push проверять порог: queue_set_threshold в tcp_conn_create (32 entries).
|
||||
// При заполнении — queue_waiter_wait на освобождение.
|
||||
|
||||
// Поставить сигналы в очередь отправки. Все данные в очереди перед сигналом
|
||||
// будут отправлены до его обработки. Кодирование: dgram=NULL → close, len=0+dgram!=NULL → FIN.
|
||||
// tcp_conn_push_fin(tc) — FIN: после отправки предшествующих данных вызывает
|
||||
// shutdown(SHUT_WR) и on_fin_sent.
|
||||
// shutdown(SHUT_WR), останавливает чтение, вызывает on_fin_sent.
|
||||
// Повторный вызов игнорируется (fin_sent уже установлен).
|
||||
// tcp_conn_push_close(tc) — CLOSE: после отправки предшествующих данных закрывает
|
||||
// сокет (close), вызывает on_closed.
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
// timeout_heap.c
|
||||
|
||||
#include "timeout_heap.h"
|
||||
#include "debug_config.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h> // For potential error printing, optional
|
||||
#include "mem.h"
|
||||
|
||||
// Helper macros for 1-based indices
|
||||
#define PARENT(i) ((i) / 2)
|
||||
#define LEFT_CHILD(i) (2 * (i))
|
||||
#define RIGHT_CHILD(i) (2 * (i) + 1)
|
||||
|
||||
TimeoutHeap *timeout_heap_create(size_t initial_capacity) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH1...");
|
||||
TimeoutHeap *h = u_malloc(sizeof(TimeoutHeap));
|
||||
if (!h) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "TH0 error...");
|
||||
return NULL;
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH2...");
|
||||
h->heap = u_malloc(sizeof(TimeoutEntry) * initial_capacity);
|
||||
if (!h->heap) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "TH1 error...");
|
||||
u_free(h);
|
||||
return NULL;
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH3...");
|
||||
h->size = 0;
|
||||
h->capacity = initial_capacity;
|
||||
h->freed_count = 0;
|
||||
h->user_data = NULL;
|
||||
h->free_callback = NULL;
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
void timeout_heap_destroy(TimeoutHeap *h) {
|
||||
if (!h) return;
|
||||
|
||||
// Free all remaining data (deleted or not)
|
||||
for (size_t i = 0; i < h->size; i++) {
|
||||
if (h->free_callback) {
|
||||
h->free_callback(h->user_data, h->heap[i].data);
|
||||
}
|
||||
}
|
||||
|
||||
u_free(h->heap);
|
||||
u_free(h);
|
||||
}
|
||||
|
||||
|
||||
static void update_index(TimeoutHeap *h, size_t idx) {
|
||||
if (h->heap[idx].index_ptr)
|
||||
*h->heap[idx].index_ptr = idx;
|
||||
}
|
||||
|
||||
static void swap_entries(TimeoutHeap *h, size_t a, size_t b) {
|
||||
TimeoutEntry temp = h->heap[a];
|
||||
h->heap[a] = h->heap[b];
|
||||
h->heap[b] = temp;
|
||||
update_index(h, a);
|
||||
update_index(h, b);
|
||||
}
|
||||
|
||||
static void bubble_up(TimeoutHeap *h, size_t i) {
|
||||
// i is 1-based
|
||||
while (i > 1 && h->heap[PARENT(i) - 1].expiration > h->heap[i - 1].expiration) {
|
||||
swap_entries(h, PARENT(i) - 1, i - 1);
|
||||
i = PARENT(i);
|
||||
}
|
||||
}
|
||||
|
||||
int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data, size_t *index_ptr) {
|
||||
if (h->size == h->capacity) {
|
||||
size_t new_cap = h->capacity ? h->capacity * 2 : 1;
|
||||
TimeoutEntry *new_heap = u_realloc(h->heap, sizeof(TimeoutEntry) * new_cap);
|
||||
if (!new_heap) return -1; // Allocation failed
|
||||
h->heap = new_heap;
|
||||
h->capacity = new_cap;
|
||||
}
|
||||
|
||||
// Insert at end (0-based)
|
||||
size_t idx = h->size++;
|
||||
h->heap[idx].expiration = expiration;
|
||||
h->heap[idx].data = data;
|
||||
h->heap[idx].index_ptr = index_ptr;
|
||||
h->heap[idx].deleted = 0;
|
||||
if (index_ptr)
|
||||
*index_ptr = idx;
|
||||
|
||||
// Bubble up (1-based)
|
||||
bubble_up(h, idx + 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void heapify_down(TimeoutHeap *h, size_t i) {
|
||||
// i is 1-based
|
||||
while (1) {
|
||||
size_t smallest = i;
|
||||
size_t left = LEFT_CHILD(i);
|
||||
size_t right = RIGHT_CHILD(i);
|
||||
|
||||
if (left <= h->size && h->heap[left - 1].expiration < h->heap[smallest - 1].expiration) {
|
||||
smallest = left;
|
||||
}
|
||||
if (right <= h->size && h->heap[right - 1].expiration < h->heap[smallest - 1].expiration) {
|
||||
smallest = right;
|
||||
}
|
||||
if (smallest == i) break;
|
||||
|
||||
swap_entries(h, smallest - 1, i - 1);
|
||||
i = smallest;
|
||||
}
|
||||
}
|
||||
|
||||
static void remove_root(TimeoutHeap *h) {
|
||||
if (h->size == 0) return;
|
||||
|
||||
// Move last to root
|
||||
h->heap[0] = h->heap[--h->size];
|
||||
update_index(h, 0);
|
||||
|
||||
// Heapify down (1-based)
|
||||
if (h->size > 0) {
|
||||
heapify_down(h, 1);
|
||||
}
|
||||
}
|
||||
|
||||
int timeout_heap_peek(TimeoutHeap *h, TimeoutEntry *out) {
|
||||
if (h->size == 0) return -1;
|
||||
|
||||
while (h->size > 0 && h->heap[0].deleted) {
|
||||
void* data_to_free = h->heap[0].data;
|
||||
remove_root(h);
|
||||
if (h->free_callback) {
|
||||
h->free_callback(h->user_data, data_to_free);
|
||||
}
|
||||
}
|
||||
if (h->size == 0) return -1;
|
||||
|
||||
*out = h->heap[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out) {
|
||||
if (h->size == 0) return -1;
|
||||
|
||||
while (h->size > 0 && h->heap[0].deleted) {
|
||||
void* data_to_free = h->heap[0].data;
|
||||
remove_root(h);
|
||||
if (h->free_callback) {
|
||||
h->free_callback(h->user_data, data_to_free);
|
||||
}
|
||||
}
|
||||
if (h->size == 0) return -1;
|
||||
|
||||
*out = h->heap[0];
|
||||
remove_root(h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data) {
|
||||
for (size_t i = 0; i < h->size; ++i) {
|
||||
if (h->heap[i].expiration == expiration && h->heap[i].data == data) {
|
||||
h->heap[i].deleted = 1;
|
||||
h->heap[i].expiration = 0;
|
||||
bubble_up(h, i + 1);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1; // Not found
|
||||
}
|
||||
|
||||
int timeout_heap_cancel_at(TimeoutHeap *h, size_t index, void *data) {
|
||||
if (index >= h->size || h->heap[index].data != data) return -1;
|
||||
|
||||
h->heap[index].deleted = 1;
|
||||
h->heap[index].expiration = 0;
|
||||
bubble_up(h, index + 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void timeout_heap_set_free_callback(TimeoutHeap *h, void* user_data, void (*callback)(void* user_data, void* data)) {
|
||||
if (!h) return;
|
||||
h->user_data = user_data;
|
||||
h->free_callback = callback;
|
||||
}
|
||||
|
||||
size_t timeout_heap_get_size(TimeoutHeap *h) {
|
||||
if (!h) return 0;
|
||||
return h->size;
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
// timeout_heap.h
|
||||
|
||||
#ifndef TIMEOUT_HEAP_H
|
||||
#define TIMEOUT_HEAP_H
|
||||
|
||||
#include <stdint.h> // For uint64_t
|
||||
#include <stddef.h> // For size_t
|
||||
|
||||
typedef uint64_t TimeoutTime; // e.g., milliseconds since epoch or from now
|
||||
|
||||
typedef struct {
|
||||
TimeoutTime expiration; // Sort key (smaller = earlier)
|
||||
void *data; // User data (e.g., callback or ID)
|
||||
size_t *index_ptr; // Pointer to node's heap_index (NULL = no tracking)
|
||||
int deleted; // 0 = active, 1 = deleted
|
||||
} TimeoutEntry;
|
||||
|
||||
typedef struct TimeoutHeap TimeoutHeap;
|
||||
|
||||
struct TimeoutHeap {
|
||||
TimeoutEntry *heap; // Dynamic array
|
||||
size_t size; // Current number of elements
|
||||
size_t capacity; // Allocated size
|
||||
size_t freed_count; // Number of freed timer nodes
|
||||
void* user_data; // User data for free callback
|
||||
void (*free_callback)(void* user_data, void* data); // Callback to free data
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new timeout heap with initial capacity.
|
||||
* @param initial_capacity Starting capacity (will grow as needed).
|
||||
* @return Pointer to the heap, or NULL on failure.
|
||||
*/
|
||||
TimeoutHeap *timeout_heap_create(size_t initial_capacity);
|
||||
|
||||
/**
|
||||
* Destroy the timeout heap and free resources.
|
||||
* @param h The heap to destroy.
|
||||
*/
|
||||
void timeout_heap_destroy(TimeoutHeap *h);
|
||||
|
||||
/**
|
||||
* Set a callback function to free data when deleted nodes are removed.
|
||||
* @param h The heap.
|
||||
* @param user_data User data passed to callback.
|
||||
* @param callback Callback function (if NULL, data is freed with free()).
|
||||
*/
|
||||
void timeout_heap_set_free_callback(TimeoutHeap *h, void* user_data, void (*callback)(void* user_data, void* data));
|
||||
|
||||
/**
|
||||
* Insert a new timeout into the heap.
|
||||
* @param h The heap.
|
||||
* @param expiration The expiration time.
|
||||
* @param data User data associated with the timeout.
|
||||
* @return 0 on success, -1 on allocation failure.
|
||||
*/
|
||||
int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data, size_t *index_ptr);
|
||||
|
||||
/**
|
||||
* Peek at the earliest non-deleted timeout without removing it.
|
||||
* @param h The heap.
|
||||
* @param out Where to store the entry.
|
||||
* @return 0 on success, -1 if empty.
|
||||
*/
|
||||
int timeout_heap_peek(TimeoutHeap *h, TimeoutEntry *out);
|
||||
|
||||
/**
|
||||
* Pop the earliest non-deleted timeout from the heap.
|
||||
* @param h The heap.
|
||||
* @param out Where to store the entry.
|
||||
* @return 0 on success, -1 if empty.
|
||||
*/
|
||||
int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out);
|
||||
|
||||
/**
|
||||
* Cancel a timeout by matching expiration and data.
|
||||
* Scans the heap linearly, so O(n) time.
|
||||
* Assumes combinations are unique; cancels the first match.
|
||||
* @param h The heap.
|
||||
* @param expiration The expiration time to match.
|
||||
* @param data The data to match.
|
||||
* @return 0 if found and canceled, -1 if not found.
|
||||
*/
|
||||
int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data);
|
||||
|
||||
int timeout_heap_cancel_at(TimeoutHeap *h, size_t index, void *data);
|
||||
|
||||
/**
|
||||
* Get the number of freed timer nodes.
|
||||
* @param h The heap.
|
||||
* @return Count of freed timer nodes.
|
||||
*/
|
||||
size_t timeout_heap_get_freed_count(TimeoutHeap *h);
|
||||
|
||||
size_t timeout_heap_get_size(TimeoutHeap *h);
|
||||
|
||||
#endif // TIMEOUT_HEAP_H
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
// twheel.c — Tickless hierarchical timing wheel (adapted from timeout.c)
|
||||
// Original: Copyright (c) 2013-2014 William Ahern, MIT license.
|
||||
|
||||
#include <limits.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/queue.h>
|
||||
|
||||
#include "twheel.h"
|
||||
#include "mem.h"
|
||||
#include "debug_config.h"
|
||||
|
||||
#define WHEEL_BIT 6
|
||||
#define WHEEL_NUM 4
|
||||
|
||||
#define WHEEL_LEN (1U << WHEEL_BIT)
|
||||
#define WHEEL_MAX (WHEEL_LEN - 1)
|
||||
#define WHEEL_MASK (WHEEL_LEN - 1)
|
||||
|
||||
#include "twheel_bitops.c"
|
||||
|
||||
#define ctz(n) ctz64(n)
|
||||
#define clz(n) clz64(n)
|
||||
#define fls(n) ((int)(64 - clz64(n)))
|
||||
|
||||
typedef uint64_t wheel_t;
|
||||
#define WHEEL_C(n) UINT64_C(n)
|
||||
|
||||
#define countof(a) (sizeof(a) / sizeof *(a))
|
||||
|
||||
#ifndef TAILQ_CONCAT
|
||||
#define TAILQ_CONCAT(head1, head2, field) do { \
|
||||
if (!TAILQ_EMPTY(head2)) { \
|
||||
*(head1)->tqh_last = (head2)->tqh_first; \
|
||||
(head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
|
||||
(head1)->tqh_last = (head2)->tqh_last; \
|
||||
TAILQ_INIT((head2)); \
|
||||
} \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#ifndef TAILQ_FOREACH_SAFE
|
||||
#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
|
||||
for ((var) = TAILQ_FIRST(head); \
|
||||
(var) && ((tvar) = TAILQ_NEXT(var, field), 1); \
|
||||
(var) = (tvar))
|
||||
#endif
|
||||
|
||||
#ifndef MAX
|
||||
#define MAX(a,b) (((a)>(b))?(a):(b))
|
||||
#endif
|
||||
#ifndef MIN
|
||||
#define MIN(a,b) (((a)<(b))?(a):(b))
|
||||
#endif
|
||||
|
||||
static inline wheel_t rotl(const wheel_t v, int c) {
|
||||
if (!(c &= (sizeof v * CHAR_BIT - 1))) return v;
|
||||
return (v << c) | (v >> (sizeof v * CHAR_BIT - c));
|
||||
}
|
||||
|
||||
static inline wheel_t rotr(const wheel_t v, int c) {
|
||||
if (!(c &= (sizeof v * CHAR_BIT - 1))) return v;
|
||||
return (v >> c) | (v << (sizeof v * CHAR_BIT - c));
|
||||
}
|
||||
|
||||
static struct twheels *twheels_init(struct twheels *T, twheel_t hz) {
|
||||
for (unsigned i = 0; i < WHEEL_NUM; i++) {
|
||||
for (unsigned j = 0; j < WHEEL_LEN; j++) {
|
||||
TAILQ_INIT(&T->wheel[i][j]);
|
||||
}
|
||||
}
|
||||
TAILQ_INIT(&T->expired);
|
||||
for (unsigned i = 0; i < WHEEL_NUM; i++) T->pending[i] = 0;
|
||||
T->curtime = 0;
|
||||
T->hertz = hz ? hz : TWHEEL_mHZ;
|
||||
return T;
|
||||
}
|
||||
|
||||
struct twheels *twheels_open(twheel_t hz) {
|
||||
struct twheels *T = u_malloc(sizeof *T);
|
||||
if (!T) { DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "twheels_open: malloc failed"); return NULL; }
|
||||
return twheels_init(T, hz);
|
||||
}
|
||||
|
||||
void twheels_close(struct twheels *T) {
|
||||
if (!T) return;
|
||||
struct twheel_list reset;
|
||||
TAILQ_INIT(&reset);
|
||||
for (unsigned i = 0; i < WHEEL_NUM; i++)
|
||||
for (unsigned j = 0; j < WHEEL_LEN; j++)
|
||||
TAILQ_CONCAT(&reset, &T->wheel[i][j], tqe);
|
||||
TAILQ_CONCAT(&reset, &T->expired, tqe);
|
||||
struct twheel *to;
|
||||
TAILQ_FOREACH(to, &reset, tqe) to->pending = NULL;
|
||||
u_free(T);
|
||||
}
|
||||
|
||||
twheel_t twheels_hz(struct twheels *T) { return T->hertz; }
|
||||
|
||||
void twheels_del(struct twheels *T, struct twheel *to) {
|
||||
if (to->pending) {
|
||||
TAILQ_REMOVE(to->pending, to, tqe);
|
||||
if (to->pending != &T->expired && TAILQ_EMPTY(to->pending)) {
|
||||
ptrdiff_t index = to->pending - &T->wheel[0][0];
|
||||
int w = index / WHEEL_LEN, slot = index % WHEEL_LEN;
|
||||
T->pending[w] &= ~(WHEEL_C(1) << slot);
|
||||
}
|
||||
to->pending = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static inline int timeout_wheel(twheel_t timeout) {
|
||||
return (fls(MIN(timeout, TWHEEL_MAX)) - 1) / WHEEL_BIT;
|
||||
}
|
||||
|
||||
static inline int timeout_slot(int wheel, twheel_t expires) {
|
||||
return WHEEL_MASK & ((expires >> (wheel * WHEEL_BIT)) - !!wheel);
|
||||
}
|
||||
|
||||
static void twheels_sched(struct twheels *T, struct twheel *to, twheel_t expires) {
|
||||
twheels_del(T, to);
|
||||
to->expires = expires;
|
||||
if (expires > T->curtime) {
|
||||
twheel_t rem = expires - T->curtime;
|
||||
int wheel = timeout_wheel(rem);
|
||||
int slot = timeout_slot(wheel, to->expires);
|
||||
to->pending = &T->wheel[wheel][slot];
|
||||
TAILQ_INSERT_TAIL(to->pending, to, tqe);
|
||||
T->pending[wheel] |= WHEEL_C(1) << slot;
|
||||
} else {
|
||||
to->pending = &T->expired;
|
||||
TAILQ_INSERT_TAIL(to->pending, to, tqe);
|
||||
}
|
||||
}
|
||||
|
||||
void twheels_add(struct twheels *T, struct twheel *to, twheel_t timeout) {
|
||||
if (to->flags & TWHEEL_INT) to->interval = MAX(1, timeout);
|
||||
if (to->flags & TWHEEL_ABS) twheels_sched(T, to, timeout);
|
||||
else twheels_sched(T, to, T->curtime + timeout);
|
||||
}
|
||||
|
||||
void twheels_update(struct twheels *T, twheel_t curtime) {
|
||||
twheel_t elapsed = curtime - T->curtime;
|
||||
struct twheel_list todo;
|
||||
TAILQ_INIT(&todo);
|
||||
|
||||
for (int wheel = 0; wheel < WHEEL_NUM; wheel++) {
|
||||
wheel_t pending;
|
||||
if ((elapsed >> (wheel * WHEEL_BIT)) > WHEEL_MAX) {
|
||||
pending = (wheel_t)~WHEEL_C(0);
|
||||
} else {
|
||||
wheel_t _elapsed = WHEEL_MASK & (elapsed >> (wheel * WHEEL_BIT));
|
||||
int oslot = WHEEL_MASK & (T->curtime >> (wheel * WHEEL_BIT));
|
||||
pending = rotl(((UINT64_C(1) << _elapsed) - 1), oslot);
|
||||
int nslot = WHEEL_MASK & (curtime >> (wheel * WHEEL_BIT));
|
||||
pending |= rotr(rotl(((WHEEL_C(1) << _elapsed) - 1), nslot), _elapsed);
|
||||
pending |= WHEEL_C(1) << nslot;
|
||||
}
|
||||
while (pending & T->pending[wheel]) {
|
||||
int slot = ctz(pending & T->pending[wheel]);
|
||||
TAILQ_CONCAT(&todo, &T->wheel[wheel][slot], tqe);
|
||||
T->pending[wheel] &= ~(UINT64_C(1) << slot);
|
||||
}
|
||||
if (!(0x1 & pending)) break;
|
||||
elapsed = MAX(elapsed, (WHEEL_LEN << (wheel * WHEEL_BIT)));
|
||||
}
|
||||
T->curtime = curtime;
|
||||
|
||||
struct twheel *to;
|
||||
while (!TAILQ_EMPTY(&todo)) {
|
||||
to = TAILQ_FIRST(&todo);
|
||||
TAILQ_REMOVE(&todo, to, tqe);
|
||||
to->pending = NULL;
|
||||
twheels_sched(T, to, to->expires);
|
||||
}
|
||||
}
|
||||
|
||||
bool twheels_pending(struct twheels *T) {
|
||||
wheel_t pending = 0;
|
||||
for (int wheel = 0; wheel < WHEEL_NUM; wheel++) pending |= T->pending[wheel];
|
||||
return !!pending;
|
||||
}
|
||||
|
||||
bool twheels_expired(struct twheels *T) { return !TAILQ_EMPTY(&T->expired); }
|
||||
|
||||
static twheel_t twheels_int(struct twheels *T) {
|
||||
twheel_t timeout = ~TWHEEL_C(0), _timeout, relmask = 0;
|
||||
for (int wheel = 0; wheel < WHEEL_NUM; wheel++) {
|
||||
if (T->pending[wheel]) {
|
||||
int slot = WHEEL_MASK & (T->curtime >> (wheel * WHEEL_BIT));
|
||||
_timeout = (ctz(rotr(T->pending[wheel], slot)) + !!wheel) << (wheel * WHEEL_BIT);
|
||||
_timeout -= relmask & T->curtime;
|
||||
timeout = MIN(_timeout, timeout);
|
||||
}
|
||||
relmask <<= WHEEL_BIT;
|
||||
relmask |= WHEEL_MASK;
|
||||
}
|
||||
return timeout;
|
||||
}
|
||||
|
||||
twheel_t twheels_timeout(struct twheels *T) {
|
||||
if (!TAILQ_EMPTY(&T->expired)) return 0;
|
||||
return twheels_int(T);
|
||||
}
|
||||
|
||||
struct twheel *twheels_get(struct twheels *T) {
|
||||
if (!TAILQ_EMPTY(&T->expired)) {
|
||||
struct twheel *to = TAILQ_FIRST(&T->expired);
|
||||
TAILQ_REMOVE(&T->expired, to, tqe);
|
||||
to->pending = NULL;
|
||||
return to;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct twheel *twheel_init(struct twheel *to, int flags) {
|
||||
memset(to, 0, sizeof *to);
|
||||
to->flags = flags;
|
||||
return to;
|
||||
}
|
||||
|
||||
bool twheel_pending(struct twheel *to) { return to->pending != NULL; }
|
||||
@@ -0,0 +1,57 @@
|
||||
// twheel.h — Tickless hierarchical timing wheel (adapted from timeout.h)
|
||||
// Original: Copyright (c) 2013-2014 William Ahern, MIT license.
|
||||
|
||||
#ifndef TWHEEL_H
|
||||
#define TWHEEL_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/queue.h>
|
||||
|
||||
#define TWHEEL_VERSION 0x20160226
|
||||
|
||||
typedef uint64_t twheel_t;
|
||||
#define TWHEEL_C(n) UINT64_C(n)
|
||||
#define TWHEEL_mHZ TWHEEL_C(1000)
|
||||
#define TWHEEL_MAX ((TWHEEL_C(1) << 24) - 1)
|
||||
|
||||
enum twheel_flags {
|
||||
TWHEEL_INT = 0x01,
|
||||
TWHEEL_ABS = 0x02,
|
||||
};
|
||||
|
||||
#define TWHEEL_INITIALIZER(flags) { (flags) }
|
||||
|
||||
struct twheel {
|
||||
int flags;
|
||||
twheel_t expires;
|
||||
struct twheel_list *pending;
|
||||
TAILQ_ENTRY(twheel) tqe;
|
||||
twheel_t interval;
|
||||
};
|
||||
|
||||
TAILQ_HEAD(twheel_list, twheel);
|
||||
|
||||
struct twheels {
|
||||
struct twheel_list wheel[4][64], expired;
|
||||
uint64_t pending[4];
|
||||
twheel_t curtime;
|
||||
twheel_t hertz;
|
||||
};
|
||||
|
||||
struct twheel *twheel_init(struct twheel *, int flags);
|
||||
bool twheel_pending(struct twheel *);
|
||||
|
||||
struct twheels *twheels_open(twheel_t hz);
|
||||
void twheels_close(struct twheels *);
|
||||
twheel_t twheels_hz(struct twheels *);
|
||||
void twheels_update(struct twheels *, twheel_t curtime);
|
||||
twheel_t twheels_timeout(struct twheels *);
|
||||
void twheels_add(struct twheels *, struct twheel *, twheel_t timeout);
|
||||
void twheels_del(struct twheels *, struct twheel *);
|
||||
struct twheel *twheels_get(struct twheels *);
|
||||
bool twheels_pending(struct twheels *);
|
||||
bool twheels_expired(struct twheels *);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,35 @@
|
||||
// twheel_bitops.c — ctz/clz for timing wheel (included by twheel.c)
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(__GNUC__) && !defined(TWHEEL_DISABLE_GNUC_BITOPS)
|
||||
#define ctz64(n) __builtin_ctzll(n)
|
||||
#define clz64(n) __builtin_clzll(n)
|
||||
#if LONG_BITS == 32
|
||||
#define ctz32(n) __builtin_ctzl(n)
|
||||
#define clz32(n) __builtin_clzl(n)
|
||||
#else
|
||||
#define ctz32(n) __builtin_ctz(n)
|
||||
#define clz32(n) __builtin_clz(n)
|
||||
#endif
|
||||
#elif defined(_MSC_VER) && !defined(TWHEEL_DISABLE_MSVC_BITOPS)
|
||||
#include <intrin.h>
|
||||
static __inline int ctz32(unsigned long val) { DWORD z=0; _BitScanForward(&z,val); return z; }
|
||||
static __inline int clz32(unsigned long val) { DWORD z=0; _BitScanReverse(&z,val); return z; }
|
||||
#ifdef _WIN64
|
||||
static __inline int ctz64(uint64_t val) { DWORD z=0; _BitScanForward64(&z,val); return z; }
|
||||
static __inline int clz64(uint64_t val) { DWORD z=0; _BitScanReverse64(&z,val); return z; }
|
||||
#else
|
||||
static __inline int ctz64(uint64_t val) { uint32_t lo=(uint32_t)val,hi=(uint32_t)(val>>32); return lo?ctz32(lo):32+ctz32(hi); }
|
||||
static __inline int clz64(uint64_t val) { uint32_t lo=(uint32_t)val,hi=(uint32_t)(val>>32); return hi?clz32(hi):32+clz32(lo); }
|
||||
#endif
|
||||
#else
|
||||
#define process_(one, cz_bits, bits) if (x < ( one << (cz_bits - bits))) { rv += bits; x <<= bits; }
|
||||
static inline int clz64(uint64_t x) { int rv=0; process_(UINT64_C(1),64,32); process_(UINT64_C(1),64,16); process_(UINT64_C(1),64,8); process_(UINT64_C(1),64,4); process_(UINT64_C(1),64,2); process_(UINT64_C(1),64,1); return rv; }
|
||||
static inline int clz32(uint32_t x) { int rv=0; process_(UINT32_C(1),32,16); process_(UINT32_C(1),32,8); process_(UINT32_C(1),32,4); process_(UINT32_C(1),32,2); process_(UINT32_C(1),32,1); return rv; }
|
||||
#undef process_
|
||||
#define process_(one, bits) if ((x & ((one << (bits))-1)) == 0) { rv += bits; x >>= bits; }
|
||||
static inline int ctz64(uint64_t x) { int rv=0; process_(UINT64_C(1),32); process_(UINT64_C(1),16); process_(UINT64_C(1),8); process_(UINT64_C(1),4); process_(UINT64_C(1),2); process_(UINT64_C(1),1); return rv; }
|
||||
static inline int ctz32(uint32_t x) { int rv=0; process_(UINT32_C(1),16); process_(UINT32_C(1),8); process_(UINT32_C(1),4); process_(UINT32_C(1),2); process_(UINT32_C(1),1); return rv; }
|
||||
#undef process_
|
||||
#endif
|
||||
+200
-386
@@ -4,7 +4,8 @@
|
||||
#include "platform_compat.h"
|
||||
#include "debug_config.h"
|
||||
#include "mem.h"
|
||||
#include "memory_pool.h"
|
||||
#include "memory_pool.h"
|
||||
#include "twheel.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
@@ -32,13 +33,12 @@
|
||||
|
||||
// Timeout node with safe cancellation
|
||||
struct timeout_node {
|
||||
struct twheel to; // MUST be first — embedded timing wheel entry
|
||||
char name[16];
|
||||
void* arg;
|
||||
timeout_callback_t callback;
|
||||
uint64_t expiration_ms; // absolute expiration time in milliseconds
|
||||
struct UASYNC* ua; // Pointer back to uasync instance for counter updates
|
||||
struct timeout_node* next; // For immediate queue (FIFO)
|
||||
size_t heap_index; // Position in timeout_heap (SIZE_MAX if not in heap)
|
||||
};
|
||||
|
||||
// Socket node with array-based storage
|
||||
@@ -287,19 +287,52 @@ static struct socket_node* socket_array_get_by_sock(struct socket_array* sa, soc
|
||||
if (index == -1 || !sa->sockets[index].active) return NULL;
|
||||
if (sa->sockets[index].type != SOCKET_NODE_TYPE_SOCK) return NULL;
|
||||
|
||||
return &sa->sockets[index];
|
||||
}
|
||||
|
||||
// Callback to u_free timeout node and update counters
|
||||
static void timeout_node_free_callback(void* user_data, void* data) {
|
||||
struct UASYNC* ua = (struct UASYNC*)user_data;
|
||||
struct timeout_node* node = (struct timeout_node*)data;
|
||||
(void)node; // Not used directly, but keep for consistency
|
||||
ua->timer_free_count++;
|
||||
memory_pool_free(ua->timeout_pool, data);
|
||||
}
|
||||
|
||||
// Helper to get current time
|
||||
return &sa->sockets[index];
|
||||
}
|
||||
|
||||
// ------ socket_node dispatch helpers (eliminate FD/SOCK type duplication) ------
|
||||
|
||||
static inline int socket_node_fd(const struct socket_node* n) {
|
||||
if (n->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
#ifdef _WIN32
|
||||
return (int)(intptr_t)n->sock;
|
||||
#else
|
||||
return n->sock;
|
||||
#endif
|
||||
}
|
||||
return n->fd;
|
||||
}
|
||||
|
||||
static inline void socket_node_dispatch_read(const struct socket_node* n) {
|
||||
if (n->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (n->read_cbk_sock) n->read_cbk_sock(n->sock, n->user_data);
|
||||
} else {
|
||||
if (n->read_cbk) n->read_cbk(n->fd, n->user_data);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void socket_node_dispatch_write(const struct socket_node* n) {
|
||||
if (n->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (n->write_cbk_sock) n->write_cbk_sock(n->sock, n->user_data);
|
||||
} else {
|
||||
if (n->write_cbk) n->write_cbk(n->fd, n->user_data);
|
||||
}
|
||||
}
|
||||
|
||||
static inline short socket_node_events(const struct socket_node* n) {
|
||||
short events = 0;
|
||||
if (n->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (n->read_cbk_sock && n->enable_read) events |= POLLIN;
|
||||
if (n->write_cbk_sock && n->enable_write) events |= POLLOUT;
|
||||
} else {
|
||||
if (n->read_cbk && n->enable_read) events |= POLLIN;
|
||||
if (n->write_cbk && n->enable_write) events |= POLLOUT;
|
||||
}
|
||||
if (n->except_cbk) events |= POLLPRI;
|
||||
return events;
|
||||
}
|
||||
|
||||
// Simplified timeout handling without reference counting
|
||||
static void get_current_time(struct timeval* tv) {
|
||||
#ifdef _WIN32
|
||||
// Для Windows используем GetTickCount или другие механизмы
|
||||
@@ -358,17 +391,6 @@ uint64_t get_time_us(void) {
|
||||
|
||||
|
||||
|
||||
// Drain wakeup pipe - read all available bytes
|
||||
static void drain_wakeup_pipe(struct UASYNC* ua) {
|
||||
if (!ua || !ua->wakeup_initialized) return;
|
||||
|
||||
char buf[64];
|
||||
while (1) {
|
||||
ssize_t n = read(ua->wakeup_pipe[0], buf, sizeof(buf));
|
||||
if (n <= 0) break;
|
||||
}
|
||||
}
|
||||
|
||||
// Process posted tasks (lock-u_free during execution)
|
||||
static void process_posted_tasks(struct UASYNC* ua) {
|
||||
if (!ua) return;
|
||||
@@ -390,7 +412,7 @@ static void process_posted_tasks(struct UASYNC* ua) {
|
||||
#endif
|
||||
|
||||
while (list) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "POSTed task get");
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "POSTed task get");
|
||||
struct posted_task* t = list;
|
||||
list = list->next;
|
||||
|
||||
@@ -435,8 +457,8 @@ static uint64_t timeval_to_ms(const struct timeval* tv) {
|
||||
|
||||
|
||||
|
||||
// Simplified timeout handling without reference counting
|
||||
|
||||
// Simplified timeout handling without reference counting
|
||||
|
||||
// Process expired timeouts with safe cancellation
|
||||
static void process_timeouts(struct UASYNC* ua) {
|
||||
if (!ua) return;
|
||||
@@ -460,63 +482,40 @@ static void process_timeouts(struct UASYNC* ua) {
|
||||
memory_pool_free(ua->timeout_pool, node);
|
||||
}
|
||||
|
||||
if (!ua->timeout_heap) return;
|
||||
if (!ua->twheel) return;
|
||||
|
||||
struct timeval now_tv;
|
||||
get_current_time(&now_tv);
|
||||
uint64_t now_ms = timeval_to_ms(&now_tv);
|
||||
|
||||
while (1) {
|
||||
TimeoutEntry entry;
|
||||
if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) break;
|
||||
if (entry.expiration > now_ms) break;
|
||||
|
||||
// Pop the expired timeout
|
||||
timeout_heap_pop(ua->timeout_heap, &entry);
|
||||
struct timeout_node* node = (struct timeout_node*)entry.data;
|
||||
// Update timing wheel to current time — moves expired to expired queue
|
||||
twheels_update(ua->twheel, now_ms);
|
||||
|
||||
// Drain all expired timeouts
|
||||
struct twheel *to;
|
||||
while ((to = twheels_get(ua->twheel))) {
|
||||
struct timeout_node* node = (struct timeout_node*)to;
|
||||
|
||||
if (node && node->callback) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "timer→%s expired", node->name[0] ? node->name : "");
|
||||
node->callback(node->arg);
|
||||
}
|
||||
|
||||
// Always u_free the node after processing
|
||||
if (node && node->ua) {
|
||||
node->ua->timer_free_count++;
|
||||
}
|
||||
memory_pool_free(ua->timeout_pool, node);
|
||||
continue; // Process next expired timeout
|
||||
}
|
||||
}
|
||||
|
||||
// Compute time to next timeout
|
||||
static void get_next_timeout(struct UASYNC* ua, struct timeval* tv) {
|
||||
if (!ua || !ua->timeout_heap) {
|
||||
tv->tv_sec = 0;
|
||||
tv->tv_usec = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
TimeoutEntry entry;
|
||||
if (timeout_heap_peek(ua->timeout_heap, &entry) != 0) {
|
||||
tv->tv_sec = 0;
|
||||
tv->tv_usec = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
struct timeval now_tv;
|
||||
get_current_time(&now_tv);
|
||||
uint64_t now_ms = timeval_to_ms(&now_tv);
|
||||
|
||||
if (entry.expiration <= now_ms) {
|
||||
tv->tv_sec = 0;
|
||||
tv->tv_usec = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t delta_ms = entry.expiration - now_ms;
|
||||
tv->tv_sec = delta_ms / 1000;
|
||||
tv->tv_usec = (delta_ms % 1000) * 1000;
|
||||
// Compute time to next timeout in milliseconds
|
||||
// Returns: 0 = timer already expired/no timers, ~0 = no pending timers, >0 = ms to wait
|
||||
static uint64_t get_next_timeout_ms(struct UASYNC* ua) {
|
||||
if (!ua || !ua->twheel) return 0;
|
||||
twheel_t t = twheels_timeout(ua->twheel);
|
||||
if (t == 0) return 0; // expired timer exists
|
||||
if (t == ~TWHEEL_C(0)) return ~TWHEEL_C(0); // no pending timers
|
||||
return (uint64_t)t;
|
||||
}
|
||||
|
||||
|
||||
@@ -524,9 +523,7 @@ static void get_next_timeout(struct UASYNC* ua, struct timeval* tv) {
|
||||
// Instance version
|
||||
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_callback_t callback, const char* name) {
|
||||
if (!ua || timeout_tb < 0 || !callback) return NULL;
|
||||
if (!ua->timeout_heap) return NULL;
|
||||
|
||||
// DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: timeout=%d.%d ms, arg=%p, callback=%p", timeout_tb/10, timeout_tb%10, arg, callback);
|
||||
if (!ua->twheel) return NULL;
|
||||
|
||||
struct timeout_node* node = memory_pool_alloc(ua->timeout_pool);
|
||||
if (!node) {
|
||||
@@ -544,24 +541,19 @@ void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_c
|
||||
node->arg = arg;
|
||||
node->callback = callback;
|
||||
node->ua = ua;
|
||||
node->heap_index = SIZE_MAX;
|
||||
|
||||
// Calculate expiration time in milliseconds
|
||||
// Calculate expiration time in milliseconds (absolute monotonic)
|
||||
struct timeval now;
|
||||
get_current_time(&now);
|
||||
timeval_add_tb(&now, timeout_tb);
|
||||
node->expiration_ms = timeval_to_ms(&now);
|
||||
uint64_t expiration_ms = timeval_to_ms(&now);
|
||||
|
||||
// Initialize wheel entry and add to timing wheel
|
||||
twheel_init(&node->to, TWHEEL_ABS);
|
||||
twheels_add(ua->twheel, &node->to, expiration_ms);
|
||||
|
||||
// Add to heap
|
||||
if (timeout_heap_push(ua->timeout_heap, node->expiration_ms, node, &node->heap_index) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to push to heap");
|
||||
memory_pool_free(ua->timeout_pool, node);
|
||||
ua->timer_free_count++; // Balance the alloc counter
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
// Immediate execution in next mainloop (FIFO order)
|
||||
void* uasync_call_soon(struct UASYNC* ua, void* user_arg, timeout_callback_t callback) {
|
||||
@@ -579,9 +571,8 @@ void* uasync_call_soon(struct UASYNC* ua, void* user_arg, timeout_callback_t cal
|
||||
node->arg = user_arg;
|
||||
node->callback = callback;
|
||||
node->ua = ua;
|
||||
node->expiration_ms = 0;
|
||||
node->next = NULL;
|
||||
node->heap_index = SIZE_MAX;
|
||||
memset(&node->to, 0, sizeof(node->to));
|
||||
|
||||
// FIFO: добавляем в конец очереди
|
||||
if (ua->immediate_queue_tail) {
|
||||
@@ -609,30 +600,26 @@ err_t uasync_call_soon_cancel(struct UASYNC* ua, void* t_id) {
|
||||
|
||||
|
||||
|
||||
// Instance version
|
||||
// Instance version
|
||||
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id) {
|
||||
if (!ua || !t_id || !ua->timeout_heap) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: invalid parameters ua=%p, t_id=%p, heap=%p",
|
||||
ua, t_id, ua ? ua->timeout_heap : NULL);
|
||||
if (!ua || !t_id || !ua->twheel) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: invalid parameters ua=%p, t_id=%p, twheel=%p",
|
||||
ua, t_id, ua ? ua->twheel : NULL);
|
||||
return ERR_FAIL;
|
||||
}
|
||||
|
||||
struct timeout_node* node = (struct timeout_node*)t_id;
|
||||
|
||||
if (node->heap_index == SIZE_MAX || node->heap_index >= ua->timeout_heap->size ||
|
||||
ua->timeout_heap->heap[node->heap_index].data != node) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: not found in heap: ua=%p, t_id=%p, node=%p, expires=%llu ms",
|
||||
ua, t_id, node, (unsigned long long)node->expiration_ms);
|
||||
if (!twheel_pending(&node->to)) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: not pending: ua=%p, t_id=%p", ua, t_id);
|
||||
return ERR_FAIL;
|
||||
}
|
||||
|
||||
if (timeout_heap_cancel_at(ua->timeout_heap, node->heap_index, node) == 0) {
|
||||
node->heap_index = SIZE_MAX;
|
||||
node->callback = NULL;
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
return ERR_FAIL;
|
||||
twheels_del(ua->twheel, &node->to);
|
||||
node->callback = NULL;
|
||||
ua->timer_free_count++;
|
||||
memory_pool_free(ua->timeout_pool, node);
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -765,36 +752,27 @@ err_t uasync_remove_socket_t(struct UASYNC* ua, socket_t sock) {
|
||||
return ERR_FAIL;
|
||||
}
|
||||
|
||||
err_t uasync_set_socket_read(struct UASYNC* ua, void* s_id, int enable) {
|
||||
static err_t socket_set_event(struct UASYNC* ua, void* s_id, int is_read, int enable) {
|
||||
if (!ua || !s_id) return ERR_FAIL;
|
||||
|
||||
struct socket_node* node = (struct socket_node*)s_id;
|
||||
if (!node->active || node->fd < 0) return ERR_FAIL;
|
||||
|
||||
int val = enable ? 1 : 0;
|
||||
if (node->enable_read == val) return ERR_OK;
|
||||
node->enable_read = val;
|
||||
int* field = is_read ? &node->enable_read : &node->enable_write;
|
||||
if (*field == val) return ERR_OK;
|
||||
*field = val;
|
||||
|
||||
#if HAS_EPOLL
|
||||
if (ua->use_epoll && ua->epoll_fd >= 0) {
|
||||
struct epoll_event ev;
|
||||
ev.events = 0;
|
||||
if (node->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (node->read_cbk_sock && node->enable_read) ev.events |= EPOLLIN;
|
||||
if (node->write_cbk_sock && node->enable_write) ev.events |= EPOLLOUT;
|
||||
ev.data.fd = node->sock;
|
||||
} else {
|
||||
if (node->read_cbk && node->enable_read) ev.events |= EPOLLIN;
|
||||
if (node->write_cbk && node->enable_write) ev.events |= EPOLLOUT;
|
||||
ev.data.fd = node->fd;
|
||||
}
|
||||
if (node->except_cbk) ev.events |= EPOLLPRI;
|
||||
ev.events = socket_node_events(node);
|
||||
ev.data.fd = socket_node_fd(node);
|
||||
#ifdef _WIN32
|
||||
int efd = (int)(intptr_t)ev.data.fd;
|
||||
epoll_ctl(ua->epoll_fd, EPOLL_CTL_MOD, (int)(intptr_t)ev.data.fd, &ev);
|
||||
#else
|
||||
int efd = ev.data.fd;
|
||||
epoll_ctl(ua->epoll_fd, EPOLL_CTL_MOD, ev.data.fd, &ev);
|
||||
#endif
|
||||
epoll_ctl(ua->epoll_fd, EPOLL_CTL_MOD, efd, &ev);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -802,41 +780,12 @@ err_t uasync_set_socket_read(struct UASYNC* ua, void* s_id, int enable) {
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
err_t uasync_set_socket_read(struct UASYNC* ua, void* s_id, int enable) {
|
||||
return socket_set_event(ua, s_id, 1, enable);
|
||||
}
|
||||
|
||||
err_t uasync_set_socket_write(struct UASYNC* ua, void* s_id, int enable) {
|
||||
if (!ua || !s_id) return ERR_FAIL;
|
||||
|
||||
struct socket_node* node = (struct socket_node*)s_id;
|
||||
if (!node->active || node->fd < 0) return ERR_FAIL;
|
||||
|
||||
int val = enable ? 1 : 0;
|
||||
if (node->enable_write == val) return ERR_OK;
|
||||
node->enable_write = val;
|
||||
|
||||
#if HAS_EPOLL
|
||||
if (ua->use_epoll && ua->epoll_fd >= 0) {
|
||||
struct epoll_event ev;
|
||||
ev.events = 0;
|
||||
if (node->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (node->read_cbk_sock && node->enable_read) ev.events |= EPOLLIN;
|
||||
if (node->write_cbk_sock && node->enable_write) ev.events |= EPOLLOUT;
|
||||
ev.data.fd = node->sock;
|
||||
} else {
|
||||
if (node->read_cbk && node->enable_read) ev.events |= EPOLLIN;
|
||||
if (node->write_cbk && node->enable_write) ev.events |= EPOLLOUT;
|
||||
ev.data.fd = node->fd;
|
||||
}
|
||||
if (node->except_cbk) ev.events |= EPOLLPRI;
|
||||
#ifdef _WIN32
|
||||
int efd = (int)(intptr_t)ev.data.fd;
|
||||
#else
|
||||
int efd = ev.data.fd;
|
||||
#endif
|
||||
epoll_ctl(ua->epoll_fd, EPOLL_CTL_MOD, efd, &ev);
|
||||
}
|
||||
#endif
|
||||
|
||||
ua->poll_fds_dirty = 1;
|
||||
return ERR_OK;
|
||||
return socket_set_event(ua, s_id, 0, enable);
|
||||
}
|
||||
|
||||
// Helper function to rebuild cached pollfd array
|
||||
@@ -869,37 +818,16 @@ static void rebuild_poll_fds(struct UASYNC* ua) {
|
||||
idx++;
|
||||
}
|
||||
|
||||
// Add socket fds using active_indices for O(1) traversal
|
||||
for (int i = 0; i < socket_count; i++) {
|
||||
int socket_array_idx = ua->sockets->active_indices[i];
|
||||
struct socket_node* cur = &ua->sockets->sockets[socket_array_idx];
|
||||
|
||||
// Handle socket_t vs int fd
|
||||
if (cur->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
// socket_t - cast to int for pollfd
|
||||
#ifdef _WIN32
|
||||
ua->poll_fds[idx].fd = (int)(intptr_t)cur->sock;
|
||||
#else
|
||||
ua->poll_fds[idx].fd = cur->sock;
|
||||
#endif
|
||||
} else {
|
||||
// Regular fd
|
||||
ua->poll_fds[idx].fd = cur->fd;
|
||||
}
|
||||
ua->poll_fds[idx].events = 0;
|
||||
ua->poll_fds[idx].revents = 0;
|
||||
|
||||
if (cur->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (cur->read_cbk_sock && cur->enable_read) ua->poll_fds[idx].events |= POLLIN;
|
||||
if (cur->write_cbk_sock && cur->enable_write) ua->poll_fds[idx].events |= POLLOUT;
|
||||
} else {
|
||||
if (cur->read_cbk && cur->enable_read) ua->poll_fds[idx].events |= POLLIN;
|
||||
if (cur->write_cbk && cur->enable_write) ua->poll_fds[idx].events |= POLLOUT;
|
||||
}
|
||||
if (cur->write_cbk && cur->enable_write) ua->poll_fds[idx].events |= POLLOUT;
|
||||
if (cur->except_cbk) ua->poll_fds[idx].events |= POLLPRI;
|
||||
|
||||
idx++;
|
||||
// Add socket fds using active_indices for O(1) traversal
|
||||
for (int i = 0; i < socket_count; i++) {
|
||||
int socket_array_idx = ua->sockets->active_indices[i];
|
||||
struct socket_node* cur = &ua->sockets->sockets[socket_array_idx];
|
||||
|
||||
ua->poll_fds[idx].fd = socket_node_fd(cur);
|
||||
ua->poll_fds[idx].events = socket_node_events(cur);
|
||||
ua->poll_fds[idx].revents = 0;
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
ua->poll_fds_count = total_fds;
|
||||
@@ -913,7 +841,7 @@ static void process_epoll_events(struct UASYNC* ua, struct epoll_event* events,
|
||||
// Check if this is the wakeup fd (data.fd is -1)
|
||||
if (events[i].data.fd < 0) {
|
||||
if (events[i].events & EPOLLIN) {
|
||||
drain_wakeup_pipe(ua);
|
||||
handle_wakeup(ua);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -936,99 +864,59 @@ static void process_epoll_events(struct UASYNC* ua, struct epoll_event* events,
|
||||
|
||||
/* Check for error conditions first */
|
||||
if (events[i].events & (EPOLLERR | EPOLLHUP)) {
|
||||
if (local_except) {
|
||||
local_except(local_fd, local_ud);
|
||||
}
|
||||
if (local_except) local_except(local_fd, local_ud);
|
||||
}
|
||||
|
||||
/* Read readiness - use appropriate callback based on socket type */
|
||||
|
||||
/* Read readiness */
|
||||
if (events[i].events & EPOLLIN) {
|
||||
if (local_type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (local_read_sock) {
|
||||
local_read_sock(local_sock, local_ud);
|
||||
}
|
||||
} else {
|
||||
if (local_read) {
|
||||
local_read(local_fd, local_ud);
|
||||
}
|
||||
}
|
||||
if (local_read_sock) local_read_sock(local_sock, local_ud);
|
||||
} else if (local_read) local_read(local_fd, local_ud);
|
||||
}
|
||||
|
||||
/* Write readiness - use appropriate callback based on socket type */
|
||||
|
||||
/* Write readiness */
|
||||
if (events[i].events & EPOLLOUT) {
|
||||
if (local_type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (local_write_sock) {
|
||||
local_write_sock(local_sock, local_ud);
|
||||
}
|
||||
} else {
|
||||
if (local_write) {
|
||||
local_write(local_fd, local_ud);
|
||||
}
|
||||
}
|
||||
if (local_write_sock) local_write_sock(local_sock, local_ud);
|
||||
} else if (local_write) local_write(local_fd, local_ud);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Instance version
|
||||
void uasync_poll(struct UASYNC* ua, int timeout_tb) {
|
||||
if (!ua) return;
|
||||
if (!ua->sockets || !ua->timeout_heap) return;
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "poll(%d sockets, %zu timers, timeout=%d.%dms)",
|
||||
ua->sockets->count, ua->timeout_heap->size, timeout_tb >= 0 ? timeout_tb / 10000 : -1,
|
||||
timeout_tb >= 0 ? (timeout_tb % 10000) / 10 : 0);
|
||||
|
||||
// Handle negative or zero timeout
|
||||
if (timeout_tb < 0) timeout_tb = -1; // Infinite wait
|
||||
else if (timeout_tb == 0) timeout_tb = 0; // No wait
|
||||
|
||||
// Get next timeout
|
||||
struct timeval next_timeout;
|
||||
get_next_timeout(ua, &next_timeout);
|
||||
|
||||
// Convert requested timeout to timeval
|
||||
struct timeval req_timeout = {0};
|
||||
if (timeout_tb >= 0) {
|
||||
req_timeout.tv_sec = timeout_tb / 10000;
|
||||
req_timeout.tv_usec = (timeout_tb % 10000) * 100;
|
||||
}
|
||||
|
||||
struct timeval poll_timeout;
|
||||
// Instance version
|
||||
void uasync_poll(struct UASYNC* ua, int timeout_tb) {
|
||||
if (!ua) return;
|
||||
if (!ua->sockets || !ua->twheel) return;
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "poll(%d sockets, timeout=%d.%dms)",
|
||||
ua->sockets->count, timeout_tb >= 0 ? timeout_tb / 10000 : -1,
|
||||
timeout_tb >= 0 ? (timeout_tb % 10000) / 10 : 0);
|
||||
|
||||
uint64_t next_ms = get_next_timeout_ms(ua); // 0=expired, ~0=no timers, >0=ms to wait
|
||||
|
||||
int timeout_ms;
|
||||
if (timeout_tb < 0) {
|
||||
poll_timeout = next_timeout;
|
||||
// Infinite wait or until next timer
|
||||
if (next_ms == 0) timeout_ms = 0; // Expired timer, don't wait
|
||||
else if (next_ms != ~TWHEEL_C(0)) timeout_ms = (int)next_ms; // Wait for next timer
|
||||
else timeout_ms = -1; // No timers, infinite wait
|
||||
} else {
|
||||
if (next_timeout.tv_sec < req_timeout.tv_sec ||
|
||||
(next_timeout.tv_sec == req_timeout.tv_sec && next_timeout.tv_usec < req_timeout.tv_usec)) {
|
||||
poll_timeout = next_timeout;
|
||||
} else {
|
||||
poll_timeout = req_timeout;
|
||||
}
|
||||
int req_ms = timeout_tb / 10; // timebase to ms
|
||||
if (next_ms == 0) timeout_ms = 0; // Expired timer, don't wait
|
||||
else if (next_ms != ~TWHEEL_C(0)) timeout_ms = (int)(next_ms < (uint64_t)req_ms ? next_ms : (uint64_t)req_ms);
|
||||
else timeout_ms = req_ms;
|
||||
}
|
||||
if (poll_timeout.tv_sec == 0 && poll_timeout.tv_usec == 0 && timeout_tb > 0) poll_timeout = req_timeout;
|
||||
|
||||
|
||||
int timeout_ms;
|
||||
if (timeout_tb < 0 && (next_timeout.tv_sec > 0 || next_timeout.tv_usec > 0)) {
|
||||
timeout_ms = (poll_timeout.tv_sec * 1000) + (poll_timeout.tv_usec / 1000);
|
||||
} else if (timeout_tb < 0) {
|
||||
timeout_ms = -1; // Infinite
|
||||
} else {
|
||||
timeout_ms = (poll_timeout.tv_sec * 1000) + (poll_timeout.tv_usec / 1000);
|
||||
}
|
||||
|
||||
// Count active sockets
|
||||
int socket_count = ua->sockets->count;
|
||||
if (socket_count == 0 && timeout_ms == -1) {
|
||||
// No sockets and infinite wait - but we have timers? Wait for timer
|
||||
if (ua->timeout_heap->size > 0) {
|
||||
timeout_ms = (poll_timeout.tv_sec * 1000) + (poll_timeout.tv_usec / 1000);
|
||||
} else {
|
||||
// Nothing to do - return immediately
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int socket_count = ua->sockets->count;
|
||||
if (socket_count == 0 && timeout_ms == -1) {
|
||||
// No sockets and infinite wait — but we might have timers
|
||||
if (twheels_pending(ua->twheel) || twheels_expired(ua->twheel))
|
||||
timeout_ms = 0; // Let process_timeouts handle them
|
||||
else
|
||||
return; // Nothing to do
|
||||
}
|
||||
|
||||
#if HAS_EPOLL
|
||||
// Use epoll on Linux if available
|
||||
if (ua->use_epoll && ua->epoll_fd >= 0) {
|
||||
@@ -1143,35 +1031,12 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
|
||||
if (!has_read && !has_write && !has_except) continue;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "select→fd=%d r=%d w=%d e=%d", (int)s, has_read, has_write, has_except);
|
||||
|
||||
if (has_except) {
|
||||
if (node->except_cbk) {
|
||||
node->except_cbk(node->fd, node->user_data);
|
||||
}
|
||||
}
|
||||
|
||||
if (has_read) {
|
||||
if (node->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (node->read_cbk_sock) {
|
||||
node->read_cbk_sock(node->sock, node->user_data);
|
||||
}
|
||||
} else {
|
||||
if (node->read_cbk) {
|
||||
node->read_cbk(node->fd, node->user_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (has_write) {
|
||||
if (node->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (node->write_cbk_sock) {
|
||||
node->write_cbk_sock(node->sock, node->user_data);
|
||||
}
|
||||
} else {
|
||||
if (node->write_cbk) {
|
||||
node->write_cbk(node->fd, node->user_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (has_except) {
|
||||
if (node->except_cbk) node->except_cbk(node->fd, node->user_data);
|
||||
}
|
||||
|
||||
if (has_read) socket_node_dispatch_read(node);
|
||||
if (has_write) socket_node_dispatch_write(node);
|
||||
}
|
||||
}
|
||||
#else
|
||||
@@ -1202,14 +1067,6 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
|
||||
if (ua->poll_fds[i].revents == 0) continue;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "poll→fd=%d rev=0x%x", ua->poll_fds[i].fd, ua->poll_fds[i].revents);
|
||||
|
||||
/* Handle wakeup fd separately */
|
||||
if (wakeup_fd_present && i == 0) {
|
||||
if (ua->poll_fds[i].revents & POLLIN) {
|
||||
drain_wakeup_pipe(ua);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Socket event - lookup by fd */
|
||||
struct socket_node* node = socket_array_get(ua->sockets, ua->poll_fds[i].fd);
|
||||
if (!node) { // Try by socket_t (in case this is a socket)
|
||||
@@ -1218,46 +1075,18 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
|
||||
}
|
||||
if (!node) continue; // Socket may have been removed
|
||||
|
||||
/* Check for error conditions first */
|
||||
if (ua->poll_fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
|
||||
/* Treat as exceptional condition */
|
||||
if (node->except_cbk) {
|
||||
node->except_cbk(node->fd, node->user_data);
|
||||
}
|
||||
}
|
||||
|
||||
/* Exceptional data (out-of-band) */
|
||||
if (ua->poll_fds[i].revents & POLLPRI) {
|
||||
if (node->except_cbk) {
|
||||
node->except_cbk(node->fd, node->user_data);
|
||||
}
|
||||
}
|
||||
|
||||
/* Read readiness - use appropriate callback based on socket type */
|
||||
if (ua->poll_fds[i].revents & POLLIN) {
|
||||
if (node->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (node->read_cbk_sock) {
|
||||
node->read_cbk_sock(node->sock, node->user_data);
|
||||
}
|
||||
} else {
|
||||
if (node->read_cbk) {
|
||||
node->read_cbk(node->fd, node->user_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Write readiness - use appropriate callback based on socket type */
|
||||
if (ua->poll_fds[i].revents & POLLOUT) {
|
||||
if (node->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (node->write_cbk_sock) {
|
||||
node->write_cbk_sock(node->sock, node->user_data);
|
||||
}
|
||||
} else {
|
||||
if (node->write_cbk) {
|
||||
node->write_cbk(node->fd, node->user_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Check for error conditions first */
|
||||
if (ua->poll_fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
|
||||
if (node->except_cbk) node->except_cbk(node->fd, node->user_data);
|
||||
}
|
||||
|
||||
/* Exceptional data (out-of-band) */
|
||||
if (ua->poll_fds[i].revents & POLLPRI) {
|
||||
if (node->except_cbk) node->except_cbk(node->fd, node->user_data);
|
||||
}
|
||||
|
||||
if (ua->poll_fds[i].revents & POLLIN) socket_node_dispatch_read(node);
|
||||
if (ua->poll_fds[i].revents & POLLOUT) socket_node_dispatch_write(node);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1267,9 +1096,6 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
|
||||
}
|
||||
|
||||
|
||||
// Put this near the top of u_async.c, after includes and before uasync_create
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
static void wakeup_read_callback_win(socket_t sock, void* arg) {
|
||||
(void)sock; // не нужен
|
||||
@@ -1330,8 +1156,8 @@ struct UASYNC* uasync_create(void) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ua->timeout_heap = timeout_heap_create(16);
|
||||
if (!ua->timeout_heap) {
|
||||
ua->twheel = twheels_open(TWHEEL_mHZ);
|
||||
if (!ua->twheel) {
|
||||
socket_array_destroy(ua->sockets);
|
||||
if (ua->wakeup_initialized) {
|
||||
#ifdef _WIN32
|
||||
@@ -1349,7 +1175,7 @@ struct UASYNC* uasync_create(void) {
|
||||
// Initialize timeout pool
|
||||
ua->timeout_pool = memory_pool_init(sizeof(struct timeout_node), "timeout_pool");
|
||||
if (!ua->timeout_pool) {
|
||||
timeout_heap_destroy(ua->timeout_heap);
|
||||
twheels_close(ua->twheel);
|
||||
socket_array_destroy(ua->sockets);
|
||||
if (ua->wakeup_initialized) {
|
||||
#ifdef _WIN32
|
||||
@@ -1364,10 +1190,7 @@ struct UASYNC* uasync_create(void) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Set callback to u_free timeout nodes and update counters
|
||||
timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback);
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH1...");
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH1...");
|
||||
// Initialize epoll on Linux
|
||||
ua->epoll_fd = -1;
|
||||
ua->use_epoll = 0;
|
||||
@@ -1479,29 +1302,11 @@ void uasync_print_resources(struct UASYNC* ua, const char* prefix) {
|
||||
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
|
||||
|
||||
// Показать активные таймеры
|
||||
if (ua->timeout_heap) {
|
||||
struct timeval now_tv;
|
||||
get_current_time(&now_tv);
|
||||
uint64_t now_ms = timeval_to_ms(&now_tv);
|
||||
size_t active_timers = 0;
|
||||
size_t deleted_timers = 0;
|
||||
for (size_t i = 0; i < ua->timeout_heap->size; i++) {
|
||||
uint64_t exp = ua->timeout_heap->heap[i].expiration;
|
||||
int64_t remain = (int64_t)(exp - now_ms);
|
||||
if (ua->timeout_heap->heap[i].deleted) {
|
||||
deleted_timers++;
|
||||
struct timeout_node* node = (struct timeout_node*)ua->timeout_heap->heap[i].data;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Timer DELETED: node=%p name='%s' exp=%llu remain=%lldms",
|
||||
node, node ? node->name : "null", (unsigned long long)exp, (long long)remain);
|
||||
} else {
|
||||
active_timers++;
|
||||
struct timeout_node* node = (struct timeout_node*)ua->timeout_heap->heap[i].data;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Timer ACTIVE: node=%p name='%s' exp=%llu remain=%lldms",
|
||||
node, node ? node->name : "null", (unsigned long long)exp, (long long)remain);
|
||||
}
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Active timers in heap: %zu, deleted: %zu, total: %zu",
|
||||
active_timers, deleted_timers, ua->timeout_heap->size);
|
||||
if (ua->twheel) {
|
||||
bool has_pending = twheels_pending(ua->twheel);
|
||||
bool has_expired = twheels_expired(ua->twheel);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Timing wheel: pending=%d expired=%d curtime=%llu",
|
||||
has_pending, has_expired, (unsigned long long)ua->twheel->curtime);
|
||||
}
|
||||
|
||||
// Показать активные сокеты
|
||||
@@ -1560,21 +1365,30 @@ void uasync_destroy(struct UASYNC* ua, int close_fds) {
|
||||
}
|
||||
ua->immediate_queue_tail = NULL;
|
||||
|
||||
// Очистить heap
|
||||
if (ua->timeout_heap) {
|
||||
while (1) {
|
||||
TimeoutEntry entry;
|
||||
if (timeout_heap_pop(ua->timeout_heap, &entry) != 0) break;
|
||||
struct timeout_node* node = (struct timeout_node*)entry.data;
|
||||
|
||||
// Free all timer nodes (avoid double-u_free bug)
|
||||
if (node) {
|
||||
ua->timer_free_count++;
|
||||
memory_pool_free(ua->timeout_pool, node);
|
||||
// Очистить timing wheel
|
||||
if (ua->twheel) {
|
||||
for (int w = 0; w < 4; w++) {
|
||||
for (int s = 0; s < 64; s++) {
|
||||
struct twheel *to;
|
||||
while (!TAILQ_EMPTY(&ua->twheel->wheel[w][s])) {
|
||||
to = TAILQ_FIRST(&ua->twheel->wheel[w][s]);
|
||||
TAILQ_REMOVE(&ua->twheel->wheel[w][s], to, tqe);
|
||||
struct timeout_node* node = (struct timeout_node*)to;
|
||||
ua->timer_free_count++;
|
||||
memory_pool_free(ua->timeout_pool, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
timeout_heap_destroy(ua->timeout_heap);
|
||||
ua->timeout_heap = NULL;
|
||||
struct twheel *to;
|
||||
while (!TAILQ_EMPTY(&ua->twheel->expired)) {
|
||||
to = TAILQ_FIRST(&ua->twheel->expired);
|
||||
TAILQ_REMOVE(&ua->twheel->expired, to, tqe);
|
||||
struct timeout_node* node = (struct timeout_node*)to;
|
||||
ua->timer_free_count++;
|
||||
memory_pool_free(ua->timeout_pool, node);
|
||||
}
|
||||
twheels_close(ua->twheel);
|
||||
ua->twheel = NULL;
|
||||
}
|
||||
|
||||
// Destroy timeout pool
|
||||
|
||||
+9
-11
@@ -6,9 +6,9 @@
|
||||
#define UASYNC_H
|
||||
|
||||
#include "platform_compat.h"
|
||||
#include "memory_pool.h"
|
||||
#include <stddef.h>
|
||||
#include <signal.h>
|
||||
#include "timeout_heap.h"
|
||||
#include "socket_compat.h"
|
||||
|
||||
typedef void (*timeout_callback_t)(void* user_arg);// передаёт user_arg из uasync_set_timeout
|
||||
@@ -26,16 +26,15 @@ typedef int err_t;
|
||||
#define SOCKET_NODE_TYPE_FD 0 // Regular file descriptor (pipe, file)
|
||||
#define SOCKET_NODE_TYPE_SOCK 1 // Socket (socket_t)
|
||||
|
||||
typedef void (*uasync_post_callback_t)(void* user_arg);
|
||||
|
||||
#include "memory_pool.h"
|
||||
typedef void (*uasync_post_callback_t)(void* user_arg);
|
||||
|
||||
struct timeout_node; // Forward declaration
|
||||
struct timeout_node;
|
||||
struct twheels;
|
||||
|
||||
struct posted_task {
|
||||
uasync_post_callback_t callback;
|
||||
void* arg;
|
||||
struct posted_task* next;
|
||||
struct posted_task {
|
||||
uasync_post_callback_t callback;
|
||||
void* arg;
|
||||
struct posted_task* next;
|
||||
};
|
||||
|
||||
// Uasync instance structure
|
||||
@@ -43,7 +42,7 @@ struct UASYNC {
|
||||
struct memory_pool* timeout_pool; // Pool for timeout_node allocation
|
||||
struct timeout_node* immediate_queue_head; // FIFO queue for immediate execution
|
||||
struct timeout_node* immediate_queue_tail;
|
||||
TimeoutHeap* timeout_heap; // Heap for timeout management
|
||||
struct twheels* twheel; // Timing wheel for timeout management
|
||||
struct socket_array* sockets; // Array-based socket management
|
||||
// Debug counters for memory allocation tracking
|
||||
size_t timer_alloc_count;
|
||||
@@ -74,7 +73,6 @@ struct UASYNC {
|
||||
|
||||
// Type definitions
|
||||
typedef struct UASYNC uasync_t;
|
||||
typedef struct UASYNC UASYNC_t;
|
||||
|
||||
// Instance API - основной API для работы с uasync
|
||||
struct UASYNC* uasync_create(void);
|
||||
|
||||
+92
-12
@@ -11,9 +11,7 @@ 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 \
|
||||
@@ -28,15 +26,10 @@ utun_CORE_SOURCES = \
|
||||
etcp_dump.c \
|
||||
secure_channel.c \
|
||||
crc32.c \
|
||||
stcp_link.c \
|
||||
stcp.c \
|
||||
stcp_server.c \
|
||||
stcp_client.c \
|
||||
pkt_normalizer.c \
|
||||
packet_dump.c \
|
||||
etcp_api.c \
|
||||
control_server.c \
|
||||
msg_transport.c \
|
||||
firewall.c \
|
||||
eim_nat.c \
|
||||
nat_transport.c \
|
||||
@@ -54,22 +47,109 @@ 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_CFLAGS = \
|
||||
utun_CORE_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_LDADD = \
|
||||
utun_CORE_LDADD = \
|
||||
$(top_builddir)/lib/libuasync.a \
|
||||
-lpthread \
|
||||
-lm \
|
||||
-lcrypto \
|
||||
$(utun_TUN_LIBS)
|
||||
-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)
|
||||
|
||||
# Copy binary to project root after building
|
||||
all-local: copy-to-root
|
||||
|
||||
+6
-34
@@ -46,8 +46,7 @@ typedef enum {
|
||||
SECTION_ALLOWED_KEYS,
|
||||
SECTION_NAT,
|
||||
SECTION_TCP_PROXY_CLIENT,
|
||||
SECTION_TCP_PROXY_SERVER,
|
||||
SECTION_MSG_TRANSPORT
|
||||
SECTION_TCP_PROXY_SERVER
|
||||
} section_type_t;
|
||||
|
||||
static char* trim(char *str) {
|
||||
@@ -387,9 +386,6 @@ 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);
|
||||
@@ -411,7 +407,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, db_path, 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, enable_timestamp, enable_function_names, enable_file_lines, enable_colors, tun_test_mode", filename, line_num, key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -440,17 +436,6 @@ 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;
|
||||
@@ -639,12 +624,7 @@ static int parse_server(const char *key, const char *value, struct CFG_SERVER *s
|
||||
srv->only_local = atoi(value) ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "transport") == 0) {
|
||||
if (strcmp(value, "tcp") == 0) srv->transport = 1;
|
||||
else srv->transport = 0; // default udp
|
||||
return 0;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown server option '%s'. Valid: addr, so_mark, fib, netif, type, mtu, only_local, transport", filename, line_num, key);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown server option '%s'. Valid: addr, so_mark, fib, netif, type, mtu, only_local", filename, line_num, key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -712,7 +692,6 @@ 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;
|
||||
@@ -741,8 +720,7 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
cfg->global.name[0] = '\0';
|
||||
cfg->global.keepalive_timeout = 2000; // Default 2 seconds
|
||||
cfg->global.keepalive_interval = 200; // Default 0.2 s
|
||||
cfg->global.bbr_max_cwnd = 1048576; // Default 1MB (was INFLIGHT_LIM_MAX)
|
||||
cfg->global.tcp_recv_buf = 0; // 0 = OS default
|
||||
cfg->global.bbr_max_cwnd = 100000; // Default 100KB
|
||||
cfg->global.firewall_rules = NULL;
|
||||
cfg->global.firewall_rule_count = 0;
|
||||
cfg->global.firewall_bypass_all = 0;
|
||||
@@ -891,14 +869,8 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
break;
|
||||
case SECTION_TCP_PROXY_SERVER:
|
||||
cfg->global.tcp_proxy_server_enabled = 1;
|
||||
if (strcmp(key, "tcp_recv_buf") == 0) {
|
||||
cfg->global.tcp_recv_buf = atoi(value);
|
||||
}
|
||||
break;
|
||||
case SECTION_MSG_TRANSPORT:
|
||||
if (parse_msg_transport(key, value, &cfg->global, filename, line_num) < 0) {
|
||||
// parse_msg_transport already printed the error
|
||||
}
|
||||
// tcp_proxy_server section has no options — its presence alone enables it
|
||||
(void)key; (void)value;
|
||||
break;
|
||||
default:
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Key outside section: %s", filename, line_num, key);
|
||||
|
||||
+1
-5
@@ -40,7 +40,6 @@ struct CFG_SERVER {
|
||||
int so_mark;
|
||||
int fib; // FreeBSD FIB (routing table), -1 = don't set
|
||||
uint8_t type; // public/nat/private
|
||||
uint8_t transport; // 0=udp (default), 1=tcp
|
||||
uint8_t ipv6_mode; // CFG_IPV6_MODE_*
|
||||
int mtu;
|
||||
uint8_t only_local; // 1 = only local connections, no forwarding
|
||||
@@ -79,7 +78,7 @@ struct CFG_CONTROL_ALLOW {
|
||||
};
|
||||
|
||||
struct CFG_ALLOWED_KEY {
|
||||
uint8_t key_bin[SC_PUBKEY_SIZE]; // public key in binary (32 bytes, X25519)
|
||||
uint8_t key_bin[SC_PUBKEY_SIZE]; // public key in binary (64 bytes)
|
||||
struct CFG_ALLOWED_KEY *next;
|
||||
};
|
||||
|
||||
@@ -100,11 +99,9 @@ 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
|
||||
@@ -164,7 +161,6 @@ struct global_config {
|
||||
|
||||
// TCP proxy server (exit node) configuration ([tcp_proxy_server] section)
|
||||
int tcp_proxy_server_enabled;
|
||||
int tcp_recv_buf; // SO_RCVBUF для TCP сокетов (0=default ОС)
|
||||
};
|
||||
|
||||
struct utun_config {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#include "../lib/mem.h"
|
||||
|
||||
#define PRIV_HEXKEY_LEN 65 // 32 bytes * 2 hex chars + null
|
||||
#define PUB_HEXKEY_LEN 65 // 32 bytes * 2 hex chars + null
|
||||
#define PUB_HEXKEY_LEN 129 // 64 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) != 64) return 0;
|
||||
for (int i = 0; i < 64; i++) {
|
||||
if (!key || strlen(key) != 128) return 0;
|
||||
for (int i = 0; i < 128; i++) {
|
||||
if (!isxdigit((unsigned char)key[i])) return 0;
|
||||
}
|
||||
return 1;
|
||||
|
||||
-1000
@@ -1,1000 +0,0 @@
|
||||
#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
@@ -1,190 +0,0 @@
|
||||
#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
|
||||
+46
-186
@@ -11,11 +11,9 @@
|
||||
#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"
|
||||
#include "../lib/timeout_heap.h"
|
||||
#include "../lib/memory_pool.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
@@ -97,16 +95,8 @@ 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
|
||||
* ============================================================================ */
|
||||
@@ -409,26 +399,13 @@ 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,
|
||||
client_write_callback,
|
||||
NULL, /* write callback - not needed for now */
|
||||
client_except_callback,
|
||||
client);
|
||||
|
||||
@@ -566,44 +543,9 @@ static void client_read_callback(socket_t fd, void* arg) {
|
||||
}
|
||||
|
||||
static void client_write_callback(socket_t fd, void* arg) {
|
||||
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;
|
||||
/* Not used for now - writes are immediate */
|
||||
(void)fd;
|
||||
(void)arg;
|
||||
}
|
||||
|
||||
static void client_except_callback(socket_t fd, void* arg) {
|
||||
@@ -617,21 +559,6 @@ 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;
|
||||
@@ -655,9 +582,7 @@ 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) {
|
||||
@@ -688,7 +613,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_SUBSCRIBE_NODES) {
|
||||
if (hdr->type < ETCPMON_CMD_LIST_CONN || hdr->type > ETCPMON_CMD_SET_DEBUG_CONFIG) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Invalid command type from client: 0x%02X", hdr->type);
|
||||
close_client(server, client);
|
||||
return;
|
||||
@@ -795,17 +720,6 @@ 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",
|
||||
@@ -880,7 +794,13 @@ static void send_conn_list(struct control_server* server, struct control_client*
|
||||
fflush(server->log_file);
|
||||
}
|
||||
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
#ifdef _WIN32
|
||||
send(client->fd, (const char*)buffer, rsp_size, 0);
|
||||
#else
|
||||
send(client->fd, buffer, rsp_size, 0);
|
||||
#endif
|
||||
|
||||
u_free(buffer);
|
||||
}
|
||||
|
||||
static void send_socket_list(struct control_server* server, struct control_client* client, uint8_t seq_id) {
|
||||
@@ -898,7 +818,8 @@ 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;
|
||||
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
send(client->fd, buffer, rsp_size, 0);
|
||||
u_free(buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -967,11 +888,17 @@ 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);
|
||||
}
|
||||
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
#ifdef _WIN32
|
||||
send(client->fd, (const char*)buffer, rsp_size, 0);
|
||||
#else
|
||||
send(client->fd, buffer, rsp_size, 0);
|
||||
#endif
|
||||
|
||||
u_free(buffer);
|
||||
}
|
||||
|
||||
static void send_metrics(struct control_server* server, struct control_client* client, uint8_t seq_id) {
|
||||
@@ -1100,11 +1027,7 @@ static void send_metrics(struct control_server* server, struct control_client* c
|
||||
}
|
||||
|
||||
/* System resources */
|
||||
if (instance->ua && instance->ua->timeout_heap) {
|
||||
rsp->etcp.active_timeouts = (uint32_t)timeout_heap_get_size(instance->ua->timeout_heap);
|
||||
} else {
|
||||
rsp->etcp.active_timeouts = 0;
|
||||
}
|
||||
rsp->etcp.active_timeouts = 0;
|
||||
|
||||
rsp->etcp.busy_memory_blocks = (uint32_t)(u_get_allocated_count() - memory_pool_get_total_free_blocks());
|
||||
|
||||
@@ -1208,7 +1131,13 @@ static void send_metrics(struct control_server* server, struct control_client* c
|
||||
fflush(server->log_file);
|
||||
}
|
||||
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
#ifdef _WIN32
|
||||
send(client->fd, (const char*)buffer, rsp_size, 0);
|
||||
#else
|
||||
send(client->fd, buffer, rsp_size, 0);
|
||||
#endif
|
||||
|
||||
u_free(buffer);
|
||||
}
|
||||
|
||||
static void send_error(struct control_client* client, uint8_t error_code, const char* msg, uint8_t seq_id) {
|
||||
@@ -1236,7 +1165,13 @@ static void send_error(struct control_client* client, uint8_t error_code, const
|
||||
fflush(server->log_file);
|
||||
}
|
||||
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
#ifdef _WIN32
|
||||
send(client->fd, (const char*)buffer, rsp_size, 0);
|
||||
#else
|
||||
send(client->fd, buffer, rsp_size, 0);
|
||||
#endif
|
||||
|
||||
u_free(buffer);
|
||||
}
|
||||
|
||||
static void send_debug_config(struct control_server* server, struct control_client* client, uint8_t seq_id) {
|
||||
@@ -1275,7 +1210,13 @@ static void send_debug_config(struct control_server* server, struct control_clie
|
||||
fflush(server->log_file);
|
||||
}
|
||||
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
#ifdef _WIN32
|
||||
send(client->fd, (const char*)buffer, rsp_size, 0);
|
||||
#else
|
||||
send(client->fd, buffer, rsp_size, 0);
|
||||
#endif
|
||||
|
||||
u_free(buffer);
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
@@ -1294,87 +1235,6 @@ 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,8 +19,6 @@ extern "C" {
|
||||
/* Forward declarations */
|
||||
struct UTUN_INSTANCE;
|
||||
struct UASYNC;
|
||||
struct NODEINFO_Q;
|
||||
struct ll_queue;
|
||||
|
||||
/* Forward declaration */
|
||||
struct control_server;
|
||||
@@ -38,10 +36,9 @@ struct control_client {
|
||||
|
||||
/* Selected connection */
|
||||
uint64_t selected_peer_id; /* 0 = none selected */
|
||||
|
||||
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 */
|
||||
|
||||
/* Client state */
|
||||
// uint8_t connected;
|
||||
};
|
||||
|
||||
/* Control server state */
|
||||
@@ -116,9 +113,6 @@ 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,11 +577,6 @@ 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,
|
||||
@@ -590,16 +585,6 @@ 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) {
|
||||
@@ -652,26 +637,6 @@ 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,7 +176,5 @@ 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
|
||||
+33
-66
@@ -207,7 +207,6 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
|
||||
etcp->ack_q = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, 0, 4, "ack_q");
|
||||
etcp->inflight_pool = memory_pool_init(sizeof(struct INFLIGHT_PACKET), "inflight_pool");
|
||||
etcp->io_pool = memory_pool_init(sizeof(struct ETCP_FRAGMENT), "io_pool");
|
||||
etcp->max_inflight = (uint32_t)instance->config->global.bbr_max_cwnd;
|
||||
etcp->optimal_inflight=100000;
|
||||
etcp->initialized=0;
|
||||
etcp->links_up=0;
|
||||
@@ -303,26 +302,23 @@ void etcp_connection_close(struct ETCP_CONN* etcp) {
|
||||
etcp->normalizer = NULL;
|
||||
}
|
||||
|
||||
// Clear links BEFORE draining queues: etcp_link_close calls
|
||||
// etcp_conn_on_inflight_lim_changed -> input_queue_try_resume
|
||||
// which accesses input_send_q, input_queue, input_wait_ack.
|
||||
if (etcp->links) {
|
||||
struct ETCP_LINK* link = etcp->links;
|
||||
while (link) {
|
||||
struct ETCP_LINK* next = link->next;
|
||||
etcp_link_close(link);
|
||||
link = next;
|
||||
}
|
||||
etcp->links = NULL;
|
||||
}
|
||||
|
||||
// Drain and free all queues
|
||||
// Drain and free all queues using helper functions
|
||||
drain_and_free_queue(&etcp->input_queue);
|
||||
drain_and_free_queue(&etcp->output_queue);
|
||||
drain_and_free_queue(&etcp->input_send_q);
|
||||
drain_and_free_queue(&etcp->input_wait_ack);
|
||||
drain_and_free_queue(&etcp->recv_q);
|
||||
drain_and_free_queue(&etcp->ack_q);
|
||||
|
||||
// Drain and free ack_q (contains ACK_PACKET from ack_pool - special handling)
|
||||
if (etcp->ack_q) {
|
||||
struct ACK_PACKET* pkt;
|
||||
while ((pkt = (struct ACK_PACKET*)queue_data_get(etcp->ack_q)) != NULL) {
|
||||
queue_entry_free((struct ll_entry*)pkt);
|
||||
}
|
||||
queue_free(etcp->ack_q);
|
||||
etcp->ack_q = NULL;
|
||||
}
|
||||
|
||||
// Free memory pools after all elements are returned
|
||||
if (etcp->inflight_pool) {
|
||||
@@ -335,6 +331,17 @@ void etcp_connection_close(struct ETCP_CONN* etcp) {
|
||||
etcp->io_pool = NULL;
|
||||
}
|
||||
|
||||
// Clear links list safely
|
||||
if (etcp->links) {
|
||||
struct ETCP_LINK* link = etcp->links;
|
||||
while (link) {
|
||||
struct ETCP_LINK* next = link->next;
|
||||
etcp_link_close(link);
|
||||
link = next;
|
||||
}
|
||||
etcp->links = NULL;
|
||||
}
|
||||
|
||||
u_free(etcp->name);
|
||||
|
||||
// Clear next pointer to prevent dangling references
|
||||
@@ -384,19 +391,12 @@ void etcp_conn_reset(struct ETCP_CONN* etcp) {
|
||||
clear_queue(etcp->recv_q);
|
||||
clear_queue(etcp->ack_q);
|
||||
|
||||
// clear_queue leaves callback_suspended=1, resume to prevent deadlock
|
||||
queue_resume_callback(etcp->input_queue);
|
||||
queue_resume_callback(etcp->input_send_q);
|
||||
queue_resume_callback(etcp->input_wait_ack);
|
||||
|
||||
// В etcp_conn_reset(), после очистки очередей добавьте:
|
||||
struct ETCP_LINK* l = etcp->links;
|
||||
while (l) {
|
||||
l->inflight_bytes = 0;
|
||||
l->inflight_packets = 0;
|
||||
l->delivered_bytes = 0;
|
||||
l->acked_bytes = 0;
|
||||
l->acked_packets = 0;
|
||||
l->last_ack_time_tb = get_time_tb();
|
||||
l = l->next;
|
||||
}
|
||||
@@ -453,11 +453,6 @@ 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");
|
||||
}
|
||||
|
||||
@@ -617,16 +612,6 @@ static void input_queue_try_resume(struct ETCP_CONN* etcp) {// при ACK
|
||||
}
|
||||
}
|
||||
|
||||
// Called from link-level etcp_link_update_inflight_lim() after inflight_lim_bytes changes.
|
||||
// Recalculates connection-level optimal_inflight and resumes input_queue if room opened up.
|
||||
void etcp_conn_on_inflight_lim_changed(struct ETCP_CONN* etcp) {
|
||||
if (!etcp) return;
|
||||
uint32_t sum = 0;
|
||||
for (struct ETCP_LINK* tl = etcp->links; tl; tl = tl->next) sum += tl->inflight_lim_bytes;
|
||||
etcp->optimal_inflight = sum;
|
||||
// input_queue_try_resume(etcp);
|
||||
}
|
||||
|
||||
void etcp_stats(struct ETCP_CONN* etcp) {
|
||||
if (!etcp) return;
|
||||
|
||||
@@ -767,6 +752,7 @@ static void ack_timeout_check(struct ETCP_CONN* etcp) {
|
||||
// shedule timer
|
||||
int64_t next_timeout=timeout - elapsed;
|
||||
if (next_timeout<0) next_timeout=0;
|
||||
if (next_timeout>timeout) next_timeout=timeout;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] retransmission timer set for %llu units", etcp->log_name, next_timeout);
|
||||
etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, next_timeout+10, etcp, ack_timeout_cb, "etcp_retrans");
|
||||
return;
|
||||
@@ -869,7 +855,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 ", link->recv_keepalive, link->remote_keepalive, (link->shaper_timer)?"wait":"rdy");
|
||||
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");
|
||||
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);
|
||||
@@ -939,6 +925,7 @@ 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 (link->send_blocked_inflight && link->inflight_bytes < link->inflight_lim_bytes) loadbalancer_link_ready(link);
|
||||
}
|
||||
|
||||
// Always add to the CURRENT link (first send or retransmission)
|
||||
@@ -1249,28 +1236,18 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d
|
||||
link->delivered_bytes += rs.delivered;
|
||||
link->last_ack_time_tb = now_tb;
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] BBR in: seq=%u del=%u iv=%uus rtt=%uus pri=%u tx_infl=%u cur=%u/%u lost=%d app=%d",
|
||||
link->local_link_id, seq, rs.delivered, rs.interval_us, rs.rtt_us,
|
||||
rs.prior_delivered, rs.tx_in_flight, link->inflight_bytes, link->inflight_lim_bytes,
|
||||
rs.lost, rs.is_app_limited);
|
||||
|
||||
uint32_t old_cwnd = link->inflight_lim_bytes;
|
||||
uint32_t old_pacing = link->bbr_pacing_rate;
|
||||
uint32_t new_cwnd = old_cwnd;
|
||||
uint32_t new_pacing = old_pacing;
|
||||
uint32_t new_cwnd = link->inflight_lim_bytes;
|
||||
uint32_t new_pacing = link->bbr_pacing_rate;
|
||||
bbr_main(link->bbr, &rs, &new_cwnd, &new_pacing, link->mtu, link->inflight_bytes,
|
||||
(link->inflight_bytes >= link->inflight_lim_bytes));
|
||||
etcp_link_update_inflight_lim(link, new_cwnd);
|
||||
link->bbr_pacing_rate = new_pacing;
|
||||
link->bandwidth = (uint32_t)((uint64_t)new_pacing * 8 / 1000);
|
||||
|
||||
if (link->send_blocked_inflight && link->inflight_bytes < new_cwnd) {
|
||||
link->send_blocked_inflight = 0;
|
||||
loadbalancer_link_ready(link);
|
||||
}
|
||||
link->bbr_loss_since_ack = 0;
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] BBR out: cwnd %u→%u pace %u→%u bw=%uK mode=%d cyc=%d infl=%u/%u",
|
||||
link->local_link_id, old_cwnd, new_cwnd, old_pacing, new_pacing,
|
||||
link->bandwidth, link->bbr->mode, link->bbr->cycle_idx, link->inflight_bytes, new_cwnd);
|
||||
|
||||
link->acked_bytes += acked_pkt->ll.len;
|
||||
link->acked_packets++;
|
||||
}
|
||||
|
||||
|
||||
@@ -1301,10 +1278,6 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d
|
||||
void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "");
|
||||
if (!pkt) return;
|
||||
if (memory_pool_is_freed(pkt->link->etcp->instance->pkt_pool, pkt)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_conn_input: pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt);
|
||||
volatile int _halt = 1; while (_halt) {}
|
||||
}
|
||||
if (!pkt->data_len) {
|
||||
memory_pool_free(pkt->link->etcp->instance->pkt_pool, pkt);
|
||||
return;
|
||||
@@ -1432,7 +1405,7 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] set ack_timer for delayed ACK send", etcp->log_name);
|
||||
etcp->ack_resp_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_TB, etcp, ack_response_timer_cb, "etcp_ack_resp");
|
||||
}
|
||||
}
|
||||
} else DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX ack dedup: seq=%d already in ack_q", etcp->log_name, seq);
|
||||
if (((int32_t)(etcp->last_delivered_id-seq)<0) && (queue_find_data_by_index(etcp->recv_q, &seq)==NULL)) {// проверяем есть ли пакет с этим seq
|
||||
uint32_t pkt_len=len-5;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] adding packet seq=%u to recv_q (last_delivered_id=%u)", etcp->log_name, seq, etcp->last_delivered_id);
|
||||
@@ -1574,12 +1547,6 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
|
||||
}
|
||||
|
||||
if (memory_pool_is_freed(pkt->link->etcp->instance->pkt_pool, pkt)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_conn_input: pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt);
|
||||
volatile int _halt = 1; while (_halt) {}
|
||||
}
|
||||
|
||||
|
||||
memory_pool_free(etcp->instance->pkt_pool, pkt); // Free the incoming dgram
|
||||
}
|
||||
|
||||
|
||||
+2
-7
@@ -13,7 +13,6 @@ extern "C" {
|
||||
|
||||
#include "pkt_normalizer.h"
|
||||
|
||||
struct stcp_link; // forward declaration
|
||||
// In struct ETCP_CONN, add:
|
||||
//struct pn_pair* normalizer;
|
||||
|
||||
@@ -105,7 +104,6 @@ struct ETCP_CONN {
|
||||
// Links (channels) - linked list
|
||||
struct ETCP_LINK* links;
|
||||
struct ETCP_LINK* last_rr_link; // последний линк, выбранный round-robin
|
||||
struct stcp_link *transport_link; // non-NULL for TCP connections
|
||||
|
||||
// Crypto and state
|
||||
struct secure_channel crypto_ctx;
|
||||
@@ -133,7 +131,7 @@ struct ETCP_CONN {
|
||||
|
||||
|
||||
// IDs and state
|
||||
uint32_t next_tx_id; // ID для добавления в очередь отправки (с этим id будет добавлен следующий пакет)
|
||||
uint32_t next_tx_id; // Next TX ID
|
||||
uint32_t last_rx_id; // Last received ID
|
||||
uint32_t last_delivered_id; // Last delivered to output_queue
|
||||
|
||||
@@ -156,7 +154,7 @@ struct ETCP_CONN {
|
||||
|
||||
// Window and inflight management
|
||||
uint32_t unacked_bytes; // Current inflight bytes
|
||||
uint32_t max_inflight; // Max inflight cap from config (bbr_max_cwnd)
|
||||
// uint32_t window_size; // Receive window - Not used
|
||||
uint32_t optimal_inflight; // Sum over links
|
||||
|
||||
// Timers
|
||||
@@ -232,9 +230,6 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp);
|
||||
// Process ACK receipt - remove acknowledged packet from inflight queues
|
||||
void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t dts);
|
||||
|
||||
// Recalculate optimal_inflight and resume input queue after link inflight_lim change
|
||||
void etcp_conn_on_inflight_lim_changed(struct ETCP_CONN* etcp);
|
||||
|
||||
// Update log_name when peer_node_id becomes known
|
||||
void etcp_update_log_name(struct ETCP_CONN* etcp);
|
||||
|
||||
|
||||
+163
-31
@@ -1,65 +1,197 @@
|
||||
/**
|
||||
* @file etcp_api.c
|
||||
* @brief ETCP API — send/bind/recv dispatch
|
||||
* @brief Реализация API для приёма-передачи пакетов через ETCP (per-instance bindings)
|
||||
*/
|
||||
|
||||
#include "etcp_api.h"
|
||||
#include "etcp.h"
|
||||
#include "pkt_normalizer.h"
|
||||
#include "utun_instance.h"
|
||||
#include "stcp_link.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include <string.h>
|
||||
#include "route_lib.h"
|
||||
#include "route_bgp.h"
|
||||
|
||||
#define DEBUG_CATEGORY_ETCP_API DEBUG_CATEGORY_ETCP
|
||||
#define DEBUG_CATEGORY_ETCP_API 1
|
||||
|
||||
void etcp_conn_set_ready_cbk(struct ETCP_CONN* e, etcp_cbk_fn fn, void* arg) {
|
||||
if (e) { e->ready_cbk = fn; e->ready_arg = arg; }
|
||||
#define ETCP_MAX_BINDINGS 256 /**< Максимальное количество bindings (по одному на каждый ID 0-255) */
|
||||
|
||||
// после создания подключения надо выждать conn_ready. только после этого можно передавать сообщения. Иначе сообщения могут потеряться.
|
||||
// conn_ready может вызваться несколько раз (после каждого переподключения - например когда удаленный узел перегрузился или возникла неустранимая ошибка).
|
||||
void etcp_conn_set_ready_cbk(struct ETCP_CONN* etcp, etcp_cbk_fn callback_fn, void* arg) {
|
||||
if (!etcp) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "NULL conn"); return; }
|
||||
etcp->ready_cbk = callback_fn;
|
||||
etcp->ready_arg = arg;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "conn=%s cbk=%p arg=%p", etcp->log_name, (void*)callback_fn, arg);
|
||||
}
|
||||
void etcp_conn_set_up_cbk(struct ETCP_CONN* e, etcp_cbk_fn fn, void* arg) {
|
||||
if (e) { e->up_cbk = fn; e->up_arg = arg; }
|
||||
|
||||
void etcp_conn_set_up_cbk(struct ETCP_CONN* etcp, etcp_cbk_fn callback_fn, void* arg) {
|
||||
if (!etcp) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "NULL conn"); return; }
|
||||
etcp->up_cbk = callback_fn;
|
||||
etcp->up_arg = arg;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "conn=%s cbk=%p arg=%p", etcp->log_name, (void*)callback_fn, arg);
|
||||
}
|
||||
void etcp_conn_set_down_cbk(struct ETCP_CONN* e, etcp_cbk_fn fn, void* arg) {
|
||||
if (e) { e->down_cbk = fn; e->down_arg = arg; }
|
||||
|
||||
void etcp_conn_set_down_cbk(struct ETCP_CONN* etcp, etcp_cbk_fn callback_fn, void* arg) {
|
||||
if (!etcp) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "NULL conn"); return; }
|
||||
etcp->down_cbk = callback_fn;
|
||||
etcp->down_arg = arg;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "conn=%s cbk=%p arg=%p", etcp->log_name, (void*)callback_fn, arg);
|
||||
}
|
||||
void etcp_set_new_conn_cbk(struct UTUN_INSTANCE* inst, etcp_cbk_fn fn, void* arg) {
|
||||
if (inst) { inst->etcp_new_conn_cbk = fn; inst->etcp_new_conn_arg = arg; }
|
||||
|
||||
// Установить callback при создании нового ETCP соединения
|
||||
void etcp_set_new_conn_cbk(struct UTUN_INSTANCE* instance, etcp_cbk_fn callback_fn, void* arg) {
|
||||
if (!instance) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_set_new_conn_cbk: NULL instance");
|
||||
return;
|
||||
}
|
||||
instance->etcp_new_conn_cbk = callback_fn;
|
||||
instance->etcp_new_conn_arg = arg;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "etcp_set_new_conn_cbk: instance=%p cbk=%p arg=%p",
|
||||
(void*)instance, (void*)callback_fn, arg);
|
||||
}
|
||||
|
||||
int etcp_bind(struct UTUN_INSTANCE* inst, uint8_t id, etcp_recv_fn callback) {
|
||||
if (!inst || !callback || (unsigned)id >= ETCP_MAX_BINDINGS) return -1;
|
||||
if (!inst) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_bind: NULL instance");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((unsigned)id >= ETCP_MAX_BINDINGS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Invalid ID %u (max=%d)", id, ETCP_MAX_BINDINGS);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!callback) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_bind: NULL callback for id=%u", id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (inst->api_bindings.callbacks[id] != NULL) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Overwriting existing callback for id=%u", id);
|
||||
}
|
||||
inst->api_bindings.callbacks[id] = callback;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Bound id=%u → callback=%p", id, (void*)callback);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int etcp_unbind(struct UTUN_INSTANCE* inst, uint8_t id) {
|
||||
if (!inst || (unsigned)id >= ETCP_MAX_BINDINGS) return -1;
|
||||
if (!inst) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: NULL instance");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((unsigned)id >= ETCP_MAX_BINDINGS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: Invalid ID %u", id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (inst->api_bindings.callbacks[id] == NULL) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: ID %u not bound", id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
inst->api_bindings.callbacks[id] = NULL;
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: Unbound ID %u for instance %p", id, (void*)inst);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int etcp_send(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
if (!conn || !entry) return -1;
|
||||
// TCP (stcp_link) — send directly
|
||||
if (conn->transport_link)
|
||||
return stcp_link_send((struct stcp_link *)conn->transport_link, entry->dgram, entry->len);
|
||||
// UDP — через normalizer
|
||||
if (!conn->normalizer || !conn->normalizer->input) return -1;
|
||||
return queue_data_put(conn->normalizer->input, entry);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "etcp_send enter conn=%p entry=%p", conn, entry);
|
||||
if (!conn) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: NULL connection");
|
||||
return -1;
|
||||
}
|
||||
if (!entry) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: NULL entry");
|
||||
return -1;
|
||||
}
|
||||
if (!conn->normalizer) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: Connection has no normalizer");
|
||||
return -1;
|
||||
}
|
||||
if (conn->initialized==0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: Connection is not initialized");
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct PKTNORM* pn = conn->normalizer;
|
||||
if (!pn || !pn->input) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: pn=%p or input null", pn);
|
||||
return -1;
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_send: queued to [%s]", conn->log_name);
|
||||
int result = queue_data_put(pn->input, entry);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_NORMALIZER, "etcp_send after put result=%d", result);
|
||||
if (result != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_send: queue_data_put failed");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void etcp_int_recv(struct ll_queue* queue, void* arg) {
|
||||
struct ETCP_CONN* conn = (struct ETCP_CONN*)arg;
|
||||
if (!queue || !conn) return;
|
||||
struct ll_entry* e = queue_data_get(queue);
|
||||
if (!e) { queue_resume_callback(queue); return; }
|
||||
if (!e->dgram || e->len == 0) { queue_entry_free(e); queue_dgram_free(e); queue_resume_callback(queue); return; }
|
||||
uint8_t id = e->dgram[0];
|
||||
|
||||
if (!queue || !conn) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Invalid arguments (queue=%p, conn=%p)",
|
||||
(void*)queue, (void*)conn);
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем элемент из очереди
|
||||
struct ll_entry* entry = queue_data_get(queue);
|
||||
if (!entry) {
|
||||
// Очередь пуста - это нормально, просто возобновляем коллбэк
|
||||
queue_resume_callback(queue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем что есть данные
|
||||
if (!entry->dgram || entry->len == 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Empty packet received");
|
||||
queue_entry_free(entry);
|
||||
queue_dgram_free(entry);
|
||||
queue_resume_callback(queue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем ID пакета (первый байт кодограммы)
|
||||
uint8_t id = entry->dgram[0];
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Received packet with ID %u from conn [%s] %p",
|
||||
id, conn->log_name, (void*)conn);
|
||||
|
||||
// Получаем instance из соединения
|
||||
struct UTUN_INSTANCE* inst = conn->instance;
|
||||
if (!inst) { queue_entry_free(e); queue_dgram_free(e); queue_resume_callback(queue); return; }
|
||||
if (inst->api_bindings.callbacks[id])
|
||||
inst->api_bindings.callbacks[id](conn, e);
|
||||
else if (inst->api_bindings.callbacks[0])
|
||||
inst->api_bindings.callbacks[0](conn, e);
|
||||
else { queue_entry_free(e); queue_dgram_free(e); }
|
||||
if (!inst) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Connection has no instance");
|
||||
queue_entry_free(entry);
|
||||
queue_dgram_free(entry);
|
||||
queue_resume_callback(queue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ищем binding для этого ID в instance
|
||||
if (inst->api_bindings.callbacks[id] != NULL) {
|
||||
// Вызываем коллбэк
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Delivering packet ID %u to callback %p",
|
||||
id, (void*)inst->api_bindings.callbacks[id]);
|
||||
inst->api_bindings.callbacks[id](conn, entry);
|
||||
} else {
|
||||
// Нет binding для этого ID - по умолчанию считаем что это данные (ID=0)
|
||||
if (inst->api_bindings.callbacks[0] != NULL) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_recv: No binding for ID %u, routing to default (ID=0)", id);
|
||||
inst->api_bindings.callbacks[0](conn, entry);
|
||||
} else {
|
||||
// Нет default binding - освобождаем пакет
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_recv: No binding for ID %u and no default handler, dropping packet", id);
|
||||
queue_entry_free(entry);
|
||||
queue_dgram_free(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Возобновляем коллбэк для получения следующего пакета
|
||||
queue_resume_callback(queue);
|
||||
}
|
||||
+1
-5
@@ -6,8 +6,6 @@
|
||||
* - etcp_send() - отправить пакет в очередь normalizer
|
||||
* - etcp_bind() - подписаться на пакеты с определенным ID
|
||||
* - etcp_int_recv() - коллбэк для сбора пакетов из всех подключений
|
||||
* !!! для обычной отправки-приёма между узлами используем более универсальный модушь etcp_router.
|
||||
* это в первую очередь - апи для использования etcp-router-ом
|
||||
*
|
||||
* Формат кодограмм: <cmd 1 byte> <data ... n bytes>
|
||||
* cmd = 0 - пакет для передачи адресату
|
||||
@@ -30,9 +28,7 @@
|
||||
#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_MSG_TRANSPORT 0x10 // msg_transport — локальный IPC транспорт сообщений
|
||||
#define ETCP_ID_CONN_MGR 0x11 // Connection Manager — management connections
|
||||
#define ETCP_ID_TCP_PROXY_CLIENT 0x07 // TCP proxy client (клиент)
|
||||
|
||||
// Forward declarations
|
||||
struct ETCP_CONN;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "etcp_bbr.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
static uint64_t bbr_now(const struct bbr* bbr) { return bbr->now_tb ? bbr->now_tb : get_time_tb(); }
|
||||
@@ -328,14 +327,10 @@ static void bbr_exit_probe_rtt(struct bbr* bbr)
|
||||
{
|
||||
bbr_reset_lower_bounds(bbr);
|
||||
if (bbr->full_bw_reached) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d exit probe_rtt → PROBE_BW",
|
||||
bbr->link_id, bbr->mode, BBR_PROBE_BW);
|
||||
bbr->mode = BBR_PROBE_BW;
|
||||
bbr_start_bw_probe_down(bbr);
|
||||
bbr_start_bw_probe_cruise(bbr);
|
||||
} else {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d exit probe_rtt → STARTUP",
|
||||
bbr->link_id, bbr->mode, BBR_STARTUP);
|
||||
bbr->mode = BBR_STARTUP;
|
||||
}
|
||||
}
|
||||
@@ -541,13 +536,11 @@ static void bbr_check_drain(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
{
|
||||
(void)rs; (void)sample_bw;
|
||||
if (bbr->mode == BBR_STARTUP && bbr->full_bw_reached) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d STARTUP→DRAIN", bbr->link_id, bbr->mode, BBR_DRAIN);
|
||||
bbr->mode = BBR_DRAIN;
|
||||
bbr_reset_congestion_signals(bbr);
|
||||
}
|
||||
if (bbr->mode == BBR_DRAIN &&
|
||||
inflight_packets <= bbr_inflight(bbr, bbr_max_bw(bbr), BBR_UNIT, mss)) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d DRAIN→PROBE_BW infl_pkt=%u", bbr->link_id, bbr->mode, BBR_PROBE_BW, inflight_packets);
|
||||
bbr->mode = BBR_PROBE_BW;
|
||||
bbr_start_bw_probe_down(bbr);
|
||||
}
|
||||
@@ -591,8 +584,6 @@ static void bbr_update_min_rtt(struct bbr* bbr, const struct bbr_rate_sample* rs
|
||||
|
||||
if (BBR_PROBE_RTT_MS > 0 && probe_rtt_expired &&
|
||||
!bbr->idle_restart && bbr->mode != BBR_PROBE_RTT) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d →PROBE_RTT cwnd=%u infl_pkt=%u",
|
||||
bbr->link_id, bbr->mode, BBR_PROBE_RTT, *cwnd, inflight_packets);
|
||||
bbr->mode = BBR_PROBE_RTT;
|
||||
bbr_save_cwnd(bbr, *cwnd);
|
||||
bbr->probe_rtt_done_stamp_tb = 0;
|
||||
@@ -619,7 +610,6 @@ static void bbr_update_min_rtt(struct bbr* bbr, const struct bbr_rate_sample* rs
|
||||
|
||||
static void bbr_reset_startup_mode(struct bbr* bbr)
|
||||
{
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d →STARTUP (reset)", bbr->link_id, bbr->mode, BBR_STARTUP);
|
||||
bbr->mode = BBR_STARTUP;
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,6 @@ struct bbr {
|
||||
uint8_t loss_round_start : 1;
|
||||
uint8_t full_bw_now : 1;
|
||||
uint8_t pad_unused : 6;
|
||||
uint8_t link_id; // local_link_id for debug logging
|
||||
uint64_t now_tb; // 0 = real get_time_tb(); >0 = test-controlled time
|
||||
bbr_cwnd_update_fn on_cwnd_update;
|
||||
void* cwnd_update_ctx;
|
||||
|
||||
+361
-439
@@ -15,7 +15,6 @@
|
||||
#include "config_parser.h"
|
||||
#include "crc32.h"
|
||||
#include "etcp.h"
|
||||
#include "stcp_link.h"
|
||||
#include "route_node.h"
|
||||
#include "route_bgp.h"
|
||||
#include "route_ping.h"
|
||||
@@ -28,22 +27,6 @@
|
||||
#include "../lib/mem.h"
|
||||
#include "etcp.h"
|
||||
|
||||
// TCP server: on new incoming connection → create minimal ETCP_CONN
|
||||
static void tcp_server_on_link(struct stcp_link *link, void *arg) {
|
||||
struct UTUN_INSTANCE *inst = (struct UTUN_INSTANCE *)arg;
|
||||
struct ETCP_CONN *conn = u_calloc(1, sizeof(struct ETCP_CONN));
|
||||
if (!conn) return;
|
||||
conn->instance = inst;
|
||||
conn->transport_link = link;
|
||||
snprintf(conn->log_name, sizeof(conn->log_name), "tcp-[%p]", (void*)link);
|
||||
conn->next = inst->connections;
|
||||
inst->connections = conn;
|
||||
inst->connections_count++;
|
||||
if (inst->etcp_new_conn_cbk) inst->etcp_new_conn_cbk(conn, inst->etcp_new_conn_arg);
|
||||
if (conn->ready_cbk) conn->ready_cbk(conn, conn->ready_arg);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "TCP server new conn=%p total=%d", (void*)conn, inst->connections_count);
|
||||
}
|
||||
|
||||
// Forward declaration
|
||||
void etcp_connections_read_callback_socket(socket_t sock, void* arg);
|
||||
static void etcp_link_remove_from_connections(struct ETCP_SOCKET* conn, struct ETCP_LINK* link);
|
||||
@@ -112,27 +95,41 @@ 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;
|
||||
|
||||
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;
|
||||
// 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;
|
||||
|
||||
// padding
|
||||
int s = rand() % (link->handshake_maxsize - link->handshake_minsize) + link->handshake_minsize;
|
||||
@@ -165,7 +162,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)link->etcp->instance->node_id, link->init_retry_count);
|
||||
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: print remote address before sending
|
||||
if (link->remote_addr.ss_family == AF_INET) {
|
||||
@@ -739,9 +736,9 @@ void etcp_socket_remove(struct ETCP_SOCKET* conn) {
|
||||
size_t i = 0;
|
||||
while (i < conn->num_channels) {
|
||||
struct ETCP_LINK* l = conn->links[i];
|
||||
int had_conn = (l->conn != NULL);
|
||||
etcp_link_close(l);
|
||||
conn->links[i] = NULL; // обнулить после — etcp_link_close обращается к массиву через remove_link
|
||||
i++;
|
||||
if (!had_conn) i++;
|
||||
}
|
||||
u_free(conn->links);
|
||||
|
||||
@@ -790,7 +787,6 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn
|
||||
link->keepalive_sent_count = 0;
|
||||
link->keepalive_recv_count = 0;
|
||||
link->ka_period_ms = KA_PERIOD_MIN_MS;
|
||||
link->inflight_lim_bytes = link->mtu * 4; // BBR init_cwnd (~4 packets)
|
||||
link->bandwidth = 10000; // начальная оценка 10 Mbps для шейпера
|
||||
link->burst_id = 0;
|
||||
link->burst_active = 0;
|
||||
@@ -798,8 +794,6 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn
|
||||
link->burst_target_bdp = 0;
|
||||
|
||||
link->delivered_bytes = 0;
|
||||
link->acked_bytes = 0;
|
||||
link->acked_packets = 0;
|
||||
link->last_ack_time_tb = get_time_tb();
|
||||
link->bbr_pacing_rate = 0;
|
||||
link->bbr_loss_since_ack = 0;
|
||||
@@ -818,7 +812,6 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn
|
||||
return NULL;
|
||||
}
|
||||
link->local_link_id = (uint8_t)free_id;
|
||||
if (link->bbr) link->bbr->link_id = (uint8_t)free_id;
|
||||
|
||||
memcpy(&link->remote_addr, remote_addr, sizeof(struct sockaddr_storage));
|
||||
|
||||
@@ -838,7 +831,12 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn
|
||||
while (l && l->next) l=l->next;
|
||||
if (l) l->next = link; else etcp->links = link;
|
||||
|
||||
etcp_link_update_inflight_lim(link, link->inflight_lim_bytes);
|
||||
link->inflight_lim_bytes = link->mtu * 4; // BBR init_cwnd (~4 packets)
|
||||
|
||||
// пересчитать connection-level optimal_inflight
|
||||
{ uint32_t sum = 0;
|
||||
for (struct ETCP_LINK* tl = etcp->links; tl; tl = tl->next) sum += tl->inflight_lim_bytes;
|
||||
etcp->optimal_inflight = sum; }
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "NEW link initialized on etcp=[%s] link=%p socket=%s id=%d is_server=%d mtu=%d", etcp->log_name, link, conn->name, link->local_link_id, link->is_server, link->mtu);
|
||||
|
||||
@@ -850,15 +848,6 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn
|
||||
return link;
|
||||
}
|
||||
|
||||
void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim) {
|
||||
if (!link || !link->etcp) return;
|
||||
if (new_lim < INFLIGHT_LIM_MIN) new_lim = INFLIGHT_LIM_MIN;
|
||||
if (new_lim > link->etcp->max_inflight) new_lim = link->etcp->max_inflight;
|
||||
|
||||
link->inflight_lim_bytes = new_lim;
|
||||
etcp_conn_on_inflight_lim_changed(link->etcp);
|
||||
}
|
||||
|
||||
void etcp_link_close(struct ETCP_LINK* link) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_CONNECTION, "");
|
||||
if (!link) return;
|
||||
@@ -875,7 +864,6 @@ void etcp_link_close(struct ETCP_LINK* link) {
|
||||
if (link->init_timer) uasync_cancel_timeout(link->etcp->instance->ua, link->init_timer);
|
||||
if (link->shaper_timer) uasync_cancel_timeout(link->etcp->instance->ua, link->shaper_timer);
|
||||
if (link->keepalive_timer) uasync_cancel_timeout(link->etcp->instance->ua, link->keepalive_timer);
|
||||
etcp_conn_on_inflight_lim_changed(link->etcp);
|
||||
u_free(link);
|
||||
return;
|
||||
}
|
||||
@@ -911,7 +899,6 @@ void etcp_link_close(struct ETCP_LINK* link) {
|
||||
|
||||
remove_link(link->conn, link->ip_port_hash);
|
||||
|
||||
etcp_conn_on_inflight_lim_changed(link->etcp);
|
||||
u_free(link->bbr);
|
||||
u_free(link);
|
||||
}
|
||||
@@ -1148,306 +1135,6 @@ 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);
|
||||
@@ -1549,28 +1236,116 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
|
||||
|
||||
pkt->data_len=pkt_len-3;
|
||||
pkt->noencrypt_len=0;
|
||||
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; }
|
||||
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);
|
||||
return;
|
||||
}
|
||||
if (code == ETCP_PONG) {
|
||||
int ret = handle_pong(e_sock, pkt, &addr, pkt_len);
|
||||
if (ret) { errorcode = ret; goto ec_fr; }
|
||||
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);
|
||||
return;
|
||||
}
|
||||
if (code!=ETCP_INIT_REQUEST && code!=ETCP_INIT_REQUEST_NOINIT) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "not an init packet, code=%02x", code);
|
||||
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);
|
||||
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;
|
||||
@@ -1596,8 +1371,7 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
|
||||
}
|
||||
}
|
||||
|
||||
struct ETCP_INIT_REQUEST_PKT* req = (struct ETCP_INIT_REQUEST_PKT*)pkt->data;
|
||||
uint32_t session_id = be32toh(*(uint32_t*)req->session_id);
|
||||
uint32_t session_id = be32toh(*(uint32_t*)ack_hdr->session_id);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "INIT request session_id=%08x", session_id);
|
||||
|
||||
struct ETCP_CONN* conn=e_sock->instance->connections;
|
||||
@@ -1627,15 +1401,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, req->link_id);
|
||||
struct ETCP_LINK* existing_link = etcp_link_find_by_remote_id(conn, ack_hdr->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, req->link_id, e_sock->name);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "[%s] found existing link for id=%d, socket=[%s]", conn->log_name, ack_hdr->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, req->link_id, e_sock->name);
|
||||
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);
|
||||
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));
|
||||
@@ -1653,10 +1427,10 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
|
||||
}
|
||||
|
||||
// Link exists - reuse it for recovery
|
||||
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;
|
||||
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;
|
||||
|
||||
// For CHANNEL_INIT (0x04): if link already initialized - no reset, otherwise reset
|
||||
// For INIT_REQUEST (0x02): always reset
|
||||
@@ -1677,14 +1451,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, req->link_id, e_sock->name);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "[%s] NO existing link for id=%d, socket=[%s]", conn->log_name, ack_hdr->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 = req->link_id;
|
||||
link->remote_socket_id = req->socket_id;
|
||||
link->remote_only_local = req->only_local;
|
||||
link->remote_type = req->type;
|
||||
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;
|
||||
|
||||
// For new links: check session_id to avoid false reinit
|
||||
if (conn->session_id != session_id) {
|
||||
@@ -1693,21 +1467,103 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
|
||||
conn->session_id = session_id;
|
||||
etcp_conn_reinit(conn);
|
||||
}
|
||||
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 в кодограмме
|
||||
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 в кодограмме
|
||||
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 = (req->mtu[0] << 8) | req->mtu[1];
|
||||
link->mtu_remote = (ack_hdr->mtu[0] << 8) | ack_hdr->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);
|
||||
|
||||
uint32_t req_src_ip; memcpy(&req_src_ip, req->src_ipv4, 4);
|
||||
uint16_t req_src_port = be16toh(*(uint16_t*)req->src_port);
|
||||
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];
|
||||
|
||||
send_init_response(e_sock, pkt, link, conn, req, &addr, send_reset, req_src_ip, req_src_port, pkt_len);
|
||||
// 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;
|
||||
|
||||
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);
|
||||
}
|
||||
return;
|
||||
|
||||
|
||||
@@ -1740,9 +1596,9 @@ process_decrypted:
|
||||
link->total_decrypted += pkt->data_len;
|
||||
|
||||
size_t offset = 0;
|
||||
uint8_t pkt_code = pkt->data[offset++];
|
||||
uint8_t code = pkt->data[offset++];
|
||||
|
||||
if (pkt_code == ETCP_KEEPALIVE) {
|
||||
if (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;
|
||||
@@ -1752,10 +1608,118 @@ process_decrypted:
|
||||
return; // KA handled, nothing more to process
|
||||
}
|
||||
|
||||
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 (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 (link->link_state == 2) {// из recovery получен нормальный пакет - восстанавливаем линк в нормальный режим
|
||||
@@ -1768,10 +1732,6 @@ process_decrypted:
|
||||
// log_dump("RECV decrypted:", pkt->data, pkt->data_len, link);
|
||||
|
||||
if (link->link_state == 3) {
|
||||
if (memory_pool_is_freed(e_sock->instance->pkt_pool, pkt)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_conn_input: pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt);
|
||||
volatile int _halt = 1; while (_halt) {}
|
||||
}
|
||||
etcp_conn_input(pkt);
|
||||
} else memory_pool_free(e_sock->instance->pkt_pool, pkt);
|
||||
return;
|
||||
@@ -1847,21 +1807,6 @@ int init_sockets(struct UTUN_INSTANCE* instance) {
|
||||
}
|
||||
}
|
||||
|
||||
// TCP transport: create stcp_server instead of UDP socket
|
||||
if (server->transport) {
|
||||
uint16_t port = ntohs(((struct sockaddr_in*)&server->ip)->sin_port);
|
||||
struct stcp_link_config scfg = {.ua = instance->ua, .my_keys = &instance->my_keys, .inst = instance};
|
||||
if (!stcp_server_listen(&scfg, port, tcp_server_on_link, instance)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to create TCP server for %s", server->name);
|
||||
fail_count++;
|
||||
} else {
|
||||
success_count++;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "TCP server %s on port %u", server->name, port);
|
||||
}
|
||||
server = server->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
struct ETCP_SOCKET* e_sock = etcp_socket_add(instance, server);
|
||||
if (e_sock && default_ip != 0) {
|
||||
e_sock->local_defaultroute_ip = default_ip;
|
||||
@@ -2004,29 +1949,6 @@ int init_connections(struct UTUN_INSTANCE* instance) {
|
||||
sock = sock->next;
|
||||
}
|
||||
|
||||
if (local_server->transport) {
|
||||
if (strlen(client->peer_public_key_hex) == 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "TCP client %s has no peer key", client->name);
|
||||
client_link = client_link->next; continue;
|
||||
}
|
||||
// TCP transport — use stcp_link instead of etcp_link_new
|
||||
uint16_t rport = ntohs(((struct sockaddr_in*)&client_link->remote_addr)->sin_port);
|
||||
struct stcp_link_config scfg = {
|
||||
.ua = instance->ua, .my_keys = &instance->my_keys, .inst = instance,
|
||||
.peer_pubkey = (const uint8_t*)client->peer_public_key_hex,
|
||||
.peer_pubkey_mode = 1, // hex
|
||||
.remote_addr = &client_link->remote_addr, .remote_port = rport
|
||||
};
|
||||
struct stcp_link *slink = stcp_link_connect(&scfg);
|
||||
if (!slink) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to create TCP link for client %s", client->name);
|
||||
client_link = client_link->next; continue;
|
||||
}
|
||||
etcp_conn->transport_link = slink;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "TCP link created for client %s", client->name);
|
||||
client_link = client_link->next; continue;
|
||||
}
|
||||
|
||||
if (!e_sock) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "No socket found for client %s link", client->name);
|
||||
client_link = client_link->next;
|
||||
|
||||
+3
-46
@@ -14,9 +14,6 @@
|
||||
#define UDP_SC_HDR_SIZE (13+8+4 + 5)// 13+8+4 - sc_nonce+tag size+crc, 5 - payload hdr
|
||||
#define ACK_REZERV 100// сколько байт резервировать под ack и прочие заголовки
|
||||
|
||||
#define INFLIGHT_LIM_MIN 8192 // 8K
|
||||
#define INFLIGHT_LIM_MAX 1048576 // 1M
|
||||
|
||||
#define PACKET_DATA_SIZE 1600//1536
|
||||
#define PACKET_DATA_MAX_MTU 1600
|
||||
#define ETCP_MAX_PAYLOAD_SIZE (PACKET_DATA_SIZE - ETCP_ACK_BASE_SIZE - 5) /* 1587 */
|
||||
@@ -54,45 +51,6 @@ 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 {
|
||||
@@ -145,7 +103,6 @@ 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
|
||||
@@ -211,6 +168,9 @@ 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;
|
||||
@@ -219,8 +179,6 @@ struct ETCP_LINK {
|
||||
size_t total_encrypted;
|
||||
size_t total_decrypted;
|
||||
uint32_t total_retransmissions;
|
||||
uint64_t acked_bytes; // подтверждённых байт на этом линке
|
||||
uint64_t acked_packets; // подтверждённых пакетов на этом линке
|
||||
|
||||
uint16_t rtt_last; // round trip (время отправки + приёма)
|
||||
uint32_t jitter; // Current jitter [>>16] x0.1 ms
|
||||
@@ -304,7 +262,6 @@ void etcp_socket_remove(struct ETCP_SOCKET* conn);
|
||||
// connection functions
|
||||
// создает новый канал связи для etcp подключения (ETCP_CONN)
|
||||
struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn, struct sockaddr_storage* remote_addr, uint8_t is_server);
|
||||
void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim);
|
||||
void etcp_link_close(struct ETCP_LINK* link);
|
||||
//int etcp_input_cbk(struct packet_buffer* pkt, struct ETCP_SOCKET* conn);// получает расшифрованный пакет
|
||||
int etcp_encrypt_send(struct ETCP_DGRAM* dgram);// зашифровывает и отправляет пакет
|
||||
|
||||
+2
-1
@@ -161,8 +161,9 @@ 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 mode=%d cycle=%d pacing=%u",
|
||||
DUMP_LINE("BBR: bytes=%u pkts=%u lim=%u blocked=%d 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);
|
||||
|
||||
+18
-6
@@ -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: tmr:%s",
|
||||
etcp->log_name, etcp->links->shaper_timer?"wait":"rdy");
|
||||
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");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -181,8 +181,20 @@ 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
|
||||
if (link->shaper_timer) return 0;
|
||||
return 1;
|
||||
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;
|
||||
}
|
||||
|
||||
void loadbalancer_link_ready(struct ETCP_LINK* link) {
|
||||
@@ -190,8 +202,8 @@ void loadbalancer_link_ready(struct ETCP_LINK* link) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "invalid link (%p)", link);
|
||||
return;
|
||||
}
|
||||
if (link->shaper_timer) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "link still blocked by shaper");
|
||||
if (loadbalancer_link_can_send(link) == 0) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "link still blocked");
|
||||
return;
|
||||
}
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "link=%p now ready, notifying ETCP_CONN", link);
|
||||
|
||||
+12
-105
@@ -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, int is_signed);
|
||||
static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, uint32_t seq_flags);
|
||||
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,16 +47,15 @@ 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, 0);
|
||||
return router_send_one_flags(rconn, payload, pl_len, 0);
|
||||
}
|
||||
|
||||
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 int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, uint32_t seq_flags) {
|
||||
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 sig_len = is_signed ? SC_SIGN_SIZE : 0;
|
||||
size_t total_len = SVC_ROUTE_HDR_SIZE + pl_len + sig_len;
|
||||
size_t total_len = SVC_ROUTE_HDR_SIZE + pl_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;
|
||||
@@ -71,57 +70,25 @@ 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 signed=%d → %016llx",
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "ETCP_SEND: svc_id=%u seq=%u len=%zu inflight=%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);
|
||||
}
|
||||
@@ -169,7 +136,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, 0);
|
||||
if (conn) router_send_one_flags(rconn, NULL, 0, ROUTER_SEQ_CLOSE_FLAG);
|
||||
// Таймеры
|
||||
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; }
|
||||
@@ -376,52 +343,6 @@ 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);
|
||||
|
||||
@@ -792,20 +713,6 @@ 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,7 +28,6 @@ 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
|
||||
|
||||
@@ -97,10 +96,6 @@ 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);
|
||||
|
||||
|
||||
@@ -1,638 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
* 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 */
|
||||
@@ -539,8 +539,8 @@ void tcp_proxy_client_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* en
|
||||
if (subcmd == TCP_PROXY_SUBCMD_ERROR) { tcp_proxy_client_handle_error(proxy, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
if (subcmd == TCP_PROXY_SUBCMD_FIN) { tcp_proxy_client_handle_fin(proxy, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
}
|
||||
if (subcmd == TCP_PROXY_SUBCMD_DATA || subcmd == TCP_PROXY_SUBCMD_CLOSE || subcmd == TCP_PROXY_SUBCMD_FIN || subcmd == TCP_PROXY_SUBCMD_ERROR) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "PROXY subcmd=%02x sid=%08x — client conn not found, silent drop", subcmd, stream_id);
|
||||
if (subcmd == TCP_PROXY_SUBCMD_ERROR) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY ERROR sid=%08x — client conn not found, silent drop", stream_id);
|
||||
queue_dgram_free(entry); queue_entry_free(entry); return;
|
||||
}
|
||||
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy client: unhandled subcmd=%02x sid=%08x", subcmd, stream_id);
|
||||
|
||||
@@ -114,16 +114,16 @@ static void on_fin_cb(struct tcp_conn* tc, void* arg) {
|
||||
if (rc->cli_closed) return;
|
||||
int pend = write_pending(tc);
|
||||
if (tc->fin_local) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x fin_local=%d — both FINs, closing",
|
||||
(int)tc->sock, rc->stream_id, tc->fin_local);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x fin_local=%d — both FINs, closing",
|
||||
(int)tc->sock, rc->stream_id, tc->fin_local);
|
||||
send_close(rc); tcp_conn_push_close(tc);
|
||||
} else if (pend) {
|
||||
tcp_conn_set_flushed(tc, on_flushed_cb);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x write_pend=%d → deferred",
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x write_pend=%d → deferred",
|
||||
(int)tc->sock, rc->stream_id, pend);
|
||||
} else {
|
||||
send_fin(rc);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x → relay now",
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x → relay now",
|
||||
(int)tc->sock, rc->stream_id);
|
||||
}
|
||||
}
|
||||
@@ -132,7 +132,7 @@ static void on_flushed_cb(struct tcp_conn* tc, void* arg) {
|
||||
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
|
||||
if (rc->cli_closed) return;
|
||||
if (tc->fin_local) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FLUSHED fd=%d sid=%08x fin_local=%d — both FINs, closing",
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FLUSHED fd=%d sid=%08x fin_local=%d — both FINs, closing",
|
||||
(int)tc->sock, rc->stream_id, tc->fin_local);
|
||||
send_close(rc); tcp_conn_push_close(tc); return;
|
||||
}
|
||||
@@ -141,7 +141,7 @@ static void on_flushed_cb(struct tcp_conn* tc, void* arg) {
|
||||
|
||||
static void on_closed_cb(struct tcp_conn* tc, void* arg) {
|
||||
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:CLOSED fd=%d sid=%08x — freeing",
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:CLOSED fd=%d sid=%08x — freeing",
|
||||
tc ? (int)tc->sock : -1, rc->stream_id);
|
||||
tcp_proxy_server_conn_free(rc);
|
||||
}
|
||||
@@ -156,7 +156,6 @@ static void on_error_cb(struct tcp_conn* tc, int err, void* arg) {
|
||||
|
||||
static void diag_timer_cb(void* arg) {
|
||||
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
|
||||
rc->diag_timer = NULL;
|
||||
struct tcp_conn* tc = rc->tc;
|
||||
if (!tc || tc->sock == SOCKET_INVALID) return;
|
||||
|
||||
@@ -168,7 +167,7 @@ static void diag_timer_cb(void* arg) {
|
||||
getsockopt(tc->sock, SOL_SOCKET, SO_RCVBUF, &rcv_buf, &optlen);
|
||||
getsockopt(tc->sock, SOL_SOCKET, SO_SNDBUF, &snd_buf, &optlen);
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET,
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET,
|
||||
"SOCK:DIAG fd=%d sid=%08x conn=%d fin=%d "
|
||||
"rq=%d(%zub) wq=%d(%zub) wbuf=%s "
|
||||
"ent_f=%d dat_f=%d tcp_rb=%zu tcp_sb=%zu",
|
||||
@@ -208,7 +207,7 @@ static void read_queue_drain_cb(struct ll_queue* q, void* arg) {
|
||||
if (ret == 0) {
|
||||
queue_resume_callback(q);
|
||||
} else {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:BACKPRESSURE fd=%d sid=%08x — pausing drain",
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:BACKPRESSURE fd=%d sid=%08x — pausing drain",
|
||||
(int)rc->tc->sock, rc->stream_id);
|
||||
etcp_router_on_send_ready(inst, rc->peer_node_id, ETCP_ID_TCP_PROXY_CLIENT,
|
||||
&rc->pause_waiter, pause_resume_cb, rc);
|
||||
@@ -235,14 +234,14 @@ static int conn_total(struct tcp_proxy_server_conn* rc) {
|
||||
|
||||
void tcp_proxy_server_conn_free(struct tcp_proxy_server_conn* rc) {
|
||||
if (!rc) return;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FREE enter rc=%p freed=%d sid=%08x", (void*)rc, rc->freed, rc->stream_id);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FREE enter rc=%p freed=%d sid=%08x", (void*)rc, rc->freed, rc->stream_id);
|
||||
if (rc->freed) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:FREE double rc=%p — IGNORED", (void*)rc);
|
||||
return;
|
||||
}
|
||||
rc->freed = 1;
|
||||
int total = conn_total(rc);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FREE fd=%d sid=%08x total=%d cli_closed=%d fin=%d error=%d",
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FREE fd=%d sid=%08x total=%d cli_closed=%d fin=%d error=%d",
|
||||
rc->tc ? (int)rc->tc->sock : -1, rc->stream_id, total, rc->cli_closed, rc->tc ? rc->tc->fin_remote : 0, rc->tc ? rc->tc->error : 0);
|
||||
if (rc->close_pending && rc->ctx && rc->ctx->inst) {
|
||||
rc->close_pending = 0;
|
||||
@@ -257,7 +256,7 @@ void tcp_proxy_server_conn_free(struct tcp_proxy_server_conn* rc) {
|
||||
if (rc->close_timer) { uasync_cancel_timeout(rc->ua, rc->close_timer); rc->close_timer = NULL; }
|
||||
if (rc->diag_timer) { uasync_cancel_timeout(rc->ua, rc->diag_timer); rc->diag_timer = NULL; }
|
||||
if (rc->ctx && rc->ctx->inst) etcp_router_cancel_send_ready(rc->ctx->inst, rc->peer_node_id, ETCP_ID_TCP_PROXY_CLIENT, &rc->pause_waiter);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FREE u_free rc=%p", (void*)rc);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FREE u_free rc=%p", (void*)rc);
|
||||
u_free(rc);
|
||||
}
|
||||
|
||||
@@ -287,11 +286,11 @@ int tcp_proxy_server_handle_connect(struct UTUN_INSTANCE* inst, struct ll_entry*
|
||||
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: socket() failed"); u_free(rc); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
|
||||
ctx->conn_count++;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:NEW fd=%d sid=%08x dest=%d.%d.%d.%d:%d total=%d",
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:NEW fd=%d sid=%08x dest=%d.%d.%d.%d:%d total=%d",
|
||||
(int)sock, stream_id, dest_ip[0],dest_ip[1],dest_ip[2],dest_ip[3],ntohs(dest_port), ctx->conn_count);
|
||||
socket_set_nonblocking(sock);
|
||||
|
||||
rc->tc = tcp_conn_create(inst->ua, sock, 1500, 8192, 4, 0, inst->config->global.tcp_recv_buf, on_fin_cb, on_error_cb, rc);
|
||||
rc->tc = tcp_conn_create(inst->ua, sock, 1500, 8192, 4, 0, on_fin_cb, on_error_cb, rc);
|
||||
if (!rc->tc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: tcp_conn_create failed"); socket_close_wrapper(sock); ctx->conn_count--; u_free(rc); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
|
||||
rc->tc->on_closed = on_closed_cb;
|
||||
queue_set_callback(rc->tc->read_queue, read_queue_drain_cb, rc);
|
||||
@@ -352,7 +351,7 @@ void tcp_proxy_server_handle_close(struct UTUN_INSTANCE* inst, uint32_t stream_i
|
||||
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
|
||||
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(ctx, stream_id);
|
||||
if (!rc) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy server: CLOSE sid=%08x — no conn", stream_id); return; }
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:CLOSE_RECV fd=%d sid=%08x total=%d fin=%d write_pend=%d",
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:CLOSE_RECV fd=%d sid=%08x total=%d fin=%d write_pend=%d",
|
||||
rc->tc ? (int)rc->tc->sock : -1, stream_id, conn_total(rc),
|
||||
rc->tc ? rc->tc->fin_remote : 0, rc->tc ? write_pending(rc->tc) : 0);
|
||||
rc->cli_closed = 1;
|
||||
@@ -374,7 +373,7 @@ void tcp_proxy_server_handle_error(struct UTUN_INSTANCE* inst, uint32_t stream_i
|
||||
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
|
||||
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(ctx, stream_id);
|
||||
if (!rc) { DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy server: ERROR sid=%08x — no conn", stream_id); return; }
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:ERROR_RECV fd=%d sid=%08x total=%d fin=%d write_pend=%d",
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:ERROR_RECV fd=%d sid=%08x total=%d fin=%d write_pend=%d",
|
||||
rc->tc ? (int)rc->tc->sock : -1, stream_id, conn_total(rc),
|
||||
rc->tc ? rc->tc->fin_remote : 0, rc->tc ? write_pending(rc->tc) : 0);
|
||||
tcp_proxy_server_handle_close(inst, stream_id);
|
||||
@@ -385,7 +384,7 @@ void tcp_proxy_server_handle_fin(struct UTUN_INSTANCE* inst, uint32_t stream_id)
|
||||
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
|
||||
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(ctx, stream_id);
|
||||
if (!rc || !rc->tc || !rc->tc->connected) return;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FIN_RECV fd=%d sid=%08x — pushing FIN to wq",
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FIN_RECV fd=%d sid=%08x — pushing FIN to wq",
|
||||
(int)rc->tc->sock, stream_id);
|
||||
tcp_conn_push_fin(rc->tc);
|
||||
}
|
||||
@@ -430,8 +429,8 @@ void tcp_proxy_server_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
if (subcmd == TCP_PROXY_SUBCMD_FIN) { tcp_proxy_server_handle_fin(inst, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
}
|
||||
}
|
||||
if (subcmd == TCP_PROXY_SUBCMD_DATA || subcmd == TCP_PROXY_SUBCMD_CLOSE || subcmd == TCP_PROXY_SUBCMD_FIN || subcmd == TCP_PROXY_SUBCMD_ERROR) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "PROXY subcmd=%02x sid=%08x — server conn not found, silent drop", subcmd, stream_id);
|
||||
if (subcmd == TCP_PROXY_SUBCMD_ERROR) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY ERROR sid=%08x — server conn not found, silent drop", stream_id);
|
||||
queue_dgram_free(entry); queue_entry_free(entry); return;
|
||||
}
|
||||
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy server: unhandled subcmd=%02x sid=%08x", subcmd, stream_id);
|
||||
|
||||
+13
-37
@@ -15,12 +15,10 @@
|
||||
#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"
|
||||
|
||||
|
||||
// ============================================================================
|
||||
@@ -246,16 +244,6 @@ 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");
|
||||
@@ -336,8 +324,6 @@ 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);
|
||||
}
|
||||
@@ -370,16 +356,9 @@ 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) {
|
||||
@@ -450,8 +429,6 @@ 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; }
|
||||
@@ -490,9 +467,19 @@ 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 = nodeinfo_find_by_id(bgp, node_id);
|
||||
struct NODEINFO_Q* nq = route_bgp_get_node(bgp, node_id);
|
||||
if (!nq || !nq->paths || !nq->paths->head) return NULL;
|
||||
struct ll_entry* e = nq->paths->head;
|
||||
struct NODEINFO_PATH* best = NULL;
|
||||
@@ -663,7 +650,6 @@ 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) {
|
||||
@@ -742,7 +728,7 @@ int route_bgp_process_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, co
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct NODEINFO_Q* nodeinfo1 = nodeinfo_find_by_id(bgp, node_id);
|
||||
struct NODEINFO_Q* nodeinfo1 = route_bgp_get_node(bgp, node_id);
|
||||
uint8_t new_ver = ni->ver;
|
||||
|
||||
if (nodeinfo1 && (int8_t)(nodeinfo1->last_ver-new_ver)>=0) {
|
||||
@@ -785,8 +771,6 @@ 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) ||
|
||||
@@ -829,12 +813,6 @@ 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)
|
||||
@@ -872,7 +850,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 = nodeinfo_find_by_id(bgp, node_id);
|
||||
struct NODEINFO_Q* nq = route_bgp_get_node(bgp, node_id);
|
||||
if (!nq) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "node not found");
|
||||
return 0;
|
||||
@@ -880,7 +858,6 @@ 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; }
|
||||
@@ -1013,7 +990,6 @@ 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;
|
||||
|
||||
+6
-5
@@ -4,7 +4,6 @@
|
||||
#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"
|
||||
@@ -89,9 +88,6 @@ 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
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -155,7 +151,12 @@ 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);
|
||||
|
||||
int nodeinfo_dyn_size(struct NODEINFO* node);
|
||||
/**
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* @brief Поиск оптимального ETCP соединения для указанного node_id.
|
||||
|
||||
+1
-12
@@ -121,13 +121,6 @@ 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");
|
||||
@@ -183,12 +176,9 @@ 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;
|
||||
@@ -203,7 +193,6 @@ 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;
|
||||
|
||||
|
||||
+1
-15
@@ -33,12 +33,6 @@ 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 {
|
||||
@@ -55,7 +49,6 @@ 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)
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -64,8 +57,7 @@ struct NODE_CONNECTIVITY {
|
||||
struct NODEINFO {
|
||||
uint64_t node_id; // (big-endian)
|
||||
uint8_t ver; // версия пакета (циклический счетчик чтобы быстро сравнивать с локальной копией - были ли обновления)
|
||||
uint8_t public_key[SC_PUBKEY_SIZE]; // X25519 pubkey
|
||||
uint8_t ed25519_public_key[SC_PUBKEY_SIZE]; // Ed25519 pubkey for signature verification
|
||||
uint8_t public_key[SC_PUBKEY_SIZE]; // node pubkey
|
||||
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 адресов узла (плоский список)
|
||||
@@ -140,10 +132,6 @@ 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; // Всегда в конце структуры - динамически расширяемый блок
|
||||
@@ -185,6 +173,4 @@ 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
|
||||
@@ -1,150 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
#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
|
||||
+1
-6
@@ -325,11 +325,6 @@ 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);
|
||||
}
|
||||
@@ -343,7 +338,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 = nodeinfo_find_by_id(bgp, req_pkt->node_id);
|
||||
struct NODEINFO_Q* nq = route_bgp_get_node(bgp, req_pkt->node_id);
|
||||
if (nq) {
|
||||
const struct NODEINFO_IPV4_ADDR* addrs;
|
||||
int count = get_node_v4_addrs(nq, &addrs);
|
||||
|
||||
+600
-272
File diff suppressed because it is too large.
Load diff
+7
-38
@@ -7,7 +7,7 @@
|
||||
|
||||
// Размеры ключей
|
||||
#define SC_PRIVKEY_SIZE 32
|
||||
#define SC_PUBKEY_SIZE 32
|
||||
#define SC_PUBKEY_SIZE 64
|
||||
#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,10 +43,14 @@ 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;
|
||||
@@ -71,39 +75,4 @@ sc_status_t sc_sha_transcode(const uint8_t *key, size_t key_len, uint8_t *data,
|
||||
// Obfuscate pubkey при передаче: XOR с SHA256(salt+peer_pubkey) || SHA256(peer_pubkey+salt)
|
||||
sc_status_t sc_obfuscate_pubkey(const uint8_t *salt, const uint8_t *peer_pubkey, const uint8_t *pubkey, uint8_t *output);
|
||||
|
||||
// --- Streaming cipher (AES-128-CTR, confidentiality only) ---
|
||||
|
||||
#define SC_STREAM_NONCE_SIZE 12
|
||||
|
||||
struct sc_stream_state {
|
||||
void *ectx; // EVP_CIPHER_CTX*
|
||||
uint8_t initialized;
|
||||
};
|
||||
|
||||
sc_status_t sc_stream_init(sc_context_t *ctx, struct sc_stream_state *state, uint32_t stream_id);
|
||||
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
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
// stcp.c — shared stcp_conn lifecycle
|
||||
#include "stcp.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/mem.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
void stcp_conn_set_tx_queue(struct stcp_conn *c, struct ll_queue *q) {
|
||||
if (!c || !q) return;
|
||||
c->tx_queue = q;
|
||||
if (c->tx_cb) queue_set_callback(q, c->tx_cb, c);
|
||||
}
|
||||
|
||||
void stcp_conn_set_rx_queue(struct stcp_conn *c, struct ll_queue *q) {
|
||||
if (!c || !q) return;
|
||||
c->rx_queue = q;
|
||||
}
|
||||
|
||||
void stcp_conn_set_on_close(struct stcp_conn *c, void (*cb)(struct stcp_conn *conn, int err, void *arg), void *arg) {
|
||||
if (!c || !cb) return;
|
||||
c->on_close = cb;
|
||||
c->close_arg = arg;
|
||||
}
|
||||
|
||||
void stcp_conn_free(struct stcp_conn *c) {
|
||||
if (!c) return;
|
||||
if (c->socket_id) { uasync_remove_socket_t(c->ua, c->sock); c->socket_id = NULL; }
|
||||
if (c->sock != SOCKET_INVALID) { socket_close_wrapper(c->sock); c->sock = SOCKET_INVALID; }
|
||||
if (c->recv_buf) { u_free(c->recv_buf); c->recv_buf = NULL; }
|
||||
if (c->send_buf) { u_free(c->send_buf); c->send_buf = NULL; }
|
||||
sc_stream_cleanup(&c->stream_send);
|
||||
sc_stream_cleanup(&c->stream_recv);
|
||||
if (c->allocated) u_free(c);
|
||||
}
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
// stcp.h — Streaming TCP: shared structures, constants, connection lifecycle
|
||||
#ifndef STCP_H
|
||||
#define STCP_H
|
||||
|
||||
#include "secure_channel.h"
|
||||
#include "crc32.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/socket_compat.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#define STCP_MAX_MSG_SIZE 65535
|
||||
#define STCP_RECV_BUF_INIT 8192
|
||||
#define STCP_RECV_BUF_MAX 131072
|
||||
#define STCP_HS_TIMEOUT 5000
|
||||
#define STCP_CONNECT_TIMEOUT 10000
|
||||
|
||||
#define STCP_HS_CLIENT_MIN (SC_PUBKEY_ENC_SIZE + 6) // 72 + 2(padding_size enc) + 4(crc) = 78
|
||||
#define STCP_HS_SERVER_MIN (SC_PUBKEY_ENC_SIZE + 7) // 72 + 1(status) + 2(padding_size) + 4(crc) = 79
|
||||
#define STCP_HS_ENC_CLIENT 6 // padding_size(2) + CRC32(4)
|
||||
#define STCP_HS_ENC_SERVER 7 // status(1) + padding_size(2) + CRC32(4)
|
||||
|
||||
#define STCP_STREAM_CLIENT_SEND 0
|
||||
#define STCP_STREAM_SERVER_SEND 1
|
||||
|
||||
enum stcp_state {
|
||||
STCP_STATE_INIT,
|
||||
STCP_STATE_HS_CLIENT_SENT,
|
||||
STCP_STATE_HS_SERVER_WAIT,
|
||||
STCP_STATE_DATA,
|
||||
STCP_STATE_CLOSED,
|
||||
STCP_STATE_ERROR
|
||||
};
|
||||
|
||||
struct stcp_conn {
|
||||
socket_t sock;
|
||||
struct UASYNC *ua;
|
||||
void *socket_id;
|
||||
|
||||
enum stcp_state state;
|
||||
uint8_t is_server;
|
||||
uint8_t allocated; // 1 = allocated by u_calloc, free in stcp_conn_free
|
||||
|
||||
uint8_t session_key[SC_SESSION_KEY_SIZE];
|
||||
struct sc_stream_state stream_send;
|
||||
struct sc_stream_state stream_recv;
|
||||
|
||||
struct SC_MYKEYS my_keys;
|
||||
uint8_t peer_pubkey[SC_PUBKEY_SIZE];
|
||||
uint8_t peer_pubkey_set;
|
||||
|
||||
struct ll_queue *rx_queue;
|
||||
struct ll_queue *tx_queue;
|
||||
void (*tx_cb)(struct ll_queue *q, void *arg);
|
||||
|
||||
uint8_t *recv_buf;
|
||||
size_t recv_buf_len;
|
||||
size_t recv_buf_cap;
|
||||
|
||||
uint8_t *send_buf;
|
||||
size_t send_len;
|
||||
size_t send_offset;
|
||||
|
||||
size_t hs_expected_len;
|
||||
uint8_t hs_key_processed;
|
||||
|
||||
void (*on_ready)(struct stcp_conn *conn, void *arg);
|
||||
void *ready_arg;
|
||||
|
||||
void (*on_close)(struct stcp_conn *conn, int err, void *arg);
|
||||
void *close_arg;
|
||||
};
|
||||
|
||||
void stcp_conn_free(struct stcp_conn *c);
|
||||
void stcp_conn_set_tx_queue(struct stcp_conn *c, struct ll_queue *q);
|
||||
void stcp_conn_set_rx_queue(struct stcp_conn *c, struct ll_queue *q);
|
||||
void stcp_conn_set_on_close(struct stcp_conn *c, void (*cb)(struct stcp_conn *conn, int err, void *arg), void *arg);
|
||||
|
||||
#endif
|
||||
@@ -1,282 +0,0 @@
|
||||
// stcp_client.c — STCP client implementation
|
||||
#include "stcp_client.h"
|
||||
#include "secure_channel.h"
|
||||
#include "crc32.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/socket_compat.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/mem.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/platform_compat.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <netinet/tcp.h>
|
||||
#endif
|
||||
|
||||
struct stcp_client {
|
||||
struct stcp_conn conn;
|
||||
struct UASYNC *ua;
|
||||
stcp_ready_cb ready_cb;
|
||||
void *ready_arg;
|
||||
uint8_t peer_pubkey[SC_PUBKEY_SIZE];
|
||||
};
|
||||
|
||||
static void client_connect_write_cb(socket_t sock, void *arg);
|
||||
static void client_conn_read_cb(socket_t sock, void *arg);
|
||||
static void client_conn_write_cb(socket_t sock, void *arg);
|
||||
static void client_tx_queue_cb(struct ll_queue *q, void *arg);
|
||||
static void stcp_conn_process_recv(struct stcp_conn *c);
|
||||
static int client_try_send(struct stcp_conn *c, const uint8_t *data, size_t len);
|
||||
static void client_do_close(struct stcp_conn *c, int err);
|
||||
|
||||
static int encrypt_and_crc(struct stcp_conn *c, const uint8_t *data, size_t data_len,
|
||||
uint8_t *output, size_t *output_len) {
|
||||
uint32_t crc = crc32_calc(data, data_len);
|
||||
output[0] = (uint8_t)(data_len >> 0);
|
||||
output[1] = (uint8_t)(data_len >> 8);
|
||||
memcpy(output + 2, data, data_len);
|
||||
output[2 + data_len + 0] = (uint8_t)(crc >> 0);
|
||||
output[2 + data_len + 1] = (uint8_t)(crc >> 8);
|
||||
output[2 + data_len + 2] = (uint8_t)(crc >> 16);
|
||||
output[2 + data_len + 3] = (uint8_t)(crc >> 24);
|
||||
if (sc_stream_xor(&c->stream_send, output + 2, data_len + 4) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "encrypt_and_crc: stream_xor failed len=%zu", data_len);
|
||||
return -1;
|
||||
}
|
||||
*output_len = 2 + data_len + 4;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int decrypt_and_check(uint8_t *data, size_t len, struct sc_stream_state *stream, size_t *out_len) {
|
||||
if (sc_stream_xor(stream, data, len) != SC_OK) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "decrypt_and_check: stream_xor failed"); return -1; }
|
||||
size_t data_len = len - SC_CRC32_SIZE;
|
||||
uint32_t recv_crc = ((uint32_t)data[data_len]) | ((uint32_t)data[data_len + 1] << 8) |
|
||||
((uint32_t)data[data_len + 2] << 16) | ((uint32_t)data[data_len + 3] << 24);
|
||||
uint32_t calc_crc = crc32_calc(data, data_len);
|
||||
if (recv_crc != calc_crc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "decrypt_and_check: CRC mismatch"); return -1; }
|
||||
*out_len = data_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void client_do_close(struct stcp_conn *c, int err) {
|
||||
if (c->state == STCP_STATE_CLOSED || c->state == STCP_STATE_ERROR) return;
|
||||
c->state = STCP_STATE_CLOSED;
|
||||
if (c->socket_id) { uasync_remove_socket_t(c->ua, c->sock); c->socket_id = NULL; }
|
||||
if (c->sock != SOCKET_INVALID) { socket_close_wrapper(c->sock); c->sock = SOCKET_INVALID; }
|
||||
if (c->recv_buf) { u_free(c->recv_buf); c->recv_buf = NULL; c->recv_buf_len = 0; c->recv_buf_cap = 0; }
|
||||
if (c->send_buf) { u_free(c->send_buf); c->send_buf = NULL; c->send_len = 0; }
|
||||
if (c->on_close) { void (*cb)(struct stcp_conn*, int, void*) = c->on_close; c->on_close = NULL; cb(c, err, c->close_arg); }
|
||||
}
|
||||
|
||||
static int client_try_send(struct stcp_conn *c, const uint8_t *data, size_t len) {
|
||||
if (c->sock == SOCKET_INVALID) return -1;
|
||||
if (c->send_buf) return -1;
|
||||
ssize_t sent = send(c->sock, data, len, 0);
|
||||
if (sent < 0) {
|
||||
int e = socket_get_error();
|
||||
if (e == ERR_AGAIN || e == ERR_WOULDBLOCK) { c->send_buf = (uint8_t *)data; c->send_len = len; c->send_offset = 0; uasync_set_socket_write(c->ua, c->socket_id, 1); return 0; }
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "client_try_send: send failed err=%d", e);
|
||||
u_free((uint8_t *)data); client_do_close(c, e); return -1;
|
||||
}
|
||||
if ((size_t)sent < len) { c->send_buf = (uint8_t *)data; c->send_len = len; c->send_offset = (size_t)sent; uasync_set_socket_write(c->ua, c->socket_id, 1); return 0; }
|
||||
u_free((uint8_t *)data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void client_conn_write_cb(socket_t sock, void *arg) {
|
||||
struct stcp_conn *c = (struct stcp_conn *)arg;
|
||||
if (!c->send_buf) { uasync_set_socket_write(c->ua, c->socket_id, 0); return; }
|
||||
ssize_t sent = send(sock, c->send_buf + c->send_offset, c->send_len - c->send_offset, 0);
|
||||
if (sent < 0) {
|
||||
int e = socket_get_error();
|
||||
if (e == ERR_AGAIN || e == ERR_WOULDBLOCK) return;
|
||||
client_do_close(c, e); return;
|
||||
}
|
||||
c->send_offset += (size_t)sent;
|
||||
if (c->send_offset >= c->send_len) { u_free(c->send_buf); c->send_buf = NULL; c->send_len = 0; c->send_offset = 0; uasync_set_socket_write(c->ua, c->socket_id, 0); }
|
||||
}
|
||||
|
||||
static void client_tx_queue_cb(struct ll_queue *q, void *arg) {
|
||||
struct stcp_conn *c = (struct stcp_conn *)arg;
|
||||
struct ll_entry *e = queue_data_get(q);
|
||||
if (!e) { queue_resume_callback(q); return; }
|
||||
if (c->state == STCP_STATE_DATA && e->dgram) {
|
||||
size_t need;
|
||||
uint8_t *buf = u_malloc(2 + e->len + SC_CRC32_SIZE);
|
||||
if (buf && !encrypt_and_crc(c, e->dgram, e->len, buf, &need)) client_try_send(c, buf, need);
|
||||
else if (buf) u_free(buf);
|
||||
}
|
||||
queue_entry_free(e);
|
||||
queue_resume_callback(q);
|
||||
}
|
||||
|
||||
static int client_derive_session(struct stcp_conn *c, const uint8_t *peer_pubkey) {
|
||||
struct secure_channel sc;
|
||||
sc_init_ctx(&sc, &c->my_keys);
|
||||
if (sc_set_peer_public_key(&sc, peer_pubkey, SC_PEER_PUBKEY_BIN) != SC_OK) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "client ECDH failed"); return -1; }
|
||||
memcpy(c->session_key, sc.session_key, SC_SESSION_KEY_SIZE);
|
||||
if (sc_stream_init(&sc, &c->stream_send, STCP_STREAM_CLIENT_SEND) != SC_OK) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "client stream_send init failed"); return -1; }
|
||||
if (sc_stream_init(&sc, &c->stream_recv, STCP_STREAM_SERVER_SEND) != SC_OK) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "client stream_recv init failed"); return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void client_send_handshake(struct stcp_conn *c, const uint8_t *server_pubkey) {
|
||||
uint8_t salt[SC_PUBKEY_ENC_SALT_SIZE];
|
||||
if (random_bytes(salt, SC_PUBKEY_ENC_SALT_SIZE) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "client random_bytes failed"); client_do_close(c, 1); return; }
|
||||
uint16_t padding = 8;
|
||||
size_t total = SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_CLIENT + padding;
|
||||
uint8_t *hs = u_malloc(total);
|
||||
if (!hs) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "client hs malloc failed"); client_do_close(c, 1); return; }
|
||||
memcpy(hs, salt, SC_PUBKEY_ENC_SALT_SIZE);
|
||||
sc_obfuscate_pubkey(salt, server_pubkey, c->my_keys.public_key, hs + SC_PUBKEY_ENC_SALT_SIZE);
|
||||
|
||||
uint8_t plain[2] = {(uint8_t)padding, (uint8_t)(padding >> 8)};
|
||||
uint32_t crc = crc32_calc(plain, 2);
|
||||
uint8_t *enc_dst = hs + SC_PUBKEY_ENC_SIZE;
|
||||
enc_dst[0] = plain[0]; enc_dst[1] = plain[1];
|
||||
enc_dst[2] = (uint8_t)(crc >> 0); enc_dst[3] = (uint8_t)(crc >> 8);
|
||||
enc_dst[4] = (uint8_t)(crc >> 16); enc_dst[5] = (uint8_t)(crc >> 24);
|
||||
if (sc_stream_xor(&c->stream_send, enc_dst, STCP_HS_ENC_CLIENT) != SC_OK) { u_free(hs); client_do_close(c, 2); return; }
|
||||
for (int i = 0; i < padding; i++) hs[SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_CLIENT + i] = (uint8_t)(salt[0] ^ i);
|
||||
|
||||
c->state = STCP_STATE_HS_CLIENT_SENT;
|
||||
client_try_send(c, hs, total);
|
||||
}
|
||||
|
||||
static void process_server_response(struct stcp_conn *c) {
|
||||
if (!c->hs_key_processed) {
|
||||
const uint8_t *salt = c->recv_buf;
|
||||
const uint8_t *enc_pubkey = salt + SC_PUBKEY_ENC_SALT_SIZE;
|
||||
uint8_t server_pubkey[SC_PUBKEY_SIZE];
|
||||
sc_obfuscate_pubkey(salt, c->my_keys.public_key, enc_pubkey, server_pubkey);
|
||||
c->hs_key_processed = 1;
|
||||
}
|
||||
uint8_t enc_hs[STCP_HS_ENC_SERVER];
|
||||
memcpy(enc_hs, c->recv_buf + SC_PUBKEY_ENC_SIZE, STCP_HS_ENC_SERVER);
|
||||
size_t hs_data_len;
|
||||
if (decrypt_and_check(enc_hs, STCP_HS_ENC_SERVER, &c->stream_recv, &hs_data_len)) { client_do_close(c, 3); return; }
|
||||
uint8_t status = enc_hs[0];
|
||||
uint16_t padding_size = (uint16_t)enc_hs[1] | ((uint16_t)enc_hs[2] << 8);
|
||||
c->hs_expected_len = SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_SERVER + padding_size;
|
||||
if (status != 0) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "server handshake status=%d", status); client_do_close(c, 4); return; }
|
||||
}
|
||||
|
||||
static void finish_server_response(struct stcp_conn *c) {
|
||||
memmove(c->recv_buf, c->recv_buf + c->hs_expected_len, c->recv_buf_len - c->hs_expected_len);
|
||||
c->recv_buf_len -= c->hs_expected_len;
|
||||
c->hs_expected_len = 0;
|
||||
c->hs_key_processed = 0;
|
||||
c->state = STCP_STATE_DATA;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "stcp_client: handshake OK, entering DATA state");
|
||||
if (c->on_ready) { void (*cb)(struct stcp_conn*, void*) = c->on_ready; c->on_ready = NULL; cb(c, c->ready_arg); }
|
||||
}
|
||||
|
||||
static void stcp_conn_process_recv(struct stcp_conn *c) {
|
||||
while (c->recv_buf_len > 0) {
|
||||
switch (c->state) {
|
||||
case STCP_STATE_HS_CLIENT_SENT:
|
||||
if (!c->hs_key_processed) {
|
||||
if (c->recv_buf_len < SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_SERVER) return;
|
||||
process_server_response(c);
|
||||
if (c->state != STCP_STATE_HS_CLIENT_SENT) return;
|
||||
}
|
||||
if (c->hs_key_processed && c->recv_buf_len >= c->hs_expected_len) { finish_server_response(c); return; }
|
||||
return;
|
||||
case STCP_STATE_DATA: {
|
||||
if (c->recv_buf_len < 2) return;
|
||||
uint16_t msg_size = (uint16_t)c->recv_buf[0] | ((uint16_t)c->recv_buf[1] << 8);
|
||||
if (msg_size > STCP_MAX_MSG_SIZE) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "client msg too large: %u", msg_size); client_do_close(c, 5); return; }
|
||||
size_t total = 2 + msg_size + SC_CRC32_SIZE;
|
||||
if (c->recv_buf_len < total) return;
|
||||
uint8_t *enc_data = c->recv_buf + 2;
|
||||
size_t data_len;
|
||||
if (decrypt_and_check(enc_data, msg_size + SC_CRC32_SIZE, &c->stream_recv, &data_len)) { client_do_close(c, 6); return; }
|
||||
if (c->rx_queue) {
|
||||
struct ll_entry *e = queue_entry_new(0);
|
||||
if (e) { e->dgram = u_malloc(data_len); if (e->dgram) { if (data_len) memcpy(e->dgram, enc_data, data_len); e->len = (uint16_t)data_len; queue_data_put(c->rx_queue, e); } else { queue_entry_free(e); } }
|
||||
}
|
||||
memmove(c->recv_buf, c->recv_buf + total, c->recv_buf_len - total);
|
||||
c->recv_buf_len -= total;
|
||||
break;
|
||||
}
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void client_conn_read_cb(socket_t sock, void *arg) {
|
||||
struct stcp_conn *c = (struct stcp_conn *)arg;
|
||||
if (c->state == STCP_STATE_CLOSED || c->state == STCP_STATE_ERROR) return;
|
||||
if (!c->recv_buf) { c->recv_buf_cap = STCP_RECV_BUF_INIT; c->recv_buf = u_malloc(c->recv_buf_cap); if (!c->recv_buf) { client_do_close(c, ENOMEM); return; } }
|
||||
if (c->recv_buf_len + 4096 > c->recv_buf_cap) {
|
||||
size_t nc = c->recv_buf_cap * 2; if (nc > STCP_RECV_BUF_MAX) nc = STCP_RECV_BUF_MAX;
|
||||
if (nc <= c->recv_buf_cap) { client_do_close(c, ENOBUFS); return; }
|
||||
uint8_t *nb = u_realloc(c->recv_buf, nc); if (!nb) { client_do_close(c, ENOMEM); return; }
|
||||
c->recv_buf = nb; c->recv_buf_cap = nc;
|
||||
}
|
||||
ssize_t n = recv(sock, c->recv_buf + c->recv_buf_len, c->recv_buf_cap - c->recv_buf_len, 0);
|
||||
if (n < 0) { int e = socket_get_error(); if (e == ERR_AGAIN || e == ERR_WOULDBLOCK) return; client_do_close(c, e); return; }
|
||||
if (n == 0) { client_do_close(c, 0); return; }
|
||||
c->recv_buf_len += (size_t)n;
|
||||
stcp_conn_process_recv(c);
|
||||
}
|
||||
|
||||
static void client_connect_write_cb(socket_t sock, void *arg) {
|
||||
struct stcp_client *cli = (struct stcp_client *)arg;
|
||||
struct stcp_conn *c = &cli->conn;
|
||||
int err = 0;
|
||||
socklen_t len = sizeof(err);
|
||||
if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&err, &len) < 0 || err != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_client connect failed err=%d", err);
|
||||
client_do_close(c, err); return;
|
||||
}
|
||||
uasync_remove_socket_t(cli->ua, sock);
|
||||
c->socket_id = uasync_add_socket_t(cli->ua, sock, client_conn_read_cb, client_conn_write_cb, NULL, c);
|
||||
if (!c->socket_id) { client_do_close(c, ENOMEM); return; }
|
||||
int opt = 1; setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (const char *)&opt, sizeof(opt));
|
||||
if (client_derive_session(c, cli->peer_pubkey)) { client_do_close(c, 1); return; }
|
||||
client_send_handshake(c, cli->peer_pubkey);
|
||||
}
|
||||
|
||||
struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uint16_t port,
|
||||
struct SC_MYKEYS *keys, const uint8_t *peer_pubkey,
|
||||
stcp_ready_cb ready_cb, void *arg) {
|
||||
if (!ua || !addr || !keys || !peer_pubkey || !ready_cb) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_client_connect: invalid args"); return NULL; }
|
||||
struct stcp_client *cli = u_calloc(1, sizeof(struct stcp_client));
|
||||
if (!cli) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_client_connect: calloc failed"); return NULL; }
|
||||
cli->ua = ua; cli->ready_cb = ready_cb; cli->ready_arg = arg;
|
||||
memcpy(cli->peer_pubkey, peer_pubkey, SC_PUBKEY_SIZE);
|
||||
struct stcp_conn *c = &cli->conn;
|
||||
c->ua = ua; c->state = STCP_STATE_INIT; c->is_server = 0; c->my_keys = *keys;
|
||||
c->on_ready = ready_cb; c->ready_arg = arg;
|
||||
c->tx_cb = client_tx_queue_cb;
|
||||
|
||||
c->sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (c->sock == SOCKET_INVALID) { u_free(cli); return NULL; }
|
||||
socket_set_nonblocking(c->sock);
|
||||
struct sockaddr_in saddr; memset(&saddr, 0, sizeof(saddr));
|
||||
saddr.sin_family = AF_INET; saddr.sin_port = htons(port);
|
||||
if (inet_pton(AF_INET, addr, &saddr.sin_addr) != 1) { socket_close_wrapper(c->sock); u_free(cli); return NULL; }
|
||||
if (connect(c->sock, (struct sockaddr *)&saddr, sizeof(saddr)) < 0 && socket_get_error() != EINPROGRESS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_client connect to %s:%u failed err=%d", addr, port, socket_get_error());
|
||||
socket_close_wrapper(c->sock); u_free(cli); return NULL;
|
||||
}
|
||||
c->socket_id = uasync_add_socket_t(ua, c->sock, NULL, client_connect_write_cb, NULL, cli);
|
||||
if (!c->socket_id) { socket_close_wrapper(c->sock); u_free(cli); return NULL; }
|
||||
return cli;
|
||||
}
|
||||
|
||||
void stcp_client_destroy(struct stcp_client *cli) {
|
||||
if (!cli) return;
|
||||
client_do_close(&cli->conn, 0);
|
||||
stcp_conn_free(&cli->conn);
|
||||
u_free(cli);
|
||||
}
|
||||
|
||||
struct stcp_conn *stcp_client_get_conn(struct stcp_client *cli) {
|
||||
return cli ? &cli->conn : NULL;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
// stcp_client.h — STCP client: connect, handshake
|
||||
#ifndef STCP_CLIENT_H
|
||||
#define STCP_CLIENT_H
|
||||
|
||||
#include "stcp.h"
|
||||
|
||||
typedef void (*stcp_ready_cb)(struct stcp_conn *conn, void *arg);
|
||||
|
||||
struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uint16_t port,
|
||||
struct SC_MYKEYS *keys, const uint8_t *peer_pubkey,
|
||||
stcp_ready_cb ready_cb, void *arg);
|
||||
void stcp_client_destroy(struct stcp_client *cli);
|
||||
struct stcp_conn *stcp_client_get_conn(struct stcp_client *cli);
|
||||
|
||||
#endif
|
||||
-202
@@ -1,202 +0,0 @@
|
||||
// stcp_link.c — STCP link management implementation
|
||||
#include "stcp_link.h"
|
||||
#include "stcp.h"
|
||||
#include "stcp_server.h"
|
||||
#include "stcp_client.h"
|
||||
#include "secure_channel.h"
|
||||
#include "etcp.h"
|
||||
#include "utun_instance.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/mem.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct stcp_server {
|
||||
struct stcp_server *srv; // stcp_server from stcp_server.h
|
||||
struct stcp_link_config cfg;
|
||||
stcp_server_on_link_cb on_link;
|
||||
void *on_link_arg;
|
||||
};
|
||||
|
||||
struct stcp_link {
|
||||
struct stcp_link_config cfg;
|
||||
struct stcp_client *cli; // only 1 client link for now
|
||||
struct stcp_conn *conn; // STCP connection
|
||||
|
||||
uint8_t ready;
|
||||
uint8_t peer_pubkey[SC_PUBKEY_SIZE];
|
||||
|
||||
stcp_link_cb on_ready_cb;
|
||||
void *ready_arg;
|
||||
void (*on_close_cb)(struct stcp_link *link, int err, void *arg);
|
||||
void *close_arg;
|
||||
|
||||
struct ll_queue *tx_queue; // owned by this link
|
||||
struct ETCP_CONN etcp_conn; // lightweight, for etcp_recv_fn compat
|
||||
};
|
||||
|
||||
// ====== rx dispatch through inst->api_bindings ======
|
||||
|
||||
static void link_rx_cb(struct ll_queue *q, void *arg) {
|
||||
struct stcp_link *link = (struct stcp_link *)arg;
|
||||
struct ll_entry *e = queue_data_get(q);
|
||||
if (!e) { queue_resume_callback(q); return; }
|
||||
|
||||
struct UTUN_INSTANCE *inst = link->cfg.inst;
|
||||
uint8_t id = (e->dgram && e->len > 0) ? e->dgram[0] : 0;
|
||||
|
||||
if (inst && inst->api_bindings.callbacks[id]) {
|
||||
inst->api_bindings.callbacks[id](&link->etcp_conn, e);
|
||||
} else if (inst && inst->api_bindings.callbacks[0]) {
|
||||
inst->api_bindings.callbacks[0](&link->etcp_conn, e);
|
||||
} else {
|
||||
queue_dgram_free(e);
|
||||
queue_entry_free(e);
|
||||
}
|
||||
queue_resume_callback(q);
|
||||
}
|
||||
|
||||
// ====== server accept → link ======
|
||||
|
||||
static void server_accept_cb(struct stcp_conn *conn, void *arg) {
|
||||
struct stcp_server *ss = (struct stcp_server *)arg;
|
||||
struct stcp_link *link = u_calloc(1, sizeof(struct stcp_link));
|
||||
if (!link) { stcp_conn_free(conn); return; }
|
||||
link->cfg = ss->cfg;
|
||||
link->ready = 1;
|
||||
link->conn = conn;
|
||||
link->etcp_conn.instance = ss->cfg.inst;
|
||||
link->etcp_conn.transport_link = (void *)link; // backpointer for stcp_link
|
||||
|
||||
struct ll_queue *rx = queue_new(conn->ua, 0, 0, 0, "srx");
|
||||
queue_set_callback(rx, link_rx_cb, link);
|
||||
stcp_conn_set_rx_queue(conn, rx);
|
||||
link->tx_queue = queue_new(conn->ua, 0, 0, 0, "stx");
|
||||
stcp_conn_set_tx_queue(conn, link->tx_queue);
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "stcp_link: server accepted connection");
|
||||
if (link->on_ready_cb) link->on_ready_cb(link, link->ready_arg);
|
||||
if (ss->on_link) ss->on_link(link, ss->on_link_arg);
|
||||
}
|
||||
|
||||
// ====== client connect → link ======
|
||||
|
||||
static void client_ready_cb(struct stcp_conn *conn, void *arg) {
|
||||
struct stcp_link *link = (struct stcp_link *)arg;
|
||||
if (!conn) return;
|
||||
link->ready = 1;
|
||||
link->conn = conn;
|
||||
link->etcp_conn.instance = link->cfg.inst;
|
||||
link->etcp_conn.transport_link = (void *)link;
|
||||
|
||||
struct ll_queue *rx = queue_new(conn->ua, 0, 0, 0, "crx");
|
||||
queue_set_callback(rx, link_rx_cb, link);
|
||||
stcp_conn_set_rx_queue(conn, rx);
|
||||
link->tx_queue = queue_new(conn->ua, 0, 0, 0, "ctx");
|
||||
stcp_conn_set_tx_queue(conn, link->tx_queue);
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "stcp_link: client connected");
|
||||
if (link->on_ready_cb) link->on_ready_cb(link, link->ready_arg);
|
||||
}
|
||||
|
||||
// ====== API ======
|
||||
|
||||
struct stcp_server *stcp_server_listen(struct stcp_link_config *cfg, uint16_t port,
|
||||
stcp_server_on_link_cb on_link, void *arg) {
|
||||
if (!cfg || !cfg->ua || !cfg->my_keys) return NULL;
|
||||
struct stcp_server *ss = u_calloc(1, sizeof(struct stcp_server));
|
||||
if (!ss) return NULL;
|
||||
ss->cfg = *cfg;
|
||||
ss->on_link = on_link;
|
||||
ss->on_link_arg = arg;
|
||||
ss->srv = stcp_server_create(cfg->ua, port, cfg->my_keys, server_accept_cb, ss);
|
||||
if (!ss->srv) { u_free(ss); return NULL; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "stcp_server_listen: port=%u", port);
|
||||
return ss;
|
||||
}
|
||||
|
||||
void stcp_link_server_destroy(struct stcp_server *ss) {
|
||||
if (!ss) return;
|
||||
if (ss->srv) stcp_server_destroy(ss->srv);
|
||||
u_free(ss);
|
||||
}
|
||||
|
||||
struct stcp_link *stcp_link_connect(struct stcp_link_config *cfg) {
|
||||
if (!cfg || !cfg->ua || !cfg->my_keys || !cfg->peer_pubkey || !cfg->remote_addr)
|
||||
return NULL;
|
||||
|
||||
char addr_str[64];
|
||||
struct sockaddr_in *sa = (struct sockaddr_in *)cfg->remote_addr;
|
||||
inet_ntop(AF_INET, &sa->sin_addr, addr_str, sizeof(addr_str));
|
||||
uint16_t port = cfg->remote_port ? cfg->remote_port : ntohs(sa->sin_port);
|
||||
|
||||
struct stcp_link *link = u_calloc(1, sizeof(struct stcp_link));
|
||||
if (!link) return NULL;
|
||||
link->cfg = *cfg;
|
||||
|
||||
// Convert hex pubkey to binary if needed
|
||||
if (cfg->peer_pubkey_mode) {
|
||||
struct secure_channel sc_tmp;
|
||||
sc_init_ctx(&sc_tmp, cfg->my_keys);
|
||||
if (sc_set_peer_public_key(&sc_tmp, cfg->peer_pubkey, 1) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_link_connect: invalid peer pubkey hex");
|
||||
u_free(link); return NULL;
|
||||
}
|
||||
memcpy(link->peer_pubkey, sc_tmp.peer_public_key, SC_PUBKEY_SIZE);
|
||||
} else {
|
||||
memcpy(link->peer_pubkey, cfg->peer_pubkey, SC_PUBKEY_SIZE);
|
||||
}
|
||||
|
||||
link->cli = stcp_client_connect(cfg->ua, addr_str, port, cfg->my_keys, link->peer_pubkey,
|
||||
client_ready_cb, link);
|
||||
if (!link->cli) { u_free(link); return NULL; }
|
||||
|
||||
link->etcp_conn.instance = cfg->inst;
|
||||
link->etcp_conn.transport_link = (void *)link;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "stcp_link_connect: connecting to %s:%u", addr_str, port);
|
||||
return link;
|
||||
}
|
||||
|
||||
void stcp_link_close(struct stcp_link *link) {
|
||||
if (!link) return;
|
||||
if (link->conn) {
|
||||
if (link->conn->rx_queue) queue_free(link->conn->rx_queue);
|
||||
if (link->tx_queue) queue_free(link->tx_queue);
|
||||
stcp_conn_free(link->conn);
|
||||
}
|
||||
if (link->cli) stcp_client_destroy(link->cli);
|
||||
u_free(link);
|
||||
}
|
||||
|
||||
int stcp_link_send(struct stcp_link *link, const uint8_t *data, size_t len) {
|
||||
if (!link || !link->ready) return -1;
|
||||
struct ll_entry *e = queue_entry_new(0);
|
||||
if (!e) return -1;
|
||||
e->dgram = u_malloc(len ? len : 1);
|
||||
if (!e->dgram) { queue_entry_free(e); return -1; }
|
||||
if (len) memcpy(e->dgram, data, len);
|
||||
e->len = (uint16_t)len;
|
||||
queue_data_put(link->conn->tx_queue, e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int stcp_link_is_ready(struct stcp_link *link) {
|
||||
return link ? link->ready : 0;
|
||||
}
|
||||
|
||||
struct ETCP_CONN *stcp_link_get_etcp_conn(struct stcp_link *link) {
|
||||
return link ? &link->etcp_conn : NULL;
|
||||
}
|
||||
|
||||
void stcp_link_set_on_ready(struct stcp_link *link, stcp_link_cb cb, void *arg) {
|
||||
if (!link) return;
|
||||
link->on_ready_cb = cb;
|
||||
link->ready_arg = arg;
|
||||
}
|
||||
|
||||
void stcp_link_set_on_close(struct stcp_link *link, void (*cb)(struct stcp_link *link, int err, void *arg), void *arg) {
|
||||
if (!link) return;
|
||||
link->on_close_cb = cb;
|
||||
link->close_arg = arg;
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// stcp_link.h — STCP link management (TCP connection via STCP protocol)
|
||||
#ifndef STCP_LINK_H
|
||||
#define STCP_LINK_H
|
||||
|
||||
#include "secure_channel.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/socket_compat.h"
|
||||
#include <stdint.h>
|
||||
|
||||
struct stcp_link; // opaque
|
||||
struct stcp_server; // opaque
|
||||
struct ETCP_CONN;
|
||||
struct UTUN_INSTANCE;
|
||||
|
||||
// ====== Link config ======
|
||||
|
||||
struct stcp_link_config {
|
||||
struct UASYNC *ua;
|
||||
struct SC_MYKEYS *my_keys; // ключи этой стороны
|
||||
struct UTUN_INSTANCE *inst; // для диспатча через api_bindings
|
||||
const uint8_t *peer_pubkey; // pubkey пира (клиент)
|
||||
int peer_pubkey_mode; // 0=binary, 1=hex
|
||||
const struct sockaddr_storage *remote_addr; // адрес пира (клиент)
|
||||
uint16_t remote_port; // порт пира (клиент)
|
||||
};
|
||||
|
||||
// ====== TCP server ======
|
||||
|
||||
typedef void (*stcp_server_on_link_cb)(struct stcp_link *link, void *arg);
|
||||
|
||||
struct stcp_server *stcp_server_listen(struct stcp_link_config *cfg, uint16_t port,
|
||||
stcp_server_on_link_cb on_link, void *arg);
|
||||
void stcp_link_server_destroy(struct stcp_server *srv);
|
||||
|
||||
// ====== TCP client link ======
|
||||
|
||||
struct stcp_link *stcp_link_connect(struct stcp_link_config *cfg);
|
||||
void stcp_link_close(struct stcp_link *link);
|
||||
int stcp_link_send(struct stcp_link *link, const uint8_t *data, size_t len);
|
||||
int stcp_link_is_ready(struct stcp_link *link);
|
||||
|
||||
struct ETCP_CONN *stcp_link_get_etcp_conn(struct stcp_link *link);
|
||||
|
||||
typedef void (*stcp_link_cb)(struct stcp_link *link, void *arg);
|
||||
void stcp_link_set_on_ready(struct stcp_link *link, stcp_link_cb cb, void *arg);
|
||||
void stcp_link_set_on_close(struct stcp_link *link, void (*cb)(struct stcp_link *link, int err, void *arg), void *arg);
|
||||
|
||||
#endif
|
||||
@@ -1,375 +0,0 @@
|
||||
// stcp_server.c — STCP server implementation
|
||||
#include "stcp_server.h"
|
||||
#include "secure_channel.h"
|
||||
#include "crc32.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/socket_compat.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/memory_pool.h"
|
||||
#include "../lib/mem.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/platform_compat.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <netinet/tcp.h>
|
||||
#endif
|
||||
|
||||
struct stcp_server {
|
||||
struct UASYNC *ua;
|
||||
socket_t listen_sock;
|
||||
void *listen_id;
|
||||
struct SC_MYKEYS my_keys;
|
||||
stcp_connect_cb connect_cb;
|
||||
void *cb_arg;
|
||||
};
|
||||
|
||||
static void server_accept_cb(socket_t sock, void *arg);
|
||||
static void server_conn_read_cb(socket_t sock, void *arg);
|
||||
static void server_conn_write_cb(socket_t sock, void *arg);
|
||||
static void tx_queue_cb(struct ll_queue *q, void *arg);
|
||||
static void stcp_conn_process_recv(struct stcp_conn *c);
|
||||
static int stcp_conn_try_send(struct stcp_conn *c, const uint8_t *data, size_t len);
|
||||
static void stcp_conn_do_close(struct stcp_conn *c, int err);
|
||||
|
||||
static int stcp_encrypt_and_crc(struct stcp_conn *c, const uint8_t *data, size_t data_len,
|
||||
uint8_t *output, size_t *output_len) {
|
||||
uint32_t crc = crc32_calc(data, data_len);
|
||||
output[0] = (data_len >> 0) & 0xFF;
|
||||
output[1] = (data_len >> 8) & 0xFF;
|
||||
memcpy(output + 2, data, data_len);
|
||||
output[2 + data_len + 0] = (uint8_t)(crc >> 0);
|
||||
output[2 + data_len + 1] = (uint8_t)(crc >> 8);
|
||||
output[2 + data_len + 2] = (uint8_t)(crc >> 16);
|
||||
output[2 + data_len + 3] = (uint8_t)(crc >> 24);
|
||||
size_t enc_len = 2 + data_len + 4;
|
||||
if (sc_stream_xor(&c->stream_send, output + 2, data_len + 4) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_encrypt_and_crc: stream_xor failed len=%zu", data_len);
|
||||
return -1;
|
||||
}
|
||||
*output_len = enc_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stcp_decrypt_and_check(uint8_t *plaintext, size_t plaintext_len,
|
||||
struct sc_stream_state *stream, size_t *out_len) {
|
||||
if (sc_stream_xor(stream, plaintext, plaintext_len) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_decrypt_and_check: stream_xor failed len=%zu", plaintext_len);
|
||||
return -1;
|
||||
}
|
||||
size_t data_len = plaintext_len - 4;
|
||||
uint32_t recv_crc = ((uint32_t)plaintext[data_len]) | ((uint32_t)plaintext[data_len + 1] << 8) |
|
||||
((uint32_t)plaintext[data_len + 2] << 16) | ((uint32_t)plaintext[data_len + 3] << 24);
|
||||
uint32_t calc_crc = crc32_calc(plaintext, data_len);
|
||||
if (recv_crc != calc_crc) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_decrypt_and_check: CRC mismatch recv=%08x calc=%08x", recv_crc, calc_crc);
|
||||
return -1;
|
||||
}
|
||||
*out_len = data_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stcp_conn_do_close(struct stcp_conn *c, int err) {
|
||||
if (c->state == STCP_STATE_CLOSED || c->state == STCP_STATE_ERROR) return;
|
||||
int prev = c->state;
|
||||
c->state = STCP_STATE_CLOSED;
|
||||
if (c->socket_id) { uasync_remove_socket_t(c->ua, c->sock); c->socket_id = NULL; }
|
||||
if (c->sock != SOCKET_INVALID) { socket_close_wrapper(c->sock); c->sock = SOCKET_INVALID; }
|
||||
if (c->recv_buf) { u_free(c->recv_buf); c->recv_buf = NULL; c->recv_buf_len = 0; c->recv_buf_cap = 0; }
|
||||
if (c->send_buf) { u_free(c->send_buf); c->send_buf = NULL; c->send_len = 0; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "stcp_conn closed is_server=%d prev_state=%d err=%d", c->is_server, prev, err);
|
||||
if (c->on_close) { void (*cb)(struct stcp_conn*, int, void*) = c->on_close; cb(c, err, c->close_arg); }
|
||||
else if (prev == STCP_STATE_HS_SERVER_WAIT) { sc_stream_cleanup(&c->stream_send); sc_stream_cleanup(&c->stream_recv); if (c->allocated) u_free(c); }
|
||||
}
|
||||
|
||||
static void stcp_conn_send_message(struct stcp_conn *c, const uint8_t *data, size_t len) {
|
||||
if (c->state != STCP_STATE_DATA) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_conn_send_message: not in DATA state"); return; }
|
||||
size_t max_enc = STCP_MAX_MSG_SIZE;
|
||||
if (len > max_enc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_conn_send_message: data too long %zu", len); return; }
|
||||
size_t need = 2 + len + 4;
|
||||
uint8_t *buf = u_malloc(need);
|
||||
if (!buf) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_conn_send_message: malloc(%zu) failed", need); return; }
|
||||
if (stcp_encrypt_and_crc(c, data, len, buf, &need)) { u_free(buf); return; }
|
||||
stcp_conn_try_send(c, buf, need);
|
||||
}
|
||||
|
||||
static int stcp_conn_try_send(struct stcp_conn *c, const uint8_t *data, size_t len) {
|
||||
if (c->sock == SOCKET_INVALID) return -1;
|
||||
if (c->send_buf) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_conn_try_send: send_buf already busy");
|
||||
return -1;
|
||||
}
|
||||
ssize_t sent = send(c->sock, data, len, 0);
|
||||
if (sent < 0) {
|
||||
int err = socket_get_error();
|
||||
if (err == ERR_AGAIN || err == ERR_WOULDBLOCK) {
|
||||
c->send_buf = (uint8_t *)data;
|
||||
c->send_len = len;
|
||||
c->send_offset = 0;
|
||||
uasync_set_socket_write(c->ua, c->socket_id, 1);
|
||||
return 0;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_conn_try_send: send failed err=%d", err);
|
||||
u_free((uint8_t *)data);
|
||||
stcp_conn_do_close(c, err);
|
||||
return -1;
|
||||
}
|
||||
if ((size_t)sent < len) {
|
||||
c->send_buf = (uint8_t *)data;
|
||||
c->send_len = len;
|
||||
c->send_offset = (size_t)sent;
|
||||
uasync_set_socket_write(c->ua, c->socket_id, 1);
|
||||
return 0;
|
||||
}
|
||||
u_free((uint8_t *)data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void server_conn_write_cb(socket_t sock, void *arg) {
|
||||
struct stcp_conn *c = (struct stcp_conn *)arg;
|
||||
if (!c->send_buf) { uasync_set_socket_write(c->ua, c->socket_id, 0); return; }
|
||||
ssize_t sent = send(sock, c->send_buf + c->send_offset, c->send_len - c->send_offset, 0);
|
||||
if (sent < 0) {
|
||||
int err = socket_get_error();
|
||||
if (err == ERR_AGAIN || err == ERR_WOULDBLOCK) return;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "server_conn_write_cb: send failed err=%d", err);
|
||||
stcp_conn_do_close(c, err);
|
||||
return;
|
||||
}
|
||||
c->send_offset += (size_t)sent;
|
||||
if (c->send_offset >= c->send_len) {
|
||||
u_free(c->send_buf); c->send_buf = NULL; c->send_len = 0; c->send_offset = 0;
|
||||
uasync_set_socket_write(c->ua, c->socket_id, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void tx_queue_cb(struct ll_queue *q, void *arg) {
|
||||
struct stcp_conn *c = (struct stcp_conn *)arg;
|
||||
struct ll_entry *e = queue_data_get(q);
|
||||
if (!e) { queue_resume_callback(q); return; }
|
||||
if (e->dgram) stcp_conn_send_message(c, e->dgram, e->len);
|
||||
queue_entry_free(e);
|
||||
queue_resume_callback(q);
|
||||
}
|
||||
|
||||
static int derive_session_and_streams(struct stcp_conn *c, const uint8_t *peer_pubkey) {
|
||||
struct secure_channel sc;
|
||||
sc_init_ctx(&sc, &c->my_keys);
|
||||
if (sc_set_peer_public_key(&sc, peer_pubkey, SC_PEER_PUBKEY_BIN) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "derive_session: ECDH failed");
|
||||
return -1;
|
||||
}
|
||||
memcpy(c->session_key, sc.session_key, SC_SESSION_KEY_SIZE);
|
||||
if (sc_stream_init(&sc, &c->stream_send, STCP_STREAM_SERVER_SEND) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "derive_session: stream_send init failed");
|
||||
return -1;
|
||||
}
|
||||
if (sc_stream_init(&sc, &c->stream_recv, STCP_STREAM_CLIENT_SEND) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "derive_session: stream_recv init failed");
|
||||
return -1;
|
||||
}
|
||||
memcpy(c->peer_pubkey, peer_pubkey, SC_PUBKEY_SIZE);
|
||||
c->peer_pubkey_set = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void process_client_handshake(struct stcp_conn *c) {
|
||||
const uint8_t *salt = c->recv_buf;
|
||||
const uint8_t *enc_pubkey = salt + SC_PUBKEY_ENC_SALT_SIZE;
|
||||
uint8_t client_pubkey[SC_PUBKEY_SIZE];
|
||||
sc_obfuscate_pubkey(salt, c->my_keys.public_key, enc_pubkey, client_pubkey);
|
||||
if (derive_session_and_streams(c, client_pubkey)) { stcp_conn_do_close(c, 1); return; }
|
||||
uint8_t enc_hs[STCP_HS_ENC_CLIENT];
|
||||
memcpy(enc_hs, c->recv_buf + SC_PUBKEY_ENC_SIZE, STCP_HS_ENC_CLIENT);
|
||||
size_t hs_data_len;
|
||||
if (stcp_decrypt_and_check(enc_hs, STCP_HS_ENC_CLIENT, &c->stream_recv, &hs_data_len)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "process_client_handshake: decrypt/CRC failed");
|
||||
stcp_conn_do_close(c, 2); return;
|
||||
}
|
||||
uint16_t padding_size = (uint16_t)enc_hs[0] | ((uint16_t)enc_hs[1] << 8);
|
||||
c->hs_expected_len = SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_CLIENT + padding_size;
|
||||
c->hs_key_processed = 1;
|
||||
}
|
||||
|
||||
static void finish_client_handshake(struct stcp_conn *c) {
|
||||
memmove(c->recv_buf, c->recv_buf + c->hs_expected_len, c->recv_buf_len - c->hs_expected_len);
|
||||
c->recv_buf_len -= c->hs_expected_len;
|
||||
c->hs_expected_len = 0;
|
||||
c->hs_key_processed = 0;
|
||||
|
||||
uint8_t salt2[SC_PUBKEY_ENC_SALT_SIZE];
|
||||
if (random_bytes(salt2, SC_PUBKEY_ENC_SALT_SIZE) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "finish_client_handshake: random_bytes failed");
|
||||
stcp_conn_do_close(c, 3); return;
|
||||
}
|
||||
uint16_t padding = 8;
|
||||
size_t total_resp = SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_SERVER + padding;
|
||||
uint8_t *resp = u_malloc(total_resp);
|
||||
if (!resp) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "finish_client_handshake: malloc failed"); stcp_conn_do_close(c, 3); return; }
|
||||
memcpy(resp, salt2, SC_PUBKEY_ENC_SALT_SIZE);
|
||||
sc_obfuscate_pubkey(salt2, c->peer_pubkey, c->my_keys.public_key, resp + SC_PUBKEY_ENC_SALT_SIZE);
|
||||
|
||||
uint8_t plain_hs[3] = {0, (uint8_t)padding, (uint8_t)(padding >> 8)}; // status=OK + padding_size
|
||||
uint32_t crc = crc32_calc(plain_hs, 3);
|
||||
uint8_t *enc_dst = resp + SC_PUBKEY_ENC_SIZE;
|
||||
enc_dst[0] = plain_hs[0]; enc_dst[1] = plain_hs[1]; enc_dst[2] = plain_hs[2];
|
||||
enc_dst[3] = (uint8_t)(crc >> 0); enc_dst[4] = (uint8_t)(crc >> 8);
|
||||
enc_dst[5] = (uint8_t)(crc >> 16); enc_dst[6] = (uint8_t)(crc >> 24);
|
||||
if (sc_stream_xor(&c->stream_send, enc_dst, STCP_HS_ENC_SERVER) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "finish_client_handshake: encrypt failed");
|
||||
u_free(resp); stcp_conn_do_close(c, 3); return;
|
||||
}
|
||||
for (int i = 0; i < padding; i++) resp[SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_SERVER + i] = (uint8_t)(salt2[0] ^ i);
|
||||
|
||||
c->state = STCP_STATE_DATA;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "stcp_server: handshake OK, entering DATA state");
|
||||
if (c->on_ready) c->on_ready(c, c->ready_arg);
|
||||
stcp_conn_try_send(c, resp, total_resp);
|
||||
}
|
||||
|
||||
static void server_conn_read_cb(socket_t sock, void *arg) {
|
||||
struct stcp_conn *c = (struct stcp_conn *)arg;
|
||||
if (c->state == STCP_STATE_CLOSED || c->state == STCP_STATE_ERROR) return;
|
||||
|
||||
if (c->recv_buf_cap == 0) {
|
||||
c->recv_buf_cap = STCP_RECV_BUF_INIT;
|
||||
c->recv_buf = u_malloc(c->recv_buf_cap);
|
||||
if (!c->recv_buf) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "server_conn_read_cb: malloc failed"); stcp_conn_do_close(c, ENOMEM); return; }
|
||||
}
|
||||
if (c->recv_buf_len + 4096 > c->recv_buf_cap) {
|
||||
size_t new_cap = c->recv_buf_cap * 2;
|
||||
if (new_cap > STCP_RECV_BUF_MAX) new_cap = STCP_RECV_BUF_MAX;
|
||||
if (new_cap <= c->recv_buf_cap) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "server_conn_read_cb: recv buf full"); stcp_conn_do_close(c, ENOBUFS); return; }
|
||||
uint8_t *nb = u_realloc(c->recv_buf, new_cap);
|
||||
if (!nb) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "server_conn_read_cb: realloc failed"); stcp_conn_do_close(c, ENOMEM); return; }
|
||||
c->recv_buf = nb; c->recv_buf_cap = new_cap;
|
||||
}
|
||||
ssize_t n = recv(sock, c->recv_buf + c->recv_buf_len, c->recv_buf_cap - c->recv_buf_len, 0);
|
||||
if (n < 0) {
|
||||
int err = socket_get_error();
|
||||
if (err == ERR_AGAIN || err == ERR_WOULDBLOCK) return;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "server_conn_read_cb: recv failed err=%d", err);
|
||||
stcp_conn_do_close(c, err); return;
|
||||
}
|
||||
if (n == 0) { DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "server_conn_read_cb: EOF"); stcp_conn_do_close(c, 0); return; }
|
||||
c->recv_buf_len += (size_t)n;
|
||||
stcp_conn_process_recv(c);
|
||||
}
|
||||
|
||||
static void stcp_conn_process_recv(struct stcp_conn *c) {
|
||||
while (c->recv_buf_len > 0) {
|
||||
switch (c->state) {
|
||||
case STCP_STATE_HS_SERVER_WAIT:
|
||||
if (!c->hs_key_processed) {
|
||||
if (c->recv_buf_len < SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_CLIENT) return;
|
||||
process_client_handshake(c);
|
||||
if (c->state != STCP_STATE_HS_SERVER_WAIT) return;
|
||||
}
|
||||
if (c->hs_key_processed && c->recv_buf_len >= c->hs_expected_len) {
|
||||
finish_client_handshake(c);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
case STCP_STATE_DATA: {
|
||||
if (c->recv_buf_len < 2) return;
|
||||
uint16_t msg_size = (uint16_t)c->recv_buf[0] | ((uint16_t)c->recv_buf[1] << 8);
|
||||
if (msg_size > STCP_MAX_MSG_SIZE) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "msg too large: %u", msg_size); stcp_conn_do_close(c, 4); return; }
|
||||
size_t total = 2 + msg_size + 4;
|
||||
if (c->recv_buf_len < total) return;
|
||||
uint8_t *enc_data = c->recv_buf + 2;
|
||||
size_t data_len;
|
||||
if (stcp_decrypt_and_check(enc_data, msg_size + 4, &c->stream_recv, &data_len)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "data decrypt/CRC failed");
|
||||
stcp_conn_do_close(c, 5); return;
|
||||
}
|
||||
if (c->rx_queue) {
|
||||
struct ll_entry *e = queue_entry_new(0);
|
||||
if (e) {
|
||||
e->dgram = u_malloc(data_len);
|
||||
if (e->dgram) { if (data_len) memcpy(e->dgram, enc_data, data_len); e->len = (uint16_t)data_len; queue_data_put(c->rx_queue, e); }
|
||||
else { queue_entry_free(e); DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "rx malloc(%zu) failed", data_len); }
|
||||
} else { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "queue_entry_new failed"); }
|
||||
}
|
||||
memmove(c->recv_buf, c->recv_buf + total, c->recv_buf_len - total);
|
||||
c->recv_buf_len -= total;
|
||||
break;
|
||||
}
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void server_accept_cb(socket_t listen_sock, void *arg) {
|
||||
struct stcp_server *srv = (struct stcp_server *)arg;
|
||||
struct sockaddr_storage cli_addr;
|
||||
socklen_t addr_len = sizeof(cli_addr);
|
||||
socket_t cli_sock = accept(listen_sock, (struct sockaddr *)&cli_addr, &addr_len);
|
||||
if (cli_sock == SOCKET_INVALID) {
|
||||
int err = socket_get_error();
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_server accept failed err=%d", err);
|
||||
return;
|
||||
}
|
||||
socket_set_nonblocking(cli_sock);
|
||||
int opt = 1;
|
||||
setsockopt(cli_sock, IPPROTO_TCP, TCP_NODELAY, (const char *)&opt, sizeof(opt));
|
||||
|
||||
struct stcp_conn *c = u_calloc(1, sizeof(struct stcp_conn));
|
||||
if (!c) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_server: calloc conn failed"); socket_close_wrapper(cli_sock); return; }
|
||||
c->sock = cli_sock;
|
||||
c->ua = srv->ua;
|
||||
c->state = STCP_STATE_HS_SERVER_WAIT;
|
||||
c->is_server = 1;
|
||||
c->allocated = 1;
|
||||
c->tx_cb = tx_queue_cb;
|
||||
c->my_keys = srv->my_keys;
|
||||
c->on_ready = srv->connect_cb;
|
||||
c->ready_arg = srv->cb_arg;
|
||||
c->socket_id = uasync_add_socket_t(srv->ua, cli_sock, server_conn_read_cb, server_conn_write_cb, NULL, c);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "stcp_server: accepted connection fd=%d", (int)cli_sock);
|
||||
}
|
||||
|
||||
struct stcp_server *stcp_server_create(struct UASYNC *ua, uint16_t port,
|
||||
struct SC_MYKEYS *keys,
|
||||
stcp_connect_cb connect_cb, void *arg) {
|
||||
if (!ua || !keys || !connect_cb) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_server_create: invalid args"); return NULL; }
|
||||
struct stcp_server *srv = u_calloc(1, sizeof(struct stcp_server));
|
||||
if (!srv) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_server_create: calloc failed"); return NULL; }
|
||||
srv->ua = ua;
|
||||
srv->my_keys = *keys;
|
||||
srv->connect_cb = connect_cb;
|
||||
srv->cb_arg = arg;
|
||||
|
||||
srv->listen_sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (srv->listen_sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_server_create: socket failed"); u_free(srv); return NULL; }
|
||||
socket_set_nonblocking(srv->listen_sock);
|
||||
int reuse = 1;
|
||||
setsockopt(srv->listen_sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse, sizeof(reuse));
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = INADDR_ANY;
|
||||
addr.sin_port = htons(port);
|
||||
if (bind(srv->listen_sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_server_create: bind port=%u failed err=%d", port, socket_get_error());
|
||||
socket_close_wrapper(srv->listen_sock); u_free(srv); return NULL;
|
||||
}
|
||||
if (listen(srv->listen_sock, 16) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "stcp_server_create: listen failed err=%d", socket_get_error());
|
||||
socket_close_wrapper(srv->listen_sock); u_free(srv); return NULL;
|
||||
}
|
||||
srv->listen_id = uasync_add_socket_t(ua, srv->listen_sock, server_accept_cb, NULL, NULL, srv);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "stcp_server: listening on port %u", port);
|
||||
return srv;
|
||||
}
|
||||
|
||||
void stcp_server_destroy(struct stcp_server *srv) {
|
||||
if (!srv) return;
|
||||
if (srv->listen_id) uasync_remove_socket_t(srv->ua, srv->listen_sock);
|
||||
if (srv->listen_sock != SOCKET_INVALID) socket_close_wrapper(srv->listen_sock);
|
||||
u_free(srv);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "stcp_server destroyed");
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
// stcp_server.h — STCP server: listen, accept, handshake
|
||||
#ifndef STCP_SERVER_H
|
||||
#define STCP_SERVER_H
|
||||
|
||||
#include "stcp.h"
|
||||
|
||||
typedef void (*stcp_connect_cb)(struct stcp_conn *conn, void *arg);
|
||||
|
||||
struct stcp_server *stcp_server_create(struct UASYNC *ua, uint16_t port,
|
||||
struct SC_MYKEYS *keys,
|
||||
stcp_connect_cb connect_cb, void *arg);
|
||||
void stcp_server_destroy(struct stcp_server *srv);
|
||||
|
||||
#endif
|
||||
+1
-33
@@ -11,9 +11,7 @@
|
||||
#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>
|
||||
@@ -130,9 +128,6 @@ 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,18 +284,10 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
|
||||
|
||||
// Диагностика ресурсов ДО cleanup
|
||||
utun_instance_diagnose_leaks(instance, "BEFORE_CLEANUP");
|
||||
if (instance->ua) uasync_print_resources(instance->ua, "INSTANCE_DESTROY_BEFORE");
|
||||
|
||||
// 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");
|
||||
@@ -377,8 +364,6 @@ 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) {
|
||||
@@ -417,9 +402,7 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
|
||||
memory_pool_destroy(instance->data_pool);
|
||||
instance->data_pool = NULL;
|
||||
}
|
||||
|
||||
if (instance->ua) uasync_print_resources(instance->ua, "INSTANCE_DESTROY_AFTER");
|
||||
|
||||
|
||||
// Note: uasync is NOT destroyed here - caller must destroy it separately
|
||||
// This allows sharing uasync between multiple instances
|
||||
instance->ua = NULL;
|
||||
@@ -496,21 +479,6 @@ 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;
|
||||
|
||||
|
||||
+1
-12
@@ -8,7 +8,6 @@
|
||||
#include "secure_channel.h"
|
||||
#include "etcp_api.h"
|
||||
#include "config_parser.h"
|
||||
#include "stcp_link.h"
|
||||
#include "firewall.h"
|
||||
#include "eim_nat.h"
|
||||
#include "nat_transport.h"
|
||||
@@ -16,9 +15,6 @@
|
||||
#include "etcp_router.h"
|
||||
#include "proxy/tcp_proxy_server.h"
|
||||
|
||||
#include "etcp_api.h"
|
||||
#include "config_parser.h"
|
||||
|
||||
// Forward declarations
|
||||
struct utun_config;
|
||||
struct uasync_s;
|
||||
@@ -31,9 +27,7 @@ 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 {
|
||||
@@ -77,9 +71,8 @@ struct UTUN_INSTANCE {
|
||||
struct memory_pool* pkt_pool;
|
||||
struct memory_pool* ack_pool;
|
||||
|
||||
// Active sockets (UDP)
|
||||
// Active sockets
|
||||
struct ETCP_SOCKET* etcp_sockets;// linked-list
|
||||
struct stcp_server *stcp_server; // TCP server (single for now)
|
||||
|
||||
// Pending one-shot pings (for callback on PONG or timeout)
|
||||
struct PING_CONTEXT* pending_pings;
|
||||
@@ -94,9 +87,6 @@ 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;
|
||||
|
||||
@@ -113,7 +103,6 @@ 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;
|
||||
|
||||
+100
-57
@@ -4,11 +4,6 @@
|
||||
check_PROGRAMS = \
|
||||
test_ll_queue \
|
||||
test_serialize \
|
||||
test_stream_cipher \
|
||||
test_stream_sign \
|
||||
test_etcp_transport \
|
||||
test_transport \
|
||||
test_stcp \
|
||||
test_packet_dump \
|
||||
test_debug_categories \
|
||||
test_config_debug \
|
||||
@@ -44,25 +39,45 @@ 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
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
# Transport objects (needed by etcp_api.c)
|
||||
STCP_LINK_OBJS = $(top_builddir)/src/utun-stcp_link.o $(top_builddir)/src/utun-stcp.o $(top_builddir)/src/utun-stcp_server.o $(top_builddir)/src/utun-stcp_client.o
|
||||
|
||||
# ETCP core objects
|
||||
ETCP_CORE_OBJS = \
|
||||
$(top_builddir)/src/utun-etcp.o \
|
||||
@@ -93,9 +108,7 @@ 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 \
|
||||
@@ -104,7 +117,6 @@ 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 \
|
||||
@@ -116,21 +128,43 @@ ETCP_FULL_OBJS = \
|
||||
$(top_builddir)/src/lwip_tcp/utun-lwip_tcp.o \
|
||||
$(top_builddir)/src/lwip_tcp/utun-lwip_tcp_in.o \
|
||||
$(top_builddir)/src/lwip_tcp/utun-lwip_tcp_out.o \
|
||||
$(STCP_LINK_OBJS) \
|
||||
$(ETCP_CORE_OBJS)
|
||||
|
||||
# Windows-specific libraries
|
||||
# Windows-specific libraries (advapi32 for CryptGenRandom, ws2_32 for sockets)
|
||||
if OS_WINDOWS
|
||||
WIN_LIBS = -lws2_32 -liphlpapi -ladvapi32 -lbcrypt -ldbghelp
|
||||
else
|
||||
WIN_LIBS =
|
||||
endif
|
||||
|
||||
# Common libraries
|
||||
# Common libraries (libuasync.a from lib directory)
|
||||
COMMON_LIBS = $(top_builddir)/lib/libuasync.a -lpthread $(WIN_LIBS)
|
||||
|
||||
# Crypto always uses OpenSSL
|
||||
# Crypto libraries (conditional)
|
||||
if USE_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
|
||||
@@ -138,41 +172,38 @@ 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
|
||||
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_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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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_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
|
||||
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_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
|
||||
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_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)
|
||||
|
||||
@@ -215,35 +246,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
|
||||
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_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
|
||||
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_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
|
||||
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_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
|
||||
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_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
|
||||
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_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
|
||||
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_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
|
||||
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_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
|
||||
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_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_nat_engine_SOURCES = test_nat_engine.c
|
||||
@@ -251,7 +282,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
|
||||
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_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_nat_stress_SOURCES = test_nat_stress.c
|
||||
@@ -266,6 +297,13 @@ 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)
|
||||
@@ -303,19 +341,21 @@ 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
|
||||
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_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
|
||||
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_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(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_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_bbr_integration_SOURCES = bbr_integration/test_bbr_integration.c
|
||||
test_bbr_integration_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
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_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
|
||||
@@ -326,6 +366,9 @@ 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
|
||||
|
||||
|
||||
@@ -124,7 +124,6 @@ static struct UTUN_INSTANCE* create_instance(struct UASYNC* u, uint64_t node_id,
|
||||
cfg->global.keepalive_timeout = 5000;
|
||||
cfg->global.keepalive_interval = 500;
|
||||
cfg->global.allowed_keys_allow_all = 1;
|
||||
cfg->global.bbr_max_cwnd = 100000;
|
||||
inst->config = cfg;
|
||||
return inst;
|
||||
}
|
||||
@@ -334,11 +333,15 @@ int main(void) {
|
||||
if (!ctx.ua) { printf("ERROR: uasync_create failed\n"); return 1; }
|
||||
|
||||
const char* s_priv =
|
||||
"38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68";
|
||||
const char* s_pub = "ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a";
|
||||
"67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb";
|
||||
const char* s_pub =
|
||||
"1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17"
|
||||
"c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9";
|
||||
const char* c_priv =
|
||||
"704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f";
|
||||
const char* c_pub = "b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01";
|
||||
"4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2";
|
||||
const char* c_pub =
|
||||
"c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3"
|
||||
"c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71";
|
||||
|
||||
printf("Creating instances...\n");
|
||||
ctx.sender = create_instance(ctx.ua, 0x1111111111111111ULL, c_priv, c_pub);
|
||||
@@ -483,7 +486,7 @@ int main(void) {
|
||||
}
|
||||
}
|
||||
|
||||
int pass = (ctx.bytes_received > 50000);
|
||||
int pass = (ctx.bytes_received > 50000); /* at least 50KB delivered */
|
||||
printf("\n[%s]\n", pass ? "PASS" : "FAIL");
|
||||
|
||||
if (ctx.log_file) {
|
||||
|
||||
@@ -71,7 +71,7 @@ static void benchmark_without_sockets(void) {
|
||||
printf("Set %d zero timeouts:\n", NUM_TIMEOUTS);
|
||||
printf(" Total time: %.3f ms\n", set_time);
|
||||
printf(" Average per timeout: %.3f us\n", (set_time * 1000.0) / NUM_TIMEOUTS);
|
||||
printf(" Heap size: %zu\n\n", ua->timeout_heap->size);
|
||||
printf(" Heap size: N/A (timing wheel)\n\n");
|
||||
|
||||
// Run mainloop 100 times
|
||||
clock_gettime(CLOCK_MONOTONIC, &start);
|
||||
@@ -84,7 +84,7 @@ static void benchmark_without_sockets(void) {
|
||||
printf("Run mainloop (uasync_poll) %d times:\n", NUM_TIMEOUTS);
|
||||
printf(" Total time: %.3f ms\n", mainloop_time);
|
||||
printf(" Average per iteration: %.3f us\n", (mainloop_time * 1000.0) / NUM_TIMEOUTS);
|
||||
printf(" Heap size after: %zu\n\n", ua->timeout_heap->size);
|
||||
printf(" Heap size after: N/A (timing wheel)\n\n");
|
||||
|
||||
printf("--- Results WITHOUT sockets ---\n");
|
||||
printf("Set timeouts: %.3f ms (%.3f us/op)\n", set_time, (set_time * 1000.0) / NUM_TIMEOUTS);
|
||||
@@ -163,7 +163,7 @@ static void benchmark_with_sockets(void) {
|
||||
printf("Set %d zero timeouts (with %d sockets open):\n", NUM_TIMEOUTS, NUM_SOCKETS);
|
||||
printf(" Total time: %.3f ms\n", set_time);
|
||||
printf(" Average per timeout: %.3f us\n", (set_time * 1000.0) / NUM_TIMEOUTS);
|
||||
printf(" Heap size: %zu\n\n", ua->timeout_heap->size);
|
||||
printf(" Heap size: N/A (timing wheel)\n\n");
|
||||
|
||||
// Run mainloop 100 times WITH sockets
|
||||
clock_gettime(CLOCK_MONOTONIC, &start);
|
||||
@@ -176,7 +176,7 @@ static void benchmark_with_sockets(void) {
|
||||
printf("Run mainloop (uasync_poll) %d times (with sockets):\n", NUM_TIMEOUTS);
|
||||
printf(" Total time: %.3f ms\n", mainloop_time);
|
||||
printf(" Average per iteration: %.3f us\n", (mainloop_time * 1000.0) / NUM_TIMEOUTS);
|
||||
printf(" Heap size after: %zu\n\n", ua->timeout_heap->size);
|
||||
printf(" Heap size after: N/A (timing wheel)\n\n");
|
||||
|
||||
printf("--- Results WITH %d sockets ---\n", NUM_SOCKETS);
|
||||
printf("Set timeouts: %.3f ms (%.3f us/op)\n", set_time, (set_time * 1000.0) / NUM_TIMEOUTS);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[global]
|
||||
my_node_id=0xAAAA000000000002
|
||||
my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f
|
||||
my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01
|
||||
my_private_key=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2
|
||||
my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71
|
||||
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=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a
|
||||
peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9
|
||||
|
||||
[tcp_proxy_client]
|
||||
enabled=yes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[global]
|
||||
my_node_id=0xAAAA000000000001
|
||||
my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68
|
||||
my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a
|
||||
my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb
|
||||
my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9
|
||||
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=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
|
||||
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
|
||||
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n"
|
||||
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
|
||||
"tun_ip=10.99.0.1/24\n"
|
||||
"tun_ifname=tun99\n"
|
||||
"# Debug and logging configuration\n"
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
/**
|
||||
* @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=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
|
||||
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
|
||||
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n"
|
||||
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\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=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
|
||||
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
|
||||
"my_private_key=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2\n"
|
||||
"my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\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=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
|
||||
"peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\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=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
|
||||
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
|
||||
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n"
|
||||
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\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=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
|
||||
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
|
||||
"my_private_key=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2\n"
|
||||
"my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\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=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
|
||||
"peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\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 = "38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68";
|
||||
const char* s_pub = "ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a";
|
||||
const char* c_priv = "704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f";
|
||||
const char* c_pub = "b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01";
|
||||
const char* s_priv = "67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb";
|
||||
const char* s_pub = "1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9";
|
||||
const char* c_priv = "4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2";
|
||||
const char* c_pub = "c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71";
|
||||
|
||||
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 = "38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68";
|
||||
const char* s_pub = "ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a";
|
||||
const char* c_priv = "704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f";
|
||||
const char* c_pub = "b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01";
|
||||
const char* s_priv = "67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb";
|
||||
const char* s_pub = "1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9";
|
||||
const char* c_priv = "4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2";
|
||||
const char* c_pub = "c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71";
|
||||
|
||||
printf("Creating instances...\n");
|
||||
server = create_instance(ua, 0x1111111111111111ULL, s_priv, s_pub);
|
||||
|
||||
Loaded 100 of 527 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user