Compare commits
69
Commits
bbr
...
v0.1.0-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
487cb8718e | ||
|
|
42d96f118d | ||
|
|
8962f58ea4 | ||
|
|
1f541d6432 | ||
|
|
e66156eb49 | ||
|
|
2cd38988e6 | ||
|
|
d58b532741 | ||
|
|
4716e54069 | ||
|
|
aa73e9d8cb | ||
|
|
993c265125 | ||
|
|
5ca0bbf9e7 | ||
|
|
0ef7ee597c | ||
|
|
7d460ca156 | ||
|
|
b738b2ff1b | ||
|
|
cc5289e37c | ||
|
|
0c5f25c96d | ||
|
|
b53758bdbe | ||
|
|
f5b1c3e789 | ||
|
|
0edd21e335 | ||
|
|
e0c38676e7 | ||
|
|
fefd2bcc52 | ||
|
|
8427e18c98 | ||
|
|
7329fa65f1 | ||
|
|
de8cce521a | ||
|
|
f06ebd2ab4 | ||
|
|
a97a38d867 | ||
|
|
aff3b2d00f | ||
|
|
ccdbab95a8 | ||
|
|
a01e2f0899 | ||
|
|
dfdec06a17 | ||
|
|
393a641e18 | ||
|
|
53d41d5c62 | ||
|
|
c60ba6eaec | ||
|
|
c5123e4baf | ||
|
|
928c2fe057 | ||
|
|
b5b6875bc5 | ||
|
|
7c953c1929 | ||
|
|
1c09ad2e5a | ||
|
|
3284dffaaf | ||
|
|
c893285d71 | ||
|
|
75028f278a | ||
|
|
7fc1b5419f | ||
|
|
dee9889423 | ||
|
|
35871891db | ||
|
|
373e4b33c7 | ||
|
|
090e2f28e4 | ||
|
|
a2c7f1e6cb | ||
|
|
21da640ebe | ||
|
|
80177c9b3e | ||
|
|
8f13f0ec4d | ||
|
|
5420e414ce | ||
|
|
69112ffde1 | ||
|
|
89d675f15d | ||
|
|
914f252b12 | ||
|
|
a7b20f268f | ||
|
|
c0207ad41d | ||
|
|
8051dfe4e5 | ||
|
|
22c24d135e | ||
|
|
d8c8c1c5b0 | ||
|
|
231081dd2f | ||
|
|
62b3695a77 | ||
|
|
0e2b0e28d8 | ||
|
|
e50ca9978e | ||
|
|
b667d6f352 | ||
|
|
6717c863e7 | ||
|
|
2bbdf8195f | ||
|
|
73f707ef9b | ||
|
|
34683f0500 | ||
|
|
150e9977df |
No files matched your search
+6
-1
@@ -1,5 +1,6 @@
|
||||
# Build directory (out-of-tree build)
|
||||
/build/
|
||||
tools/chatgui/build/
|
||||
|
||||
# Build outputs
|
||||
*.o
|
||||
@@ -59,7 +60,7 @@ Makefile.in
|
||||
.cache/
|
||||
|
||||
# Debug outputs
|
||||
core
|
||||
/core
|
||||
vgcore.*
|
||||
callgrind.out.*
|
||||
|
||||
@@ -96,6 +97,9 @@ utun.conf
|
||||
tools/bping/bping
|
||||
tools/proxy/udp_proxy
|
||||
|
||||
# Reference projects (not part of utun)
|
||||
tools/tdesktop-dev/
|
||||
|
||||
# Debug/temp artifacts
|
||||
*.bak
|
||||
*.debug
|
||||
@@ -109,3 +113,4 @@ build_win.log
|
||||
/wintun.dll
|
||||
src/lwip_orig/
|
||||
lib/timeout/
|
||||
utun.pid
|
||||
@@ -15,7 +15,7 @@ This file contains essential information for AI coding agents working in the uTu
|
||||
**Repository:** uTun - Secure VPN tunnel with ETCP protocol
|
||||
**Language:** C (C99)
|
||||
**Build System:** GNU Autotools (autoconf/automake)
|
||||
**Cryptography:** TinyCrypt + OpenSSL (AES-CCM, ECC, SHA256)
|
||||
**Cryptography:** OpenSSL (AES-CCM, X25519, SHA256)
|
||||
|
||||
## Build Commands
|
||||
|
||||
@@ -56,6 +56,16 @@ cd tests && make # Build only tests
|
||||
./build_direct.sh # Компиляция вручную с x86_64-w64-mingw32-gcc
|
||||
```
|
||||
|
||||
### ASAN Build (AddressSanitizer, Linux)
|
||||
```bash
|
||||
./build.sh --asan -j4 # lib/ + src/ only (tests not ASAN-compatible)
|
||||
```
|
||||
Запуск с ASAN (detect_leaks отключены чтобы не спамить при shutdown):
|
||||
```bash
|
||||
ASAN_OPTIONS=detect_leaks=0:halt_on_error=0 ./src/utun -f -p /tmp/utun.pid -l /tmp/utun.log
|
||||
```
|
||||
Краш-логи ASAN пишутся в `/tmp/utun_asan.<pid>`.
|
||||
|
||||
## Test Commands
|
||||
|
||||
### Run All Tests
|
||||
@@ -142,14 +152,14 @@ gcc -I../src -I../lib -I../tinycrypt/lib/include \
|
||||
### Key Sizes
|
||||
| Constant | Value | Description |
|
||||
|----------|-------|-------------|
|
||||
| `SC_PRIVKEY_SIZE` | 32 | ECC private key |
|
||||
| `SC_PUBKEY_SIZE` | 64 | ECC public key |
|
||||
| `SC_PRIVKEY_SIZE` | 32 | X25519 private key |
|
||||
| `SC_PUBKEY_SIZE` | 32 | X25519 public key |
|
||||
| `SC_NONCE_SIZE` | 13 | CCM nonce (exactly 13 bytes) |
|
||||
| `SC_SESSION_KEY_SIZE` | 16 | AES-128 session key |
|
||||
| `SC_TAG_SIZE` | 16 | CCM auth tag |
|
||||
| `SC_CRC32_SIZE` | 4 | CRC32 checksum |
|
||||
| `SC_PUBKEY_ENC_SALT_SIZE` | 8 | Salt for pubkey obfuscation |
|
||||
| `SC_PUBKEY_ENC_SIZE` | 72 | Total pubkey+salt block sent unencrypted |
|
||||
| `SC_PUBKEY_ENC_SIZE` | 40 | Total pubkey+salt block sent unencrypted |
|
||||
|
||||
### Using Secure Channel (secure_channel.h)
|
||||
```c
|
||||
@@ -211,6 +221,8 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
|
||||
├── doc/ # Technical Specifications
|
||||
├── tools/ # Auxiliary tools
|
||||
│ ├── etcpmon/ # GUI монитор ETCP
|
||||
│ ├── chatgui/ # GUI чат (Qt 5, отдельная сборка через CMake)
|
||||
│ ├── tdesktop-dev/ # Референс: Telegram Desktop (только для изучения)
|
||||
│ ├── proxy/ # UDP прокси для тестов
|
||||
│ └── bping/ # BPing (bandwidth ping)
|
||||
├── tinycrypt/ # TinyCrypt crypto library (external)
|
||||
@@ -246,7 +258,7 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
|
||||
- `route_node.c/h` - Route node (peer) management
|
||||
|
||||
**Crypto (src/)**
|
||||
- `secure_channel.c/h` - AES-CCM encryption with ECC key exchange, pubkey obfuscation
|
||||
- `secure_channel.c/h` - AES-CCM encryption with X25519 key exchange, pubkey obfuscation
|
||||
- `crc32.c/h` - CRC32 checksums
|
||||
|
||||
**NAT (src/)**
|
||||
@@ -285,7 +297,7 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
|
||||
- **LL_QUEUE:** Lock-free queue with auto-callback, hash index lookup, threshold waiter
|
||||
- **Memory Pool:** Fast allocation for hot-path objects (packets, inflight entries, fragments)
|
||||
- **ETCP:** TCP-like reliable protocol with encryption, multi-link, load balancing
|
||||
- **Secure Channel:** AES-CCM + ECC key exchange, nonce-based encryption, pubkey obfuscation
|
||||
- **Secure Channel:** AES-CCM + X25519 key exchange, nonce-based encryption, pubkey obfuscation
|
||||
|
||||
## Queue Usage Rules
|
||||
|
||||
@@ -337,6 +349,141 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
|
||||
- Для каждого отладочного вывода подумай какая из доступной информация будет полезна чтобы можно было наиболее завершенно оценить состояние алгоритма и состояний влияющих на алгоритм.
|
||||
|
||||
|
||||
## chatgui (GUI Chat Client)
|
||||
|
||||
Chatgui — десктопный GUI-чат на Qt 5, отдельный проект внутри репозитория. Не связан с autotools-сборкой utun.
|
||||
|
||||
### Технологии
|
||||
- **Язык:** C++17
|
||||
- **Фреймворк:** Qt 5.15+ (Widgets + Network)
|
||||
- **Сборка:** CMake 3.16+
|
||||
- **БД:** SQLite3 (WAL mode, встроенный `db/sqlite3.c` amalgamation)
|
||||
- **Анимации:** rlottie (C API) + zlib (gzip-декомпрессия TGS)
|
||||
|
||||
### Сборка
|
||||
```bash
|
||||
# Установка зависимостей
|
||||
sudo apt install librlottie-dev zlib1g-dev qtbase5-dev
|
||||
|
||||
# Сборка
|
||||
cd tools/chatgui && mkdir -p build && cd build
|
||||
cmake .. && make -j4
|
||||
./chatgui
|
||||
```
|
||||
|
||||
### Структура файлов (src/)
|
||||
|
||||
| Файл | Назначение |
|
||||
|------|-----------|
|
||||
| `mainwindow.h/cpp` | Главное окно, QSplitter (ChannelList \| MessageList \| AccountList), трей |
|
||||
| `chatview.h/cpp` | QListView с фоновым изображением, hover-сигнал, drag-to-select |
|
||||
| `messagelist.h/cpp` | QStandardItemModel, stub-данные, инициализация анимаций, hover→activate |
|
||||
| `messagedelegate.h/cpp` | QStyledItemDelegate: бабблы, текст, цитаты, реакции, оверлей анимаций |
|
||||
| `emoji.h/cpp` | EmojiData/EmojiCategory/EmojiType, 4 категории, `g_animatedEmojiMap` |
|
||||
| `emojipanel.h/cpp` | Эмодзи-пикер: QTabWidget + QGridLayout, EmojiButton с hover-анимацией |
|
||||
| `emojitabbar.h/cpp` | Кастомный QTabBar для эмодзи-панели |
|
||||
| `inputbar.h/cpp` | Поле ввода (QTextEdit), вставка эмодзи, отправка по Enter |
|
||||
| `lottieicon.h/cpp` | Загрузка TGS (gzip→zlib), рендер кадров через `lottie_animation_render` |
|
||||
| `animtimer.h/cpp` | Синглтон-таймер 30fps, refcount activate/deactivate, сигнал ticked |
|
||||
| `channellist.h/cpp` `channeldelegate.h/cpp` | Список каналов с аватарами и превью |
|
||||
| `accountlist.h/cpp` | Список аккаунтов/пользователей |
|
||||
|
||||
### Другие поддиректории
|
||||
- `db/` — SQLite3 amalgamation (`sqlite3.c/h`), `db_manager.h/cpp` (схема: nodes, channels, messages, keys, addresses)
|
||||
- `transport/` — TCP-клиент для связи с uTun (`msg_client.h/cpp`, бинарный протокол `[size:2][type:1][id:8][options:1][data...]`)
|
||||
- `resources/` — `bg.jpg`, `chatgui.qrc` (встраивает 5 TGS в бинарник), `animations/*.tgs`
|
||||
|
||||
### Система анимированных эмодзи
|
||||
|
||||
#### Формат
|
||||
TGS = gzip-сжатый Lottie JSON. Холст 512×512, 60fps, 90–180 кадров. Декомпрессия через zlib (`inflateInit2` с `16+MAX_WBITS`).
|
||||
|
||||
#### Ключевые компоненты
|
||||
```
|
||||
LottieIcon — загрузка TGS, рендер кадров (rlottie C API)
|
||||
↓
|
||||
AnimTimer (синглтон) — таймер 33ms, refcount activate/deactivate
|
||||
├── в сообщениях: → MessageDelegate::paint()
|
||||
│ drawAnimatedEmojiOverlay() — QTextLayout → позиции эмодзи → drawImage()
|
||||
│ drawStatusBar() — анимированные реакции вместо drawText()
|
||||
│
|
||||
├── в панели: → EmojiButton (наследует QPushButton)
|
||||
│ per-button QTimer, enterEvent/leaveEvent, собственный paintEvent
|
||||
│
|
||||
└── активация: → ChatView::hoveredIndexChanged
|
||||
MessageList проверяет текст/реакции сообщения на g_animatedEmojiMap
|
||||
есть анимация → AnimTimer::activate() / нет → deactivate()
|
||||
```
|
||||
|
||||
#### Инициализация
|
||||
1. `MessageList` конструктор: загружает все `EmojiType::Animation` из `builtinEmojiSet()`, создаёт `LottieIcon`, регистрирует в `AnimTimer`
|
||||
2. **Должно быть ДО создания `EmojiPanel`** — иначе `EmojiButton` получит `nullptr`
|
||||
3. `g_animatedEmojiMap` заполняется при старте (Unicode → animPath)
|
||||
|
||||
#### Текущий маппинг (5 эмодзи)
|
||||
| Unicode | Shortcode | TGS-файл |
|
||||
|---------|-----------|----------|
|
||||
| ✨ `U+2728` | `:sparkles:` | `sparkles_emoji.tgs` |
|
||||
| 🏳️ `U+1F3F3` | `:flag:` | `white_flag_emoji.tgs` |
|
||||
| 👋 `U+1F44B` | `:wave:` | `greeting.tgs` |
|
||||
| ✋ `U+270B` | `:raised_hand:` | `stop.tgs` |
|
||||
| 🤖 `U+1F916` | `:robot:` | `robot.tgs` |
|
||||
|
||||
#### Рендеринг в сообщениях
|
||||
- Текст рисуется через `QPainter::drawText(Qt::TextWordWrap)`
|
||||
- `drawAnimatedEmojiOverlay()` повторяет layout через `QTextLayout` с теми же параметрами
|
||||
- Для каждого эмодзи из `g_animatedEmojiMap` находит пиксельную позицию через `QTextLine::cursorToX()`
|
||||
- Рендерит кадр через `LottieIcon::renderFrame(currentFrame(), size)` и рисует `drawImage()`
|
||||
- Размер эмодзи масштабирован ×1.3 относительно высоты строки
|
||||
- Аналогично для реакций в `drawStatusBar()`
|
||||
|
||||
#### Drag-to-select
|
||||
- Одиночный клик снимает выделение (если было), не стартует новое
|
||||
- Движение ≥5px от точки нажатия стартует выделение (`m_selStart`/`m_selEnd`)
|
||||
- Ctrl+C копирует: для одного сообщения — выделенный фрагмент, для нескольких — `[time] author: text`
|
||||
|
||||
### Важные правила chatgui
|
||||
- `CMAKE_AUTORCC ON` обязателен для компиляции `.qrc`-ресурсов
|
||||
- Иконки должны быть зарегистрированы в `AnimTimer` ДО создания `EmojiPanel`
|
||||
- `EmojiButton` НЕ использует `Q_OBJECT` (в анонимном namespace) — таймер через лямбду, каст через `dynamic_cast`
|
||||
- Координаты `indexAt()`: `viewport()->mapFrom(this, event->pos())`
|
||||
|
||||
---
|
||||
|
||||
## tdesktop-dev (референсный проект Telegram Desktop)
|
||||
|
||||
**Расположение:** `tools/tdesktop-dev/` — полный клон [Telegram Desktop](https://github.com/telegramdesktop/tdesktop).
|
||||
|
||||
**Статус:** референс. Не собирается, не модифицируется, в `.gitignore`.
|
||||
|
||||
**Назначение:**
|
||||
- Источник TGS-анимаций (`Telegram/Resources/animations/` — 99+ файлов)
|
||||
- Образец использования rlottie/lib_lottie API
|
||||
- Архитектурный референс для GUI-компонентов
|
||||
|
||||
**Правила:**
|
||||
- НЕ модифицировать файлы внутри `tdesktop-dev/`
|
||||
- НЕ коммитить изменения из `tdesktop-dev/`
|
||||
- НЕ ссылаться в сборке на код/библиотеки из tdesktop-dev
|
||||
- Можно копировать TGS-файлы в `tools/chatgui/resources/animations/`
|
||||
|
||||
**Полезные поддиректории:**
|
||||
- `Telegram/Resources/animations/` — TGS-анимации (dice, reactions, emoji, UI)
|
||||
- `Telegram/lib_lottie/` — C++ обёртка над rlottie (SinglePlayer, MultiPlayer, Icon, FrameGenerator)
|
||||
- `Telegram/ThirdParty/rlottie/` — submodule rlottie (обычно не выкачан)
|
||||
|
||||
**rlottie C API (ключевые функции):**
|
||||
```c
|
||||
Lottie_Animation *lottie_animation_from_data(const char *data, const char *key, const char *resource_path);
|
||||
void lottie_animation_get_size(const Lottie_Animation *anim, size_t *w, size_t *h);
|
||||
size_t lottie_animation_get_totalframe(const Lottie_Animation *anim);
|
||||
double lottie_animation_get_framerate(const Lottie_Animation *anim);
|
||||
void lottie_animation_render(Lottie_Animation *anim, size_t frame, uint32_t *buf, size_t w, size_t h, size_t stride);
|
||||
void lottie_animation_destroy(Lottie_Animation *anim);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Documentation Files
|
||||
- `/doc/etcp_protocol.txt` - ETCP протокол (формат кодограмм, ACK, handshake, keepalive)
|
||||
- `/doc/etcp_arch.md` - ETCP архитектура
|
||||
@@ -353,7 +500,7 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
|
||||
- **Commit Messages:** Use imperative mood, concise (50-72 chars)
|
||||
- **Language:** Mix of English (technical) and Russian (business logic)
|
||||
- **Tags:** Version tags follow vX.Y.Z format
|
||||
- "cp" or "кп" in prompt = do commit and push (всех изменений на текущий момент, не откатывая)
|
||||
- "cp" or "кп" in prompt = do commit and push (всех изменений на текущий момент, не откатывая). Запрещено откатывать "лишние" изменения сделаные вне сессии (если сильно надо - сделать бэкап). если нет дополнительных указаний - комить всё что изменилось как есть.
|
||||
|
||||
## Quick Start for New Features
|
||||
1. Add new source file to `src/Makefile.am` under `utun_SOURCES`
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
# Bootstrap Makefile for utun
|
||||
# All build artifacts (.o) go to build/ directory
|
||||
# Final binary (utun) is copied to project root
|
||||
|
||||
BUILD_DIR = build
|
||||
|
||||
all:
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
@if test ! -f $(BUILD_DIR)/config.status || test ./configure -nt $(BUILD_DIR)/config.status; then \
|
||||
echo "Configuring in $(BUILD_DIR)/..."; \
|
||||
cd $(BUILD_DIR) && ../configure; \
|
||||
fi
|
||||
@$(MAKE) -C $(BUILD_DIR)
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILD_DIR)
|
||||
|
||||
distclean: clean
|
||||
-rm -f config.status config.log config.h stamp-h1
|
||||
-rm -rf autom4te.cache/
|
||||
|
||||
check: all
|
||||
@$(MAKE) -C $(BUILD_DIR) check
|
||||
|
||||
install:
|
||||
@$(MAKE) -C $(BUILD_DIR) install
|
||||
|
||||
.PHONY: all clean distclean check install
|
||||
@@ -5,6 +5,7 @@
|
||||
# -h, --help Show this help
|
||||
# --clean Clean before build
|
||||
# --full Full rebuild (autoreconf + configure + make)
|
||||
# --asan Build with AddressSanitizer (debug)
|
||||
# -j[N] Build with N parallel jobs (default: 4)
|
||||
|
||||
set -e # Exit on error
|
||||
@@ -21,6 +22,7 @@ fi
|
||||
# Parse arguments
|
||||
CLEAN=0
|
||||
FULL_REBUILD=0
|
||||
USE_ASAN=0
|
||||
JOBS="-j4"
|
||||
MAKE_ARGS=""
|
||||
|
||||
@@ -32,6 +34,7 @@ while [[ $# -gt 0 ]]; do
|
||||
echo " -h, --help Show this help"
|
||||
echo " --clean Clean before build"
|
||||
echo " --full Full rebuild (autoreconf + configure + make)"
|
||||
echo " --asan Build with AddressSanitizer"
|
||||
echo " -j[N] Build with N parallel jobs (default: 4)"
|
||||
exit 0
|
||||
;;
|
||||
@@ -43,6 +46,10 @@ while [[ $# -gt 0 ]]; do
|
||||
FULL_REBUILD=1
|
||||
shift
|
||||
;;
|
||||
--asan)
|
||||
USE_ASAN=1
|
||||
shift
|
||||
;;
|
||||
-j*)
|
||||
JOBS="$1"
|
||||
shift
|
||||
@@ -122,13 +129,33 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
# Generate Makefiles if needed
|
||||
if [ ! -f Makefile ]; then
|
||||
echo ""
|
||||
echo "Running ./configure..."
|
||||
if [ "$USE_ASAN" -eq 1 ]; then
|
||||
./configure CFLAGS="-g -Og -fsanitize=address -fno-omit-frame-pointer -fno-pie" LDFLAGS="-no-pie"
|
||||
else
|
||||
./configure
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build (all .o files go to build/ directory, binary copied to project root)
|
||||
echo ""
|
||||
echo "Compiling with $JOBS..."
|
||||
BUILD_SUCCESS=0
|
||||
make $JOBS $MAKE_ARGS 2>&1 | tee build.log
|
||||
if [ ${PIPESTATUS[0]} -eq 0 ]; then
|
||||
if [ "$USE_ASAN" -eq 1 ]; then
|
||||
echo "(AddressSanitizer enabled — lib/ and src/ only, skipping tests)"
|
||||
AF="-fsanitize=address -fno-omit-frame-pointer -g -Og"
|
||||
AL="-fsanitize=address"
|
||||
(cd lib && make clean $JOBS $MAKE_ARGS 2>&1 && make "CFLAGS+=$AF" "LDFLAGS+=$AL" $JOBS $MAKE_ARGS 2>&1) || { echo "lib build failed"; exit 1; }
|
||||
(cd src && make clean $JOBS $MAKE_ARGS 2>&1 && make "CFLAGS+=$AF" "LDFLAGS+=$AL" $JOBS $MAKE_ARGS 2>&1) || { echo "src build failed"; exit 1; }
|
||||
BUILD_SUCCESS=1
|
||||
else
|
||||
make $JOBS $MAKE_ARGS 2>&1 | tee build.log
|
||||
if [ ${PIPESTATUS[0]} -eq 0 ]; then
|
||||
BUILD_SUCCESS=1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $BUILD_SUCCESS -eq 1 ]; then
|
||||
|
||||
+37
-6
@@ -1,8 +1,24 @@
|
||||
#!/bin/sh
|
||||
#pkg install -y autoconf openssl
|
||||
|
||||
set -e
|
||||
|
||||
# Проверка и установка зависимостей
|
||||
need_install=""
|
||||
|
||||
for cmd in autoreconf aclocal automake libtoolize; do
|
||||
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||
need_install=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$need_install" ]; then
|
||||
echo "Устанавливаются необходимые пакеты..."
|
||||
pkg update
|
||||
pkg install -y autoconf automake libtool pkgconf openssl
|
||||
fi
|
||||
|
||||
mkdir -p m4
|
||||
set -e
|
||||
|
||||
echo "=== Полная пересборка uTun ==="
|
||||
|
||||
@@ -12,18 +28,33 @@ make distclean 2>/dev/null || true
|
||||
rm -f config.h config.status config.log
|
||||
rm -f configure.log autoreconf.log build.log
|
||||
|
||||
# 2. Автогенерация (autoreconf)
|
||||
echo "[2/4] Автогенерация (autoreconf -fi)..."
|
||||
# 2. Генерация configure
|
||||
echo "[2/4] Автогенерация..."
|
||||
autoreconf -fi 2>&1 | tee autoreconf.log
|
||||
|
||||
# Проверяем, появился ли configure
|
||||
if [ ! -f configure ]; then
|
||||
echo "Ошибка: autoreconf не создал configure."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
chmod +x configure
|
||||
|
||||
# 3. Конфигурация
|
||||
echo "[3/4] Конфигурация..."
|
||||
./configure --prefix=/usr/local 2>&1 | tee configure.log
|
||||
|
||||
# Определяем количество ядер
|
||||
if command -v nproc >/dev/null 2>&1; then
|
||||
JOBS=$(nproc)
|
||||
else
|
||||
JOBS=$(sysctl -n hw.ncpu)
|
||||
fi
|
||||
|
||||
# 4. Сборка
|
||||
echo "[4/4] Сборка..."
|
||||
make -j$(nproc) 2>&1 | tee build.log
|
||||
make -j"$JOBS" 2>&1 | tee build.log
|
||||
|
||||
echo ""
|
||||
echo
|
||||
echo "=== Готово ==="
|
||||
echo "Бинарный файл: src/utun"
|
||||
+7
-2
@@ -28,14 +28,19 @@ libuasync_a_SOURCES = \
|
||||
serialize.c \
|
||||
serialize.h \
|
||||
tcp_io.c \
|
||||
tcp_io.h
|
||||
tcp_io.h \
|
||||
liblmdb/mdb.c \
|
||||
liblmdb/midl.c \
|
||||
liblmdb/lmdb.h \
|
||||
liblmdb/midl.h
|
||||
|
||||
libuasync_a_CFLAGS = \
|
||||
-D_ISOC99_SOURCE \
|
||||
-DDEBUG_OUTPUT_STDERR \
|
||||
-g \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/lib
|
||||
-I$(top_srcdir)/lib \
|
||||
-I$(srcdir)/liblmdb
|
||||
|
||||
# Clean build directory
|
||||
clean-local:
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
mtest
|
||||
mtest[23456]
|
||||
testdb
|
||||
mdb_copy
|
||||
mdb_stat
|
||||
mdb_dump
|
||||
mdb_load
|
||||
*.lo
|
||||
*.[ao]
|
||||
*.so
|
||||
*.exe
|
||||
*[~#]
|
||||
*.bak
|
||||
*.orig
|
||||
*.rej
|
||||
*.gcov
|
||||
*.gcda
|
||||
*.gcno
|
||||
core
|
||||
core.*
|
||||
valgrind.*
|
||||
man/
|
||||
html/
|
||||
@@ -0,0 +1,317 @@
|
||||
LMDB 0.9 Change Log
|
||||
|
||||
LMDB 0.9.36 Engineering
|
||||
ITS#7772 - limit sub-page growth when re-adding existing data
|
||||
ITS#8335 - more input validation for put(MDB_MULTIPLE)
|
||||
ITS#8386 - be a bit more precise that mdb_get retrieves data in intro.doc
|
||||
ITS#8590 - suppress SIGPIPE on Mac OSX for mdb_copy/compact
|
||||
ITS#8739 - don't use fdatasync on FreeBSD 11.0 and older
|
||||
ITS#8824 - mdb_dump: cleanup check for MDB_SUCCESS
|
||||
ITS#9388 - fix mdb_stat page counts for dupsort DBs
|
||||
ITS#10054 - fix write of 2GB values
|
||||
ITS#10108 - mdb_dump: fix comment
|
||||
ITS#10454 - fix metapage sync on Mac OSX
|
||||
ITS#10504 - silence linker waarning on Mac OSX
|
||||
ITS#10522 - reject 0-length values in DUPSORT DBs
|
||||
|
||||
LMDB 0.9.35 Release (2026/01/27)
|
||||
ITS#10434 - Fix typo affecting Mac OSX
|
||||
|
||||
LMDB 0.9.34 Release (2026/01/26)
|
||||
ITS#9564 - fix race condition freeing spilled pages at end of transaction
|
||||
ITS#10222 - Update mdb_dump(1) and mdb_load(1) man pages for append (-a) option
|
||||
ITS#10275 - mdb_load: add -Q option to use NOSYNC
|
||||
ITS#10296 - fix fdatasync on MacOS
|
||||
ITS#10342 - fix memleak in mdb_txn_begin for nested txns
|
||||
ITS#10346 - fix mdb_env_copy2 with values > (2GB-16)
|
||||
ITS#10355 - fix mplay build on musl
|
||||
ITS#10396 - fix mdb_cursor_del0 with multiple DUPSORT cursors
|
||||
ITS#10419 - add support for NetBSD
|
||||
ITS#10420 - add support for Haiku
|
||||
ITS#10421 - mdb_load: check for malicious input
|
||||
|
||||
LMDB 0.9.33 Release (2024/05/21)
|
||||
ITS#9037 mdb_page_search: fix error code when DBI record is missing
|
||||
ITS#10198 For win32, stop passing ignored parameter
|
||||
ITS#10212 Fix meta page usage by read only tools
|
||||
|
||||
LMDB 0.9.32 Release (2024/01/29)
|
||||
ITS#9378 - Add ability to replay log and replay log tool
|
||||
ITS#10095 - partial revert of ITS#9278. The patch was incorrect and introduced numerous race conditions.
|
||||
ITS#10125 - mdb_load: fix cursor reinit in Append mode
|
||||
ITS#10137 - Allow users to define MDB_IDL_LOGN
|
||||
|
||||
LMDB 0.9.31 Release (2023/07/10)
|
||||
ITS#8447 - Fix cursor_put(MDB_CURRENT) on DUPSORT DB with different sized data
|
||||
|
||||
LMDB 0.9.30 Release (2023/02/08)
|
||||
ITS#9806 - LMDB page_split: key threshold depends on page size
|
||||
ITS#9916 - avoid gcc optimization bug on sparc64 linux
|
||||
ITS#9919 - Mark infrequently used functions as cold
|
||||
ITS#9723 - clear C_EOF on cursor with MDB_FIRST_DUP
|
||||
ITS#9030 - Use sys/cachectl.h rather than asm/cachectl.h on mips
|
||||
|
||||
LMDB 0.9.29 Release (2021/03/16)
|
||||
ITS#9461 refix ITS#9376
|
||||
ITS#9500 fix regression from ITS#8662
|
||||
|
||||
LMDB 0.9.28 Release (2021/02/04)
|
||||
ITS#8662 add -a append option to mdb_load
|
||||
|
||||
LMDB 0.9.27 Release (2020/10/26)
|
||||
ITS#9376 fix repeated DUPSORT cursor deletes
|
||||
|
||||
LMDB 0.9.26 Release (2020/08/11)
|
||||
ITS#9278 fix robust mutex cleanup for FreeBSD
|
||||
|
||||
LMDB 0.9.25 Release (2020/01/30)
|
||||
ITS#9068 fix mdb_dump/load backslashes in printable content
|
||||
ITS#9118 add MAP_NOSYNC for FreeBSD
|
||||
ITS#9155 free mt_spill_pgs in non-nested txn on end
|
||||
|
||||
LMDB 0.9.24 Release (2019/07/24)
|
||||
ITS#8969 Tweak mdb_page_split
|
||||
ITS#8975 WIN32 fix writemap set_mapsize crash
|
||||
ITS#9007 Fix loose pages in WRITEMAP
|
||||
|
||||
LMDB 0.9.23 Release (2018/12/19)
|
||||
ITS#8756 Fix loose pages in dirty list
|
||||
ITS#8831 Fix mdb_load flag init
|
||||
ITS#8844 Fix mdb_env_close in forked process
|
||||
Documentation
|
||||
ITS#8857 mdb_cursor_del doesn't invalidate cursor
|
||||
ITS#8908 GET_MULTIPLE etc don't change passed in key
|
||||
|
||||
LMDB 0.9.22 Release (2018/03/22)
|
||||
Fix MDB_DUPSORT alignment bug (ITS#8819)
|
||||
Fix regression with new db from 0.9.19 (ITS#8760)
|
||||
Fix liblmdb to build on Solaris (ITS#8612)
|
||||
Fix delete behavior with DUPSORT DB (ITS#8622)
|
||||
Fix mdb_cursor_get/mdb_cursor_del behavior (ITS#8722)
|
||||
|
||||
LMDB 0.9.21 Release (2017/06/01)
|
||||
Fix xcursor after cursor_del (ITS#8622)
|
||||
|
||||
LMDB 0.9.20 (Withdrawn)
|
||||
Fix mdb_load with escaped plaintext (ITS#8558)
|
||||
Fix mdb_cursor_last / mdb_put interaction (ITS#8557)
|
||||
|
||||
LMDB 0.9.19 Release (2016/12/28)
|
||||
Fix mdb_env_cwalk cursor init (ITS#8424)
|
||||
Fix robust mutexes on Solaris 10/11 (ITS#8339)
|
||||
Tweak Win32 error message buffer
|
||||
Fix MDB_GET_BOTH on non-dup record (ITS#8393)
|
||||
Optimize mdb_drop
|
||||
Fix xcursors after mdb_cursor_del (ITS#8406)
|
||||
Fix MDB_NEXT_DUP after mdb_cursor_del (ITS#8412)
|
||||
Fix mdb_cursor_put resetting C_EOF (ITS#8489)
|
||||
Fix mdb_env_copyfd2 to return EPIPE on SIGPIPE (ITS#8504)
|
||||
Fix mdb_env_copy with empty DB (ITS#8209)
|
||||
Fix behaviors with fork (ITS#8505)
|
||||
Fix mdb_dbi_open with mainDB cursors (ITS#8542)
|
||||
Fix robust mutexes on kFreeBSD (ITS#8554)
|
||||
Fix utf8_to_utf16 error checks (ITS#7992)
|
||||
Fix F_NOCACHE on MacOS, error is non-fatal (ITS#7682)
|
||||
Build
|
||||
Make shared lib suffix overridable (ITS#8481)
|
||||
Documentation
|
||||
Cleanup doxygen nits
|
||||
Note reserved vs actual mem/disk usage
|
||||
|
||||
|
||||
LMDB 0.9.18 Release (2016/02/05)
|
||||
Fix robust mutex detection on glibc 2.10-11 (ITS#8330)
|
||||
Fix page_search_root assert on FreeDB (ITS#8336)
|
||||
Fix MDB_APPENDDUP vs. rewrite(single item) (ITS#8334)
|
||||
Fix mdb_copy of large files on Windows
|
||||
Fix subcursor move after delete (ITS#8355)
|
||||
Fix mdb_midl_shirnk off-by-one (ITS#8363)
|
||||
Check for utf8_to_utf16 failures (ITS#7992)
|
||||
Catch strdup failure in mdb_dbi_open
|
||||
Build
|
||||
Additional makefile var tweaks (ITS#8169)
|
||||
Documentation
|
||||
Add Getting Started page
|
||||
Update WRITEMAP description
|
||||
|
||||
|
||||
LMDB 0.9.17 Release (2015/11/30)
|
||||
Fix ITS#7377 catch calloc failure
|
||||
Fix ITS#8237 regression from ITS#7589
|
||||
Fix ITS#8238 page_split for DUPFIXED pages
|
||||
Fix ITS#8221 MDB_PAGE_FULL on delete/rebalance
|
||||
Fix ITS#8258 rebalance/split assert
|
||||
Fix ITS#8263 cursor_put cursor tracking
|
||||
Fix ITS#8264 cursor_del cursor tracking
|
||||
Fix ITS#8310 cursor_del cursor tracking
|
||||
Fix ITS#8299 mdb_del cursor tracking
|
||||
Fix ITS#8300 mdb_del cursor tracking
|
||||
Fix ITS#8304 mdb_del cursor tracking
|
||||
Fix ITS#7771 fakepage cursor tracking
|
||||
Fix ITS#7789 ensure mapsize >= pages in use
|
||||
Fix ITS#7971 mdb_txn_renew0() new reader slots
|
||||
Fix ITS#7969 use __sync_synchronize on non-x86
|
||||
Fix ITS#8311 page_split from update_key
|
||||
Fix ITS#8312 loose pages in nested txn
|
||||
Fix ITS#8313 mdb_rebalance dummy cursor
|
||||
Fix ITS#8315 dirty_room in nested txn
|
||||
Fix ITS#8323 dirty_list in nested txn
|
||||
Fix ITS#8316 page_merge cursor tracking
|
||||
Fix ITS#8321 cursor tracking
|
||||
Fix ITS#8319 mdb_load error messages
|
||||
Fix ITS#8320 mdb_load plaintext input
|
||||
Added mdb_txn_id() (ITS#7994)
|
||||
Added robust mutex support
|
||||
Miscellaneous cleanup/simplification
|
||||
Build
|
||||
Create install dirs if needed (ITS#8256)
|
||||
Fix ThreadProc decl on Win32/MSVC (ITS#8270)
|
||||
Added ssize_t typedef for MSVC (ITS#8067)
|
||||
Use ANSI apis on Windows (ITS#8069)
|
||||
Use O_SYNC if O_DSYNC,MDB_DSYNC are not defined (ITS#7209)
|
||||
Allow passing AR to make (ITS#8168)
|
||||
Allow passing mandir to make install (ITS#8169)
|
||||
|
||||
LMDB 0.9.16 Release (2015/08/14)
|
||||
Fix cursor EOF bug (ITS#8190)
|
||||
Fix handling of subDB records (ITS#8181)
|
||||
Fix mdb_midl_shrink() usage (ITS#8200)
|
||||
|
||||
LMDB 0.9.15 Release (2015/06/19)
|
||||
Fix txn init (ITS#7961,#7987)
|
||||
Fix MDB_PREV_DUP (ITS#7955,#7671)
|
||||
Fix compact of empty env (ITS#7956)
|
||||
Fix mdb_copy file mode
|
||||
Fix mdb_env_close() after failed mdb_env_open()
|
||||
Fix mdb_rebalance collapsing root (ITS#8062)
|
||||
Fix mdb_load with large values (ITS#8066)
|
||||
Fix to retry writes on EINTR (ITS#8106)
|
||||
Fix mdb_cursor_del on empty DB (ITS#8109)
|
||||
Fix MDB_INTEGERDUP key compare (ITS#8117)
|
||||
Fix error handling (ITS#7959,#8157,etc.)
|
||||
Fix race conditions (ITS#7969,7970)
|
||||
Added workaround for fdatasync bug in ext3fs
|
||||
Build
|
||||
Don't use -fPIC for static lib
|
||||
Update .gitignore (ITS#7952,#7953)
|
||||
Cleanup for "make test" (ITS#7841), "make clean", mtest*.c
|
||||
Misc. Android/Windows cleanup
|
||||
Documentation
|
||||
Fix MDB_APPEND doc
|
||||
Fix MDB_MAXKEYSIZE doc (ITS#8156)
|
||||
Fix mdb_cursor_put,mdb_cursor_del EACCES description
|
||||
Fix mdb_env_sync(MDB_RDONLY env) doc (ITS#8021)
|
||||
Clarify MDB_WRITEMAP doc (ITS#8021)
|
||||
Clarify mdb_env_open doc
|
||||
Clarify mdb_dbi_open doc
|
||||
|
||||
LMDB 0.9.14 Release (2014/09/20)
|
||||
Fix to support 64K page size (ITS#7713)
|
||||
Fix to persist decreased as well as increased mapsizes (ITS#7789)
|
||||
Fix cursor bug when deleting last node of a DUPSORT key
|
||||
Fix mdb_env_info to return FIXEDMAP address
|
||||
Fix ambiguous error code from writing to closed DBI (ITS#7825)
|
||||
Fix mdb_copy copying past end of file (ITS#7886)
|
||||
Fix cursor bugs from page_merge/rebalance
|
||||
Fix to dirty fewer pages in deletes (mdb_page_loose())
|
||||
Fix mdb_dbi_open creating subDBs (ITS#7917)
|
||||
Fix mdb_cursor_get(_DUP) with single value (ITS#7913)
|
||||
Fix Windows compat issues in mtests (ITS#7879)
|
||||
Add compacting variant of mdb_copy
|
||||
Add BigEndian integer key compare code
|
||||
Add mdb_dump/mdb_load utilities
|
||||
|
||||
LMDB 0.9.13 Release (2014/06/18)
|
||||
Fix mdb_page_alloc unlimited overflow page search
|
||||
Documentation
|
||||
Re-fix MDB_CURRENT doc (ITS#7793)
|
||||
Fix MDB_GET_MULTIPLE/MDB_NEXT_MULTIPLE doc
|
||||
|
||||
LMDB 0.9.12 Release (2014/06/13)
|
||||
Fix MDB_GET_BOTH regression (ITS#7875,#7681)
|
||||
Fix MDB_MULTIPLE writing multiple keys (ITS#7834)
|
||||
Fix mdb_rebalance (ITS#7829)
|
||||
Fix mdb_page_split (ITS#7815)
|
||||
Fix md_entries count (ITS#7861,#7828,#7793)
|
||||
Fix MDB_CURRENT (ITS#7793)
|
||||
Fix possible crash on Windows DLL detach
|
||||
Misc code cleanup
|
||||
Documentation
|
||||
mdb_cursor_put: cursor moves on error (ITS#7771)
|
||||
|
||||
|
||||
LMDB 0.9.11 Release (2014/01/15)
|
||||
Add mdb_env_set_assert() (ITS#7775)
|
||||
Fix: invalidate txn on page allocation errors (ITS#7377)
|
||||
Fix xcursor tracking in mdb_cursor_del0() (ITS#7771)
|
||||
Fix corruption from deletes (ITS#7756)
|
||||
Fix Windows/MSVC build issues
|
||||
Raise safe limit of max MDB_MAXKEYSIZE
|
||||
Misc code cleanup
|
||||
Documentation
|
||||
Remove spurious note about non-overlapping flags (ITS#7665)
|
||||
|
||||
LMDB 0.9.10 Release (2013/11/12)
|
||||
Add MDB_NOMEMINIT option
|
||||
Fix mdb_page_split() again (ITS#7589)
|
||||
Fix MDB_NORDAHEAD definition (ITS#7734)
|
||||
Fix mdb_cursor_del() positioning (ITS#7733)
|
||||
Partial fix for larger page sizes (ITS#7713)
|
||||
Fix Windows64/MSVC build issues
|
||||
|
||||
LMDB 0.9.9 Release (2013/10/24)
|
||||
Add mdb_env_get_fd()
|
||||
Add MDB_NORDAHEAD option
|
||||
Add MDB_NOLOCK option
|
||||
Avoid wasting space in mdb_page_split() (ITS#7589)
|
||||
Fix mdb_page_merge() cursor fixup (ITS#7722)
|
||||
Fix mdb_cursor_del() on last delete (ITS#7718)
|
||||
Fix adding WRITEMAP on existing env (ITS#7715)
|
||||
Fix nested txns (ITS#7515)
|
||||
Fix mdb_env_copy() O_DIRECT bug (ITS#7682)
|
||||
Fix mdb_cursor_set(SET_RANGE) return code (ITS#7681)
|
||||
Fix mdb_rebalance() cursor fixup (ITS#7701)
|
||||
Misc code cleanup
|
||||
Documentation
|
||||
Note that by default, readers need write access
|
||||
|
||||
|
||||
LMDB 0.9.8 Release (2013/09/09)
|
||||
Allow mdb_env_set_mapsize() on an open environment
|
||||
Fix mdb_dbi_flags() (ITS#7672)
|
||||
Fix mdb_page_unspill() in nested txns
|
||||
Fix mdb_cursor_get(CURRENT|NEXT) after a delete
|
||||
Fix mdb_cursor_get(DUP) to always return key (ITS#7671)
|
||||
Fix mdb_cursor_del() to always advance to next item (ITS#7670)
|
||||
Fix mdb_cursor_set(SET_RANGE) for tree with single page (ITS#7681)
|
||||
Fix mdb_env_copy() retry open if O_DIRECT fails (ITS#7682)
|
||||
Tweak mdb_page_spill() to be less aggressive
|
||||
Documentation
|
||||
Update caveats since mdb_reader_check() added in 0.9.7
|
||||
|
||||
LMDB 0.9.7 Release (2013/08/17)
|
||||
Don't leave stale lockfile on failed RDONLY open (ITS#7664)
|
||||
Fix mdb_page_split() ref beyond cursor depth
|
||||
Fix read txn data race (ITS#7635)
|
||||
Fix mdb_rebalance (ITS#7536, #7538)
|
||||
Fix mdb_drop() (ITS#7561)
|
||||
Misc DEBUG macro fixes
|
||||
Add MDB_NOTLS envflag
|
||||
Add mdb_env_copyfd()
|
||||
Add mdb_txn_env() (ITS#7660)
|
||||
Add mdb_dbi_flags() (ITS#7661)
|
||||
Add mdb_env_get_maxkeysize()
|
||||
Add mdb_env_reader_list()/mdb_env_reader_check()
|
||||
Add mdb_page_spill/unspill, remove hard txn size limit
|
||||
Use shorter names for semaphores (ITS#7615)
|
||||
Build
|
||||
Fix install target (ITS#7656)
|
||||
Documentation
|
||||
Misc updates for cursors, DB handles, data lifetime
|
||||
|
||||
LMDB 0.9.6 Release (2013/02/25)
|
||||
Many fixes/enhancements
|
||||
|
||||
LMDB 0.9.5 Release (2012/11/30)
|
||||
Renamed from libmdb to liblmdb
|
||||
Many fixes/enhancements
|
||||
@@ -0,0 +1,20 @@
|
||||
Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted only as authorized by the OpenLDAP
|
||||
Public License.
|
||||
|
||||
A copy of this license is available in the file LICENSE in the
|
||||
top-level directory of the distribution or, alternatively, at
|
||||
<http://www.OpenLDAP.org/license.html>.
|
||||
|
||||
OpenLDAP is a registered trademark of the OpenLDAP Foundation.
|
||||
|
||||
Individual files and/or contributed packages may be copyright by
|
||||
other parties and/or subject to additional restrictions.
|
||||
|
||||
This work also contains materials derived from public sources.
|
||||
|
||||
Additional information about OpenLDAP can be obtained at
|
||||
<http://www.openldap.org/>.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,47 @@
|
||||
The OpenLDAP Public License
|
||||
Version 2.8, 17 August 2003
|
||||
|
||||
Redistribution and use of this software and associated documentation
|
||||
("Software"), with or without modification, are permitted provided
|
||||
that the following conditions are met:
|
||||
|
||||
1. Redistributions in source form must retain copyright statements
|
||||
and notices,
|
||||
|
||||
2. Redistributions in binary form must reproduce applicable copyright
|
||||
statements and notices, this list of conditions, and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution, and
|
||||
|
||||
3. Redistributions must contain a verbatim copy of this document.
|
||||
|
||||
The OpenLDAP Foundation may revise this license from time to time.
|
||||
Each revision is distinguished by a version number. You may use
|
||||
this Software under terms of this license revision or under the
|
||||
terms of any subsequent revision of the license.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE OPENLDAP FOUNDATION AND ITS
|
||||
CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
||||
SHALL THE OPENLDAP FOUNDATION, ITS CONTRIBUTORS, OR THE AUTHOR(S)
|
||||
OR OWNER(S) OF THE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The names of the authors and copyright holders must not be used in
|
||||
advertising or otherwise to promote the sale, use or other dealing
|
||||
in this Software without specific, written prior permission. Title
|
||||
to copyright in this Software shall at all times remain with copyright
|
||||
holders.
|
||||
|
||||
OpenLDAP is a registered trademark of the OpenLDAP Foundation.
|
||||
|
||||
Copyright 1999-2003 The OpenLDAP Foundation, Redwood City,
|
||||
California, USA. All Rights Reserved. Permission to copy and
|
||||
distribute verbatim copies of this document is granted.
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright 2015-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
/** @page starting Getting Started
|
||||
|
||||
LMDB is compact, fast, powerful, and robust and implements a simplified
|
||||
variant of the BerkeleyDB (BDB) API. (BDB is also very powerful, and verbosely
|
||||
documented in its own right.) After reading this page, the main
|
||||
\ref mdb documentation should make sense. Thanks to Bert Hubert
|
||||
for creating the
|
||||
<a href="https://github.com/ahupowerdns/ahutils/blob/master/lmdb-semantics.md">
|
||||
initial version</a> of this writeup.
|
||||
|
||||
Everything starts with an environment, created by #mdb_env_create().
|
||||
Once created, this environment must also be opened with #mdb_env_open().
|
||||
|
||||
#mdb_env_open() gets passed a name which is interpreted as a directory
|
||||
path. Note that this directory must exist already, it is not created
|
||||
for you. Within that directory, a lock file and a storage file will be
|
||||
generated. If you don't want to use a directory, you can pass the
|
||||
#MDB_NOSUBDIR option, in which case the path you provided is used
|
||||
directly as the data file, and another file with a "-lock" suffix
|
||||
added will be used for the lock file.
|
||||
|
||||
Once the environment is open, a transaction can be created within it
|
||||
using #mdb_txn_begin(). Transactions may be read-write or read-only,
|
||||
and read-write transactions may be nested. A transaction must only
|
||||
be used by one thread at a time. Transactions are always required,
|
||||
even for read-only access. The transaction provides a consistent
|
||||
view of the data.
|
||||
|
||||
Once a transaction has been created, a database can be opened within it
|
||||
using #mdb_dbi_open(). If only one database will ever be used in the
|
||||
environment, a NULL can be passed as the database name. For named
|
||||
databases, the #MDB_CREATE flag must be used to create the database
|
||||
if it doesn't already exist. Also, #mdb_env_set_maxdbs() must be
|
||||
called after #mdb_env_create() and before #mdb_env_open() to set the
|
||||
maximum number of named databases you want to support.
|
||||
|
||||
Note: a single transaction can open multiple databases. Generally
|
||||
databases should only be opened once, by the first transaction in
|
||||
the process. After the first transaction completes, the database
|
||||
handles can freely be used by all subsequent transactions.
|
||||
|
||||
Within a transaction, #mdb_get() can retrieve and #mdb_put() can store single
|
||||
key/value pairs if that is all you need to do (but see \ref Cursors
|
||||
below if you want to do more).
|
||||
|
||||
A key/value pair is expressed as two #MDB_val structures. This struct
|
||||
has two fields, \c mv_size and \c mv_data. The data is a \c void pointer to
|
||||
an array of \c mv_size bytes.
|
||||
|
||||
Because LMDB is very efficient (and usually zero-copy), the data returned
|
||||
in an #MDB_val structure may be memory-mapped straight from disk. In
|
||||
other words <b>look but do not touch</b> (or free() for that matter).
|
||||
Once a transaction is closed, the values can no longer be used, so
|
||||
make a copy if you need to keep them after that.
|
||||
|
||||
@section Cursors Cursors
|
||||
|
||||
To do more powerful things, we must use a cursor.
|
||||
|
||||
Within the transaction, a cursor can be created with #mdb_cursor_open().
|
||||
With this cursor we can store/retrieve/delete (multiple) values using
|
||||
#mdb_cursor_get(), #mdb_cursor_put(), and #mdb_cursor_del().
|
||||
|
||||
#mdb_cursor_get() positions itself depending on the cursor operation
|
||||
requested, and for some operations, on the supplied key. For example,
|
||||
to list all key/value pairs in a database, use operation #MDB_FIRST for
|
||||
the first call to #mdb_cursor_get(), and #MDB_NEXT on subsequent calls,
|
||||
until the end is hit.
|
||||
|
||||
To retrieve all keys starting from a specified key value, use #MDB_SET.
|
||||
For more cursor operations, see the \ref mdb docs.
|
||||
|
||||
When using #mdb_cursor_put(), either the function will position the
|
||||
cursor for you based on the \b key, or you can use operation
|
||||
#MDB_CURRENT to use the current position of the cursor. Note that
|
||||
\b key must then match the current position's key.
|
||||
|
||||
@subsection summary Summarizing the Opening
|
||||
|
||||
So we have a cursor in a transaction which opened a database in an
|
||||
environment which is opened from a filesystem after it was
|
||||
separately created.
|
||||
|
||||
Or, we create an environment, open it from a filesystem, create a
|
||||
transaction within it, open a database within that transaction,
|
||||
and create a cursor within all of the above.
|
||||
|
||||
Got it?
|
||||
|
||||
@section thrproc Threads and Processes
|
||||
|
||||
LMDB uses POSIX locks on files, and these locks have issues if one
|
||||
process opens a file multiple times. Because of this, do not
|
||||
#mdb_env_open() a file multiple times from a single process. Instead,
|
||||
share the LMDB environment that has opened the file across all threads.
|
||||
Otherwise, if a single process opens the same environment multiple times,
|
||||
closing it once will remove all the locks held on it, and the other
|
||||
instances will be vulnerable to corruption from other processes.
|
||||
|
||||
Also note that a transaction is tied to one thread by default using
|
||||
Thread Local Storage. If you want to pass read-only transactions across
|
||||
threads, you can use the #MDB_NOTLS option on the environment.
|
||||
|
||||
@section txns Transactions, Rollbacks, etc.
|
||||
|
||||
To actually get anything done, a transaction must be committed using
|
||||
#mdb_txn_commit(). Alternatively, all of a transaction's operations
|
||||
can be discarded using #mdb_txn_abort(). In a read-only transaction,
|
||||
any cursors will \b not automatically be freed. In a read-write
|
||||
transaction, all cursors will be freed and must not be used again.
|
||||
|
||||
For read-only transactions, obviously there is nothing to commit to
|
||||
storage. The transaction still must eventually be aborted to close
|
||||
any database handle(s) opened in it, or committed to keep the
|
||||
database handles around for reuse in new transactions.
|
||||
|
||||
In addition, as long as a transaction is open, a consistent view of
|
||||
the database is kept alive, which requires storage. A read-only
|
||||
transaction that no longer requires this consistent view should
|
||||
be terminated (committed or aborted) when the view is no longer
|
||||
needed (but see below for an optimization).
|
||||
|
||||
There can be multiple simultaneously active read-only transactions
|
||||
but only one that can write. Once a single read-write transaction
|
||||
is opened, all further attempts to begin one will block until the
|
||||
first one is committed or aborted. This has no effect on read-only
|
||||
transactions, however, and they may continue to be opened at any time.
|
||||
|
||||
@section dupkeys Duplicate Keys
|
||||
|
||||
#mdb_get() and #mdb_put() respectively have no and only some support
|
||||
for multiple key/value pairs with identical keys. If there are multiple
|
||||
values for a key, #mdb_get() will only return the first value.
|
||||
|
||||
When multiple values for one key are required, pass the #MDB_DUPSORT
|
||||
flag to #mdb_dbi_open(). In an #MDB_DUPSORT database, by default
|
||||
#mdb_put() will not replace the value for a key if the key existed
|
||||
already. Instead it will add the new value to the key. In addition,
|
||||
#mdb_del() will pay attention to the value field too, allowing for
|
||||
specific values of a key to be deleted.
|
||||
|
||||
Finally, additional cursor operations become available for
|
||||
traversing through and retrieving duplicate values.
|
||||
|
||||
@section optim Some Optimization
|
||||
|
||||
If you frequently begin and abort read-only transactions, as an
|
||||
optimization, it is possible to only reset and renew a transaction.
|
||||
|
||||
#mdb_txn_reset() releases any old copies of data kept around for
|
||||
a read-only transaction. To reuse this reset transaction, call
|
||||
#mdb_txn_renew() on it. Any cursors in this transaction must also
|
||||
be renewed using #mdb_cursor_renew().
|
||||
|
||||
Note that #mdb_txn_reset() is similar to #mdb_txn_abort() and will
|
||||
close any databases you opened within the transaction.
|
||||
|
||||
To permanently free a transaction, reset or not, use #mdb_txn_abort().
|
||||
|
||||
@section cleanup Cleaning Up
|
||||
|
||||
For read-only transactions, any cursors created within it must
|
||||
be closed using #mdb_cursor_close().
|
||||
|
||||
It is very rarely necessary to close a database handle, and in
|
||||
general they should just be left open.
|
||||
|
||||
@section onward The Full API
|
||||
|
||||
The full \ref mdb documentation lists further details, like how to:
|
||||
|
||||
\li size a database (the default limits are intentionally small)
|
||||
\li drop and clean a database
|
||||
\li detect and report errors
|
||||
\li optimize (bulk) loading speed
|
||||
\li (temporarily) reduce robustness to gain even more speed
|
||||
\li gather statistics about the database
|
||||
\li define custom sort orders
|
||||
|
||||
*/
|
||||
+1608
File diff suppressed because it is too large.
Load diff
+10504
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,55 @@
|
||||
.TH MDB_COPY 1 "2014/07/01" "LMDB 0.9.14"
|
||||
.\" Copyright 2012-2021 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.SH NAME
|
||||
mdb_copy \- LMDB environment copy tool
|
||||
.SH SYNOPSIS
|
||||
.B mdb_copy
|
||||
[\c
|
||||
.BR \-V ]
|
||||
[\c
|
||||
.BR \-c ]
|
||||
[\c
|
||||
.BR \-n ]
|
||||
.B srcpath
|
||||
[\c
|
||||
.BR dstpath ]
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mdb_copy
|
||||
utility copies an LMDB environment. The environment can
|
||||
be copied regardless of whether it is currently in use.
|
||||
No lockfile is created, since it gets recreated at need.
|
||||
|
||||
If
|
||||
.I dstpath
|
||||
is specified it must be the path of an empty directory
|
||||
for storing the backup. Otherwise, the backup will be
|
||||
written to stdout.
|
||||
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-V
|
||||
Write the library version number to the standard output, and exit.
|
||||
.TP
|
||||
.BR \-c
|
||||
Compact while copying. Only current data pages will be copied; freed
|
||||
or unused pages will be omitted from the copy. This option will
|
||||
slow down the backup process as it is more CPU-intensive.
|
||||
Currently it fails if the environment has suffered a page leak.
|
||||
.TP
|
||||
.BR \-n
|
||||
Open LDMB environment(s) which do not use subdirectories.
|
||||
|
||||
.SH DIAGNOSTICS
|
||||
Exit status is zero if no errors occur.
|
||||
Errors result in a non-zero exit status and
|
||||
a diagnostic message being written to standard error.
|
||||
.SH CAVEATS
|
||||
This utility can trigger significant file size growth if run
|
||||
in parallel with write transactions, because pages which they
|
||||
free during copying cannot be reused until the copy is done.
|
||||
.SH "SEE ALSO"
|
||||
.BR mdb_stat (1)
|
||||
.SH AUTHOR
|
||||
Howard Chu of Symas Corporation <http://www.symas.com>
|
||||
@@ -0,0 +1,82 @@
|
||||
/* mdb_copy.c - memory-mapped database backup tool */
|
||||
/*
|
||||
* Copyright 2012-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#define MDB_STDOUT GetStdHandle(STD_OUTPUT_HANDLE)
|
||||
#else
|
||||
#define MDB_STDOUT 1
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
static void
|
||||
sighandle(int sig)
|
||||
{
|
||||
}
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int rc;
|
||||
MDB_env *env;
|
||||
const char *progname = argv[0], *act;
|
||||
unsigned flags = MDB_RDONLY;
|
||||
unsigned cpflags = 0;
|
||||
|
||||
for (; argc > 1 && argv[1][0] == '-'; argc--, argv++) {
|
||||
if (argv[1][1] == 'n' && argv[1][2] == '\0')
|
||||
flags |= MDB_NOSUBDIR;
|
||||
else if (argv[1][1] == 'c' && argv[1][2] == '\0')
|
||||
cpflags |= MDB_CP_COMPACT;
|
||||
else if (argv[1][1] == 'V' && argv[1][2] == '\0') {
|
||||
printf("%s\n", MDB_VERSION_STRING);
|
||||
exit(0);
|
||||
} else
|
||||
argc = 0;
|
||||
}
|
||||
|
||||
if (argc<2 || argc>3) {
|
||||
fprintf(stderr, "usage: %s [-V] [-c] [-n] srcpath [dstpath]\n", progname);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
#ifdef SIGPIPE
|
||||
signal(SIGPIPE, sighandle);
|
||||
#endif
|
||||
#ifdef SIGHUP
|
||||
signal(SIGHUP, sighandle);
|
||||
#endif
|
||||
signal(SIGINT, sighandle);
|
||||
signal(SIGTERM, sighandle);
|
||||
|
||||
act = "opening environment";
|
||||
rc = mdb_env_create(&env);
|
||||
if (rc == MDB_SUCCESS) {
|
||||
rc = mdb_env_open(env, argv[1], flags, 0600);
|
||||
}
|
||||
if (rc == MDB_SUCCESS) {
|
||||
act = "copying";
|
||||
if (argc == 2)
|
||||
rc = mdb_env_copyfd2(env, MDB_STDOUT, cpflags);
|
||||
else
|
||||
rc = mdb_env_copy2(env, argv[2], cpflags);
|
||||
}
|
||||
if (rc)
|
||||
fprintf(stderr, "%s: %s failed, error %d (%s)\n",
|
||||
progname, act, rc, mdb_strerror(rc));
|
||||
mdb_env_close(env);
|
||||
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
.TH MDB_DUMP 1 "2015/09/30" "LMDB 0.9.17"
|
||||
.\" Copyright 2014-2021 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.SH NAME
|
||||
mdb_dump \- LMDB environment export tool
|
||||
.SH SYNOPSIS
|
||||
.B mdb_dump
|
||||
[\c
|
||||
.BR \-V ]
|
||||
[\c
|
||||
.BI \-f \ file\fR]
|
||||
[\c
|
||||
.BR \-l ]
|
||||
[\c
|
||||
.BR \-n ]
|
||||
[\c
|
||||
.BR \-p ]
|
||||
[\c
|
||||
.BR \-a \ |
|
||||
.BI \-s \ subdb\fR]
|
||||
.BR \ envpath
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mdb_dump
|
||||
utility reads a database and writes its contents to the
|
||||
standard output using a portable flat-text format
|
||||
understood by the
|
||||
.BR mdb_load (1)
|
||||
utility.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-V
|
||||
Write the library version number to the standard output, and exit.
|
||||
.TP
|
||||
.BR \-f \ file
|
||||
Write to the specified file instead of to the standard output.
|
||||
.TP
|
||||
.BR \-l
|
||||
List the databases stored in the environment. Just the
|
||||
names will be listed, no data will be output.
|
||||
.TP
|
||||
.BR \-n
|
||||
Dump an LMDB database which does not use subdirectories.
|
||||
.TP
|
||||
.BR \-p
|
||||
If characters in either the key or data items are printing characters (as
|
||||
defined by isprint(3)), output them directly. This option permits users to
|
||||
use standard text editors and tools to modify the contents of databases.
|
||||
|
||||
Note: different systems may have different notions about what characters
|
||||
are considered printing characters, and databases dumped in this manner may
|
||||
be less portable to external systems.
|
||||
.TP
|
||||
.BR \-a
|
||||
Dump all of the subdatabases in the environment.
|
||||
.TP
|
||||
.BR \-s \ subdb
|
||||
Dump a specific subdatabase. If no database is specified, only the main database is dumped.
|
||||
.SH DIAGNOSTICS
|
||||
Exit status is zero if no errors occur.
|
||||
Errors result in a non-zero exit status and
|
||||
a diagnostic message being written to standard error.
|
||||
|
||||
Dumping databases that use user-defined comparison functions will output
|
||||
records with the ordering imposed by those comparison functions. If
|
||||
.B mdb_load
|
||||
is invoked without including the
|
||||
.B -a
|
||||
option when reloading those records, the new databases will likely be
|
||||
damaged beyond repair, permitting neither record storage nor retrieval.
|
||||
|
||||
.SH "SEE ALSO"
|
||||
.BR mdb_load (1)
|
||||
.SH AUTHOR
|
||||
Howard Chu of Symas Corporation <http://www.symas.com>
|
||||
@@ -0,0 +1,319 @@
|
||||
/* mdb_dump.c - memory-mapped database dump tool */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define Z "I"
|
||||
#else
|
||||
#define Z "z"
|
||||
#endif
|
||||
|
||||
#define PRINT 1
|
||||
static int mode;
|
||||
|
||||
typedef struct flagbit {
|
||||
int bit;
|
||||
char *name;
|
||||
} flagbit;
|
||||
|
||||
flagbit dbflags[] = {
|
||||
{ MDB_REVERSEKEY, "reversekey" },
|
||||
{ MDB_DUPSORT, "dupsort" },
|
||||
{ MDB_INTEGERKEY, "integerkey" },
|
||||
{ MDB_DUPFIXED, "dupfixed" },
|
||||
{ MDB_INTEGERDUP, "integerdup" },
|
||||
{ MDB_REVERSEDUP, "reversedup" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
static volatile sig_atomic_t gotsig;
|
||||
|
||||
static void dumpsig( int sig )
|
||||
{
|
||||
gotsig=1;
|
||||
}
|
||||
|
||||
static const char hexc[] = "0123456789abcdef";
|
||||
|
||||
static void hex(unsigned char c)
|
||||
{
|
||||
putchar(hexc[c >> 4]);
|
||||
putchar(hexc[c & 0xf]);
|
||||
}
|
||||
|
||||
static void text(MDB_val *v)
|
||||
{
|
||||
unsigned char *c, *end;
|
||||
|
||||
putchar(' ');
|
||||
c = v->mv_data;
|
||||
end = c + v->mv_size;
|
||||
while (c < end) {
|
||||
if (isprint(*c)) {
|
||||
if (*c == '\\')
|
||||
putchar('\\');
|
||||
putchar(*c);
|
||||
} else {
|
||||
putchar('\\');
|
||||
hex(*c);
|
||||
}
|
||||
c++;
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
static void byte(MDB_val *v)
|
||||
{
|
||||
unsigned char *c, *end;
|
||||
|
||||
putchar(' ');
|
||||
c = v->mv_data;
|
||||
end = c + v->mv_size;
|
||||
while (c < end) {
|
||||
hex(*c++);
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
/* Dump in BDB-compatible format */
|
||||
static int dumpit(MDB_txn *txn, MDB_dbi dbi, char *name)
|
||||
{
|
||||
MDB_cursor *mc;
|
||||
MDB_stat ms;
|
||||
MDB_val key, data;
|
||||
MDB_envinfo info;
|
||||
unsigned int flags;
|
||||
int rc, i;
|
||||
|
||||
rc = mdb_dbi_flags(txn, dbi, &flags);
|
||||
if (rc) return rc;
|
||||
|
||||
rc = mdb_stat(txn, dbi, &ms);
|
||||
if (rc) return rc;
|
||||
|
||||
rc = mdb_env_info(mdb_txn_env(txn), &info);
|
||||
if (rc) return rc;
|
||||
|
||||
printf("VERSION=3\n");
|
||||
printf("format=%s\n", mode & PRINT ? "print" : "bytevalue");
|
||||
if (name)
|
||||
printf("database=%s\n", name);
|
||||
printf("type=btree\n");
|
||||
printf("mapsize=%" Z "u\n", info.me_mapsize);
|
||||
if (info.me_mapaddr)
|
||||
printf("mapaddr=%p\n", info.me_mapaddr);
|
||||
printf("maxreaders=%u\n", info.me_maxreaders);
|
||||
|
||||
if (flags & MDB_DUPSORT)
|
||||
printf("duplicates=1\n");
|
||||
|
||||
for (i=0; dbflags[i].bit; i++)
|
||||
if (flags & dbflags[i].bit)
|
||||
printf("%s=1\n", dbflags[i].name);
|
||||
|
||||
printf("db_pagesize=%d\n", ms.ms_psize);
|
||||
printf("HEADER=END\n");
|
||||
|
||||
rc = mdb_cursor_open(txn, dbi, &mc);
|
||||
if (rc) return rc;
|
||||
|
||||
while ((rc = mdb_cursor_get(mc, &key, &data, MDB_NEXT)) == MDB_SUCCESS) {
|
||||
if (gotsig) {
|
||||
rc = EINTR;
|
||||
break;
|
||||
}
|
||||
if (mode & PRINT) {
|
||||
text(&key);
|
||||
text(&data);
|
||||
} else {
|
||||
byte(&key);
|
||||
byte(&data);
|
||||
}
|
||||
}
|
||||
printf("DATA=END\n");
|
||||
if (rc == MDB_NOTFOUND)
|
||||
rc = MDB_SUCCESS;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void usage(char *prog)
|
||||
{
|
||||
fprintf(stderr, "usage: %s [-V] [-f output] [-l] [-n] [-p] [-a|-s subdb] dbpath\n", prog);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int i, rc;
|
||||
MDB_env *env;
|
||||
MDB_txn *txn;
|
||||
MDB_dbi dbi;
|
||||
char *prog = argv[0];
|
||||
char *envname;
|
||||
char *subname = NULL;
|
||||
int alldbs = 0, envflags = 0, list = 0;
|
||||
|
||||
if (argc < 2) {
|
||||
usage(prog);
|
||||
}
|
||||
|
||||
/* -a: dump all subDBs
|
||||
* -s: dump only the named subDB
|
||||
* -n: use NOSUBDIR flag on env_open
|
||||
* -p: use printable characters
|
||||
* -f: write to file instead of stdout
|
||||
* -V: print version and exit
|
||||
* (default) dump only the main DB
|
||||
*/
|
||||
while ((i = getopt(argc, argv, "af:lnps:V")) != EOF) {
|
||||
switch(i) {
|
||||
case 'V':
|
||||
printf("%s\n", MDB_VERSION_STRING);
|
||||
exit(0);
|
||||
break;
|
||||
case 'l':
|
||||
list = 1;
|
||||
/*FALLTHROUGH*/
|
||||
case 'a':
|
||||
if (subname)
|
||||
usage(prog);
|
||||
alldbs++;
|
||||
break;
|
||||
case 'f':
|
||||
if (freopen(optarg, "w", stdout) == NULL) {
|
||||
fprintf(stderr, "%s: %s: reopen: %s\n",
|
||||
prog, optarg, strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
case 'n':
|
||||
envflags |= MDB_NOSUBDIR;
|
||||
break;
|
||||
case 'p':
|
||||
mode |= PRINT;
|
||||
break;
|
||||
case 's':
|
||||
if (alldbs)
|
||||
usage(prog);
|
||||
subname = optarg;
|
||||
break;
|
||||
default:
|
||||
usage(prog);
|
||||
}
|
||||
}
|
||||
|
||||
if (optind != argc - 1)
|
||||
usage(prog);
|
||||
|
||||
#ifdef SIGPIPE
|
||||
signal(SIGPIPE, dumpsig);
|
||||
#endif
|
||||
#ifdef SIGHUP
|
||||
signal(SIGHUP, dumpsig);
|
||||
#endif
|
||||
signal(SIGINT, dumpsig);
|
||||
signal(SIGTERM, dumpsig);
|
||||
|
||||
envname = argv[optind];
|
||||
rc = mdb_env_create(&env);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (alldbs || subname) {
|
||||
mdb_env_set_maxdbs(env, 2);
|
||||
}
|
||||
|
||||
rc = mdb_env_open(env, envname, envflags | MDB_RDONLY, 0664);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
rc = mdb_open(txn, subname, 0, &dbi);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
|
||||
if (alldbs) {
|
||||
MDB_cursor *cursor;
|
||||
MDB_val key;
|
||||
int count = 0;
|
||||
|
||||
rc = mdb_cursor_open(txn, dbi, &cursor);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
while ((rc = mdb_cursor_get(cursor, &key, NULL, MDB_NEXT_NODUP)) == 0) {
|
||||
char *str;
|
||||
MDB_dbi db2;
|
||||
if (memchr(key.mv_data, '\0', key.mv_size))
|
||||
continue;
|
||||
count++;
|
||||
str = malloc(key.mv_size+1);
|
||||
memcpy(str, key.mv_data, key.mv_size);
|
||||
str[key.mv_size] = '\0';
|
||||
rc = mdb_open(txn, str, 0, &db2);
|
||||
if (rc == MDB_SUCCESS) {
|
||||
if (list) {
|
||||
printf("%s\n", str);
|
||||
list++;
|
||||
} else {
|
||||
rc = dumpit(txn, db2, str);
|
||||
if (rc)
|
||||
break;
|
||||
}
|
||||
mdb_close(env, db2);
|
||||
}
|
||||
free(str);
|
||||
if (rc) continue;
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
if (!count) {
|
||||
fprintf(stderr, "%s: %s does not contain multiple databases\n", prog, envname);
|
||||
rc = MDB_NOTFOUND;
|
||||
} else if (rc == MDB_NOTFOUND) {
|
||||
rc = MDB_SUCCESS;
|
||||
}
|
||||
} else {
|
||||
rc = dumpit(txn, dbi, subname);
|
||||
}
|
||||
if (rc && rc != MDB_NOTFOUND)
|
||||
fprintf(stderr, "%s: %s: %s\n", prog, envname, mdb_strerror(rc));
|
||||
|
||||
mdb_close(env, dbi);
|
||||
txn_abort:
|
||||
mdb_txn_abort(txn);
|
||||
env_close:
|
||||
mdb_env_close(env);
|
||||
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
.TH MDB_LOAD 1 "2015/09/30" "LMDB 0.9.17"
|
||||
.\" Copyright 2014-2021 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.SH NAME
|
||||
mdb_load \- LMDB environment import tool
|
||||
.SH SYNOPSIS
|
||||
.B mdb_load
|
||||
[\c
|
||||
.BR \-V ]
|
||||
[\c
|
||||
.BR \-a ]
|
||||
[\c
|
||||
.BI \-f \ file\fR]
|
||||
[\c
|
||||
.BR \-n ]
|
||||
[\c
|
||||
.BI \-s \ subdb\fR]
|
||||
[\c
|
||||
.BR \-N ]
|
||||
[\c
|
||||
.BR \-Q ]
|
||||
[\c
|
||||
.BR \-T ]
|
||||
.BR \ envpath
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mdb_load
|
||||
utility reads from the standard input and loads it into the
|
||||
LMDB environment
|
||||
.BR envpath .
|
||||
|
||||
The input to
|
||||
.B mdb_load
|
||||
must be in the output format specified by the
|
||||
.BR mdb_dump (1)
|
||||
utility or as specified by the
|
||||
.B -T
|
||||
option below.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-V
|
||||
Write the library version number to the standard output, and exit.
|
||||
.TP
|
||||
.BR \-a
|
||||
Append all records in the order they appear in the input. The input is assumed to already be
|
||||
in correctly sorted order and no sorting or checking for redundant values will be performed.
|
||||
This option must be used to reload data that was produced by running
|
||||
.B mdb_dump
|
||||
on a database that uses custom compare functions.
|
||||
.TP
|
||||
.BR \-f \ file
|
||||
Read from the specified file instead of from the standard input.
|
||||
.TP
|
||||
.BR \-n
|
||||
Load an LMDB database which does not use subdirectories.
|
||||
.TP
|
||||
.BR \-s \ subdb
|
||||
Load a specific subdatabase. If no database is specified, data is loaded into the main database.
|
||||
.TP
|
||||
.BR \-N
|
||||
Don't overwrite existing records when loading into an already existing database; just skip them.
|
||||
.TP
|
||||
.BR \-Q
|
||||
Quick mode, uses MDB_NOSYNC for faster loading. Forces sync with mdb_env_sync() before exiting.
|
||||
.TP
|
||||
.BR \-T
|
||||
Load data from simple text files. The input must be paired lines of text, where the first
|
||||
line of the pair is the key item, and the second line of the pair is its corresponding
|
||||
data item.
|
||||
|
||||
A simple escape mechanism, where newline and backslash (\\) characters are special, is
|
||||
applied to the text input. Newline characters are interpreted as record separators.
|
||||
Backslash characters in the text will be interpreted in one of two ways: If the backslash
|
||||
character precedes another backslash character, the pair will be interpreted as a literal
|
||||
backslash. If the backslash character precedes any other character, the two characters
|
||||
following the backslash will be interpreted as a hexadecimal specification of a single
|
||||
character; for example, \\0a is a newline character in the ASCII character set.
|
||||
|
||||
For this reason, any backslash or newline characters that naturally occur in the text
|
||||
input must be escaped to avoid misinterpretation by
|
||||
.BR mdb_load .
|
||||
|
||||
.SH DIAGNOSTICS
|
||||
Exit status is zero if no errors occur.
|
||||
Errors result in a non-zero exit status and
|
||||
a diagnostic message being written to standard error.
|
||||
|
||||
.SH "SEE ALSO"
|
||||
.BR mdb_dump (1)
|
||||
.SH AUTHOR
|
||||
Howard Chu of Symas Corporation <http://www.symas.com>
|
||||
@@ -0,0 +1,515 @@
|
||||
/* mdb_load.c - memory-mapped database load tool */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <unistd.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define PRINT 1
|
||||
#define NOHDR 2
|
||||
static int mode;
|
||||
|
||||
static char *subname = NULL;
|
||||
|
||||
static size_t lineno;
|
||||
static int version;
|
||||
|
||||
static int flags;
|
||||
|
||||
static char *prog;
|
||||
|
||||
static int Eof;
|
||||
|
||||
static MDB_envinfo info;
|
||||
|
||||
static MDB_val kbuf, dbuf;
|
||||
static MDB_val k0buf;
|
||||
|
||||
#ifdef _WIN32
|
||||
#define Z "I"
|
||||
#else
|
||||
#define Z "z"
|
||||
#endif
|
||||
|
||||
#define STRLENOF(s) (sizeof(s)-1)
|
||||
|
||||
typedef struct flagbit {
|
||||
int bit;
|
||||
char *name;
|
||||
int len;
|
||||
} flagbit;
|
||||
|
||||
#define S(s) s, STRLENOF(s)
|
||||
|
||||
flagbit dbflags[] = {
|
||||
{ MDB_REVERSEKEY, S("reversekey") },
|
||||
{ MDB_DUPSORT, S("dupsort") },
|
||||
{ MDB_INTEGERKEY, S("integerkey") },
|
||||
{ MDB_DUPFIXED, S("dupfixed") },
|
||||
{ MDB_INTEGERDUP, S("integerdup") },
|
||||
{ MDB_REVERSEDUP, S("reversedup") },
|
||||
{ 0, NULL, 0 }
|
||||
};
|
||||
|
||||
static void readhdr(void)
|
||||
{
|
||||
char *ptr;
|
||||
|
||||
flags = 0;
|
||||
while (fgets(dbuf.mv_data, dbuf.mv_size, stdin) != NULL) {
|
||||
lineno++;
|
||||
if (!strncmp(dbuf.mv_data, "VERSION=", STRLENOF("VERSION="))) {
|
||||
version=atoi((char *)dbuf.mv_data+STRLENOF("VERSION="));
|
||||
if (version > 3) {
|
||||
fprintf(stderr, "%s: line %" Z "d: unsupported VERSION %d\n",
|
||||
prog, lineno, version);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "HEADER=END", STRLENOF("HEADER=END"))) {
|
||||
break;
|
||||
} else if (!strncmp(dbuf.mv_data, "format=", STRLENOF("format="))) {
|
||||
if (!strncmp((char *)dbuf.mv_data+STRLENOF("FORMAT="), "print", STRLENOF("print")))
|
||||
mode |= PRINT;
|
||||
else if (strncmp((char *)dbuf.mv_data+STRLENOF("FORMAT="), "bytevalue", STRLENOF("bytevalue"))) {
|
||||
fprintf(stderr, "%s: line %" Z "d: unsupported FORMAT %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("FORMAT="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "database=", STRLENOF("database="))) {
|
||||
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
|
||||
if (ptr) *ptr = '\0';
|
||||
if (subname) free(subname);
|
||||
subname = strdup((char *)dbuf.mv_data+STRLENOF("database="));
|
||||
} else if (!strncmp(dbuf.mv_data, "type=", STRLENOF("type="))) {
|
||||
if (strncmp((char *)dbuf.mv_data+STRLENOF("type="), "btree", STRLENOF("btree"))) {
|
||||
fprintf(stderr, "%s: line %" Z "d: unsupported type %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("type="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "mapaddr=", STRLENOF("mapaddr="))) {
|
||||
int i;
|
||||
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
|
||||
if (ptr) *ptr = '\0';
|
||||
i = sscanf((char *)dbuf.mv_data+STRLENOF("mapaddr="), "%p", &info.me_mapaddr);
|
||||
if (i != 1) {
|
||||
fprintf(stderr, "%s: line %" Z "d: invalid mapaddr %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("mapaddr="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "mapsize=", STRLENOF("mapsize="))) {
|
||||
int i;
|
||||
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
|
||||
if (ptr) *ptr = '\0';
|
||||
i = sscanf((char *)dbuf.mv_data+STRLENOF("mapsize="), "%" Z "u", &info.me_mapsize);
|
||||
if (i != 1) {
|
||||
fprintf(stderr, "%s: line %" Z "d: invalid mapsize %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("mapsize="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "maxreaders=", STRLENOF("maxreaders="))) {
|
||||
int i;
|
||||
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
|
||||
if (ptr) *ptr = '\0';
|
||||
i = sscanf((char *)dbuf.mv_data+STRLENOF("maxreaders="), "%u", &info.me_maxreaders);
|
||||
if (i != 1) {
|
||||
fprintf(stderr, "%s: line %" Z "d: invalid maxreaders %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("maxreaders="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else {
|
||||
int i;
|
||||
for (i=0; dbflags[i].bit; i++) {
|
||||
if (!strncmp(dbuf.mv_data, dbflags[i].name, dbflags[i].len) &&
|
||||
((char *)dbuf.mv_data)[dbflags[i].len] == '=') {
|
||||
flags |= dbflags[i].bit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dbflags[i].bit) {
|
||||
ptr = memchr(dbuf.mv_data, '=', dbuf.mv_size);
|
||||
if (!ptr) {
|
||||
fprintf(stderr, "%s: line %" Z "d: unexpected format\n",
|
||||
prog, lineno);
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
*ptr = '\0';
|
||||
fprintf(stderr, "%s: line %" Z "d: unrecognized keyword ignored: %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void badend(void)
|
||||
{
|
||||
fprintf(stderr, "%s: line %" Z "d: unexpected end of input\n",
|
||||
prog, lineno);
|
||||
}
|
||||
|
||||
static int unhex(unsigned char *c2)
|
||||
{
|
||||
int x, c;
|
||||
x = *c2++ & 0x4f;
|
||||
if (x & 0x40)
|
||||
x -= 55;
|
||||
c = x << 4;
|
||||
x = *c2 & 0x4f;
|
||||
if (x & 0x40)
|
||||
x -= 55;
|
||||
c |= x;
|
||||
return c;
|
||||
}
|
||||
|
||||
static int readline(MDB_val *out, MDB_val *buf)
|
||||
{
|
||||
unsigned char *c1, *c2, *end;
|
||||
size_t len, l2;
|
||||
int c;
|
||||
|
||||
if (!(mode & NOHDR)) {
|
||||
c = fgetc(stdin);
|
||||
if (c == EOF) {
|
||||
Eof = 1;
|
||||
return EOF;
|
||||
}
|
||||
if (c != ' ') {
|
||||
lineno++;
|
||||
if (fgets(buf->mv_data, buf->mv_size, stdin) == NULL) {
|
||||
badend:
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
if (c == 'D' && !strncmp(buf->mv_data, "ATA=END", STRLENOF("ATA=END")))
|
||||
return EOF;
|
||||
goto badend;
|
||||
}
|
||||
}
|
||||
if (fgets(buf->mv_data, buf->mv_size, stdin) == NULL) {
|
||||
Eof = 1;
|
||||
return EOF;
|
||||
}
|
||||
lineno++;
|
||||
|
||||
c1 = buf->mv_data;
|
||||
len = strlen((char *)c1);
|
||||
if (!len) {
|
||||
/* This can only happen with an intentionally invalid input
|
||||
* with a NUL byte after the leading SPACE
|
||||
*/
|
||||
goto badend;
|
||||
}
|
||||
l2 = len;
|
||||
|
||||
/* Is buffer too short? */
|
||||
while (c1[len-1] != '\n') {
|
||||
buf->mv_data = realloc(buf->mv_data, buf->mv_size*2);
|
||||
if (!buf->mv_data) {
|
||||
Eof = 1;
|
||||
fprintf(stderr, "%s: line %" Z "d: out of memory, line too long\n",
|
||||
prog, lineno);
|
||||
return EOF;
|
||||
}
|
||||
c1 = buf->mv_data;
|
||||
c1 += l2;
|
||||
if (fgets((char *)c1, buf->mv_size+1, stdin) == NULL) {
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
buf->mv_size *= 2;
|
||||
len = strlen((char *)c1);
|
||||
l2 += len;
|
||||
}
|
||||
c1 = c2 = buf->mv_data;
|
||||
len = l2;
|
||||
c1[--len] = '\0';
|
||||
end = c1 + len;
|
||||
|
||||
if (mode & PRINT) {
|
||||
while (c2 < end) {
|
||||
if (*c2 == '\\') {
|
||||
if (c2[1] == '\\') {
|
||||
*c1++ = *c2;
|
||||
} else {
|
||||
if (c2+3 > end || !isxdigit(c2[1]) || !isxdigit(c2[2])) {
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
*c1++ = unhex(++c2);
|
||||
}
|
||||
c2 += 2;
|
||||
} else {
|
||||
/* copies are redundant when no escapes were used */
|
||||
*c1++ = *c2++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* odd length not allowed */
|
||||
if (len & 1) {
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
while (c2 < end) {
|
||||
if (!isxdigit(*c2) || !isxdigit(c2[1])) {
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
*c1++ = unhex(c2);
|
||||
c2 += 2;
|
||||
}
|
||||
}
|
||||
c2 = out->mv_data = buf->mv_data;
|
||||
out->mv_size = c1 - c2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void usage(void)
|
||||
{
|
||||
fprintf(stderr, "usage: %s [-V] [-a] [-f input] [-n] [-s name] [-N] [-T] dbpath\n", prog);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static int greater(const MDB_val *a, const MDB_val *b)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int i, rc;
|
||||
MDB_env *env;
|
||||
MDB_txn *txn;
|
||||
MDB_cursor *mc;
|
||||
MDB_dbi dbi;
|
||||
char *envname;
|
||||
int envflags = MDB_NOSYNC, putflags = 0;
|
||||
int dohdr = 0, append = 0;
|
||||
MDB_val prevk;
|
||||
|
||||
prog = argv[0];
|
||||
|
||||
if (argc < 2) {
|
||||
usage();
|
||||
}
|
||||
|
||||
/* -a: append records in input order
|
||||
* -f: load file instead of stdin
|
||||
* -n: use NOSUBDIR flag on env_open
|
||||
* -s: load into named subDB
|
||||
* -N: use NOOVERWRITE on puts
|
||||
* -Q: quick mode using NOSYNC
|
||||
* -T: read plaintext
|
||||
* -V: print version and exit
|
||||
*/
|
||||
while ((i = getopt(argc, argv, "af:ns:NQTV")) != EOF) {
|
||||
switch(i) {
|
||||
case 'V':
|
||||
printf("%s\n", MDB_VERSION_STRING);
|
||||
exit(0);
|
||||
break;
|
||||
case 'a':
|
||||
append = 1;
|
||||
break;
|
||||
case 'f':
|
||||
if (freopen(optarg, "r", stdin) == NULL) {
|
||||
fprintf(stderr, "%s: %s: reopen: %s\n",
|
||||
prog, optarg, strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
case 'n':
|
||||
envflags |= MDB_NOSUBDIR;
|
||||
break;
|
||||
case 's':
|
||||
subname = strdup(optarg);
|
||||
break;
|
||||
case 'N':
|
||||
putflags = MDB_NOOVERWRITE|MDB_NODUPDATA;
|
||||
break;
|
||||
case 'Q':
|
||||
envflags |= MDB_NOSYNC;
|
||||
break;
|
||||
case 'T':
|
||||
mode |= NOHDR | PRINT;
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
}
|
||||
}
|
||||
|
||||
if (optind != argc - 1)
|
||||
usage();
|
||||
|
||||
dbuf.mv_size = 4096;
|
||||
dbuf.mv_data = malloc(dbuf.mv_size);
|
||||
|
||||
if (!(mode & NOHDR))
|
||||
readhdr();
|
||||
|
||||
envname = argv[optind];
|
||||
rc = mdb_env_create(&env);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
mdb_env_set_maxdbs(env, 2);
|
||||
|
||||
if (info.me_maxreaders)
|
||||
mdb_env_set_maxreaders(env, info.me_maxreaders);
|
||||
|
||||
if (info.me_mapsize)
|
||||
mdb_env_set_mapsize(env, info.me_mapsize);
|
||||
|
||||
if (info.me_mapaddr)
|
||||
envflags |= MDB_FIXEDMAP;
|
||||
|
||||
rc = mdb_env_open(env, envname, envflags, 0664);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
kbuf.mv_size = mdb_env_get_maxkeysize(env) * 2 + 2;
|
||||
kbuf.mv_data = malloc(kbuf.mv_size * 2);
|
||||
k0buf.mv_size = kbuf.mv_size;
|
||||
k0buf.mv_data = (char *)kbuf.mv_data + kbuf.mv_size;
|
||||
prevk.mv_data = k0buf.mv_data;
|
||||
|
||||
while(!Eof) {
|
||||
MDB_val key, data;
|
||||
int batch = 0;
|
||||
flags = 0;
|
||||
int appflag;
|
||||
|
||||
if (!dohdr) {
|
||||
dohdr = 1;
|
||||
} else if (!(mode & NOHDR))
|
||||
readhdr();
|
||||
|
||||
rc = mdb_txn_begin(env, NULL, 0, &txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
rc = mdb_dbi_open(txn, subname, flags|MDB_CREATE, &dbi);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_dbi_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
prevk.mv_size = 0;
|
||||
if (append) {
|
||||
mdb_set_compare(txn, dbi, greater);
|
||||
if (flags & MDB_DUPSORT)
|
||||
mdb_set_dupsort(txn, dbi, greater);
|
||||
}
|
||||
|
||||
rc = mdb_cursor_open(txn, dbi, &mc);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
|
||||
while(1) {
|
||||
rc = readline(&key, &kbuf);
|
||||
if (rc) /* rc == EOF */
|
||||
break;
|
||||
|
||||
rc = readline(&data, &dbuf);
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: line %" Z "d: failed to read key value\n", prog, lineno);
|
||||
goto txn_abort;
|
||||
}
|
||||
|
||||
if (append) {
|
||||
appflag = MDB_APPEND;
|
||||
if (flags & MDB_DUPSORT) {
|
||||
if (prevk.mv_size == key.mv_size && !memcmp(prevk.mv_data, key.mv_data, key.mv_size))
|
||||
appflag = MDB_CURRENT|MDB_APPENDDUP;
|
||||
else {
|
||||
memcpy(prevk.mv_data, key.mv_data, key.mv_size);
|
||||
prevk.mv_size = key.mv_size;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
appflag = 0;
|
||||
}
|
||||
rc = mdb_cursor_put(mc, &key, &data, putflags|appflag);
|
||||
if (rc == MDB_KEYEXIST && putflags)
|
||||
continue;
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: line %" Z "d: mdb_cursor_put failed, error %d %s\n", prog, lineno, rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
batch++;
|
||||
if (batch == 100) {
|
||||
rc = mdb_txn_commit(txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: line %" Z "d: txn_commit: %s\n",
|
||||
prog, lineno, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
rc = mdb_txn_begin(env, NULL, 0, &txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
rc = mdb_cursor_open(txn, dbi, &mc);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
if (append) {
|
||||
MDB_val k, d;
|
||||
mdb_cursor_get(mc, &k, &d, MDB_LAST);
|
||||
memcpy(prevk.mv_data, k.mv_data, k.mv_size);
|
||||
prevk.mv_size = k.mv_size;
|
||||
}
|
||||
batch = 0;
|
||||
}
|
||||
}
|
||||
rc = mdb_txn_commit(txn);
|
||||
txn = NULL;
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: line %" Z "d: txn_commit: %s\n",
|
||||
prog, lineno, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
if (envflags & MDB_NOSYNC) {
|
||||
rc = mdb_env_sync(env, 1);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_sync failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
}
|
||||
mdb_dbi_close(env, dbi);
|
||||
}
|
||||
|
||||
txn_abort:
|
||||
mdb_txn_abort(txn);
|
||||
env_close:
|
||||
mdb_env_close(env);
|
||||
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
.TH MDB_STAT 1 "2015/09/30" "LMDB 0.9.17"
|
||||
.\" Copyright 2012-2021 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.SH NAME
|
||||
mdb_stat \- LMDB environment status tool
|
||||
.SH SYNOPSIS
|
||||
.B mdb_stat
|
||||
[\c
|
||||
.BR \-V ]
|
||||
[\c
|
||||
.BR \-e ]
|
||||
[\c
|
||||
.BR \-f [ f [ f ]]]
|
||||
[\c
|
||||
.BR \-n ]
|
||||
[\c
|
||||
.BR \-r [ r ]]
|
||||
[\c
|
||||
.BR \-a \ |
|
||||
.BI \-s \ subdb\fR]
|
||||
.BR \ envpath
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mdb_stat
|
||||
utility displays the status of an LMDB environment.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-V
|
||||
Write the library version number to the standard output, and exit.
|
||||
.TP
|
||||
.BR \-e
|
||||
Display information about the database environment.
|
||||
.TP
|
||||
.BR \-f
|
||||
Display information about the environment freelist.
|
||||
If \fB\-ff\fP is given, summarize each freelist entry.
|
||||
If \fB\-fff\fP is given, display the full list of page IDs in the freelist.
|
||||
.TP
|
||||
.BR \-n
|
||||
Display the status of an LMDB database which does not use subdirectories.
|
||||
.TP
|
||||
.BR \-r
|
||||
Display information about the environment reader table.
|
||||
Shows the process ID, thread ID, and transaction ID for each active
|
||||
reader slot. The process ID and transaction ID are in decimal, the
|
||||
thread ID is in hexadecimal. The transaction ID is displayed as "-"
|
||||
if the reader does not currently have a read transaction open.
|
||||
If \fB\-rr\fP is given, check for stale entries in the reader
|
||||
table and clear them. The reader table will be printed again
|
||||
after the check is performed.
|
||||
.TP
|
||||
.BR \-a
|
||||
Display the status of all of the subdatabases in the environment.
|
||||
.TP
|
||||
.BR \-s \ subdb
|
||||
Display the status of a specific subdatabase.
|
||||
.SH DIAGNOSTICS
|
||||
Exit status is zero if no errors occur.
|
||||
Errors result in a non-zero exit status and
|
||||
a diagnostic message being written to standard error.
|
||||
.SH "SEE ALSO"
|
||||
.BR mdb_copy (1)
|
||||
.SH AUTHOR
|
||||
Howard Chu of Symas Corporation <http://www.symas.com>
|
||||
@@ -0,0 +1,263 @@
|
||||
/* mdb_stat.c - memory-mapped database status tool */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define Z "I"
|
||||
#else
|
||||
#define Z "z"
|
||||
#endif
|
||||
|
||||
static void prstat(MDB_stat *ms)
|
||||
{
|
||||
#if 0
|
||||
printf(" Page size: %u\n", ms->ms_psize);
|
||||
#endif
|
||||
printf(" Tree depth: %u\n", ms->ms_depth);
|
||||
printf(" Branch pages: %"Z"u\n", ms->ms_branch_pages);
|
||||
printf(" Leaf pages: %"Z"u\n", ms->ms_leaf_pages);
|
||||
printf(" Overflow pages: %"Z"u\n", ms->ms_overflow_pages);
|
||||
printf(" Entries: %"Z"u\n", ms->ms_entries);
|
||||
}
|
||||
|
||||
static void usage(char *prog)
|
||||
{
|
||||
fprintf(stderr, "usage: %s [-V] [-n] [-e] [-r[r]] [-f[f[f]]] [-a|-s subdb] dbpath\n", prog);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int i, rc;
|
||||
MDB_env *env;
|
||||
MDB_txn *txn;
|
||||
MDB_dbi dbi;
|
||||
MDB_stat mst;
|
||||
MDB_envinfo mei;
|
||||
char *prog = argv[0];
|
||||
char *envname;
|
||||
char *subname = NULL;
|
||||
int alldbs = 0, envinfo = 0, envflags = 0, freinfo = 0, rdrinfo = 0;
|
||||
|
||||
if (argc < 2) {
|
||||
usage(prog);
|
||||
}
|
||||
|
||||
/* -a: print stat of main DB and all subDBs
|
||||
* -s: print stat of only the named subDB
|
||||
* -e: print env info
|
||||
* -f: print freelist info
|
||||
* -r: print reader info
|
||||
* -n: use NOSUBDIR flag on env_open
|
||||
* -V: print version and exit
|
||||
* (default) print stat of only the main DB
|
||||
*/
|
||||
while ((i = getopt(argc, argv, "Vaefnrs:")) != EOF) {
|
||||
switch(i) {
|
||||
case 'V':
|
||||
printf("%s\n", MDB_VERSION_STRING);
|
||||
exit(0);
|
||||
break;
|
||||
case 'a':
|
||||
if (subname)
|
||||
usage(prog);
|
||||
alldbs++;
|
||||
break;
|
||||
case 'e':
|
||||
envinfo++;
|
||||
break;
|
||||
case 'f':
|
||||
freinfo++;
|
||||
break;
|
||||
case 'n':
|
||||
envflags |= MDB_NOSUBDIR;
|
||||
break;
|
||||
case 'r':
|
||||
rdrinfo++;
|
||||
break;
|
||||
case 's':
|
||||
if (alldbs)
|
||||
usage(prog);
|
||||
subname = optarg;
|
||||
break;
|
||||
default:
|
||||
usage(prog);
|
||||
}
|
||||
}
|
||||
|
||||
if (optind != argc - 1)
|
||||
usage(prog);
|
||||
|
||||
envname = argv[optind];
|
||||
rc = mdb_env_create(&env);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (alldbs || subname) {
|
||||
mdb_env_set_maxdbs(env, 4);
|
||||
}
|
||||
|
||||
rc = mdb_env_open(env, envname, envflags | MDB_RDONLY, 0664);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
if (envinfo) {
|
||||
(void)mdb_env_stat(env, &mst);
|
||||
(void)mdb_env_info(env, &mei);
|
||||
printf("Environment Info\n");
|
||||
printf(" Map address: %p\n", mei.me_mapaddr);
|
||||
printf(" Map size: %"Z"u\n", mei.me_mapsize);
|
||||
printf(" Page size: %u\n", mst.ms_psize);
|
||||
printf(" Max pages: %"Z"u\n", mei.me_mapsize / mst.ms_psize);
|
||||
printf(" Number of pages used: %"Z"u\n", mei.me_last_pgno+1);
|
||||
printf(" Last transaction ID: %"Z"u\n", mei.me_last_txnid);
|
||||
printf(" Max readers: %u\n", mei.me_maxreaders);
|
||||
printf(" Number of readers used: %u\n", mei.me_numreaders);
|
||||
}
|
||||
|
||||
if (rdrinfo) {
|
||||
printf("Reader Table Status\n");
|
||||
rc = mdb_reader_list(env, (MDB_msg_func *)fputs, stdout);
|
||||
if (rdrinfo > 1) {
|
||||
int dead;
|
||||
mdb_reader_check(env, &dead);
|
||||
printf(" %d stale readers cleared.\n", dead);
|
||||
rc = mdb_reader_list(env, (MDB_msg_func *)fputs, stdout);
|
||||
}
|
||||
if (!(subname || alldbs || freinfo))
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
if (freinfo) {
|
||||
MDB_cursor *cursor;
|
||||
MDB_val key, data;
|
||||
size_t pages = 0, *iptr;
|
||||
|
||||
printf("Freelist Status\n");
|
||||
dbi = 0;
|
||||
rc = mdb_cursor_open(txn, dbi, &cursor);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
rc = mdb_stat(txn, dbi, &mst);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
prstat(&mst);
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
iptr = data.mv_data;
|
||||
pages += *iptr;
|
||||
if (freinfo > 1) {
|
||||
char *bad = "";
|
||||
size_t pg, prev;
|
||||
ssize_t i, j, span = 0;
|
||||
j = *iptr++;
|
||||
for (i = j, prev = 1; --i >= 0; ) {
|
||||
pg = iptr[i];
|
||||
if (pg <= prev)
|
||||
bad = " [bad sequence]";
|
||||
prev = pg;
|
||||
pg += span;
|
||||
for (; i >= span && iptr[i-span] == pg; span++, pg++) ;
|
||||
}
|
||||
printf(" Transaction %"Z"u, %"Z"d pages, maxspan %"Z"d%s\n",
|
||||
*(size_t *)key.mv_data, j, span, bad);
|
||||
if (freinfo > 2) {
|
||||
for (--j; j >= 0; ) {
|
||||
pg = iptr[j];
|
||||
for (span=1; --j >= 0 && iptr[j] == pg+span; span++) ;
|
||||
printf(span>1 ? " %9"Z"u[%"Z"d]\n" : " %9"Z"u\n",
|
||||
pg, span);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
printf(" Free pages: %"Z"u\n", pages);
|
||||
}
|
||||
|
||||
rc = mdb_open(txn, subname, 0, &dbi);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
|
||||
rc = mdb_stat(txn, dbi, &mst);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
printf("Status of %s\n", subname ? subname : "Main DB");
|
||||
prstat(&mst);
|
||||
|
||||
if (alldbs) {
|
||||
MDB_cursor *cursor;
|
||||
MDB_val key;
|
||||
|
||||
rc = mdb_cursor_open(txn, dbi, &cursor);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
while ((rc = mdb_cursor_get(cursor, &key, NULL, MDB_NEXT_NODUP)) == 0) {
|
||||
char *str;
|
||||
MDB_dbi db2;
|
||||
if (memchr(key.mv_data, '\0', key.mv_size))
|
||||
continue;
|
||||
str = malloc(key.mv_size+1);
|
||||
memcpy(str, key.mv_data, key.mv_size);
|
||||
str[key.mv_size] = '\0';
|
||||
rc = mdb_open(txn, str, 0, &db2);
|
||||
if (rc == MDB_SUCCESS)
|
||||
printf("Status of %s\n", str);
|
||||
free(str);
|
||||
if (rc) continue;
|
||||
rc = mdb_stat(txn, db2, &mst);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
prstat(&mst);
|
||||
mdb_close(env, db2);
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
}
|
||||
|
||||
if (rc == MDB_NOTFOUND)
|
||||
rc = MDB_SUCCESS;
|
||||
|
||||
mdb_close(env, dbi);
|
||||
txn_abort:
|
||||
mdb_txn_abort(txn);
|
||||
env_close:
|
||||
mdb_env_close(env);
|
||||
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
/** @file midl.c
|
||||
* @brief ldap bdb back-end ID List functions */
|
||||
/* $OpenLDAP$ */
|
||||
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
|
||||
*
|
||||
* Copyright 2000-2021 The OpenLDAP Foundation.
|
||||
* Portions Copyright 2001-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include "midl.h"
|
||||
|
||||
/** @defgroup internal LMDB Internals
|
||||
* @{
|
||||
*/
|
||||
/** @defgroup idls ID List Management
|
||||
* @{
|
||||
*/
|
||||
#define CMP(x,y) ( (x) < (y) ? -1 : (x) > (y) )
|
||||
|
||||
unsigned mdb_midl_search( MDB_IDL ids, MDB_ID id )
|
||||
{
|
||||
/*
|
||||
* binary search of id in ids
|
||||
* if found, returns position of id
|
||||
* if not found, returns first position greater than id
|
||||
*/
|
||||
unsigned base = 0;
|
||||
unsigned cursor = 1;
|
||||
int val = 0;
|
||||
unsigned n = ids[0];
|
||||
|
||||
while( 0 < n ) {
|
||||
unsigned pivot = n >> 1;
|
||||
cursor = base + pivot + 1;
|
||||
val = CMP( ids[cursor], id );
|
||||
|
||||
if( val < 0 ) {
|
||||
n = pivot;
|
||||
|
||||
} else if ( val > 0 ) {
|
||||
base = cursor;
|
||||
n -= pivot + 1;
|
||||
|
||||
} else {
|
||||
return cursor;
|
||||
}
|
||||
}
|
||||
|
||||
if( val > 0 ) {
|
||||
++cursor;
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
#if 0 /* superseded by append/sort */
|
||||
int mdb_midl_insert( MDB_IDL ids, MDB_ID id )
|
||||
{
|
||||
unsigned x, i;
|
||||
|
||||
x = mdb_midl_search( ids, id );
|
||||
assert( x > 0 );
|
||||
|
||||
if( x < 1 ) {
|
||||
/* internal error */
|
||||
return -2;
|
||||
}
|
||||
|
||||
if ( x <= ids[0] && ids[x] == id ) {
|
||||
/* duplicate */
|
||||
assert(0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( ++ids[0] >= MDB_IDL_DB_MAX ) {
|
||||
/* no room */
|
||||
--ids[0];
|
||||
return -2;
|
||||
|
||||
} else {
|
||||
/* insert id */
|
||||
for (i=ids[0]; i>x; i--)
|
||||
ids[i] = ids[i-1];
|
||||
ids[x] = id;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
MDB_IDL mdb_midl_alloc(int num)
|
||||
{
|
||||
MDB_IDL ids = malloc((num+2) * sizeof(MDB_ID));
|
||||
if (ids) {
|
||||
*ids++ = num;
|
||||
*ids = 0;
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
void mdb_midl_free(MDB_IDL ids)
|
||||
{
|
||||
if (ids)
|
||||
free(ids-1);
|
||||
}
|
||||
|
||||
void mdb_midl_shrink( MDB_IDL *idp )
|
||||
{
|
||||
MDB_IDL ids = *idp;
|
||||
if (*(--ids) > MDB_IDL_UM_MAX &&
|
||||
(ids = realloc(ids, (MDB_IDL_UM_MAX+2) * sizeof(MDB_ID))))
|
||||
{
|
||||
*ids++ = MDB_IDL_UM_MAX;
|
||||
*idp = ids;
|
||||
}
|
||||
}
|
||||
|
||||
static int mdb_midl_grow( MDB_IDL *idp, int num )
|
||||
{
|
||||
MDB_IDL idn = *idp-1;
|
||||
/* grow it */
|
||||
idn = realloc(idn, (*idn + num + 2) * sizeof(MDB_ID));
|
||||
if (!idn)
|
||||
return ENOMEM;
|
||||
*idn++ += num;
|
||||
*idp = idn;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_midl_need( MDB_IDL *idp, unsigned num )
|
||||
{
|
||||
MDB_IDL ids = *idp;
|
||||
num += ids[0];
|
||||
if (num > ids[-1]) {
|
||||
num = (num + num/4 + (256 + 2)) & -256;
|
||||
if (!(ids = realloc(ids-1, num * sizeof(MDB_ID))))
|
||||
return ENOMEM;
|
||||
*ids++ = num - 2;
|
||||
*idp = ids;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_midl_append( MDB_IDL *idp, MDB_ID id )
|
||||
{
|
||||
MDB_IDL ids = *idp;
|
||||
/* Too big? */
|
||||
if (ids[0] >= ids[-1]) {
|
||||
if (mdb_midl_grow(idp, MDB_IDL_UM_MAX))
|
||||
return ENOMEM;
|
||||
ids = *idp;
|
||||
}
|
||||
ids[0]++;
|
||||
ids[ids[0]] = id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app )
|
||||
{
|
||||
MDB_IDL ids = *idp;
|
||||
/* Too big? */
|
||||
if (ids[0] + app[0] >= ids[-1]) {
|
||||
if (mdb_midl_grow(idp, app[0]))
|
||||
return ENOMEM;
|
||||
ids = *idp;
|
||||
}
|
||||
memcpy(&ids[ids[0]+1], &app[1], app[0] * sizeof(MDB_ID));
|
||||
ids[0] += app[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_midl_append_range( MDB_IDL *idp, MDB_ID id, unsigned n )
|
||||
{
|
||||
MDB_ID *ids = *idp, len = ids[0];
|
||||
/* Too big? */
|
||||
if (len + n > ids[-1]) {
|
||||
if (mdb_midl_grow(idp, n | MDB_IDL_UM_MAX))
|
||||
return ENOMEM;
|
||||
ids = *idp;
|
||||
}
|
||||
ids[0] = len + n;
|
||||
ids += len;
|
||||
while (n)
|
||||
ids[n--] = id++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mdb_midl_xmerge( MDB_IDL idl, MDB_IDL merge )
|
||||
{
|
||||
MDB_ID old_id, merge_id, i = merge[0], j = idl[0], k = i+j, total = k;
|
||||
idl[0] = (MDB_ID)-1; /* delimiter for idl scan below */
|
||||
old_id = idl[j];
|
||||
while (i) {
|
||||
merge_id = merge[i--];
|
||||
for (; old_id < merge_id; old_id = idl[--j])
|
||||
idl[k--] = old_id;
|
||||
idl[k--] = merge_id;
|
||||
}
|
||||
idl[0] = total;
|
||||
}
|
||||
|
||||
/* Quicksort + Insertion sort for small arrays */
|
||||
|
||||
#define SMALL 8
|
||||
#define MIDL_SWAP(a,b) { itmp=(a); (a)=(b); (b)=itmp; }
|
||||
|
||||
void
|
||||
mdb_midl_sort( MDB_IDL ids )
|
||||
{
|
||||
/* Max possible depth of int-indexed tree * 2 items/level */
|
||||
int istack[sizeof(int)*CHAR_BIT * 2];
|
||||
int i,j,k,l,ir,jstack;
|
||||
MDB_ID a, itmp;
|
||||
|
||||
ir = (int)ids[0];
|
||||
l = 1;
|
||||
jstack = 0;
|
||||
for(;;) {
|
||||
if (ir - l < SMALL) { /* Insertion sort */
|
||||
for (j=l+1;j<=ir;j++) {
|
||||
a = ids[j];
|
||||
for (i=j-1;i>=1;i--) {
|
||||
if (ids[i] >= a) break;
|
||||
ids[i+1] = ids[i];
|
||||
}
|
||||
ids[i+1] = a;
|
||||
}
|
||||
if (jstack == 0) break;
|
||||
ir = istack[jstack--];
|
||||
l = istack[jstack--];
|
||||
} else {
|
||||
k = (l + ir) >> 1; /* Choose median of left, center, right */
|
||||
MIDL_SWAP(ids[k], ids[l+1]);
|
||||
if (ids[l] < ids[ir]) {
|
||||
MIDL_SWAP(ids[l], ids[ir]);
|
||||
}
|
||||
if (ids[l+1] < ids[ir]) {
|
||||
MIDL_SWAP(ids[l+1], ids[ir]);
|
||||
}
|
||||
if (ids[l] < ids[l+1]) {
|
||||
MIDL_SWAP(ids[l], ids[l+1]);
|
||||
}
|
||||
i = l+1;
|
||||
j = ir;
|
||||
a = ids[l+1];
|
||||
for(;;) {
|
||||
do i++; while(ids[i] > a);
|
||||
do j--; while(ids[j] < a);
|
||||
if (j < i) break;
|
||||
MIDL_SWAP(ids[i],ids[j]);
|
||||
}
|
||||
ids[l+1] = ids[j];
|
||||
ids[j] = a;
|
||||
jstack += 2;
|
||||
if (ir-i+1 >= j-l) {
|
||||
istack[jstack] = ir;
|
||||
istack[jstack-1] = i;
|
||||
ir = j-1;
|
||||
} else {
|
||||
istack[jstack] = j-1;
|
||||
istack[jstack-1] = l;
|
||||
l = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id )
|
||||
{
|
||||
/*
|
||||
* binary search of id in ids
|
||||
* if found, returns position of id
|
||||
* if not found, returns first position greater than id
|
||||
*/
|
||||
unsigned base = 0;
|
||||
unsigned cursor = 1;
|
||||
int val = 0;
|
||||
unsigned n = (unsigned)ids[0].mid;
|
||||
|
||||
while( 0 < n ) {
|
||||
unsigned pivot = n >> 1;
|
||||
cursor = base + pivot + 1;
|
||||
val = CMP( id, ids[cursor].mid );
|
||||
|
||||
if( val < 0 ) {
|
||||
n = pivot;
|
||||
|
||||
} else if ( val > 0 ) {
|
||||
base = cursor;
|
||||
n -= pivot + 1;
|
||||
|
||||
} else {
|
||||
return cursor;
|
||||
}
|
||||
}
|
||||
|
||||
if( val > 0 ) {
|
||||
++cursor;
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id )
|
||||
{
|
||||
unsigned x, i;
|
||||
|
||||
x = mdb_mid2l_search( ids, id->mid );
|
||||
|
||||
if( x < 1 ) {
|
||||
/* internal error */
|
||||
return -2;
|
||||
}
|
||||
|
||||
if ( x <= ids[0].mid && ids[x].mid == id->mid ) {
|
||||
/* duplicate */
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( ids[0].mid >= MDB_IDL_UM_MAX ) {
|
||||
/* too big */
|
||||
return -2;
|
||||
|
||||
} else {
|
||||
/* insert id */
|
||||
ids[0].mid++;
|
||||
for (i=(unsigned)ids[0].mid; i>x; i--)
|
||||
ids[i] = ids[i-1];
|
||||
ids[x] = *id;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id )
|
||||
{
|
||||
/* Too big? */
|
||||
if (ids[0].mid >= MDB_IDL_UM_MAX) {
|
||||
return -2;
|
||||
}
|
||||
ids[0].mid++;
|
||||
ids[ids[0].mid] = *id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
/** @} */
|
||||
@@ -0,0 +1,188 @@
|
||||
/** @file midl.h
|
||||
* @brief LMDB ID List header file.
|
||||
*
|
||||
* This file was originally part of back-bdb but has been
|
||||
* modified for use in libmdb. Most of the macros defined
|
||||
* in this file are unused, just left over from the original.
|
||||
*
|
||||
* This file is only used internally in libmdb and its definitions
|
||||
* are not exposed publicly.
|
||||
*/
|
||||
/* $OpenLDAP$ */
|
||||
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
|
||||
*
|
||||
* Copyright 2000-2021 The OpenLDAP Foundation.
|
||||
* Portions Copyright 2001-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
#ifndef _MDB_MIDL_H_
|
||||
#define _MDB_MIDL_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** @defgroup internal LMDB Internals
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** @defgroup idls ID List Management
|
||||
* @{
|
||||
*/
|
||||
/** A generic unsigned ID number. These were entryIDs in back-bdb.
|
||||
* Preferably it should have the same size as a pointer.
|
||||
*/
|
||||
typedef size_t MDB_ID;
|
||||
|
||||
/** An IDL is an ID List, a sorted array of IDs. The first
|
||||
* element of the array is a counter for how many actual
|
||||
* IDs are in the list. In the original back-bdb code, IDLs are
|
||||
* sorted in ascending order. For libmdb IDLs are sorted in
|
||||
* descending order.
|
||||
*/
|
||||
typedef MDB_ID *MDB_IDL;
|
||||
|
||||
/* IDL sizes - likely should be even bigger
|
||||
* limiting factors: sizeof(ID), thread stack size
|
||||
*/
|
||||
#ifndef MDB_IDL_LOGN
|
||||
#define MDB_IDL_LOGN 16 /* DB_SIZE is 2^16, UM_SIZE is 2^17 */
|
||||
#endif
|
||||
#define MDB_IDL_DB_SIZE (1<<MDB_IDL_LOGN)
|
||||
#define MDB_IDL_UM_SIZE (1<<(MDB_IDL_LOGN+1))
|
||||
|
||||
#define MDB_IDL_DB_MAX (MDB_IDL_DB_SIZE-1)
|
||||
#define MDB_IDL_UM_MAX (MDB_IDL_UM_SIZE-1)
|
||||
|
||||
#define MDB_IDL_SIZEOF(ids) (((ids)[0]+1) * sizeof(MDB_ID))
|
||||
#define MDB_IDL_IS_ZERO(ids) ( (ids)[0] == 0 )
|
||||
#define MDB_IDL_CPY( dst, src ) (memcpy( dst, src, MDB_IDL_SIZEOF( src ) ))
|
||||
#define MDB_IDL_FIRST( ids ) ( (ids)[1] )
|
||||
#define MDB_IDL_LAST( ids ) ( (ids)[(ids)[0]] )
|
||||
|
||||
/** Current max length of an #mdb_midl_alloc()ed IDL */
|
||||
#define MDB_IDL_ALLOCLEN( ids ) ( (ids)[-1] )
|
||||
|
||||
/** Append ID to IDL. The IDL must be big enough. */
|
||||
#define mdb_midl_xappend(idl, id) do { \
|
||||
MDB_ID *xidl = (idl), xlen = ++(xidl[0]); \
|
||||
xidl[xlen] = (id); \
|
||||
} while (0)
|
||||
|
||||
/** Search for an ID in an IDL.
|
||||
* @param[in] ids The IDL to search.
|
||||
* @param[in] id The ID to search for.
|
||||
* @return The index of the first ID greater than or equal to \b id.
|
||||
*/
|
||||
unsigned mdb_midl_search( MDB_IDL ids, MDB_ID id );
|
||||
|
||||
/** Allocate an IDL.
|
||||
* Allocates memory for an IDL of the given size.
|
||||
* @return IDL on success, NULL on failure.
|
||||
*/
|
||||
MDB_IDL mdb_midl_alloc(int num);
|
||||
|
||||
/** Free an IDL.
|
||||
* @param[in] ids The IDL to free.
|
||||
*/
|
||||
void mdb_midl_free(MDB_IDL ids);
|
||||
|
||||
/** Shrink an IDL.
|
||||
* Return the IDL to the default size if it has grown larger.
|
||||
* @param[in,out] idp Address of the IDL to shrink.
|
||||
*/
|
||||
void mdb_midl_shrink(MDB_IDL *idp);
|
||||
|
||||
/** Make room for num additional elements in an IDL.
|
||||
* @param[in,out] idp Address of the IDL.
|
||||
* @param[in] num Number of elements to make room for.
|
||||
* @return 0 on success, ENOMEM on failure.
|
||||
*/
|
||||
int mdb_midl_need(MDB_IDL *idp, unsigned num);
|
||||
|
||||
/** Append an ID onto an IDL.
|
||||
* @param[in,out] idp Address of the IDL to append to.
|
||||
* @param[in] id The ID to append.
|
||||
* @return 0 on success, ENOMEM if the IDL is too large.
|
||||
*/
|
||||
int mdb_midl_append( MDB_IDL *idp, MDB_ID id );
|
||||
|
||||
/** Append an IDL onto an IDL.
|
||||
* @param[in,out] idp Address of the IDL to append to.
|
||||
* @param[in] app The IDL to append.
|
||||
* @return 0 on success, ENOMEM if the IDL is too large.
|
||||
*/
|
||||
int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app );
|
||||
|
||||
/** Append an ID range onto an IDL.
|
||||
* @param[in,out] idp Address of the IDL to append to.
|
||||
* @param[in] id The lowest ID to append.
|
||||
* @param[in] n Number of IDs to append.
|
||||
* @return 0 on success, ENOMEM if the IDL is too large.
|
||||
*/
|
||||
int mdb_midl_append_range( MDB_IDL *idp, MDB_ID id, unsigned n );
|
||||
|
||||
/** Merge an IDL onto an IDL. The destination IDL must be big enough.
|
||||
* @param[in] idl The IDL to merge into.
|
||||
* @param[in] merge The IDL to merge.
|
||||
*/
|
||||
void mdb_midl_xmerge( MDB_IDL idl, MDB_IDL merge );
|
||||
|
||||
/** Sort an IDL.
|
||||
* @param[in,out] ids The IDL to sort.
|
||||
*/
|
||||
void mdb_midl_sort( MDB_IDL ids );
|
||||
|
||||
/** An ID2 is an ID/pointer pair.
|
||||
*/
|
||||
typedef struct MDB_ID2 {
|
||||
MDB_ID mid; /**< The ID */
|
||||
void *mptr; /**< The pointer */
|
||||
} MDB_ID2;
|
||||
|
||||
/** An ID2L is an ID2 List, a sorted array of ID2s.
|
||||
* The first element's \b mid member is a count of how many actual
|
||||
* elements are in the array. The \b mptr member of the first element is unused.
|
||||
* The array is sorted in ascending order by \b mid.
|
||||
*/
|
||||
typedef MDB_ID2 *MDB_ID2L;
|
||||
|
||||
/** Search for an ID in an ID2L.
|
||||
* @param[in] ids The ID2L to search.
|
||||
* @param[in] id The ID to search for.
|
||||
* @return The index of the first ID2 whose \b mid member is greater than or equal to \b id.
|
||||
*/
|
||||
unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id );
|
||||
|
||||
|
||||
/** Insert an ID2 into a ID2L.
|
||||
* @param[in,out] ids The ID2L to insert into.
|
||||
* @param[in] id The ID2 to insert.
|
||||
* @return 0 on success, -1 if the ID was already present in the ID2L.
|
||||
*/
|
||||
int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id );
|
||||
|
||||
/** Append an ID2 into a ID2L.
|
||||
* @param[in,out] ids The ID2L to append into.
|
||||
* @param[in] id The ID2 to append.
|
||||
* @return 0 on success, -2 if the ID2L is too big.
|
||||
*/
|
||||
int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id );
|
||||
|
||||
/** @} */
|
||||
/** @} */
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* _MDB_MIDL_H_ */
|
||||
@@ -0,0 +1,580 @@
|
||||
/* mplay.c - memory-mapped database log replay */
|
||||
/*
|
||||
* Copyright 2011-2023 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
#define MDB_SCNy(t) "z" #t
|
||||
|
||||
#define SCMP(s) s, (sizeof(s)-1)
|
||||
char inbuf[8192];
|
||||
char *dbuf, *kbuf;
|
||||
size_t dbufsize;
|
||||
int maxkey;
|
||||
|
||||
#define SOFF(s) (sizeof(s)+1)
|
||||
|
||||
#define MAXENVS 16
|
||||
#define MAXTXNS 16
|
||||
#define MAXCRSS 16
|
||||
|
||||
#define MAXPIDS 16
|
||||
|
||||
typedef struct crspair {
|
||||
void *tcrs; /* scanned text pointer */
|
||||
MDB_cursor *rcrs;
|
||||
} crspair;
|
||||
|
||||
typedef struct txnpair {
|
||||
void *ttxn; /* scanned text pointer */
|
||||
MDB_txn *rtxn;
|
||||
crspair cursors[MAXCRSS];
|
||||
int ncursors;
|
||||
} txnpair;
|
||||
|
||||
typedef struct envpair {
|
||||
void *tenv;
|
||||
MDB_env *renv;
|
||||
txnpair txns[MAXTXNS];
|
||||
int ntxns;
|
||||
} envpair;
|
||||
|
||||
envpair envs[MAXENVS];
|
||||
int nenvs;
|
||||
|
||||
envpair *lastenv;
|
||||
txnpair *lasttxn;
|
||||
crspair *lastcrs;
|
||||
|
||||
typedef struct pidpair {
|
||||
int tpid;
|
||||
pid_t rpid;
|
||||
int fdout, fdin;
|
||||
} pidpair;
|
||||
|
||||
pidpair *lastpid;
|
||||
|
||||
pidpair pids[MAXPIDS];
|
||||
int npids;
|
||||
|
||||
unsigned long lcount;
|
||||
|
||||
static int unhex(unsigned char *c2)
|
||||
{
|
||||
int x, c;
|
||||
x = *c2++ & 0x4f;
|
||||
if (x & 0x40)
|
||||
x -= 55;
|
||||
c = x << 4;
|
||||
x = *c2 & 0x4f;
|
||||
if (x & 0x40)
|
||||
x -= 55;
|
||||
c |= x;
|
||||
return c;
|
||||
}
|
||||
|
||||
int inhex(char *in, char *out)
|
||||
{
|
||||
char *c2 = out;
|
||||
while (isxdigit(*in)) {
|
||||
*c2++ = unhex((unsigned char *)in);
|
||||
in += 2;
|
||||
}
|
||||
return c2 - out;
|
||||
}
|
||||
|
||||
static void addenv(void *tenv, MDB_env *renv)
|
||||
{
|
||||
assert(nenvs < MAXENVS);
|
||||
envs[nenvs].tenv = tenv;
|
||||
envs[nenvs].renv = renv;
|
||||
envs[nenvs].ntxns = 0;
|
||||
lastenv = envs+nenvs;
|
||||
nenvs++;
|
||||
}
|
||||
|
||||
static envpair *findenv(void *tenv)
|
||||
{
|
||||
int i;
|
||||
if (!lastenv || lastenv->tenv != tenv) {
|
||||
for (i=0; i<nenvs; i++)
|
||||
if (envs[i].tenv == tenv)
|
||||
break;
|
||||
assert(i < nenvs);
|
||||
lastenv = &envs[i];
|
||||
}
|
||||
return lastenv;
|
||||
}
|
||||
|
||||
static void delenv(envpair *ep)
|
||||
{
|
||||
int i = ep - envs;
|
||||
for (; i<nenvs-1; i++)
|
||||
envs[i] = envs[i+1];
|
||||
nenvs--;
|
||||
lastenv = NULL;
|
||||
}
|
||||
|
||||
static void addtxn(void *tenv, void *ttxn, MDB_txn *rtxn)
|
||||
{
|
||||
envpair *ep;
|
||||
txnpair *tp;
|
||||
|
||||
ep = findenv(tenv);
|
||||
assert(ep->ntxns < MAXTXNS);
|
||||
tp = ep->txns+ep->ntxns;
|
||||
tp->ttxn = ttxn;
|
||||
tp->rtxn = rtxn;
|
||||
tp->ncursors = 0;
|
||||
ep->ntxns++;
|
||||
lasttxn = tp;
|
||||
}
|
||||
|
||||
static txnpair *findtxn(void *ttxn)
|
||||
{
|
||||
int i, j;
|
||||
if (lasttxn && lasttxn->ttxn == ttxn)
|
||||
return lasttxn;
|
||||
if (lastenv) {
|
||||
for (i=0; i<lastenv->ntxns; i++) {
|
||||
if (lastenv->txns[i].ttxn == ttxn) {
|
||||
lasttxn = lastenv->txns+i;
|
||||
return lasttxn;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i=0; i<nenvs; i++) {
|
||||
if (envs+i == lastenv) continue;
|
||||
for (j=0; j<envs[i].ntxns; j++) {
|
||||
if (envs[i].txns[j].ttxn == ttxn) {
|
||||
lastenv = envs+i;
|
||||
lasttxn = envs[i].txns+j;
|
||||
return lasttxn;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(0); /* should have found it */
|
||||
}
|
||||
|
||||
static void deltxn(txnpair *tp)
|
||||
{
|
||||
int i = tp - lastenv->txns;
|
||||
for (; i<lastenv->ntxns-1; i++)
|
||||
lastenv->txns[i] = lastenv->txns[i+1];
|
||||
lastenv->ntxns--;
|
||||
lasttxn = NULL;
|
||||
}
|
||||
|
||||
static void addcrs(txnpair *tp, void *tcrs, MDB_cursor *rcrs)
|
||||
{
|
||||
int j = tp->ncursors;
|
||||
assert(tp->ncursors < MAXCRSS);
|
||||
|
||||
tp->cursors[j].tcrs = tcrs;
|
||||
tp->cursors[j].rcrs = rcrs;
|
||||
tp->ncursors++;
|
||||
lastcrs = tp->cursors+j;
|
||||
}
|
||||
|
||||
static crspair *findcrs(void *tcrs)
|
||||
{
|
||||
int i, j, k;
|
||||
envpair *ep;
|
||||
txnpair *tp;
|
||||
crspair *cp;
|
||||
if (lastcrs && lastcrs->tcrs == tcrs)
|
||||
return lastcrs;
|
||||
if (lasttxn) {
|
||||
for (k=0, cp=lasttxn->cursors; k<lasttxn->ncursors; k++, cp++) {
|
||||
if (cp->tcrs == tcrs) {
|
||||
lastcrs = cp;
|
||||
return lastcrs;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastenv) {
|
||||
for (j=0, tp=lastenv->txns; j<lastenv->ntxns; j++, tp++){
|
||||
if (tp == lasttxn)
|
||||
continue;
|
||||
for (k=0, cp = tp->cursors; k<tp->ncursors; k++, cp++) {
|
||||
if (cp->tcrs == tcrs) {
|
||||
lastcrs = cp;
|
||||
lasttxn = tp;
|
||||
return lastcrs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i=0, ep=envs; i<nenvs; i++, ep++) {
|
||||
for (j=0, tp=ep->txns; j<ep->ntxns; j++, tp++) {
|
||||
if (tp == lasttxn)
|
||||
continue;
|
||||
for (k=0, cp = tp->cursors; k<tp->ncursors; k++, cp++) {
|
||||
if (cp->tcrs == tcrs) {
|
||||
lastcrs = cp;
|
||||
lasttxn = tp;
|
||||
lastenv = ep;
|
||||
return lastcrs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(0); /* should have found it already */
|
||||
}
|
||||
|
||||
static void delcrs(void *tcrs)
|
||||
{
|
||||
int i;
|
||||
crspair *cp = findcrs(tcrs);
|
||||
mdb_cursor_close(cp->rcrs);
|
||||
for (i = cp - lasttxn->cursors; i<lasttxn->ncursors-1; i++)
|
||||
lasttxn->cursors[i] = lasttxn->cursors[i+1];
|
||||
lasttxn->ncursors--;
|
||||
lastcrs = NULL;
|
||||
}
|
||||
|
||||
void child()
|
||||
{
|
||||
int rc;
|
||||
MDB_val key, data;
|
||||
char *ptr;
|
||||
|
||||
while (fgets(inbuf, sizeof(inbuf), stdin)) {
|
||||
ptr = inbuf;
|
||||
if (!strncmp(ptr, SCMP("exit")))
|
||||
break;
|
||||
|
||||
if (!strncmp(ptr, SCMP("mdb_env_create"))) {
|
||||
void *tenv;
|
||||
MDB_env *renv;
|
||||
sscanf(ptr+SOFF("mdb_env_create"), "%p", &tenv);
|
||||
E(mdb_env_create(&renv));
|
||||
addenv(tenv, renv);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_env_set_maxdbs"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
unsigned int maxdbs;
|
||||
sscanf(ptr+SOFF("mdb_env_set_maxdbs"), "%p, %u", &tenv, &maxdbs);
|
||||
ep = findenv(tenv);
|
||||
E(mdb_env_set_maxdbs(ep->renv, maxdbs));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_env_set_mapsize"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
size_t mapsize;
|
||||
sscanf(ptr+SOFF("mdb_env_set_mapsize"), "%p, %"MDB_SCNy(u), &tenv, &mapsize);
|
||||
ep = findenv(tenv);
|
||||
E(mdb_env_set_mapsize(ep->renv, mapsize));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_env_open"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
char *path;
|
||||
int len;
|
||||
unsigned int flags, mode;
|
||||
sscanf(ptr+SOFF("mdb_env_open"), "%p, %n", &tenv, &len);
|
||||
path = ptr+SOFF("mdb_env_open")+len;
|
||||
ptr = strchr(path, ',');
|
||||
*ptr++ = '\0';
|
||||
sscanf(ptr, "%u, %o", &flags, &mode);
|
||||
ep = findenv(tenv);
|
||||
E(mdb_env_open(ep->renv, path, flags, mode));
|
||||
if (!maxkey) {
|
||||
maxkey = mdb_env_get_maxkeysize(ep->renv);
|
||||
kbuf = malloc(maxkey+2);
|
||||
dbuf = malloc(maxkey+2);
|
||||
dbufsize = maxkey;
|
||||
}
|
||||
} else if (!strncmp(ptr, SCMP("mdb_env_close"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
sscanf(ptr+SOFF("mdb_env_close"), "%p", &tenv);
|
||||
ep = findenv(tenv);
|
||||
mdb_env_close(ep->renv);
|
||||
delenv(ep);
|
||||
if (!nenvs) /* if no other envs left, this process is done */
|
||||
break;
|
||||
} else if (!strncmp(ptr, SCMP("mdb_txn_begin"))) {
|
||||
unsigned int flags;
|
||||
void *tenv, *ttxn;
|
||||
envpair *ep;
|
||||
MDB_txn *rtxn;
|
||||
sscanf(ptr+SOFF("mdb_txn_begin"), "%p, %*p, %u = %p", &tenv, &flags, &ttxn);
|
||||
ep = findenv(tenv);
|
||||
E(mdb_txn_begin(ep->renv, NULL, flags, &rtxn));
|
||||
addtxn(tenv, ttxn, rtxn);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_txn_commit"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
sscanf(ptr+SOFF("mdb_txn_commit"), "%p", &ttxn);
|
||||
tp = findtxn(ttxn);
|
||||
E(mdb_txn_commit(tp->rtxn));
|
||||
deltxn(tp);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_txn_abort"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
sscanf(ptr+SOFF("mdb_txn_abort"), "%p", &ttxn);
|
||||
tp = findtxn(ttxn);
|
||||
mdb_txn_abort(tp->rtxn);
|
||||
deltxn(tp);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_dbi_open"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
char *dbname;
|
||||
unsigned int flags;
|
||||
unsigned int tdbi;
|
||||
MDB_dbi dbi;
|
||||
sscanf(ptr+SOFF("mdb_dbi_open"), "%p, ", &ttxn);
|
||||
dbname = strchr(ptr+SOFF("mdb_dbi_open"), ',');
|
||||
dbname += 2;
|
||||
ptr = strchr(dbname, ',');
|
||||
*ptr++ = '\0';
|
||||
if (!strcmp(dbname, "(null)"))
|
||||
dbname = NULL;
|
||||
sscanf(ptr, "%u = %u", &flags, &tdbi);
|
||||
tp = findtxn(ttxn);
|
||||
E(mdb_dbi_open(tp->rtxn, dbname, flags, &dbi));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_dbi_close"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
unsigned int tdbi;
|
||||
sscanf(ptr+SOFF("mdb_dbi_close"), "%p, %u", &tenv, &tdbi);
|
||||
ep = findenv(tenv);
|
||||
mdb_dbi_close(ep->renv, tdbi);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_cursor_open"))) {
|
||||
void *ttxn, *tcrs;
|
||||
txnpair *tp;
|
||||
MDB_cursor *rcrs;
|
||||
unsigned int tdbi;
|
||||
sscanf(ptr+SOFF("mdb_cursor_open"), "%p, %u = %p", &ttxn, &tdbi, &tcrs);
|
||||
tp = findtxn(ttxn);
|
||||
E(mdb_cursor_open(tp->rtxn, tdbi, &rcrs));
|
||||
addcrs(tp, tcrs, rcrs);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_cursor_close"))) {
|
||||
void *tcrs;
|
||||
sscanf(ptr+SOFF("mdb_cursor_close"), "%p", &tcrs);
|
||||
delcrs(tcrs);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_cursor_put"))) {
|
||||
void *tcrs;
|
||||
crspair *cp;
|
||||
unsigned int flags;
|
||||
int len;
|
||||
sscanf(ptr+SOFF("mdb_cursor_put"), "%p, ", &tcrs);
|
||||
cp = findcrs(tcrs);
|
||||
ptr = strchr(ptr+SOFF("mdb_cursor_put"), ',');
|
||||
sscanf(ptr+1, "%"MDB_SCNy(u)",", &key.mv_size);
|
||||
if (key.mv_size) {
|
||||
ptr = strchr(ptr, '[');
|
||||
inhex(ptr+1, kbuf);
|
||||
key.mv_data = kbuf;
|
||||
ptr += key.mv_size * 2 + 2;
|
||||
}
|
||||
ptr = strchr(ptr+1, ',');
|
||||
sscanf(ptr+1, "%"MDB_SCNy(u)"%n", &data.mv_size, &len);
|
||||
if (data.mv_size > dbufsize) {
|
||||
dbuf = realloc(dbuf, data.mv_size+2);
|
||||
assert(dbuf != NULL);
|
||||
dbufsize = data.mv_size;
|
||||
}
|
||||
ptr += len+1;
|
||||
if (*ptr == '[') {
|
||||
inhex(ptr+1, dbuf);
|
||||
data.mv_data = dbuf;
|
||||
ptr += data.mv_size * 2 + 2;
|
||||
} else {
|
||||
sprintf(dbuf, "%09ld", (long)mdb_txn_id(lasttxn->rtxn));
|
||||
}
|
||||
sscanf(ptr+1, "%u", &flags);
|
||||
E(mdb_cursor_put(cp->rcrs, &key, &data, flags));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_cursor_del"))) {
|
||||
void *tcrs;
|
||||
crspair *cp;
|
||||
unsigned int flags;
|
||||
sscanf(ptr+SOFF("mdb_cursor_del"), "%p, %u", &tcrs, &flags);
|
||||
cp = findcrs(tcrs);
|
||||
E(mdb_cursor_del(cp->rcrs, flags));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_put"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
unsigned int tdbi, flags;
|
||||
int len;
|
||||
sscanf(ptr+SOFF("mdb_put"),"%p, %u, %"MDB_SCNy(u), &ttxn, &tdbi, &key.mv_size);
|
||||
tp = findtxn(ttxn);
|
||||
ptr = strchr(ptr+SOFF("mdb_put"), '[');
|
||||
inhex(ptr+1, kbuf);
|
||||
key.mv_data = kbuf;
|
||||
ptr += key.mv_size * 2 + 2;
|
||||
sscanf(ptr+1, "%"MDB_SCNy(u)"%n", &data.mv_size, &len);
|
||||
if (data.mv_size > dbufsize) {
|
||||
dbuf = realloc(dbuf, data.mv_size+2);
|
||||
assert(dbuf != NULL);
|
||||
dbufsize = data.mv_size;
|
||||
}
|
||||
ptr += len+1;
|
||||
if (*ptr == '[') {
|
||||
inhex(ptr+1, dbuf);
|
||||
ptr += data.mv_size * 2 + 2;
|
||||
} else {
|
||||
sprintf(dbuf, "%09ld", (long)mdb_txn_id(tp->rtxn));
|
||||
}
|
||||
data.mv_data = dbuf;
|
||||
sscanf(ptr+1, "%u", &flags);
|
||||
RES(MDB_KEYEXIST,mdb_put(tp->rtxn, tdbi, &key, &data, flags));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_del"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
unsigned int tdbi;
|
||||
int len;
|
||||
sscanf(ptr+SOFF("mdb_del"),"%p, %u, %"MDB_SCNy(u), &ttxn, &tdbi, &key.mv_size);
|
||||
tp = findtxn(ttxn);
|
||||
ptr = strchr(ptr+SOFF("mdb_del"), '[');
|
||||
inhex(ptr+1, kbuf);
|
||||
key.mv_data = kbuf;
|
||||
ptr += key.mv_size * 2 + 2;
|
||||
sscanf(ptr+1, "%"MDB_SCNy(u)"%n", &data.mv_size, &len);
|
||||
if (data.mv_size > dbufsize) {
|
||||
dbuf = realloc(dbuf, data.mv_size+2);
|
||||
assert(dbuf != NULL);
|
||||
dbufsize = data.mv_size;
|
||||
}
|
||||
ptr += len+1;
|
||||
if (*ptr == '[') {
|
||||
inhex(ptr+1, dbuf);
|
||||
} else {
|
||||
sprintf(dbuf, "%09ld", (long)mdb_txn_id(tp->rtxn));
|
||||
}
|
||||
data.mv_data = dbuf;
|
||||
RES(MDB_NOTFOUND,mdb_del(tp->rtxn, tdbi, &key, &data));
|
||||
}
|
||||
write(1, "\n", 1);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static pidpair *addpid(int tpid)
|
||||
{
|
||||
int fdout[2], fdin[2];
|
||||
pid_t pid;
|
||||
assert(npids < MAXPIDS);
|
||||
pids[npids].tpid = tpid;
|
||||
pipe(fdout);
|
||||
pipe(fdin);
|
||||
if ((pid = fork()) == 0) {
|
||||
/* child */
|
||||
fflush(stdin);
|
||||
fflush(stdout);
|
||||
dup2(fdout[0], 0);
|
||||
dup2(fdin[1], 1);
|
||||
child();
|
||||
return 0; /* NOTREACHED */
|
||||
} else {
|
||||
pids[npids].rpid = pid;
|
||||
pids[npids].fdout = fdout[1];
|
||||
pids[npids].fdin = fdin[0];
|
||||
lastpid = pids+npids;
|
||||
npids++;
|
||||
return lastpid;
|
||||
}
|
||||
}
|
||||
|
||||
static pidpair *findpid(int tpid)
|
||||
{
|
||||
int i;
|
||||
if (!lastpid || lastpid->tpid != tpid) {
|
||||
for (i=0; i<npids; i++)
|
||||
if (pids[i].tpid == tpid)
|
||||
break;
|
||||
if (i == npids)
|
||||
return NULL;
|
||||
lastpid = &pids[i];
|
||||
}
|
||||
return lastpid;
|
||||
}
|
||||
|
||||
volatile pid_t killpid;
|
||||
|
||||
static void delpid(int tpid)
|
||||
{
|
||||
pidpair *pp = findpid(tpid);
|
||||
if (pp) {
|
||||
pid_t kpid = pp->rpid;
|
||||
killpid = kpid;
|
||||
write(pp->fdout, "exit\n", sizeof("exit"));
|
||||
while (killpid == kpid)
|
||||
usleep(10000);
|
||||
}
|
||||
}
|
||||
|
||||
static void reaper(int sig)
|
||||
{
|
||||
int status, i;
|
||||
pid_t pid = waitpid(-1, &status, 0);
|
||||
if (pid > 0) {
|
||||
fprintf(stderr, "# %s %d\n", WIFEXITED(status) ? "exited" : "killed", pid);
|
||||
for (i=0; i<npids; i++)
|
||||
if (pids[i].rpid == pid)
|
||||
break;
|
||||
assert(i < npids);
|
||||
close(pids[i].fdout);
|
||||
close(pids[i].fdin);
|
||||
for (;i<npids-1; i++)
|
||||
pids[i] = pids[i+1];
|
||||
npids--;
|
||||
killpid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
signal(SIGCHLD, reaper);
|
||||
|
||||
while (fgets(inbuf, sizeof(inbuf), stdin)) {
|
||||
pidpair *pp;
|
||||
int tpid, len;
|
||||
char c, *ptr;
|
||||
lcount++;
|
||||
|
||||
if (inbuf[0] == '#' && !strncmp(inbuf+1, SCMP(" killed"))) {
|
||||
sscanf(inbuf+SOFF("killed"),"%d", &tpid);
|
||||
delpid(tpid);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inbuf[0] != '>')
|
||||
continue;
|
||||
ptr = inbuf+1;
|
||||
sscanf(ptr, "%d:%n", &tpid, &len);
|
||||
pp = findpid(tpid);
|
||||
if (!pp)
|
||||
pp = addpid(tpid); /* new process */
|
||||
|
||||
ptr = inbuf+len+1;
|
||||
len = strlen(ptr);
|
||||
write(pp->fdout, ptr, len); /* send command and wait for ack */
|
||||
read(pp->fdin, &c, 1);
|
||||
}
|
||||
while (npids)
|
||||
delpid(pids[0].tpid);
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/* mtest.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor, *cur2;
|
||||
MDB_cursor_op op;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[32] = "";
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
count = (rand()%384) + 64;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = rand()%1024;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_maxreaders(env, 1));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP /*|MDB_NOSYNC*/, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, NULL, 0, &dbi));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
for (i=0;i<count;i++) {
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
/* Set <data> in each iteration, since MDB_NOOVERWRITE may modify it */
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NOOVERWRITE))) {
|
||||
j++;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
}
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
j=0;
|
||||
key.mv_data = sval;
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(sval, "%03x ", values[i]);
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, NULL))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor last\n");
|
||||
E(mdb_cursor_get(cursor, &key, &data, MDB_LAST));
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor last/prev\n");
|
||||
E(mdb_cursor_get(cursor, &key, &data, MDB_LAST));
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
E(mdb_cursor_get(cursor, &key, &data, MDB_PREV));
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
printf("Deleting with cursor\n");
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cur2));
|
||||
for (i=0; i<50; i++) {
|
||||
if (RES(MDB_NOTFOUND, mdb_cursor_get(cur2, &key, &data, MDB_NEXT)))
|
||||
break;
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
E(mdb_del(txn, dbi, &key, NULL));
|
||||
}
|
||||
|
||||
printf("Restarting cursor in txn\n");
|
||||
for (op=MDB_FIRST, i=0; i<=32; op=MDB_NEXT, i++) {
|
||||
if (RES(MDB_NOTFOUND, mdb_cursor_get(cur2, &key, &data, op)))
|
||||
break;
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
mdb_cursor_close(cur2);
|
||||
E(mdb_txn_commit(txn));
|
||||
|
||||
printf("Restarting cursor outside txn\n");
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
for (op=MDB_FIRST, i=0; i<=32; op=MDB_NEXT, i++) {
|
||||
if (RES(MDB_NOTFOUND, mdb_cursor_get(cursor, &key, &data, op)))
|
||||
break;
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/* mtest2.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Just like mtest.c, but using a subDB instead of the main DB */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[32] = "";
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
count = (rand()%384) + 64;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = rand()%1024;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_maxreaders(env, 1));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id1", MDB_CREATE, &dbi));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
for (i=0;i<count;i++) {
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NOOVERWRITE)))
|
||||
j++;
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
j=0;
|
||||
key.mv_data = sval;
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(sval, "%03x ", values[i]);
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, NULL))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/* mtest3.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Tests for sorted duplicate DBs */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[32];
|
||||
char kval[sizeof(int)];
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
memset(sval, 0, sizeof(sval));
|
||||
|
||||
count = (rand()%384) + 64;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = rand()%1024;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id2", MDB_CREATE|MDB_DUPSORT, &dbi));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
for (i=0;i<count;i++) {
|
||||
if (!(i & 0x0f))
|
||||
sprintf(kval, "%03x", values[i]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA)))
|
||||
j++;
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
j=0;
|
||||
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(kval, "%03x", values[i & ~0x0f]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/* mtest4.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Tests for sorted duplicate DBs with fixed-size keys */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[8];
|
||||
char kval[sizeof(int)];
|
||||
|
||||
memset(sval, 0, sizeof(sval));
|
||||
|
||||
count = 510;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = i*5;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id4", MDB_CREATE|MDB_DUPSORT|MDB_DUPFIXED, &dbi));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
strcpy(kval, "001");
|
||||
for (i=0;i<count;i++) {
|
||||
sprintf(sval, "%07x", values[i]);
|
||||
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA)))
|
||||
j++;
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
/* there should be one full page of dups now.
|
||||
*/
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
/* test all 3 branches of split code:
|
||||
* 1: new key in lower half
|
||||
* 2: new key at split point
|
||||
* 3: new key in upper half
|
||||
*/
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
sprintf(sval, "%07x", values[3]+1);
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
(void)RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA));
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
sprintf(sval, "%07x", values[255]+1);
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
(void)RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA));
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
sprintf(sval, "%07x", values[500]+1);
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
(void)RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA));
|
||||
E(mdb_txn_commit(txn));
|
||||
|
||||
/* Try MDB_NEXT_MULTIPLE */
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT_MULTIPLE)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
j=0;
|
||||
|
||||
for (i= count - 1; i > -1; i-= (rand()%3)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(sval, "%07x", values[i]);
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/* mtest5.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Tests for sorted duplicate DBs using cursor_put */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[32];
|
||||
char kval[sizeof(int)];
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
memset(sval, 0, sizeof(sval));
|
||||
|
||||
count = (rand()%384) + 64;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = rand()%1024;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id2", MDB_CREATE|MDB_DUPSORT, &dbi));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
for (i=0;i<count;i++) {
|
||||
if (!(i & 0x0f))
|
||||
sprintf(kval, "%03x", values[i]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
if (RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NODUPDATA)))
|
||||
j++;
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
mdb_cursor_close(cursor);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
j=0;
|
||||
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(kval, "%03x", values[i & ~0x0f]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/* mtest6.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Tests for DB splits and merges */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
char dkbuf[1024];
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data, sdata;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
long kval;
|
||||
char *sval;
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id6", MDB_CREATE|MDB_INTEGERKEY, &dbi));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
E(mdb_stat(txn, dbi, &mst));
|
||||
|
||||
sval = calloc(1, mst.ms_psize / 4);
|
||||
key.mv_size = sizeof(long);
|
||||
key.mv_data = &kval;
|
||||
sdata.mv_size = mst.ms_psize / 4 - 30;
|
||||
sdata.mv_data = sval;
|
||||
|
||||
printf("Adding 12 values, should yield 3 splits\n");
|
||||
for (i=0;i<12;i++) {
|
||||
kval = i*5;
|
||||
sprintf(sval, "%08x", kval);
|
||||
data = sdata;
|
||||
(void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE));
|
||||
}
|
||||
printf("Adding 12 more values, should yield 3 splits\n");
|
||||
for (i=0;i<12;i++) {
|
||||
kval = i*5+4;
|
||||
sprintf(sval, "%08x", kval);
|
||||
data = sdata;
|
||||
(void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE));
|
||||
}
|
||||
printf("Adding 12 more values, should yield 3 splits\n");
|
||||
for (i=0;i<12;i++) {
|
||||
kval = i*5+1;
|
||||
sprintf(sval, "%08x", kval);
|
||||
data = sdata;
|
||||
(void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE));
|
||||
}
|
||||
E(mdb_cursor_get(cursor, &key, &data, MDB_FIRST));
|
||||
|
||||
do {
|
||||
printf("key: %p %s, data: %p %.*s\n",
|
||||
key.mv_data, mdb_dkey(&key, dkbuf),
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
} while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0);
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_commit(txn);
|
||||
|
||||
#if 0
|
||||
j=0;
|
||||
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(kval, "%03x", values[i & ~0x0f]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
#endif
|
||||
mdb_env_close(env);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/* sample-bdb.txt - BerkeleyDB toy/sample
|
||||
*
|
||||
* Do a line-by-line comparison of this and sample-mdb.txt
|
||||
*/
|
||||
/*
|
||||
* Copyright 2012-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <db.h>
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int rc;
|
||||
DB_ENV *env;
|
||||
DB *dbi;
|
||||
DBT key, data;
|
||||
DB_TXN *txn;
|
||||
DBC *cursor;
|
||||
char sval[32], kval[32];
|
||||
|
||||
/* Note: Most error checking omitted for simplicity */
|
||||
|
||||
#define FLAGS (DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_TXN|DB_INIT_MPOOL|DB_CREATE|DB_THREAD)
|
||||
rc = db_env_create(&env, 0);
|
||||
rc = env->open(env, "./testdb", FLAGS, 0664);
|
||||
rc = db_create(&dbi, env, 0);
|
||||
rc = env->txn_begin(env, NULL, &txn, 0);
|
||||
rc = dbi->open(dbi, txn, "test.bdb", NULL, DB_BTREE, DB_CREATE, 0664);
|
||||
|
||||
memset(&key, 0, sizeof(DBT));
|
||||
memset(&data, 0, sizeof(DBT));
|
||||
key.size = sizeof(int);
|
||||
key.data = sval;
|
||||
data.size = sizeof(sval);
|
||||
data.data = sval;
|
||||
|
||||
sprintf(sval, "%03x %d foo bar", 32, 3141592);
|
||||
rc = dbi->put(dbi, txn, &key, &data, 0);
|
||||
rc = txn->commit(txn, 0);
|
||||
if (rc) {
|
||||
fprintf(stderr, "txn->commit: (%d) %s\n", rc, db_strerror(rc));
|
||||
goto leave;
|
||||
}
|
||||
rc = env->txn_begin(env, NULL, &txn, 0);
|
||||
rc = dbi->cursor(dbi, txn, &cursor, 0);
|
||||
key.flags = DB_DBT_USERMEM;
|
||||
key.data = kval;
|
||||
key.ulen = sizeof(kval);
|
||||
data.flags = DB_DBT_USERMEM;
|
||||
data.data = sval;
|
||||
data.ulen = sizeof(sval);
|
||||
while ((rc = cursor->c_get(cursor, &key, &data, DB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.data, (int) key.size, (char *) key.data,
|
||||
data.data, (int) data.size, (char *) data.data);
|
||||
}
|
||||
rc = cursor->c_close(cursor);
|
||||
rc = txn->abort(txn);
|
||||
leave:
|
||||
rc = dbi->close(dbi, 0);
|
||||
rc = env->close(env, 0);
|
||||
return rc;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/* sample-mdb.txt - MDB toy/sample
|
||||
*
|
||||
* Do a line-by-line comparison of this and sample-bdb.txt
|
||||
*/
|
||||
/*
|
||||
* Copyright 2012-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_cursor *cursor;
|
||||
char sval[32];
|
||||
|
||||
/* Note: Most error checking omitted for simplicity */
|
||||
|
||||
rc = mdb_env_create(&env);
|
||||
rc = mdb_env_open(env, "./testdb", 0, 0664);
|
||||
rc = mdb_txn_begin(env, NULL, 0, &txn);
|
||||
rc = mdb_dbi_open(txn, NULL, 0, &dbi);
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = sval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
sprintf(sval, "%03x %d foo bar", 32, 3141592);
|
||||
rc = mdb_put(txn, dbi, &key, &data, 0);
|
||||
rc = mdb_txn_commit(txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_commit: (%d) %s\n", rc, mdb_strerror(rc));
|
||||
goto leave;
|
||||
}
|
||||
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
|
||||
rc = mdb_cursor_open(txn, dbi, &cursor);
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
leave:
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<tagfile>
|
||||
<compound kind="page">
|
||||
<name>mdb_copy_1</name>
|
||||
<title>mdb_copy - environment copy tool</title>
|
||||
<filename>mdb_copy.1</filename>
|
||||
</compound>
|
||||
<compound kind="page">
|
||||
<name>mdb_dump_1</name>
|
||||
<title>mdb_dump - environment export tool</title>
|
||||
<filename>mdb_dump.1</filename>
|
||||
</compound>
|
||||
<compound kind="page">
|
||||
<name>mdb_load_1</name>
|
||||
<title>mdb_load - environment import tool</title>
|
||||
<filename>mdb_load.1</filename>
|
||||
</compound>
|
||||
<compound kind="page">
|
||||
<name>mdb_stat_1</name>
|
||||
<title>mdb_stat - environment status tool</title>
|
||||
<filename>mdb_stat.1</filename>
|
||||
</compound>
|
||||
</tagfile>
|
||||
+8
-4
@@ -130,11 +130,11 @@ void queue_free(struct ll_queue* q) {
|
||||
// ВАЖНО: Не освобождаем элементы в очереди - они должны быть извлечены отдельно
|
||||
// Это упрощает архитектуру и предотвращает double-u_free
|
||||
|
||||
// Освободить список waiters (сбросить handle->internal)
|
||||
// Освободить список waiters (сбросить handle->internal, отменить отложенные вызовы)
|
||||
struct queue_waiter* w = q->waiter_head;
|
||||
while (w) {
|
||||
struct queue_waiter* next = w->next;
|
||||
if (w->handle) w->handle->internal = NULL;
|
||||
if (w->handle) { w->handle->internal = NULL; if (w->handle->call_soon_id) { uasync_call_soon_cancel(q->ua, w->handle->call_soon_id); w->handle->call_soon_id = NULL; } }
|
||||
u_free(w);
|
||||
w = next;
|
||||
}
|
||||
@@ -305,14 +305,17 @@ static uint32_t make_hash(const void* data, uint16_t len) {// алгоритм F
|
||||
|
||||
static void waiter_defer_cb(void* arg) {
|
||||
struct queue_waiter_handle* h = (struct queue_waiter_handle*)arg;
|
||||
h->defer_cb(h->defer_q, h->defer_arg);
|
||||
h->call_soon_id = NULL;
|
||||
if (h->defer_cb) h->defer_cb(h->defer_q, h->defer_arg);
|
||||
else DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "waiter_defer_cb: NULL callback (stale?) q=%p", (void*)h->defer_q);
|
||||
}
|
||||
|
||||
static inline void queue_waiter_call(struct ll_queue* q, queue_threshold_callback_fn cb, void* arg,
|
||||
struct queue_waiter_handle* h) {
|
||||
if (q->waiter_defer) {
|
||||
if (h->call_soon_id) { DEBUG_WARN(DEBUG_CATEGORY_LL_QUEUE, "queue_waiter_call: overwriting existing call_soon_id=%p", h->call_soon_id); uasync_call_soon_cancel(q->ua, h->call_soon_id); }
|
||||
h->defer_q = q; h->defer_cb = cb; h->defer_arg = arg;
|
||||
uasync_call_soon(q->ua, h, waiter_defer_cb);
|
||||
h->call_soon_id = uasync_call_soon(q->ua, h, waiter_defer_cb);
|
||||
} else {
|
||||
cb(q, arg);
|
||||
}
|
||||
@@ -692,6 +695,7 @@ int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h,
|
||||
|
||||
void queue_waiter_cancel(struct ll_queue* q, struct queue_waiter_handle* h) {
|
||||
if (!q || !h || !h->internal) return;
|
||||
if (h->call_soon_id) { uasync_call_soon_cancel(q->ua, h->call_soon_id); h->call_soon_id = NULL; }
|
||||
|
||||
struct queue_waiter* waiter = h->internal;
|
||||
struct queue_waiter* prev = NULL;
|
||||
|
||||
@@ -117,6 +117,7 @@ struct queue_waiter_handle {
|
||||
struct ll_queue* defer_q;
|
||||
queue_threshold_callback_fn defer_cb;
|
||||
void* defer_arg;
|
||||
void* call_soon_id; ///< хендл отложенного вызова (для отмены)
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -89,6 +89,7 @@ int socket_set_nonblocking(socket_t sock) {
|
||||
}
|
||||
|
||||
int socket_close_wrapper(socket_t sock) {
|
||||
if (sock == 0) return 0; // never close fd 0
|
||||
return close(sock);
|
||||
}
|
||||
|
||||
|
||||
+30
-15
@@ -32,7 +32,7 @@ struct tcp_conn* tcp_conn_create(
|
||||
void (*on_error)(struct tcp_conn* tc, int err, void* arg),
|
||||
void* arg)
|
||||
{
|
||||
if (!ua || sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: invalid args ua=%p", ua); return NULL; }
|
||||
if (!ua || sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: invalid args ua=%p", (void*)ua); return NULL; }
|
||||
|
||||
struct tcp_conn* tc = u_calloc(1, sizeof(struct tcp_conn));
|
||||
if (!tc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: u_calloc failed"); return NULL; }
|
||||
@@ -104,8 +104,17 @@ struct tcp_conn* tcp_conn_create(
|
||||
return tc;
|
||||
}
|
||||
|
||||
static void tcp_conn_deferred_free(void* arg) {
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
if (tc->entry_pool) memory_pool_destroy(tc->entry_pool);
|
||||
if (tc->data_pool) memory_pool_destroy(tc->data_pool);
|
||||
u_free(tc);
|
||||
}
|
||||
|
||||
void tcp_conn_destroy(struct tcp_conn* tc) {
|
||||
if (!tc) return;
|
||||
if (tc->destroyed) return;
|
||||
tc->destroyed = 1;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_destroy: fd=%d connected=%d error=%d fin_remote=%d fin_local=%d closed=%d",
|
||||
(int)tc->sock, tc->connected, tc->error, tc->fin_remote, tc->fin_local, tc->closed);
|
||||
|
||||
@@ -127,14 +136,12 @@ void tcp_conn_destroy(struct tcp_conn* tc) {
|
||||
if (e->dgram && e->dgram != &tcp_fin_sentinel) memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e); queue_resume_callback(tc->write_queue);
|
||||
}
|
||||
queue_free(tc->read_queue);
|
||||
queue_free(tc->write_queue);
|
||||
queue_free(tc->read_queue); tc->read_queue = NULL;
|
||||
queue_free(tc->write_queue); tc->write_queue = NULL;
|
||||
|
||||
if (tc->write_buf) memory_pool_free(tc->data_pool, tc->write_buf);
|
||||
if (tc->sock != SOCKET_INVALID) socket_close_wrapper(tc->sock);
|
||||
memory_pool_destroy(tc->entry_pool);
|
||||
memory_pool_destroy(tc->data_pool);
|
||||
u_free(tc);
|
||||
if (tc->sock != SOCKET_INVALID) { socket_close_wrapper(tc->sock); tc->sock = SOCKET_INVALID; }
|
||||
uasync_call_soon(tc->ua, tc, tcp_conn_deferred_free);
|
||||
}
|
||||
|
||||
static void tcp_conn_handle_error(struct tcp_conn* tc, int err)
|
||||
@@ -157,13 +164,15 @@ static void tcp_conn_handle_error(struct tcp_conn* tc, int err)
|
||||
}
|
||||
}
|
||||
|
||||
if (tc->socket_id) {
|
||||
if (tc->sock != SOCKET_INVALID) {
|
||||
uasync_remove_socket_t(tc->ua, tc->sock);
|
||||
tc->socket_id = NULL;
|
||||
}
|
||||
if (tc->sock != SOCKET_INVALID) {
|
||||
#if HAS_EPOLL
|
||||
if (tc->ua && tc->ua->use_epoll && tc->ua->epoll_fd >= 0)
|
||||
epoll_ctl(tc->ua->epoll_fd, EPOLL_CTL_DEL, (int)tc->sock, NULL);
|
||||
#endif
|
||||
socket_close_wrapper(tc->sock);
|
||||
tc->sock = SOCKET_INVALID;
|
||||
tc->sock = SOCKET_INVALID; // ДО on_error: read/write в том же epoll event увидят INVALID
|
||||
}
|
||||
|
||||
queue_set_callback(tc->write_queue, NULL, NULL);
|
||||
@@ -183,7 +192,7 @@ static void tcp_conn_handle_error(struct tcp_conn* tc, int err)
|
||||
static void read_cb(socket_t sock, void* arg) {
|
||||
(void)sock;
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
if (!tc || tc->sock == SOCKET_INVALID) return;
|
||||
if (!tc || tc->sock == SOCKET_INVALID || !tc->read_queue) return;
|
||||
|
||||
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
|
||||
uint8_t* buf = memory_pool_alloc(tc->data_pool);
|
||||
@@ -200,8 +209,10 @@ static void read_cb(socket_t sock, void* arg) {
|
||||
if (n > 0) {
|
||||
e->dgram = buf;
|
||||
e->len = (uint16_t)n;
|
||||
queue_data_put(tc->read_queue, e);
|
||||
if (tc->read_queue->count >= tc->read_high_water && !tc->read_paused) {
|
||||
struct ll_queue* rq = tc->read_queue;
|
||||
queue_data_put(rq, e);
|
||||
// tc мог быть уничтожен через callback chain — read_queue обнулён
|
||||
if (rq && tc->read_queue && tc->read_queue->count >= tc->read_high_water && !tc->read_paused) {
|
||||
uasync_set_socket_read(tc->ua, tc->socket_id, 0);
|
||||
tc->read_paused = 1;
|
||||
queue_waiter_wait(tc->read_queue, &tc->read_waiter, resume_read_cb, tc);
|
||||
@@ -366,7 +377,7 @@ static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
|
||||
static void write_cb(socket_t sock, void* arg) {
|
||||
(void)sock;
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
if (!tc || tc->sock == SOCKET_INVALID) return;
|
||||
if (!tc || tc->sock == SOCKET_INVALID || !tc->write_queue) return;
|
||||
|
||||
if (!tc->connected) {
|
||||
int err = 0;
|
||||
@@ -384,6 +395,10 @@ static void write_cb(socket_t sock, void* arg) {
|
||||
if (flush_write_buf(tc)) return;
|
||||
if (!tc->write_buf) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: write_cb flushed fd=%d wmon=%d (resume WQ cb)", (int)tc->sock, tc->write_monitor);
|
||||
if (tc->write_queue->waiter_defer) {
|
||||
uasync_set_socket_write(tc->ua, tc->socket_id, 0);
|
||||
tc->write_monitor = 0;
|
||||
}
|
||||
queue_resume_callback(tc->write_queue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ struct tcp_conn {
|
||||
uint8_t fin_remote; // FIN получен от удалённой стороны (recv == 0)
|
||||
uint8_t fin_local; // FIN отправлен удалённой стороне (shutdown SHUT_WR)
|
||||
uint8_t closed; // сокет полностью закрыт (close)
|
||||
uint8_t destroyed; // 1 = tcp_conn_destroy вызван, tc ожидает отложенного free
|
||||
|
||||
// Частичная отправка (из data_pool, не в очереди — досылается первой)
|
||||
uint8_t* write_buf;
|
||||
|
||||
+611
-603
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,27 @@
|
||||
UTUN
|
||||
|
||||
Идея: объединить узлы в единую локальную сеть.
|
||||
Чтобы узлы сами находили оптимальные линки между собой, пробивали NAT где это можно, где нельзя - выбирали посредника с хорошей связью; чтобы подсто было добавлять новые узлы и адреса узлов сразу виделись во всей сети.
|
||||
|
||||
Потенциальные варианты использования:
|
||||
- локальная сеть. хочу объединить офис, сотрудников (включая их работчие подсети), телефоны, офисы, сервер доступа итд в одно адресное пространство.
|
||||
- чат с файлообменником. узлы сети - это мемберы группы и одновременно сети.
|
||||
- CDN. узлы - потребители контента - это мемберы сети, которые распространяют трафик.
|
||||
|
||||
То есть задача utun - обеспечить связность группы узлов и адаптацию работы всей сети к подключению-отключении отдельных узлов.
|
||||
|
||||
Механизмы:
|
||||
1. Пробитие nat.
|
||||
2. автообновление списка узлов и их адресов для подключения
|
||||
3. шифрование трафика и маскировка handshake.
|
||||
handshake просиходит без сигнатур по которым можно опреедлить тип сервиса, весь трафик выглядлит как случайный набор байт случайного размера
|
||||
4. многоканальные подключения, агрегирование каналов, исключение мёртвых каналов
|
||||
подключиться к узлу можно через несколько линков. UTUN будет объединять пропускную способность каналов, наблюдая за состоянием каналов, не допуская перегрузки и обеспечивая быстрые переповторы через другие каналы при потерях или большой задержке.
|
||||
5. динамическая маршрутизация. Узлы могут назначать себе IP адреса и при подключении к сети анонсировать свои адреса/подсети.
|
||||
это максимально упрощает добавление нового узла в сеть.
|
||||
6. автоматический роутинг между узлами: узлы маршрутизируют трафик между собой.
|
||||
используя статические подключения, прописанные в конфиге.
|
||||
7. прямые подключения и поиск лучших подключений
|
||||
узлы могут подключаться друг к другу напрямую, либо если оба узла за строгим NAT - выбирать оптимального посредника.
|
||||
Узлы определяют свой тип подключения и NAT. Например у ростелеком - EIM NAT который можно использовать и для входящих подклюений.
|
||||
Можно использовать IPv6.
|
||||
+10
-92
@@ -11,7 +11,9 @@ utun_CORE_SOURCES = \
|
||||
route_bgp.c \
|
||||
route_ping.c \
|
||||
route_node.c \
|
||||
route_node_lmdb.c \
|
||||
route_connectivity.c \
|
||||
conn_mgr.c \
|
||||
routing.c \
|
||||
tun_if.c \
|
||||
tun_route.c \
|
||||
@@ -26,6 +28,7 @@ utun_CORE_SOURCES = \
|
||||
etcp_dump.c \
|
||||
secure_channel.c \
|
||||
crc32.c \
|
||||
stcp_link.c \
|
||||
stcp.c \
|
||||
stcp_server.c \
|
||||
stcp_client.c \
|
||||
@@ -33,6 +36,7 @@ utun_CORE_SOURCES = \
|
||||
packet_dump.c \
|
||||
etcp_api.c \
|
||||
control_server.c \
|
||||
msg_transport.c \
|
||||
firewall.c \
|
||||
eim_nat.c \
|
||||
nat_transport.c \
|
||||
@@ -41,6 +45,7 @@ utun_CORE_SOURCES = \
|
||||
etcp_router.c \
|
||||
proxy/tcp_proxy_server.c \
|
||||
proxy/udp_proxy.c \
|
||||
proxy/socks_proxy.c \
|
||||
proxy/icmp_proxy.c \
|
||||
lwip_tcp/lwip_pbuf.c \
|
||||
lwip_tcp/lwip_tcp.c \
|
||||
@@ -50,109 +55,22 @@ utun_CORE_SOURCES = \
|
||||
# Platform-specific TUN libs (Windows only)
|
||||
utun_TUN_LIBS = @TUN_LIBS@
|
||||
|
||||
# TinyCrypt sources (only used without OpenSSL)
|
||||
utun_TINYCRYPT_SOURCES = \
|
||||
$(top_srcdir)/tinycrypt/lib/source/aes_encrypt.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/aes_decrypt.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/cbc_mode.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/ccm_mode.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/cmac_mode.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/ctr_mode.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/ecc.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/ecc_dh.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/ecc_dsa.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/ecc_platform_specific.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/hmac.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/sha256.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/utils.c
|
||||
|
||||
# Combine sources based on OpenSSL usage
|
||||
if USE_OPENSSL
|
||||
utun_SOURCES = $(utun_CORE_SOURCES) $(utun_TUN_SOURCES)
|
||||
else
|
||||
utun_SOURCES = $(utun_CORE_SOURCES) $(utun_TINYCRYPT_SOURCES) $(utun_TUN_SOURCES)
|
||||
endif
|
||||
|
||||
# Include paths
|
||||
utun_CORE_CFLAGS = \
|
||||
utun_CFLAGS = \
|
||||
-I$(top_srcdir)/lib \
|
||||
-I$(top_srcdir)/src/uip \
|
||||
-I$(top_srcdir)/tinycrypt/lib/include \
|
||||
-I$(top_srcdir)/tinycrypt/lib/source \
|
||||
-g \
|
||||
$(DEBUG_FLAGS)
|
||||
|
||||
utun_CFLAGS = $(utun_CORE_CFLAGS)
|
||||
|
||||
# Libraries
|
||||
utun_CORE_LDADD = \
|
||||
utun_LDADD = \
|
||||
$(top_builddir)/lib/libuasync.a \
|
||||
-lpthread \
|
||||
-lm
|
||||
|
||||
if USE_OPENSSL
|
||||
utun_LDADD = $(utun_CORE_LDADD) -lcrypto $(utun_TUN_LIBS)
|
||||
else
|
||||
utun_LDADD = $(utun_CORE_LDADD) $(utun_TUN_LIBS)
|
||||
endif
|
||||
|
||||
# TinyCrypt object files (for tests that need them)
|
||||
TINYCRYPT_SRCDIR = $(top_srcdir)/tinycrypt/lib/source
|
||||
|
||||
TINYCRYPT_OBJS = \
|
||||
utun-aes_encrypt.o \
|
||||
utun-aes_decrypt.o \
|
||||
utun-ccm_mode.o \
|
||||
utun-cmac_mode.o \
|
||||
utun-ctr_mode.o \
|
||||
utun-ecc.o \
|
||||
utun-ecc_dh.o \
|
||||
utun-ecc_dsa.o \
|
||||
utun-sha256.o \
|
||||
utun-ecc_platform_specific.o \
|
||||
utun-utils.o
|
||||
|
||||
# Rules to build TinyCrypt objects (for tests)
|
||||
TINYCRYPT_CFLAGS = -g -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
|
||||
utun-aes_encrypt.o: $(TINYCRYPT_SRCDIR)/aes_encrypt.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
utun-aes_decrypt.o: $(TINYCRYPT_SRCDIR)/aes_decrypt.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
utun-ccm_mode.o: $(TINYCRYPT_SRCDIR)/ccm_mode.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
utun-cmac_mode.o: $(TINYCRYPT_SRCDIR)/cmac_mode.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
utun-ctr_mode.o: $(TINYCRYPT_SRCDIR)/ctr_mode.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
utun-ecc.o: $(TINYCRYPT_SRCDIR)/ecc.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
utun-ecc_dh.o: $(TINYCRYPT_SRCDIR)/ecc_dh.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
utun-ecc_dsa.o: $(TINYCRYPT_SRCDIR)/ecc_dsa.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
utun-sha256.o: $(TINYCRYPT_SRCDIR)/sha256.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
# Force rebuild of ecc_platform_specific.o to pick up platform changes
|
||||
utun-ecc_platform_specific.o: $(TINYCRYPT_SRCDIR)/ecc_platform_specific.c FORCE
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
FORCE:
|
||||
|
||||
utun-utils.o: $(TINYCRYPT_SRCDIR)/utils.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
# Build TinyCrypt objects for tests (convenience target)
|
||||
tinycrypt-objects: $(TINYCRYPT_OBJS)
|
||||
-lm \
|
||||
-lcrypto \
|
||||
$(utun_TUN_LIBS)
|
||||
|
||||
# Copy binary to project root after building
|
||||
all-local: copy-to-root
|
||||
|
||||
+67
-8
@@ -46,7 +46,8 @@ typedef enum {
|
||||
SECTION_ALLOWED_KEYS,
|
||||
SECTION_NAT,
|
||||
SECTION_TCP_PROXY_CLIENT,
|
||||
SECTION_TCP_PROXY_SERVER
|
||||
SECTION_TCP_PROXY_SERVER,
|
||||
SECTION_MSG_TRANSPORT
|
||||
} section_type_t;
|
||||
|
||||
static char* trim(char *str) {
|
||||
@@ -356,6 +357,10 @@ static int parse_global(const char *key, const char *value, struct global_config
|
||||
global->my_node_id = strtoull(value, NULL, 16);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "tun_enabled") == 0) {
|
||||
global->tun_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "tun_ifname") == 0) {
|
||||
snprintf(global->tun_ifname, sizeof(global->tun_ifname), "%s", value);
|
||||
return 0;
|
||||
@@ -376,6 +381,10 @@ static int parse_global(const char *key, const char *value, struct global_config
|
||||
global->keepalive_interval = atoi(value);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "keepalive_adaptive") == 0) {
|
||||
global->keepalive_adaptive = atoi(value);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "bbr_max_cwnd") == 0) {
|
||||
global->bbr_max_cwnd = atoi(value);
|
||||
return 0;
|
||||
@@ -386,6 +395,9 @@ static int parse_global(const char *key, const char *value, struct global_config
|
||||
if (strcmp(key, "log_file") == 0) {
|
||||
return assign_string(global->log_file, sizeof(global->log_file), value);
|
||||
}
|
||||
if (strcmp(key, "db_path") == 0) {
|
||||
return assign_string(global->db_path, sizeof(global->db_path), value);
|
||||
}
|
||||
/* debug_categories key is deprecated - use [debug] section instead */
|
||||
if (strcmp(key, "enable_timestamp") == 0) {
|
||||
global->enable_timestamp = atoi(value);
|
||||
@@ -407,7 +419,7 @@ static int parse_global(const char *key, const char *value, struct global_config
|
||||
global->tun_test_mode = atoi(value);
|
||||
return 0;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown global option '%s'. Valid: my_node_name, my_private_key, my_public_key, my_node_id, tun_ifname, tun_ip, mtu, keepalive_timeout, keepalive_interval, bbr_max_cwnd, debug_level, log_file, enable_timestamp, enable_function_names, enable_file_lines, enable_colors, tun_test_mode", filename, line_num, key);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown global option '%s'. Valid: my_node_name, my_private_key, my_public_key, my_node_id, tun_ifname, tun_ip, mtu, keepalive_timeout, keepalive_interval, keepalive_adaptive, bbr_max_cwnd, debug_level, log_file, db_path, enable_timestamp, enable_function_names, enable_file_lines, enable_colors, tun_test_mode", filename, line_num, key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -436,6 +448,17 @@ static int parse_control(const char *key, const char *value, struct global_confi
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int parse_msg_transport(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
|
||||
if (strcmp(key, "port") == 0) {
|
||||
char port_str[16];
|
||||
snprintf(port_str, sizeof(port_str), "%s", value);
|
||||
parse_sockaddr("127.0.0.1", port_str, &global->msg_transport_sock);
|
||||
return 0;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown msg_transport option '%s'. Valid: port", filename, line_num, key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int parse_tcp_proxy_client(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
|
||||
if (strcmp(key, "enabled") == 0) {
|
||||
global->tcp_proxy_client_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
|
||||
@@ -457,6 +480,22 @@ static int parse_tcp_proxy_client(const char *key, const char *value, struct glo
|
||||
global->tcp_proxy_client_via_node_id = strtoull(value, NULL, 16);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "socks_enabled") == 0) {
|
||||
global->tcp_proxy_client_socks_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "socks_addr") == 0) {
|
||||
strncpy(global->tcp_proxy_client_socks_addr, value, sizeof(global->tcp_proxy_client_socks_addr) - 1);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "http_proxy_enabled") == 0) {
|
||||
global->tcp_proxy_client_http_proxy_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "http_proxy_addr") == 0) {
|
||||
strncpy(global->tcp_proxy_client_http_proxy_addr, value, sizeof(global->tcp_proxy_client_http_proxy_addr) - 1);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "forward") == 0) {
|
||||
if (global->tcp_proxy_client_mapping_count >= MAX_TCP_PROXY_CLIENT_MAPPINGS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Too many tcp_proxy_client forward rules (max %d)", MAX_TCP_PROXY_CLIENT_MAPPINGS);
|
||||
@@ -493,7 +532,7 @@ static int parse_tcp_proxy_client(const char *key, const char *value, struct glo
|
||||
global->tcp_proxy_client_mapping_count++;
|
||||
return 0;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown tcp_proxy_client option '%s'. Valid: enabled, tun_name, tun_ip, mtu, via_node, forward", filename, line_num, key);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown tcp_proxy_client option '%s'. Valid: enabled, tun_name, tun_ip, mtu, via_node, forward, socks_enabled, socks_addr, http_proxy_enabled, http_proxy_addr", filename, line_num, key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -624,7 +663,12 @@ static int parse_server(const char *key, const char *value, struct CFG_SERVER *s
|
||||
srv->only_local = atoi(value) ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown server option '%s'. Valid: addr, so_mark, fib, netif, type, mtu, only_local", filename, line_num, key);
|
||||
if (strcmp(key, "transport") == 0) {
|
||||
if (strcmp(value, "tcp") == 0) srv->transport = 1;
|
||||
else srv->transport = 0; // default udp
|
||||
return 0;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown server option '%s'. Valid: addr, so_mark, fib, netif, type, mtu, only_local, transport", filename, line_num, key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -692,6 +736,7 @@ static section_type_t parse_section_header(const char *line, char *name, size_t
|
||||
if (strcasecmp(section, "nat") == 0) return SECTION_NAT;
|
||||
if (strcasecmp(section, "tcp_proxy_client") == 0) return SECTION_TCP_PROXY_CLIENT;
|
||||
if (strcasecmp(section, "tcp_proxy_server") == 0) return SECTION_TCP_PROXY_SERVER;
|
||||
if (strcasecmp(section, "msg_transport") == 0) return SECTION_MSG_TRANSPORT;
|
||||
|
||||
char *colon = strchr(section, ':');
|
||||
if (!colon) return SECTION_UNKNOWN;
|
||||
@@ -720,6 +765,7 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
cfg->global.name[0] = '\0';
|
||||
cfg->global.keepalive_timeout = 2000; // Default 2 seconds
|
||||
cfg->global.keepalive_interval = 200; // Default 0.2 s
|
||||
cfg->global.keepalive_adaptive = 1; // Default: adaptive enabled
|
||||
cfg->global.bbr_max_cwnd = 1048576; // Default 1MB (was INFLIGHT_LIM_MAX)
|
||||
cfg->global.tcp_recv_buf = 0; // 0 = OS default
|
||||
cfg->global.firewall_rules = NULL;
|
||||
@@ -874,6 +920,11 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
cfg->global.tcp_recv_buf = atoi(value);
|
||||
}
|
||||
break;
|
||||
case SECTION_MSG_TRANSPORT:
|
||||
if (parse_msg_transport(key, value, &cfg->global, filename, line_num) < 0) {
|
||||
// parse_msg_transport already printed the error
|
||||
}
|
||||
break;
|
||||
default:
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Key outside section: %s", filename, line_num, key);
|
||||
break;
|
||||
@@ -890,10 +941,18 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
cfg->clients = cur_client;
|
||||
}
|
||||
|
||||
// Set default TUN interface name if not provided
|
||||
if (cfg->global.tun_ifname[0] == '\0') {
|
||||
snprintf(cfg->global.tun_ifname, sizeof(cfg->global.tun_ifname), "tun0");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Auto-generated TUN interface name: %s", cfg->global.tun_ifname);
|
||||
// Post-parse defaults: tun_enabled=1 if not explicitly disabled
|
||||
if (!cfg->global.tun_enabled && cfg->global.tun_ifname[0] == '\0' && cfg->global.tun_ip.family == AF_UNSPEC) {
|
||||
// No TUN config at all — treat as explicitly off (for non-root SOCKS/HTTP-only mode)
|
||||
} else if (!cfg->global.tun_enabled) {
|
||||
// TUN was explicitly disabled via tun_enabled=no
|
||||
} else {
|
||||
// TUN enabled (default) — set default ifname if not provided
|
||||
cfg->global.tun_enabled = 1;
|
||||
if (cfg->global.tun_ifname[0] == '\0') {
|
||||
snprintf(cfg->global.tun_ifname, sizeof(cfg->global.tun_ifname), "tun0");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Auto-generated TUN interface name: %s", cfg->global.tun_ifname);
|
||||
}
|
||||
}
|
||||
|
||||
return cfg;
|
||||
|
||||
+10
-1
@@ -40,6 +40,7 @@ struct CFG_SERVER {
|
||||
int so_mark;
|
||||
int fib; // FreeBSD FIB (routing table), -1 = don't set
|
||||
uint8_t type; // public/nat/private
|
||||
uint8_t transport; // 0=udp (default), 1=tcp
|
||||
uint8_t ipv6_mode; // CFG_IPV6_MODE_*
|
||||
int mtu;
|
||||
uint8_t only_local; // 1 = only local connections, no forwarding
|
||||
@@ -78,7 +79,7 @@ struct CFG_CONTROL_ALLOW {
|
||||
};
|
||||
|
||||
struct CFG_ALLOWED_KEY {
|
||||
uint8_t key_bin[SC_PUBKEY_SIZE]; // public key in binary (64 bytes)
|
||||
uint8_t key_bin[SC_PUBKEY_SIZE]; // public key in binary (32 bytes, X25519)
|
||||
struct CFG_ALLOWED_KEY *next;
|
||||
};
|
||||
|
||||
@@ -94,14 +95,17 @@ struct global_config {
|
||||
char my_private_key_hex[MAX_KEY_LEN];
|
||||
char my_public_key_hex[MAX_KEY_LEN];
|
||||
uint64_t my_node_id;
|
||||
int tun_enabled; // 1 = create TUN (default), 0 = skip TUN (run without root)
|
||||
char tun_ifname[16]; // TUN interface name (e.g., "tun12")
|
||||
struct IP tun_ip;
|
||||
int mtu;
|
||||
struct sockaddr_storage control_sock;
|
||||
char control_ip[MAX_ADDR_LEN]; // Control server IP address
|
||||
struct sockaddr_storage msg_transport_sock;
|
||||
|
||||
// Debug and logging configuration
|
||||
char log_file[256]; // Path to log file (empty = stdout)
|
||||
char db_path[256]; // Path to LMDB nodeinfo database (empty = disabled)
|
||||
char debug_level[16]; // debug level: error, warn, info, debug, trace
|
||||
int enable_timestamp; // enable timestamps in logs
|
||||
int enable_function_names; // enable function names in logs
|
||||
@@ -118,6 +122,7 @@ struct global_config {
|
||||
int tun_test_mode; // test mode: 1 = don't open real TUN, queues only
|
||||
int keepalive_timeout; // keepalive timeout in ms (default: 2000)
|
||||
int keepalive_interval; // keepalive interval in ms (default: 200)
|
||||
int keepalive_adaptive; // 1 = adaptive period (default), 0 = fixed interval
|
||||
int bbr_max_cwnd; // BBR cwnd cap in bytes (default: 100000)
|
||||
|
||||
// Firewall configuration
|
||||
@@ -158,6 +163,10 @@ struct global_config {
|
||||
uint64_t tcp_proxy_client_via_node_id; // через этот узел проксируются все forward-правила
|
||||
struct tcp_proxy_client_mapping_config tcp_proxy_client_mappings[MAX_TCP_PROXY_CLIENT_MAPPINGS];
|
||||
int tcp_proxy_client_mapping_count;
|
||||
int tcp_proxy_client_socks_enabled;
|
||||
char tcp_proxy_client_socks_addr[64]; // e.g. "127.0.0.1:1080"
|
||||
int tcp_proxy_client_http_proxy_enabled;
|
||||
char tcp_proxy_client_http_proxy_addr[64]; // e.g. "127.0.0.1:8080"
|
||||
|
||||
// TCP proxy server (exit node) configuration ([tcp_proxy_server] section)
|
||||
int tcp_proxy_server_enabled;
|
||||
|
||||
+41
-26
@@ -15,7 +15,7 @@
|
||||
#include "../lib/mem.h"
|
||||
|
||||
#define PRIV_HEXKEY_LEN 65 // 32 bytes * 2 hex chars + null
|
||||
#define PUB_HEXKEY_LEN 129 // 64 bytes * 2 hex chars + null
|
||||
#define PUB_HEXKEY_LEN 65 // 32 bytes * 2 hex chars + null
|
||||
#define HEXNODEID_LEN 17 // 8 bytes * 2 hex chars + null
|
||||
#define MAX_LINE_LEN 1024
|
||||
|
||||
@@ -41,8 +41,8 @@ static int is_valid_priv_key(const char *key) {
|
||||
}
|
||||
|
||||
static int is_valid_pub_key(const char *key) {
|
||||
if (!key || strlen(key) != 128) return 0;
|
||||
for (int i = 0; i < 128; i++) {
|
||||
if (!key || strlen(key) != 64) return 0;
|
||||
for (int i = 0; i < 64; i++) {
|
||||
if (!isxdigit((unsigned char)key[i])) return 0;
|
||||
}
|
||||
return 1;
|
||||
@@ -151,21 +151,25 @@ static int insert_or_replace_option(char **buf, size_t *buf_len, size_t *buf_cap
|
||||
char *opt_pos = find_option(*buf, *buf_len, option);
|
||||
|
||||
if (opt_pos) {
|
||||
// Find option line end
|
||||
char *line_end = strchr(opt_pos, '\n');
|
||||
// Determine if old line has trailing \n
|
||||
char *nl = strchr(opt_pos, '\n');
|
||||
int has_trailing_nl = (nl != NULL);
|
||||
char *line_end = nl ? nl : *buf + *buf_len;
|
||||
|
||||
if (!line_end) line_end = *buf + *buf_len;
|
||||
|
||||
// Prepare new line
|
||||
char new_line[MAX_LINE_LEN];
|
||||
int new_len = snprintf(new_line, sizeof(new_line), "%s=%s\n", option, value);
|
||||
|
||||
int new_len;
|
||||
if (has_trailing_nl) {
|
||||
new_len = snprintf(new_line, sizeof(new_line), "%s=%s\n", option, value);
|
||||
} else {
|
||||
new_len = snprintf(new_line, sizeof(new_line), "%s=%s", option, value);
|
||||
}
|
||||
if (new_len <= 0 || new_len >= (int)sizeof(new_line)) return -1;
|
||||
|
||||
|
||||
// Calculate length difference
|
||||
size_t old_line_len = line_end - opt_pos + 1;
|
||||
size_t old_line_len = has_trailing_nl ? (line_end - opt_pos + 1) : (line_end - opt_pos);
|
||||
long len_diff = new_len - old_line_len;
|
||||
|
||||
|
||||
// Ensure buffer capacity
|
||||
if (*buf_len + len_diff + 1 > *buf_capacity) {
|
||||
*buf_capacity = *buf_len + len_diff + 1024;
|
||||
@@ -177,11 +181,11 @@ static int insert_or_replace_option(char **buf, size_t *buf_len, size_t *buf_cap
|
||||
opt_pos = find_option(*buf, *buf_len, option);
|
||||
|
||||
if (!opt_pos) return -1;
|
||||
line_end = strchr(opt_pos, '\n');
|
||||
|
||||
if (!line_end) line_end = *buf + *buf_len;
|
||||
nl = strchr(opt_pos, '\n');
|
||||
has_trailing_nl = (nl != NULL);
|
||||
line_end = nl ? nl : *buf + *buf_len;
|
||||
}
|
||||
|
||||
|
||||
// Move content and insert new line
|
||||
memmove(opt_pos + new_len, line_end, *buf_len - (line_end - *buf) + 1);
|
||||
|
||||
@@ -194,24 +198,31 @@ static int insert_or_replace_option(char **buf, size_t *buf_len, size_t *buf_cap
|
||||
char *section_end = find_section_global(*buf, *buf_len);
|
||||
|
||||
if (!section_end) return -1;
|
||||
|
||||
|
||||
// Find end of global section (next [ or end of buffer)
|
||||
char *insert_pos = strchr(section_end, '[');
|
||||
int need_leading_nl = 0;
|
||||
|
||||
if (!insert_pos) insert_pos = *buf + *buf_len;
|
||||
else {
|
||||
if (!insert_pos) {
|
||||
insert_pos = *buf + *buf_len;
|
||||
if (*buf_len > 0 && (*buf)[*buf_len - 1] != '\n')
|
||||
need_leading_nl = 1;
|
||||
} else {
|
||||
// Move to beginning of next line
|
||||
char *prev_nl = insert_pos;
|
||||
while (prev_nl > *buf && *prev_nl != '\n') prev_nl--;
|
||||
if (*prev_nl == '\n') insert_pos = prev_nl + 1;
|
||||
}
|
||||
|
||||
|
||||
// Prepare new line
|
||||
char new_line[MAX_LINE_LEN];
|
||||
int new_len = snprintf(new_line, sizeof(new_line), "%s=%s\n", option, value);
|
||||
|
||||
int new_len;
|
||||
if (need_leading_nl)
|
||||
new_len = snprintf(new_line, sizeof(new_line), "\n%s=%s\n", option, value);
|
||||
else
|
||||
new_len = snprintf(new_line, sizeof(new_line), "%s=%s\n", option, value);
|
||||
if (new_len <= 0 || new_len >= (int)sizeof(new_line)) return -1;
|
||||
|
||||
|
||||
// Ensure buffer capacity
|
||||
if (*buf_len + new_len + 1 > *buf_capacity) {
|
||||
*buf_capacity = *buf_len + new_len + 1024;
|
||||
@@ -224,15 +235,19 @@ static int insert_or_replace_option(char **buf, size_t *buf_len, size_t *buf_cap
|
||||
|
||||
if (!section_end) return -1;
|
||||
insert_pos = strchr(section_end, '[');
|
||||
need_leading_nl = 0;
|
||||
|
||||
if (!insert_pos) insert_pos = *buf + *buf_len;
|
||||
else {
|
||||
if (!insert_pos) {
|
||||
insert_pos = *buf + *buf_len;
|
||||
if (*buf_len > 0 && (*buf)[*buf_len - 1] != '\n')
|
||||
need_leading_nl = 1;
|
||||
} else {
|
||||
char *prev_nl = insert_pos;
|
||||
while (prev_nl > *buf && *prev_nl != '\n') prev_nl--;
|
||||
if (*prev_nl == '\n') insert_pos = prev_nl + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Move content and insert new line
|
||||
memmove(insert_pos + new_len, insert_pos, *buf_len - (insert_pos - *buf) + 1);
|
||||
|
||||
|
||||
+1000
@@ -0,0 +1,1000 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
#include "../lib/platform_compat.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "utun_instance.h"
|
||||
#include "etcp.h"
|
||||
#include "etcp_connections.h"
|
||||
#include "etcp_router.h"
|
||||
#include "config_parser.h"
|
||||
#include "secure_channel.h"
|
||||
#include "route_node.h"
|
||||
#include "route_bgp.h"
|
||||
#include "route_ping.h"
|
||||
#include "route_connectivity.h"
|
||||
#include "conn_mgr.h"
|
||||
|
||||
static struct CONN_MGR_ENTRY* cm_find_entry(struct CONN_MGR* mgr, uint64_t node_id);
|
||||
static void cm_start_phase_direct(struct CONN_MGR_ENTRY* entry);
|
||||
static void cm_start_phase_reverse(struct CONN_MGR_ENTRY* entry);
|
||||
static int cm_start_phase_indirect(struct CONN_MGR_ENTRY* entry);
|
||||
static void cm_start_local_scan(struct CONN_MGR_ENTRY* entry);
|
||||
static void cm_idle_timer_cb(void* arg);
|
||||
static void cm_deliver_result(struct CONN_MGR_ENTRY* entry, int result);
|
||||
static void cm_bg_ping_timer_cb(void* arg);
|
||||
static int cm_nat_compatible(struct ETCP_SOCKET* our, uint8_t target_meta_type);
|
||||
static uint16_t cm_get_node_min_rtt(struct NODEINFO_Q* nq);
|
||||
static int cm_has_direct_ip(struct NODEINFO_Q* nq);
|
||||
static int cm_has_local_addr(struct NODEINFO_Q* nq);
|
||||
static void cm_entry_destroy(struct CONN_MGR_ENTRY* entry);
|
||||
static uint64_t cm_get_node_max_probe_time(struct NODEINFO_Q* nq);
|
||||
static int cm_is_rtt_fresh(struct NODEINFO_Q* nq, uint64_t now_tb);
|
||||
|
||||
static void conn_mgr_router_recv_handler(struct ETCP_CONN* conn, struct ll_entry* entry);
|
||||
static void cm_handle_direct_req(struct ETCP_CONN* conn, const uint8_t* data, size_t len);
|
||||
static void cm_handle_interm_exchange_req(struct ETCP_CONN* conn, struct CONN_MGR_INTERM_EXCHANGE_REQ* req);
|
||||
static void cm_handle_interm_exchange_resp(struct CONN_MGR* mgr, const uint8_t* data, size_t len);
|
||||
static void cm_handle_interm_selected(struct CONN_MGR* mgr, const uint8_t* data, size_t len);
|
||||
static void cm_handle_disconnect(struct CONN_MGR* mgr, uint64_t node_id);
|
||||
static void cm_direct_ready_cb(struct ETCP_CONN* conn, void* arg);
|
||||
static void cm_reverse_ready_cb(struct ETCP_CONN* conn, void* arg);
|
||||
static void cm_reverse_timeout_cb(void* arg);
|
||||
static void cm_exchange_timeout_cb(void* arg);
|
||||
static void cm_candidate_ping_timer_cb(void* arg);
|
||||
|
||||
struct cm_reverse_pending {
|
||||
struct cm_reverse_pending* next;
|
||||
uint32_t request_id;
|
||||
struct CONN_MGR_ENTRY* entry;
|
||||
struct ETCP_CONN** conns;
|
||||
uint8_t conn_count;
|
||||
};
|
||||
|
||||
struct cm_exchange_pending {
|
||||
struct cm_exchange_pending* next;
|
||||
uint32_t request_id;
|
||||
struct CONN_MGR_ENTRY* entry;
|
||||
void* timeout_timer;
|
||||
struct CONN_MGR_INTERM_EXCHANGE_RESP cached_resp;
|
||||
uint8_t resp_received;
|
||||
uint8_t probes_done;
|
||||
};
|
||||
|
||||
struct CONN_MGR* conn_mgr_init(struct UTUN_INSTANCE* instance) {
|
||||
if (!instance) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_init: NULL instance"); return NULL; }
|
||||
struct CONN_MGR* mgr = u_calloc(1, sizeof(struct CONN_MGR));
|
||||
if (!mgr) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_init: alloc failed"); return NULL; }
|
||||
mgr->instance = instance;
|
||||
mgr->entry_capacity = 8;
|
||||
mgr->entries = u_calloc(mgr->entry_capacity, sizeof(struct CONN_MGR_ENTRY));
|
||||
if (!mgr->entries) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_init: entries alloc failed"); u_free(mgr); return NULL; }
|
||||
etcp_router_bind(instance, ETCP_ID_CONN_MGR, conn_mgr_router_recv_handler);
|
||||
mgr->bg_ping_timer = uasync_set_timeout(instance->ua, CONN_MGR_BG_PING_INTERVAL_TB, mgr, cm_bg_ping_timer_cb, "conn_mgr_bg_ping");
|
||||
mgr->candidate_ping_timer = uasync_set_timeout(instance->ua, CONN_MGR_CANDIDATE_PING_TB, mgr, cm_candidate_ping_timer_cb, "conn_mgr_cand_ping");
|
||||
mgr->bg_ping_cycle_start_tb = get_time_tb();
|
||||
mgr->initialized = 1;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: initialized, bg_ping started");
|
||||
return mgr;
|
||||
}
|
||||
|
||||
void conn_mgr_destroy(struct CONN_MGR* mgr) {
|
||||
if (!mgr) return;
|
||||
if (mgr->bg_ping_timer) { uasync_cancel_timeout(mgr->instance->ua, mgr->bg_ping_timer); mgr->bg_ping_timer = NULL; }
|
||||
if (mgr->candidate_ping_timer) { uasync_cancel_timeout(mgr->instance->ua, mgr->candidate_ping_timer); mgr->candidate_ping_timer = NULL; }
|
||||
etcp_router_unbind(mgr->instance, ETCP_ID_CONN_MGR);
|
||||
for (size_t i = 0; i < mgr->entry_count; i++) cm_entry_destroy(&mgr->entries[i]);
|
||||
u_free(mgr->entries);
|
||||
mgr->initialized = 0;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: destroyed, entries=%zu", (size_t)mgr->entry_count);
|
||||
u_free(mgr);
|
||||
}
|
||||
|
||||
static void cm_entry_destroy(struct CONN_MGR_ENTRY* entry) {
|
||||
if (!entry || entry->state == CONN_MGR_STATE_DISCONNECTED) return;
|
||||
if (entry->idle_timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->idle_timer); entry->idle_timer = NULL; }
|
||||
if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; }
|
||||
entry->state = CONN_MGR_STATE_DISCONNECTED;
|
||||
entry->conn_type = CONN_TYPE_NONE;
|
||||
}
|
||||
|
||||
static struct CONN_MGR_ENTRY* cm_ensure_entry(struct CONN_MGR* mgr, uint64_t node_id) {
|
||||
struct CONN_MGR_ENTRY* e = cm_find_entry(mgr, node_id);
|
||||
if (e) return e;
|
||||
if (mgr->entry_count >= mgr->entry_capacity) {
|
||||
size_t new_cap = mgr->entry_capacity * 2;
|
||||
struct CONN_MGR_ENTRY* new_e = u_realloc(mgr->entries, new_cap * sizeof(struct CONN_MGR_ENTRY));
|
||||
if (!new_e) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: realloc entries failed"); return NULL; }
|
||||
memset(new_e + mgr->entry_capacity, 0, (new_cap - mgr->entry_capacity) * sizeof(struct CONN_MGR_ENTRY));
|
||||
mgr->entries = new_e;
|
||||
mgr->entry_capacity = new_cap;
|
||||
}
|
||||
struct CONN_MGR_ENTRY* new_entry = &mgr->entries[mgr->entry_count++];
|
||||
memset(new_entry, 0, sizeof(*new_entry));
|
||||
new_entry->node_id = node_id;
|
||||
new_entry->mgr = mgr;
|
||||
return new_entry;
|
||||
}
|
||||
|
||||
static struct CONN_MGR_ENTRY* cm_find_entry(struct CONN_MGR* mgr, uint64_t node_id) {
|
||||
for (size_t i = 0; i < mgr->entry_count; i++)
|
||||
if (mgr->entries[i].node_id == node_id && mgr->entries[i].state != CONN_MGR_STATE_DISCONNECTED)
|
||||
return &mgr->entries[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void cm_deliver_result(struct CONN_MGR_ENTRY* entry, int result) {
|
||||
struct cm_cb_node* node = entry->cb_list;
|
||||
while (node) { struct cm_cb_node* next = node->next; node->cb(result, entry->node_id, node->arg); u_free(node); node = next; }
|
||||
entry->cb_list = NULL;
|
||||
}
|
||||
|
||||
static void cm_update_nodeinfo(struct CONN_MGR_ENTRY* entry) {
|
||||
struct ROUTE_BGP* bgp = entry->mgr->instance->bgp;
|
||||
if (!bgp) return;
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(bgp, entry->node_id);
|
||||
if (!nq) return;
|
||||
nq->conn_mgr_type = entry->conn_type;
|
||||
memcpy(nq->conn_mgr_intermediaries, entry->intermediaries, sizeof(entry->intermediaries));
|
||||
nq->conn_mgr_intermediariy_count = entry->intermediariy_count;
|
||||
}
|
||||
|
||||
static void cm_clear_nodeinfo(struct CONN_MGR* mgr, uint64_t node_id) {
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(mgr->instance->bgp, node_id);
|
||||
if (!nq) return;
|
||||
nq->conn_mgr_type = CONN_TYPE_NONE;
|
||||
nq->conn_mgr_intermediariy_count = 0;
|
||||
}
|
||||
|
||||
int conn_mgr_connect_node(struct CONN_MGR* mgr, uint64_t node_id, uint32_t idle_timeout_ms,
|
||||
conn_mgr_connect_callback_t cb, void* cb_arg) {
|
||||
if (!mgr) return CONN_MGR_ERR_INTERNAL;
|
||||
struct ROUTE_BGP* bgp = mgr->instance->bgp;
|
||||
if (!bgp) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_connect: BGP not initialized"); return CONN_MGR_ERR_INTERNAL; }
|
||||
struct NODEINFO_Q* target = nodeinfo_find_by_id(bgp, node_id);
|
||||
if (!target) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_connect: node 0x%016llx not found", (unsigned long long)node_id); return CONN_MGR_ERR_NOT_FOUND; }
|
||||
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id);
|
||||
if (entry && entry->state == CONN_MGR_STATE_CONNECTED) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_connect: node 0x%016llx already connected", (unsigned long long)node_id);
|
||||
return CONN_MGR_ERR_ALREADY_CONNECTED;
|
||||
}
|
||||
if (entry && entry->state == CONN_MGR_STATE_CONNECTING) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr_connect: node 0x%016llx already connecting, adding cb", (unsigned long long)node_id);
|
||||
struct cm_cb_node* cn = u_calloc(1, sizeof(struct cm_cb_node));
|
||||
if (cn) { cn->cb = cb; cn->arg = cb_arg; cn->next = entry->cb_list; entry->cb_list = cn; }
|
||||
return CONN_MGR_OK;
|
||||
}
|
||||
struct ETCP_CONN* existing = route_bgp_find_conn_for_node(bgp, node_id);
|
||||
if (existing && existing->peer_node_id == node_id && existing->links) {
|
||||
struct ETCP_LINK* l = existing->links;
|
||||
while (l) { if (l->link_status && l->initialized) break; l = l->next; }
|
||||
if (l) {
|
||||
entry = cm_ensure_entry(mgr, node_id);
|
||||
if (!entry) return CONN_MGR_ERR_INTERNAL;
|
||||
entry->state = CONN_MGR_STATE_CONNECTED;
|
||||
entry->conn_type = CONN_TYPE_DIRECT;
|
||||
entry->idle_timeout_ms = idle_timeout_ms;
|
||||
entry->alien = target->alien;
|
||||
entry->last_traffic_tb = get_time_tb();
|
||||
{ struct cm_cb_node* cn = u_calloc(1, sizeof(struct cm_cb_node));
|
||||
if (cn) { cn->cb = cb; cn->arg = cb_arg; entry->cb_list = cn; } }
|
||||
cm_update_nodeinfo(entry);
|
||||
cm_deliver_result(entry, CONN_MGR_OK);
|
||||
return CONN_MGR_OK;
|
||||
}
|
||||
}
|
||||
entry = cm_ensure_entry(mgr, node_id);
|
||||
if (!entry) return CONN_MGR_ERR_INTERNAL;
|
||||
entry->state = CONN_MGR_STATE_CONNECTING;
|
||||
entry->alien = target->alien;
|
||||
{ struct cm_cb_node* cn = u_calloc(1, sizeof(struct cm_cb_node));
|
||||
if (cn) { cn->cb = cb; cn->arg = cb_arg; cn->next = entry->cb_list; entry->cb_list = cn; } }
|
||||
entry->idle_timeout_ms = idle_timeout_ms;
|
||||
entry->last_traffic_tb = get_time_tb();
|
||||
entry->local_scan_state = CM_TRY_NONE;
|
||||
entry->main_connect_state = CM_TRY_NONE;
|
||||
entry->main.phase = 0;
|
||||
entry->main.timer = NULL;
|
||||
entry->main.request_id = 0;
|
||||
int has_our_local = cm_has_local_addr(bgp->local_node);
|
||||
int has_target_local = cm_has_local_addr(target);
|
||||
if (has_our_local && has_target_local) { entry->local_scan_state = CM_TRY_PENDING; cm_start_local_scan(entry); }
|
||||
entry->main_connect_state = CM_TRY_PENDING;
|
||||
cm_start_phase_direct(entry);
|
||||
return CONN_MGR_OK;
|
||||
}
|
||||
|
||||
int conn_mgr_disconnect_node(struct CONN_MGR* mgr, uint64_t node_id) {
|
||||
if (!mgr) return CONN_MGR_ERR_INTERNAL;
|
||||
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id);
|
||||
if (!entry) return CONN_MGR_ERR_NOT_FOUND;
|
||||
struct CONN_MGR_DISCONNECT pkt;
|
||||
pkt.cmd = ETCP_ID_CONN_MGR;
|
||||
pkt.subcmd = CONN_MGR_SUBCMD_DISCONNECT;
|
||||
pkt.node_id = node_id;
|
||||
struct ll_entry* qe = queue_entry_new(sizeof(pkt));
|
||||
if (qe) { memcpy(qe->data, &pkt, sizeof(pkt)); etcp_route_send(mgr->instance, node_id, qe, 1); }
|
||||
cm_clear_nodeinfo(mgr, node_id);
|
||||
cm_entry_destroy(entry);
|
||||
entry->state = CONN_MGR_STATE_DISCONNECTED;
|
||||
entry->conn_type = CONN_TYPE_NONE;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: disconnected node 0x%016llx", (unsigned long long)node_id);
|
||||
return CONN_MGR_OK;
|
||||
}
|
||||
|
||||
int conn_mgr_set_idle_timeout(struct CONN_MGR* mgr, uint64_t node_id, uint32_t timeout_ms) {
|
||||
if (!mgr) return CONN_MGR_ERR_INTERNAL;
|
||||
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id);
|
||||
if (!entry) return CONN_MGR_ERR_NOT_FOUND;
|
||||
entry->idle_timeout_ms = timeout_ms;
|
||||
if (entry->state == CONN_MGR_STATE_CONNECTED && timeout_ms > 0 && !entry->idle_timer)
|
||||
entry->idle_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_IDLE_CHECK_INTERVAL_TB, entry, cm_idle_timer_cb, "conn_mgr_idle");
|
||||
if (timeout_ms == 0 && entry->idle_timer) { uasync_cancel_timeout(mgr->instance->ua, entry->idle_timer); entry->idle_timer = NULL; }
|
||||
return CONN_MGR_OK;
|
||||
}
|
||||
|
||||
int conn_mgr_get_status(struct CONN_MGR* mgr, uint64_t node_id, uint8_t* out_state, uint8_t* out_conn_type) {
|
||||
if (!mgr || !out_state || !out_conn_type) return CONN_MGR_ERR_INTERNAL;
|
||||
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id);
|
||||
if (!entry) { *out_state = CONN_MGR_STATE_DISCONNECTED; *out_conn_type = CONN_TYPE_NONE; return CONN_MGR_OK; }
|
||||
*out_state = entry->state;
|
||||
*out_conn_type = entry->conn_type;
|
||||
return CONN_MGR_OK;
|
||||
}
|
||||
|
||||
int conn_mgr_add_alien_node(struct CONN_MGR* mgr, const uint8_t* nodeinfo_data, size_t len) {
|
||||
if (!mgr || !nodeinfo_data || len < sizeof(struct NODEINFO)) return CONN_MGR_ERR_INTERNAL;
|
||||
struct ROUTE_BGP* bgp = mgr->instance->bgp;
|
||||
if (!bgp) return CONN_MGR_ERR_INTERNAL;
|
||||
const struct NODEINFO* ni = (const struct NODEINFO*)nodeinfo_data;
|
||||
uint64_t node_id = ni->node_id;
|
||||
struct NODEINFO_Q* existing = nodeinfo_find_by_id(bgp, node_id);
|
||||
if (existing) { DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: alien node 0x%016llx already exists, ignoring", (unsigned long long)node_id); return CONN_MGR_OK; }
|
||||
size_t dyn_sz = len - sizeof(struct NODEINFO);
|
||||
size_t data_sz = sizeof(struct NODEINFO_Q) - sizeof(struct ll_entry) + dyn_sz + 8;
|
||||
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)queue_entry_new(data_sz);
|
||||
if (!nq) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: alien node alloc failed"); return CONN_MGR_ERR_INTERNAL; }
|
||||
memcpy(&nq->node, nodeinfo_data, len);
|
||||
nq->alien = 1;
|
||||
nq->dirty = 0;
|
||||
nq->last_ver = ni->ver;
|
||||
nq->connectivity.ping_req_time = 0;
|
||||
if (bgp->nodes) queue_data_put_with_index(bgp->nodes, &nq->ll);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: added alien node 0x%016llx", (unsigned long long)node_id);
|
||||
return CONN_MGR_OK;
|
||||
}
|
||||
|
||||
int conn_mgr_send(struct CONN_MGR* mgr, uint64_t node_id, struct ll_entry* entry) {
|
||||
if (!mgr || !entry) return -1;
|
||||
struct CONN_MGR_ENTRY* e = cm_find_entry(mgr, node_id);
|
||||
if (!e || e->state != CONN_MGR_STATE_CONNECTED) { queue_entry_free(entry); return -1; }
|
||||
e->last_traffic_tb = get_time_tb();
|
||||
struct ETCP_ROUTER_CONN* rconn = etcp_router_conn_get(mgr->instance, node_id, ETCP_ID_CONN_MGR);
|
||||
if (!rconn) { queue_entry_free(entry); return -1; }
|
||||
int ret = etcp_router_conn_send(rconn, entry->dgram, entry->len);
|
||||
queue_entry_free(entry);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void conn_mgr_update_best_candidates(struct CONN_MGR* mgr, uint64_t node_id, uint16_t rtt) {
|
||||
if (!mgr || node_id == mgr->instance->node_id || rtt == 0) return;
|
||||
for (uint8_t i = 0; i < mgr->best_candidate_count; i++) {
|
||||
if (mgr->best_candidates[i].node_id == node_id) {
|
||||
mgr->best_candidates[i].rtt = rtt;
|
||||
for (uint8_t j = i; j > 0 && mgr->best_candidates[j].rtt < mgr->best_candidates[j-1].rtt; j--) {
|
||||
struct CONN_MGR_CANDIDATE tmp = mgr->best_candidates[j];
|
||||
mgr->best_candidates[j] = mgr->best_candidates[j-1];
|
||||
mgr->best_candidates[j-1] = tmp;
|
||||
}
|
||||
for (uint8_t j = i; j + 1 < mgr->best_candidate_count && mgr->best_candidates[j].rtt > mgr->best_candidates[j+1].rtt; j++) {
|
||||
struct CONN_MGR_CANDIDATE tmp = mgr->best_candidates[j];
|
||||
mgr->best_candidates[j] = mgr->best_candidates[j+1];
|
||||
mgr->best_candidates[j+1] = tmp;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (mgr->best_candidate_count < CONN_MGR_MAX_CANDIDATES) {
|
||||
mgr->best_candidates[mgr->best_candidate_count].node_id = node_id;
|
||||
mgr->best_candidates[mgr->best_candidate_count].rtt = rtt;
|
||||
mgr->best_candidate_count++;
|
||||
} else if (rtt < mgr->best_candidates[CONN_MGR_MAX_CANDIDATES - 1].rtt) {
|
||||
mgr->best_candidates[CONN_MGR_MAX_CANDIDATES - 1].node_id = node_id;
|
||||
mgr->best_candidates[CONN_MGR_MAX_CANDIDATES - 1].rtt = rtt;
|
||||
} else return;
|
||||
for (uint8_t i = mgr->best_candidate_count - 1; i > 0; i--) {
|
||||
if (mgr->best_candidates[i].rtt >= mgr->best_candidates[i-1].rtt) break;
|
||||
struct CONN_MGR_CANDIDATE tmp = mgr->best_candidates[i];
|
||||
mgr->best_candidates[i] = mgr->best_candidates[i-1];
|
||||
mgr->best_candidates[i-1] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
static void cm_idle_timer_cb(void* arg) {
|
||||
struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg;
|
||||
struct CONN_MGR* mgr = entry->mgr;
|
||||
uint64_t now_tb = get_time_tb();
|
||||
if (entry->state != CONN_MGR_STATE_CONNECTED || entry->idle_timeout_ms == 0) return;
|
||||
uint64_t idle = now_tb - entry->last_traffic_tb;
|
||||
if (idle > (uint64_t)entry->idle_timeout_ms * 10) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: node 0x%016llx idle %llums, disconnecting",
|
||||
(unsigned long long)entry->node_id, (unsigned long long)(idle / 10));
|
||||
conn_mgr_disconnect_node(mgr, entry->node_id);
|
||||
return;
|
||||
}
|
||||
entry->idle_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_IDLE_CHECK_INTERVAL_TB, entry, cm_idle_timer_cb, "conn_mgr_idle");
|
||||
}
|
||||
|
||||
static int cm_nat_compatible(struct ETCP_SOCKET* our, uint8_t target_meta_type) {
|
||||
uint8_t tt = target_meta_type;
|
||||
if (tt == NAT_VERIFIED_DIRECT) tt = CFG_SERVER_TYPE_PUBLIC;
|
||||
else if (tt >= NAT_VERIFIED_UNKNOWN) tt = CFG_SERVER_TYPE_PUBLIC;
|
||||
uint8_t ot = our->type;
|
||||
if (ot == CFG_SERVER_TYPE_PUBLIC) return 1;
|
||||
if (ot == CFG_SERVER_TYPE_NAT && our->nat_type == NAT_TYPE_EIM && tt == CFG_SERVER_TYPE_PUBLIC) return 1;
|
||||
if (ot == CFG_SERVER_TYPE_NAT && our->nat_type == NAT_TYPE_EIM && tt == CFG_SERVER_TYPE_NAT) return 1;
|
||||
if (ot == CFG_SERVER_TYPE_PRIVATE && tt == CFG_SERVER_TYPE_PRIVATE) return 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint16_t cm_get_node_min_rtt(struct NODEINFO_Q* nq) {
|
||||
uint16_t rtt = 0xFFFF;
|
||||
if (nq->connectivity.interface_status == PROBE_RESULT_REACHABLE && nq->connectivity.interface_min_rtt < rtt)
|
||||
rtt = nq->connectivity.interface_min_rtt;
|
||||
if (nq->connectivity.nat_status == PROBE_RESULT_REACHABLE && nq->connectivity.nat_min_rtt < rtt)
|
||||
rtt = nq->connectivity.nat_min_rtt;
|
||||
if (nq->connectivity.real_status == PROBE_RESULT_REACHABLE && nq->connectivity.real_min_rtt < rtt)
|
||||
rtt = nq->connectivity.real_min_rtt;
|
||||
return rtt;
|
||||
}
|
||||
|
||||
static uint64_t cm_get_node_max_probe_time(struct NODEINFO_Q* nq) {
|
||||
uint64_t t = nq->connectivity.interface_probe_time;
|
||||
if (nq->connectivity.nat_probe_time > t) t = nq->connectivity.nat_probe_time;
|
||||
if (nq->connectivity.real_probe_time > t) t = nq->connectivity.real_probe_time;
|
||||
return t;
|
||||
}
|
||||
|
||||
static int cm_is_rtt_fresh(struct NODEINFO_Q* nq, uint64_t now_tb) {
|
||||
uint64_t last = cm_get_node_max_probe_time(nq);
|
||||
return (last > 0 && (now_tb - last) < (uint64_t)(CONN_MGR_CANDIDATE_CACHE_MS * 10));
|
||||
}
|
||||
|
||||
static int cm_has_direct_ip(struct NODEINFO_Q* nq) {
|
||||
if (!nq) return 0;
|
||||
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
|
||||
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
|
||||
int addr_count = get_node_v4_addrs(nq, &addrs);
|
||||
int meta_count = get_node_v4_sockets_meta(nq, &metas);
|
||||
for (int i = 0; i < addr_count; i++) {
|
||||
if (addrs[i].type == ADDR_TYPE_REAL || addrs[i].type == ADDR_TYPE_NAT) {
|
||||
for (int j = 0; j < meta_count; j++) {
|
||||
if (metas[j].id == addrs[i].socket_id &&
|
||||
(metas[j].type == CFG_SERVER_TYPE_PUBLIC || metas[j].type == NAT_VERIFIED_EIM || metas[j].type == NAT_VERIFIED_DIRECT))
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cm_has_local_addr(struct NODEINFO_Q* nq) {
|
||||
if (!nq) return 0;
|
||||
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
|
||||
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
|
||||
int addr_count = get_node_v4_addrs(nq, &addrs);
|
||||
int meta_count = get_node_v4_sockets_meta(nq, &metas);
|
||||
for (int i = 0; i < addr_count; i++) {
|
||||
if (addrs[i].type == ADDR_TYPE_INTERFACE) {
|
||||
for (int j = 0; j < meta_count; j++) {
|
||||
if (metas[j].id == addrs[i].socket_id && metas[j].type == CFG_SERVER_TYPE_PRIVATE)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cm_ping_cb(int success, uint16_t rtt, void* arg, uint64_t nonce,
|
||||
const uint8_t* resp_data, size_t resp_data_len);
|
||||
static void cm_direct_timeout_cb(void* arg);
|
||||
|
||||
struct cm_ping_ctx {
|
||||
struct CONN_MGR_ENTRY* entry;
|
||||
struct sockaddr_storage addr;
|
||||
struct ETCP_SOCKET* sock;
|
||||
uint8_t phase; /* 0=local_scan, 1=direct */
|
||||
uint8_t attempt;
|
||||
};
|
||||
|
||||
static void cm_start_local_scan(struct CONN_MGR_ENTRY* entry) {
|
||||
struct ROUTE_BGP* bgp = entry->mgr->instance->bgp;
|
||||
struct NODEINFO_Q* target = nodeinfo_find_by_id(bgp, entry->node_id);
|
||||
if (!target) { entry->local_scan_state = CM_TRY_FAILED; return; }
|
||||
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
|
||||
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
|
||||
int addr_count = get_node_v4_addrs(target, &addrs);
|
||||
int meta_count = get_node_v4_sockets_meta(target, &metas);
|
||||
for (int i = 0; i < addr_count; i++) {
|
||||
if (addrs[i].type != ADDR_TYPE_INTERFACE) continue;
|
||||
for (int j = 0; j < meta_count; j++) {
|
||||
if (metas[j].id != addrs[i].socket_id || metas[j].type != CFG_SERVER_TYPE_PRIVATE) continue;
|
||||
struct ETCP_SOCKET* s = entry->mgr->instance->etcp_sockets;
|
||||
while (s) {
|
||||
if (s->type == CFG_SERVER_TYPE_PRIVATE && s->local_addr.ss_family == AF_INET) {
|
||||
struct sockaddr_in sin;
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
memcpy(&sin.sin_addr.s_addr, addrs[i].addr, 4);
|
||||
sin.sin_port = htons(addrs[i].port);
|
||||
struct sockaddr_storage sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
memcpy(&sa, &sin, sizeof(sin));
|
||||
struct cm_ping_ctx* ctx = u_calloc(1, sizeof(struct cm_ping_ctx));
|
||||
if (!ctx) continue;
|
||||
ctx->entry = entry;
|
||||
ctx->addr = sa;
|
||||
ctx->sock = s;
|
||||
ctx->phase = 0;
|
||||
ctx->attempt = 0;
|
||||
etcp_send_ping_to_socket(entry->mgr->instance, s, target->node.public_key, &sa,
|
||||
CONN_MGR_LOCAL_SCAN_TIMEOUT_MS, cm_ping_cb, ctx, NULL, 0);
|
||||
return;
|
||||
}
|
||||
s = s->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
entry->local_scan_state = CM_TRY_FAILED;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: local scan no candidates for 0x%016llx", (unsigned long long)entry->node_id);
|
||||
}
|
||||
|
||||
static void cm_start_phase_direct(struct CONN_MGR_ENTRY* entry) {
|
||||
if (entry->main_connect_state == CM_TRY_OK) return;
|
||||
if (entry->local_scan_state == CM_TRY_OK) return;
|
||||
struct ROUTE_BGP* bgp = entry->mgr->instance->bgp;
|
||||
struct NODEINFO_Q* target = nodeinfo_find_by_id(bgp, entry->node_id);
|
||||
if (!target) { cm_deliver_result(entry, CONN_MGR_ERR_NOT_FOUND); return; }
|
||||
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
|
||||
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
|
||||
int addr_count = get_node_v4_addrs(target, &addrs);
|
||||
int meta_count = get_node_v4_sockets_meta(target, &metas);
|
||||
uint8_t priority_order[] = { ADDR_TYPE_REAL, ADDR_TYPE_NAT, ADDR_TYPE_INTERFACE };
|
||||
for (int pri = 0; pri < 3; pri++) {
|
||||
for (int i = 0; i < addr_count; i++) {
|
||||
if (addrs[i].type != priority_order[pri]) continue;
|
||||
for (int j = 0; j < meta_count; j++) {
|
||||
if (metas[j].id != addrs[i].socket_id) continue;
|
||||
struct ETCP_SOCKET* s = entry->mgr->instance->etcp_sockets;
|
||||
while (s) {
|
||||
int compat = cm_nat_compatible(s, metas[j].type);
|
||||
if (compat && s->local_addr.ss_family == AF_INET) {
|
||||
struct sockaddr_in sin;
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
memcpy(&sin.sin_addr.s_addr, addrs[i].addr, 4);
|
||||
sin.sin_port = htons(addrs[i].port);
|
||||
struct sockaddr_storage sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
memcpy(&sa, &sin, sizeof(sin));
|
||||
struct ETCP_CONN* conn = etcp_connection_create(entry->mgr->instance, NULL);
|
||||
if (conn) {
|
||||
etcp_conn_set_ready_cbk(conn, cm_direct_ready_cb, entry);
|
||||
sc_set_peer_public_key(&conn->crypto_ctx, target->node.public_key, 0);
|
||||
struct ETCP_LINK* link = etcp_link_new(conn, s, &sa, 0);
|
||||
if (link) {
|
||||
entry->main.timer = uasync_set_timeout(entry->mgr->instance->ua,
|
||||
CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS * 10, entry, cm_direct_timeout_cb, "conn_mgr_direct");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 1 (direct) started INIT to 0x%016llx",
|
||||
(unsigned long long)entry->node_id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
s = s->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 1 (direct) no compatible pairs for 0x%016llx",
|
||||
(unsigned long long)entry->node_id);
|
||||
int has_our = cm_has_direct_ip(bgp->local_node);
|
||||
int has_target = cm_has_direct_ip(target);
|
||||
if (has_our && !has_target) { cm_start_phase_reverse(entry); return; }
|
||||
cm_start_phase_indirect(entry);
|
||||
}
|
||||
|
||||
static void cm_direct_timeout_cb(void* arg) {
|
||||
struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg;
|
||||
entry->main.timer = NULL;
|
||||
struct ROUTE_BGP* bgp = entry->mgr->instance->bgp;
|
||||
struct NODEINFO_Q* target = nodeinfo_find_by_id(bgp, entry->node_id);
|
||||
int has_our = cm_has_direct_ip(bgp->local_node);
|
||||
int has_target = target ? cm_has_direct_ip(target) : 0;
|
||||
if (entry->local_scan_state != CM_TRY_OK) {
|
||||
if (has_our && !has_target) { cm_start_phase_reverse(entry); return; }
|
||||
cm_start_phase_indirect(entry);
|
||||
return;
|
||||
}
|
||||
if (entry->local_scan_state == CM_TRY_FAILED && entry->main_connect_state == CM_TRY_FAILED)
|
||||
cm_deliver_result(entry, CONN_MGR_ERR_UNREACHABLE);
|
||||
}
|
||||
|
||||
static void cm_direct_ready_cb(struct ETCP_CONN* conn, void* arg) {
|
||||
struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg;
|
||||
if (!conn || conn->peer_node_id != entry->node_id) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: direct ready cb peer mismatch conn=%p peer=0x%llx entry=0x%llx",
|
||||
(void*)conn, (unsigned long long)conn->peer_node_id, (unsigned long long)entry->node_id);
|
||||
return;
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: direct ready for 0x%016llx", (unsigned long long)entry->node_id);
|
||||
if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; }
|
||||
entry->main_connect_state = CM_TRY_OK;
|
||||
entry->conn_type = CONN_TYPE_DIRECT;
|
||||
entry->state = CONN_MGR_STATE_CONNECTED;
|
||||
cm_update_nodeinfo(entry);
|
||||
cm_deliver_result(entry, CONN_MGR_OK);
|
||||
}
|
||||
|
||||
static void cm_ping_cb(int success, uint16_t rtt, void* arg, uint64_t nonce,
|
||||
const uint8_t* resp_data, size_t resp_data_len) {
|
||||
struct cm_ping_ctx* ctx = (struct cm_ping_ctx*)arg;
|
||||
struct CONN_MGR_ENTRY* entry = ctx->entry;
|
||||
(void)nonce; (void)resp_data; (void)resp_data_len;
|
||||
if (entry->main_connect_state == CM_TRY_OK) { u_free(ctx); return; }
|
||||
if (success && ctx->phase == 0) {
|
||||
entry->local_scan_state = CM_TRY_OK;
|
||||
entry->conn_type = CONN_TYPE_DIRECT;
|
||||
entry->state = CONN_MGR_STATE_CONNECTED;
|
||||
if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: local scan OK for 0x%016llx rtt=%u", (unsigned long long)entry->node_id, rtt);
|
||||
cm_update_nodeinfo(entry);
|
||||
cm_deliver_result(entry, CONN_MGR_OK);
|
||||
u_free(ctx);
|
||||
return;
|
||||
}
|
||||
ctx->attempt++;
|
||||
if (ctx->attempt < CONN_MGR_LOCAL_SCAN_ATTEMPTS && ctx->phase == 0) {
|
||||
etcp_send_ping_to_socket(entry->mgr->instance, ctx->sock,
|
||||
nodeinfo_find_by_id(entry->mgr->instance->bgp, entry->node_id)->node.public_key,
|
||||
&ctx->addr, CONN_MGR_LOCAL_SCAN_TIMEOUT_MS, cm_ping_cb, ctx, NULL, 0);
|
||||
return;
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: %s failed for 0x%016llx after %d attempts",
|
||||
ctx->phase == 0 ? "local scan" : "direct", (unsigned long long)entry->node_id, ctx->attempt);
|
||||
if (ctx->phase == 0) entry->local_scan_state = CM_TRY_FAILED;
|
||||
u_free(ctx);
|
||||
}
|
||||
|
||||
static void cm_start_phase_reverse(struct CONN_MGR_ENTRY* entry) {
|
||||
struct ROUTE_BGP* bgp = entry->mgr->instance->bgp;
|
||||
struct NODEINFO_Q* local = bgp->local_node;
|
||||
if (!local) { cm_deliver_result(entry, CONN_MGR_ERR_INTERNAL); return; }
|
||||
uint32_t req_id = ++entry->mgr->next_request_id;
|
||||
entry->main.request_id = req_id;
|
||||
entry->main.phase = 2;
|
||||
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
|
||||
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
|
||||
int addr_count = get_node_v4_addrs(local, &addrs);
|
||||
int meta_count = get_node_v4_sockets_meta(local, &metas);
|
||||
uint8_t direct_count = 0;
|
||||
struct { uint8_t type; uint8_t ip[4]; uint16_t port; uint8_t socket_id; } out_addrs[8];
|
||||
for (int i = 0; i < addr_count && direct_count < 8; i++) {
|
||||
if (addrs[i].type != ADDR_TYPE_REAL && addrs[i].type != ADDR_TYPE_NAT) continue;
|
||||
for (int j = 0; j < meta_count; j++) {
|
||||
if (metas[j].id == addrs[i].socket_id &&
|
||||
(metas[j].type == CFG_SERVER_TYPE_PUBLIC || metas[j].type == NAT_VERIFIED_EIM || metas[j].type == NAT_VERIFIED_DIRECT)) {
|
||||
out_addrs[direct_count].type = addrs[i].type;
|
||||
memcpy(out_addrs[direct_count].ip, addrs[i].addr, 4);
|
||||
out_addrs[direct_count].port = htons(addrs[i].port);
|
||||
out_addrs[direct_count].socket_id = addrs[i].socket_id;
|
||||
direct_count++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (direct_count == 0) { cm_start_phase_indirect(entry); return; }
|
||||
size_t pkt_size = CONN_MGR_DIRECT_REQ_HDR_SIZE + (size_t)direct_count * 8;
|
||||
uint8_t* pkt = u_malloc(pkt_size);
|
||||
if (!pkt) { cm_deliver_result(entry, CONN_MGR_ERR_INTERNAL); return; }
|
||||
struct CONN_MGR_DIRECT_REQ* req = (struct CONN_MGR_DIRECT_REQ*)pkt;
|
||||
req->cmd = ETCP_ID_CONN_MGR;
|
||||
req->subcmd = CONN_MGR_SUBCMD_DIRECT_REQ;
|
||||
req->request_id = req_id;
|
||||
req->addr_count = direct_count;
|
||||
uint8_t* p = pkt + CONN_MGR_DIRECT_REQ_HDR_SIZE;
|
||||
for (uint8_t i = 0; i < direct_count; i++) {
|
||||
*p++ = out_addrs[i].type;
|
||||
memcpy(p, out_addrs[i].ip, 4); p += 4;
|
||||
memcpy(p, &out_addrs[i].port, 2); p += 2;
|
||||
*p++ = out_addrs[i].socket_id;
|
||||
}
|
||||
struct ll_entry* qe = queue_entry_new(pkt_size);
|
||||
if (!qe) { u_free(pkt); cm_deliver_result(entry, CONN_MGR_ERR_INTERNAL); return; }
|
||||
memcpy(qe->data, pkt, pkt_size);
|
||||
qe->len = (uint16_t)pkt_size;
|
||||
u_free(pkt);
|
||||
etcp_route_send(entry->mgr->instance, entry->node_id, qe, 1);
|
||||
|
||||
struct cm_reverse_pending* rp = u_calloc(1, sizeof(struct cm_reverse_pending));
|
||||
if (rp) { rp->request_id = req_id; rp->entry = entry; rp->next = entry->mgr->reverse_pending;
|
||||
entry->mgr->reverse_pending = rp; }
|
||||
entry->main.timer = uasync_set_timeout(entry->mgr->instance->ua, CONN_MGR_CONNECT_REVERSE_TIMEOUT_MS * 10,
|
||||
entry, cm_reverse_timeout_cb, "conn_mgr_reverse");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 2 (reverse) sent DIRECT_REQ to 0x%016llx with %u addrs",
|
||||
(unsigned long long)entry->node_id, direct_count);
|
||||
}
|
||||
|
||||
static void cm_reverse_timeout_cb(void* arg) {
|
||||
struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg;
|
||||
entry->main.timer = NULL;
|
||||
struct cm_reverse_pending** pp = &entry->mgr->reverse_pending;
|
||||
while (*pp) { if ((*pp)->entry == entry) { struct cm_reverse_pending* rp = *pp; *pp = rp->next; u_free(rp); break; } pp = &(*pp)->next; }
|
||||
cm_start_phase_indirect(entry);
|
||||
}
|
||||
|
||||
static void cm_reverse_ready_cb(struct ETCP_CONN* conn, void* arg) {
|
||||
struct cm_reverse_pending* rp = (struct cm_reverse_pending*)arg;
|
||||
struct CONN_MGR_ENTRY* entry = rp->entry;
|
||||
if (!conn || conn->peer_node_id != entry->node_id) { u_free(rp); return; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: reverse ready for 0x%016llx", (unsigned long long)entry->node_id);
|
||||
if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; }
|
||||
struct cm_reverse_pending** pp = &entry->mgr->reverse_pending;
|
||||
while (*pp) { if (*pp == rp) { *pp = rp->next; break; } pp = &(*pp)->next; }
|
||||
entry->main_connect_state = CM_TRY_OK;
|
||||
entry->conn_type = CONN_TYPE_REVERSE;
|
||||
entry->state = CONN_MGR_STATE_CONNECTED;
|
||||
cm_update_nodeinfo(entry);
|
||||
cm_deliver_result(entry, CONN_MGR_OK);
|
||||
u_free(rp);
|
||||
}
|
||||
|
||||
static int cm_start_phase_indirect(struct CONN_MGR_ENTRY* entry) {
|
||||
struct CONN_MGR* mgr = entry->mgr;
|
||||
if (mgr->best_candidate_count == 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 3 (indirect) no candidates for 0x%016llx",
|
||||
(unsigned long long)entry->node_id);
|
||||
return CONN_MGR_ERR_UNREACHABLE;
|
||||
}
|
||||
uint32_t req_id = ++mgr->next_request_id;
|
||||
entry->main.request_id = req_id;
|
||||
entry->main.phase = 3;
|
||||
struct CONN_MGR_INTERM_EXCHANGE_REQ req;
|
||||
memset(&req, 0, sizeof(req));
|
||||
req.cmd = ETCP_ID_CONN_MGR;
|
||||
req.subcmd = CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ;
|
||||
req.request_id = req_id;
|
||||
req.candidate_count = mgr->best_candidate_count > 4 ? 4 : mgr->best_candidate_count;
|
||||
for (uint8_t i = 0; i < req.candidate_count; i++)
|
||||
req.candidates[i] = mgr->best_candidates[i];
|
||||
struct ll_entry* qe = queue_entry_new(sizeof(req));
|
||||
if (!qe) return CONN_MGR_ERR_INTERNAL;
|
||||
memcpy(qe->data, &req, sizeof(req));
|
||||
etcp_route_send(entry->mgr->instance, entry->node_id, qe, 1);
|
||||
entry->main.timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS * 10,
|
||||
entry, cm_exchange_timeout_cb, "conn_mgr_interm_exch");
|
||||
{
|
||||
struct cm_exchange_pending* ep = u_calloc(1, sizeof(struct cm_exchange_pending));
|
||||
if (ep) { ep->request_id = req_id; ep->entry = entry; ep->timeout_timer = entry->main.timer;
|
||||
ep->next = mgr->exchange_pending; mgr->exchange_pending = ep; }
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 3 (indirect) sent EXCHANGE_REQ to 0x%016llx with %u candidates",
|
||||
(unsigned long long)entry->node_id, req.candidate_count);
|
||||
return CONN_MGR_OK;
|
||||
}
|
||||
|
||||
static void cm_exchange_timeout_cb(void* arg) {
|
||||
struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg;
|
||||
entry->main.timer = NULL;
|
||||
struct cm_exchange_pending** pp = &entry->mgr->exchange_pending;
|
||||
while (*pp) { if ((*pp)->entry == entry) { struct cm_exchange_pending* ep = *pp; *pp = ep->next; u_free(ep); break; } pp = &(*pp)->next; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: exchange timeout for 0x%016llx", (unsigned long long)entry->node_id);
|
||||
cm_deliver_result(entry, CONN_MGR_ERR_TIMEOUT);
|
||||
}
|
||||
|
||||
static void cm_compute_intermediaries(struct CONN_MGR_ENTRY* entry, struct CONN_MGR_INTERM_EXCHANGE_RESP* resp) {
|
||||
struct { uint64_t node_id; uint64_t total_rtt; } all[8];
|
||||
uint8_t count = 0;
|
||||
|
||||
for (uint8_t i = 0; i < resp->your_count && count < 8; i++) {
|
||||
uint64_t id = resp->your_candidates[i].node_id;
|
||||
uint16_t our_rtt = 0;
|
||||
for (uint8_t j = 0; j < CONN_MGR_MAX_CANDIDATES; j++) {
|
||||
if (entry->mgr->best_candidates[j].node_id == id) { our_rtt = entry->mgr->best_candidates[j].rtt; break; }
|
||||
}
|
||||
all[count].node_id = id;
|
||||
all[count].total_rtt = (uint64_t)our_rtt + resp->your_candidates[i].rtt;
|
||||
count++;
|
||||
}
|
||||
for (uint8_t i = 0; i < resp->my_count && count < 8; i++) {
|
||||
uint64_t id = resp->my_candidates[i].node_id;
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(entry->mgr->instance->bgp, id);
|
||||
uint16_t our_rtt = nq ? cm_get_node_min_rtt(nq) : 0;
|
||||
if (our_rtt == 0xFFFF) our_rtt = 0;
|
||||
all[count].node_id = id;
|
||||
all[count].total_rtt = (uint64_t)our_rtt + resp->my_candidates[i].rtt;
|
||||
count++;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < count; i++)
|
||||
for (uint8_t j = i + 1; j < count; j++)
|
||||
if (all[j].total_rtt < all[i].total_rtt) { typeof(all[0]) t = all[i]; all[i] = all[j]; all[j] = t; }
|
||||
|
||||
uint8_t sel = count > CONN_MGR_MAX_INTERMEDIARIES ? CONN_MGR_MAX_INTERMEDIARIES : count;
|
||||
for (uint8_t i = 0; i < sel; i++) entry->intermediaries[i] = all[i].node_id;
|
||||
entry->intermediariy_count = sel;
|
||||
entry->rr_idx = 0;
|
||||
|
||||
struct CONN_MGR_INTERM_SELECTED pkt;
|
||||
memset(&pkt, 0, sizeof(pkt));
|
||||
pkt.cmd = ETCP_ID_CONN_MGR;
|
||||
pkt.subcmd = CONN_MGR_SUBCMD_INTERM_SELECTED;
|
||||
pkt.request_id = 0;
|
||||
pkt.count = sel;
|
||||
for (uint8_t i = 0; i < sel; i++) {
|
||||
pkt.selected[i].node_id = all[i].node_id;
|
||||
pkt.selected[i].rtt = 0;
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(entry->mgr->instance->bgp, all[i].node_id);
|
||||
if (nq) pkt.selected[i].rtt = cm_get_node_min_rtt(nq);
|
||||
}
|
||||
struct ll_entry* qe = queue_entry_new(sizeof(pkt));
|
||||
if (qe) { memcpy(qe->data, &pkt, sizeof(pkt)); etcp_route_send(entry->mgr->instance, entry->node_id, qe, 1); }
|
||||
|
||||
entry->conn_type = CONN_TYPE_INDIRECT;
|
||||
entry->state = CONN_MGR_STATE_CONNECTED;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: indirect OK for 0x%016llx via %u intermediaries",
|
||||
(unsigned long long)entry->node_id, sel);
|
||||
cm_update_nodeinfo(entry);
|
||||
cm_deliver_result(entry, CONN_MGR_OK);
|
||||
}
|
||||
|
||||
static void cm_handle_interm_exchange_resp(struct CONN_MGR* mgr, const uint8_t* data, size_t len) {
|
||||
const struct CONN_MGR_INTERM_EXCHANGE_RESP* resp = (const struct CONN_MGR_INTERM_EXCHANGE_RESP*)data;
|
||||
size_t min_size = offsetof(struct CONN_MGR_INTERM_EXCHANGE_RESP, your_candidates);
|
||||
if (len < min_size) return;
|
||||
struct cm_exchange_pending* ep = mgr->exchange_pending;
|
||||
while (ep) { if (ep->request_id == resp->request_id) break; ep = ep->next; }
|
||||
if (!ep) return;
|
||||
|
||||
if (ep->entry->main.timer) { uasync_cancel_timeout(mgr->instance->ua, ep->entry->main.timer); ep->entry->main.timer = NULL; }
|
||||
|
||||
memcpy(&ep->cached_resp, resp, len < sizeof(ep->cached_resp) ? len : sizeof(ep->cached_resp));
|
||||
ep->resp_received = 1;
|
||||
ep->probes_done = 0;
|
||||
|
||||
uint8_t need_probe = 0;
|
||||
uint64_t now_tb = get_time_tb();
|
||||
for (uint8_t i = 0; i < ep->cached_resp.my_count && i < 4; i++) {
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(mgr->instance->bgp, ep->cached_resp.my_candidates[i].node_id);
|
||||
if (!nq || !cm_is_rtt_fresh(nq, now_tb)) { need_probe++; route_connectivity_probe_node(mgr->instance, nq); }
|
||||
}
|
||||
if (need_probe == 0) { ep->probes_done = 1; cm_compute_intermediaries(ep->entry, &ep->cached_resp);
|
||||
struct cm_exchange_pending** pp = &mgr->exchange_pending;
|
||||
while (*pp) { if (*pp == ep) { *pp = ep->next; break; } pp = &(*pp)->next; }
|
||||
u_free(ep); return; }
|
||||
uasync_set_timeout(mgr->instance->ua, 2000, ep, NULL, "cm_exch_probe");
|
||||
}
|
||||
|
||||
static void cm_handle_interm_selected(struct CONN_MGR* mgr, const uint8_t* data, size_t len) {
|
||||
const struct CONN_MGR_INTERM_SELECTED* sel = (const struct CONN_MGR_INTERM_SELECTED*)data;
|
||||
if (len < offsetof(struct CONN_MGR_INTERM_SELECTED, selected)) return;
|
||||
struct CONN_MGR_ENTRY* entry = NULL;
|
||||
for (size_t i = 0; i < mgr->entry_count; i++)
|
||||
if (mgr->entries[i].state == CONN_MGR_STATE_CONNECTING) { entry = &mgr->entries[i]; break; }
|
||||
if (!entry) return;
|
||||
uint8_t cnt = sel->count > CONN_MGR_MAX_INTERMEDIARIES ? CONN_MGR_MAX_INTERMEDIARIES : sel->count;
|
||||
for (uint8_t i = 0; i < cnt; i++) entry->intermediaries[i] = sel->selected[i].node_id;
|
||||
entry->intermediariy_count = cnt;
|
||||
entry->rr_idx = 0;
|
||||
entry->conn_type = CONN_TYPE_INDIRECT;
|
||||
entry->state = CONN_MGR_STATE_CONNECTED;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: got INTERM_SELECTED count=%u", cnt);
|
||||
}
|
||||
|
||||
static void conn_mgr_router_recv_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
if (!entry || !entry->dgram || entry->len < 3) return;
|
||||
uint8_t* dgram = entry->dgram + 1; // skip svc_id byte from router
|
||||
size_t len = entry->len - 1;
|
||||
uint8_t subcmd = dgram[1];
|
||||
struct CONN_MGR* mgr = conn->instance->conn_mgr;
|
||||
|
||||
if (mgr) {
|
||||
struct CONN_MGR_ENTRY* e = cm_find_entry(mgr, conn->peer_node_id);
|
||||
if (e && e->state == CONN_MGR_STATE_CONNECTED) e->last_traffic_tb = get_time_tb();
|
||||
}
|
||||
|
||||
switch (subcmd) {
|
||||
case CONN_MGR_SUBCMD_DIRECT_REQ:
|
||||
cm_handle_direct_req(conn, dgram, len);
|
||||
break;
|
||||
case CONN_MGR_SUBCMD_DIRECT_RESP:
|
||||
break;
|
||||
case CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ:
|
||||
if (len >= CONN_MGR_INTERM_EXCHANGE_REQ_SIZE)
|
||||
cm_handle_interm_exchange_req(conn, (struct CONN_MGR_INTERM_EXCHANGE_REQ*)dgram);
|
||||
break;
|
||||
case CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP:
|
||||
if (mgr) cm_handle_interm_exchange_resp(mgr, dgram, len);
|
||||
break;
|
||||
case CONN_MGR_SUBCMD_INTERM_SELECTED:
|
||||
if (mgr) cm_handle_interm_selected(mgr, dgram, len);
|
||||
break;
|
||||
case CONN_MGR_SUBCMD_DISCONNECT:
|
||||
if (len >= CONN_MGR_DISCONNECT_SIZE && mgr) {
|
||||
struct CONN_MGR_DISCONNECT* disc = (struct CONN_MGR_DISCONNECT*)dgram;
|
||||
cm_handle_disconnect(mgr, disc->node_id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void cm_handle_direct_req(struct ETCP_CONN* conn, const uint8_t* data, size_t len) {
|
||||
struct CONN_MGR* mgr = conn->instance->conn_mgr;
|
||||
if (!mgr) return;
|
||||
struct CONN_MGR_DIRECT_REQ* req = (struct CONN_MGR_DIRECT_REQ*)data;
|
||||
size_t expected = CONN_MGR_DIRECT_REQ_HDR_SIZE + (size_t)req->addr_count * 8;
|
||||
if (len < expected) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: DIRECT_REQ too short"); return; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: got DIRECT_REQ from 0x%016llx with %u addrs",
|
||||
(unsigned long long)conn->peer_node_id, req->addr_count);
|
||||
uint8_t* p = (uint8_t*)data + CONN_MGR_DIRECT_REQ_HDR_SIZE;
|
||||
uint64_t src_node_id = conn->peer_node_id;
|
||||
for (uint8_t i = 0; i < req->addr_count; i++) {
|
||||
uint8_t type = *p++;
|
||||
uint8_t ip[4]; memcpy(ip, p, 4); p += 4;
|
||||
uint16_t port; memcpy(&port, p, 2); p += 2;
|
||||
uint8_t sock_id = *p++;
|
||||
(void)sock_id; (void)type;
|
||||
struct ETCP_SOCKET* s = conn->instance->etcp_sockets;
|
||||
while (s) {
|
||||
if (s->type == CFG_SERVER_TYPE_PUBLIC && s->local_addr.ss_family == AF_INET) {
|
||||
struct sockaddr_in sin;
|
||||
memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET;
|
||||
memcpy(&sin.sin_addr.s_addr, ip, 4); sin.sin_port = port;
|
||||
struct sockaddr_storage sa;
|
||||
memset(&sa, 0, sizeof(sa)); memcpy(&sa, &sin, sizeof(sin));
|
||||
struct ETCP_CONN* new_conn = etcp_connection_create(conn->instance, NULL);
|
||||
if (new_conn) {
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(conn->instance->bgp, src_node_id);
|
||||
if (nq) sc_set_peer_public_key(&new_conn->crypto_ctx, nq->node.public_key, 0);
|
||||
struct cm_reverse_pending* rp = u_calloc(1, sizeof(struct cm_reverse_pending));
|
||||
if (rp) { rp->request_id = req->request_id; rp->entry = NULL; rp->next = mgr->reverse_pending;
|
||||
etcp_conn_set_ready_cbk(new_conn, cm_reverse_ready_cb, rp); mgr->reverse_pending = rp; }
|
||||
etcp_link_new(new_conn, s, &sa, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
s = s->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void cm_handle_interm_exchange_req(struct ETCP_CONN* conn,
|
||||
struct CONN_MGR_INTERM_EXCHANGE_REQ* req) {
|
||||
struct CONN_MGR* mgr = conn->instance->conn_mgr;
|
||||
if (!mgr) return;
|
||||
uint64_t now_tb = get_time_tb();
|
||||
for (uint8_t i = 0; i < req->candidate_count && i < 4; i++) {
|
||||
uint64_t cand_id = req->candidates[i].node_id;
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(mgr->instance->bgp, cand_id);
|
||||
if (nq && cm_is_rtt_fresh(nq, now_tb)) continue;
|
||||
route_connectivity_probe_node(mgr->instance, nq);
|
||||
}
|
||||
struct CONN_MGR_INTERM_EXCHANGE_RESP resp;
|
||||
memset(&resp, 0, sizeof(resp));
|
||||
resp.cmd = ETCP_ID_CONN_MGR;
|
||||
resp.subcmd = CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP;
|
||||
resp.request_id = req->request_id;
|
||||
resp.my_count = mgr->best_candidate_count > 4 ? 4 : mgr->best_candidate_count;
|
||||
for (uint8_t i = 0; i < resp.my_count; i++)
|
||||
resp.my_candidates[i] = mgr->best_candidates[i];
|
||||
resp.your_count = req->candidate_count > 4 ? 4 : req->candidate_count;
|
||||
for (uint8_t i = 0; i < resp.your_count; i++) {
|
||||
resp.your_candidates[i].node_id = req->candidates[i].node_id;
|
||||
struct NODEINFO_Q* nq2 = nodeinfo_find_by_id(mgr->instance->bgp, req->candidates[i].node_id);
|
||||
uint16_t rtt = nq2 ? cm_get_node_min_rtt(nq2) : 0;
|
||||
resp.your_candidates[i].rtt = rtt;
|
||||
}
|
||||
size_t resp_size = offsetof(struct CONN_MGR_INTERM_EXCHANGE_RESP, your_candidates) +
|
||||
(size_t)resp.your_count * sizeof(struct CONN_MGR_CANDIDATE);
|
||||
struct ll_entry* qe = queue_entry_new(resp_size);
|
||||
if (qe) {
|
||||
memcpy(qe->data, &resp, resp_size);
|
||||
etcp_route_send(mgr->instance, conn->peer_node_id, qe, 1);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: sent EXCHANGE_RESP to 0x%016llx my=%u your=%u",
|
||||
(unsigned long long)conn->peer_node_id, resp.my_count, resp.your_count);
|
||||
}
|
||||
|
||||
static void cm_handle_disconnect(struct CONN_MGR* mgr, uint64_t node_id) {
|
||||
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id);
|
||||
if (!entry) return;
|
||||
cm_clear_nodeinfo(mgr, node_id);
|
||||
cm_entry_destroy(entry);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: remote disconnect from 0x%016llx", (unsigned long long)node_id);
|
||||
}
|
||||
|
||||
static void cm_bg_ping_timer_cb(void* arg) {
|
||||
struct CONN_MGR* mgr = (struct CONN_MGR*)arg;
|
||||
struct ROUTE_BGP* bgp = mgr->instance->bgp;
|
||||
if (!bgp || !bgp->nodes) {
|
||||
mgr->bg_ping_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_BG_PING_INTERVAL_TB, mgr, cm_bg_ping_timer_cb, "conn_mgr_bg_ping");
|
||||
return;
|
||||
}
|
||||
uint64_t now_tb = get_time_tb();
|
||||
struct ll_entry* e = bgp->nodes->head;
|
||||
size_t total = 0;
|
||||
while (e) { total++; e = e->next; }
|
||||
if (total == 0) {
|
||||
mgr->bg_ping_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_BG_PING_INTERVAL_TB, mgr, cm_bg_ping_timer_cb, "conn_mgr_bg_ping");
|
||||
return;
|
||||
}
|
||||
if (mgr->bg_ping_cursor >= total) {
|
||||
mgr->bg_ping_cursor = 0;
|
||||
if ((now_tb - mgr->bg_ping_cycle_start_tb) < CONN_MGR_BG_PING_CYCLE_MIN_TB) {
|
||||
uint64_t delay = CONN_MGR_BG_PING_CYCLE_MIN_TB - (now_tb - mgr->bg_ping_cycle_start_tb);
|
||||
mgr->bg_ping_timer = uasync_set_timeout(mgr->instance->ua, (int)delay, mgr, cm_bg_ping_timer_cb, "conn_mgr_bg_ping");
|
||||
return;
|
||||
}
|
||||
mgr->bg_ping_cycle_start_tb = now_tb;
|
||||
}
|
||||
size_t cursor = 0;
|
||||
e = bgp->nodes->head;
|
||||
while (e && cursor < mgr->bg_ping_cursor) { cursor++; e = e->next; }
|
||||
if (e) {
|
||||
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)e;
|
||||
uint64_t node_id = nq->node.node_id;
|
||||
if (node_id != mgr->instance->node_id) {
|
||||
struct ETCP_CONN* dconn = route_bgp_find_conn_for_node(bgp, node_id);
|
||||
int has_active = 0;
|
||||
if (dconn && dconn->links) {
|
||||
struct ETCP_LINK* l = dconn->links;
|
||||
while (l) { if (l->initialized && l->link_status) { has_active = 1; break; } l = l->next; }
|
||||
}
|
||||
if (!has_active) {
|
||||
etcp_send_ping(mgr->instance, nq->node.public_key, NULL, CONN_PROBE_TIMEOUT_MS,
|
||||
NULL, NULL, NULL, 0);
|
||||
}
|
||||
}
|
||||
mgr->bg_ping_cursor++;
|
||||
}
|
||||
mgr->bg_ping_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_BG_PING_INTERVAL_TB, mgr, cm_bg_ping_timer_cb, "conn_mgr_bg_ping");
|
||||
}
|
||||
|
||||
static void cm_candidate_ping_timer_cb(void* arg) {
|
||||
struct CONN_MGR* mgr = (struct CONN_MGR*)arg;
|
||||
uint64_t now_tb = get_time_tb();
|
||||
for (uint8_t i = 0; i < mgr->best_candidate_count; i++) {
|
||||
uint64_t cand_id = mgr->best_candidates[i].node_id;
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(mgr->instance->bgp, cand_id);
|
||||
if (!nq) {
|
||||
memmove(&mgr->best_candidates[i], &mgr->best_candidates[i+1], (mgr->best_candidate_count - i - 1) * sizeof(mgr->best_candidates[0]));
|
||||
mgr->best_candidate_count--; i--; continue;
|
||||
}
|
||||
uint64_t last = cm_get_node_max_probe_time(nq);
|
||||
if (last > 0 && (now_tb - last) >= CONN_MGR_CANDIDATE_STALE_TB) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: candidate 0x%016llx stale, removing", (unsigned long long)cand_id);
|
||||
memmove(&mgr->best_candidates[i], &mgr->best_candidates[i+1], (mgr->best_candidate_count - i - 1) * sizeof(mgr->best_candidates[0]));
|
||||
mgr->best_candidate_count--; i--; continue;
|
||||
}
|
||||
struct ETCP_CONN* dconn = route_bgp_find_conn_for_node(mgr->instance->bgp, cand_id);
|
||||
int has_active = 0;
|
||||
if (dconn && dconn->links) {
|
||||
struct ETCP_LINK* l = dconn->links;
|
||||
while (l) { if (l->initialized && l->link_status) { has_active = 1; break; } l = l->next; }
|
||||
}
|
||||
if (has_active)
|
||||
etcp_send_ping(mgr->instance, nq->node.public_key, &dconn->links->remote_addr, CONN_PROBE_TIMEOUT_MS, NULL, NULL, NULL, 0);
|
||||
else
|
||||
route_connectivity_probe_node(mgr->instance, nq);
|
||||
}
|
||||
mgr->candidate_ping_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_CANDIDATE_PING_TB,
|
||||
mgr, cm_candidate_ping_timer_cb, "conn_mgr_cand_ping");
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
#ifndef CONN_MGR_H
|
||||
#define CONN_MGR_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "etcp_api.h"
|
||||
#include "etcp_router.h"
|
||||
#include "route_node.h"
|
||||
|
||||
struct UTUN_INSTANCE;
|
||||
struct ETCP_CONN;
|
||||
struct NODEINFO_Q;
|
||||
struct ROUTE_BGP;
|
||||
|
||||
#define CONN_MGR_MAX_CANDIDATES 3
|
||||
#define CONN_MGR_CANDIDATE_CACHE_MS 20000
|
||||
#define CONN_MGR_CANDIDATE_STALE_TB 300000
|
||||
#define CONN_MGR_CANDIDATE_PING_TB 20000
|
||||
#define CONN_MGR_BG_PING_INTERVAL_TB 1000
|
||||
#define CONN_MGR_BG_PING_CYCLE_MIN_TB 100000
|
||||
#define CONN_MGR_IDLE_CHECK_INTERVAL_TB 10000
|
||||
#define CONN_MGR_LOCAL_SCAN_ATTEMPTS 3
|
||||
#define CONN_MGR_LOCAL_SCAN_TIMEOUT_MS 100
|
||||
#define CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS 5000
|
||||
#define CONN_MGR_CONNECT_REVERSE_TIMEOUT_MS 15000
|
||||
#define CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS 15000
|
||||
|
||||
#define CONN_MGR_OK 0
|
||||
#define CONN_MGR_ERR_NOT_FOUND -1
|
||||
#define CONN_MGR_ERR_NO_ADDRESSES -2
|
||||
#define CONN_MGR_ERR_TIMEOUT -3
|
||||
#define CONN_MGR_ERR_UNREACHABLE -4
|
||||
#define CONN_MGR_ERR_REFUSED -5
|
||||
#define CONN_MGR_ERR_ALREADY_CONNECTED -6
|
||||
#define CONN_MGR_ERR_INTERNAL -7
|
||||
|
||||
enum CONN_MGR_STATE {
|
||||
CONN_MGR_STATE_DISCONNECTED = 0,
|
||||
CONN_MGR_STATE_CONNECTING = 1,
|
||||
CONN_MGR_STATE_CONNECTED = 2,
|
||||
};
|
||||
|
||||
enum CM_TRY_STATE {
|
||||
CM_TRY_NONE = 0,
|
||||
CM_TRY_PENDING = 1,
|
||||
CM_TRY_OK = 2,
|
||||
CM_TRY_FAILED = 3,
|
||||
};
|
||||
|
||||
#define CONN_MGR_SUBCMD_DIRECT_REQ 0x01
|
||||
#define CONN_MGR_SUBCMD_DIRECT_RESP 0x02
|
||||
#define CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ 0x03
|
||||
#define CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP 0x04
|
||||
#define CONN_MGR_SUBCMD_INTERM_SELECTED 0x05
|
||||
#define CONN_MGR_SUBCMD_DISCONNECT 0x06
|
||||
|
||||
struct CONN_MGR_CANDIDATE {
|
||||
uint64_t node_id;
|
||||
uint16_t rtt;
|
||||
} __attribute__((packed));
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct CONN_MGR_DIRECT_REQ {
|
||||
uint8_t cmd;
|
||||
uint8_t subcmd;
|
||||
uint32_t request_id;
|
||||
uint8_t addr_count;
|
||||
};
|
||||
|
||||
struct CONN_MGR_DIRECT_RESP {
|
||||
uint8_t cmd;
|
||||
uint8_t subcmd;
|
||||
uint32_t request_id;
|
||||
uint8_t accepted;
|
||||
};
|
||||
|
||||
struct CONN_MGR_INTERM_EXCHANGE_REQ {
|
||||
uint8_t cmd;
|
||||
uint8_t subcmd;
|
||||
uint32_t request_id;
|
||||
uint8_t candidate_count;
|
||||
struct CONN_MGR_CANDIDATE candidates[4];
|
||||
};
|
||||
|
||||
struct CONN_MGR_INTERM_EXCHANGE_RESP {
|
||||
uint8_t cmd;
|
||||
uint8_t subcmd;
|
||||
uint32_t request_id;
|
||||
uint8_t my_count;
|
||||
uint8_t your_count;
|
||||
struct CONN_MGR_CANDIDATE my_candidates[4];
|
||||
struct CONN_MGR_CANDIDATE your_candidates[4];
|
||||
};
|
||||
|
||||
struct CONN_MGR_INTERM_SELECTED {
|
||||
uint8_t cmd;
|
||||
uint8_t subcmd;
|
||||
uint32_t request_id;
|
||||
uint8_t count;
|
||||
struct CONN_MGR_CANDIDATE selected[3];
|
||||
};
|
||||
|
||||
struct CONN_MGR_DISCONNECT {
|
||||
uint8_t cmd;
|
||||
uint8_t subcmd;
|
||||
uint64_t node_id;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
#define CONN_MGR_DIRECT_REQ_HDR_SIZE sizeof(struct CONN_MGR_DIRECT_REQ)
|
||||
#define CONN_MGR_DIRECT_RESP_HDR_SIZE sizeof(struct CONN_MGR_DIRECT_RESP)
|
||||
#define CONN_MGR_INTERM_EXCHANGE_REQ_SIZE sizeof(struct CONN_MGR_INTERM_EXCHANGE_REQ)
|
||||
#define CONN_MGR_INTERM_EXCHANGE_RESP_SIZE sizeof(struct CONN_MGR_INTERM_EXCHANGE_RESP)
|
||||
#define CONN_MGR_INTERM_SELECTED_SIZE sizeof(struct CONN_MGR_INTERM_SELECTED)
|
||||
#define CONN_MGR_DISCONNECT_SIZE sizeof(struct CONN_MGR_DISCONNECT)
|
||||
|
||||
typedef void (*conn_mgr_connect_callback_t)(int result, uint64_t node_id, void* arg);
|
||||
|
||||
struct cm_cb_node {
|
||||
conn_mgr_connect_callback_t cb;
|
||||
void* arg;
|
||||
struct cm_cb_node* next;
|
||||
};
|
||||
|
||||
struct CONN_MGR_ENTRY {
|
||||
uint64_t node_id;
|
||||
uint8_t state;
|
||||
uint8_t conn_type;
|
||||
uint8_t alien;
|
||||
uint32_t idle_timeout_ms;
|
||||
uint64_t last_traffic_tb;
|
||||
void* idle_timer;
|
||||
|
||||
struct cm_cb_node* cb_list;
|
||||
|
||||
uint64_t intermediaries[CONN_MGR_MAX_INTERMEDIARIES];
|
||||
uint8_t intermediariy_count;
|
||||
uint8_t rr_idx;
|
||||
|
||||
uint8_t local_scan_state;
|
||||
uint8_t main_connect_state;
|
||||
struct {
|
||||
uint8_t phase;
|
||||
void* timer;
|
||||
uint32_t request_id;
|
||||
void* conn_ctx;
|
||||
} main;
|
||||
|
||||
struct CONN_MGR* mgr;
|
||||
};
|
||||
|
||||
struct CONN_MGR {
|
||||
struct UTUN_INSTANCE* instance;
|
||||
struct CONN_MGR_ENTRY* entries;
|
||||
size_t entry_count;
|
||||
size_t entry_capacity;
|
||||
|
||||
void* bg_ping_timer;
|
||||
size_t bg_ping_cursor;
|
||||
uint64_t bg_ping_cycle_start_tb;
|
||||
|
||||
void* candidate_ping_timer;
|
||||
|
||||
struct CONN_MGR_CANDIDATE best_candidates[CONN_MGR_MAX_CANDIDATES];
|
||||
uint8_t best_candidate_count;
|
||||
|
||||
uint32_t next_request_id;
|
||||
uint8_t initialized;
|
||||
|
||||
struct cm_reverse_pending* reverse_pending;
|
||||
struct cm_exchange_pending* exchange_pending;
|
||||
};
|
||||
|
||||
struct CONN_MGR* conn_mgr_init(struct UTUN_INSTANCE* instance);
|
||||
void conn_mgr_destroy(struct CONN_MGR* mgr);
|
||||
|
||||
int conn_mgr_connect_node(struct CONN_MGR* mgr, uint64_t node_id,
|
||||
uint32_t idle_timeout_ms,
|
||||
conn_mgr_connect_callback_t cb, void* cb_arg);
|
||||
int conn_mgr_disconnect_node(struct CONN_MGR* mgr, uint64_t node_id);
|
||||
int conn_mgr_set_idle_timeout(struct CONN_MGR* mgr, uint64_t node_id, uint32_t timeout_ms);
|
||||
int conn_mgr_get_status(struct CONN_MGR* mgr, uint64_t node_id,
|
||||
uint8_t* out_state, uint8_t* out_conn_type);
|
||||
int conn_mgr_add_alien_node(struct CONN_MGR* mgr, const uint8_t* nodeinfo_data, size_t len);
|
||||
int conn_mgr_send(struct CONN_MGR* mgr, uint64_t node_id, struct ll_entry* entry);
|
||||
|
||||
void conn_mgr_update_best_candidates(struct CONN_MGR* mgr, uint64_t node_id, uint16_t rtt);
|
||||
|
||||
#endif
|
||||
+242
-45
@@ -11,6 +11,8 @@
|
||||
#include "tun_if.h"
|
||||
#include "route_lib.h"
|
||||
#include "route_bgp.h"
|
||||
#include "etcp_router.h"
|
||||
#include "route_node.h"
|
||||
#include "pkt_normalizer.h"
|
||||
#include "../tools/etcpmon/etcpmon_protocol.h"
|
||||
#include "../lib/u_async.h"
|
||||
@@ -89,6 +91,7 @@ static void client_write_callback(socket_t fd, void* arg);
|
||||
static void client_except_callback(socket_t fd, void* arg);
|
||||
static void handle_client_data(struct control_server* server, struct control_client* client);
|
||||
static void close_client(struct control_server* server, struct control_client* client);
|
||||
static void control_client_idle_cb(void* arg);
|
||||
|
||||
static void send_conn_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
|
||||
static void send_socket_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
|
||||
@@ -96,8 +99,16 @@ static void send_metrics(struct control_server* server, struct control_client* c
|
||||
static void send_debug_config(struct control_server* server, struct control_client* client, uint8_t seq_id);
|
||||
static void send_error(struct control_client* client, uint8_t error_code, const char* msg, uint8_t seq_id);
|
||||
|
||||
static void control_server_send_node_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
|
||||
static void send_single_node_info(struct control_server* server, struct control_client* client, struct NODEINFO_Q* nq);
|
||||
static int control_client_send(struct control_server* server, struct control_client* client, uint8_t* dgram, uint16_t len);
|
||||
|
||||
static struct ETCP_CONN* find_connection_by_peer_id(struct UTUN_INSTANCE* instance, uint64_t peer_id);
|
||||
|
||||
/* ETCP ID/subcmd constants for BGP_NODEINFO_PACKET (from route_bgp.h) */
|
||||
#define CTRL_BGP_CMD 0x01
|
||||
#define CTRL_BGP_SUBCMD 0x04
|
||||
|
||||
/* ============================================================================
|
||||
* Server Initialization
|
||||
* ============================================================================ */
|
||||
@@ -400,13 +411,27 @@ static void accept_callback(socket_t fd, void* arg) {
|
||||
|
||||
client->fd = client_fd;
|
||||
client->server = server; /* Store back pointer to server */
|
||||
// client->connected = 1;
|
||||
client->selected_peer_id = 0;
|
||||
|
||||
|
||||
client->output_queue = queue_new(server->ua, 0, 0, 0, "ctrl_out");
|
||||
if (!client->output_queue) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to create output queue for client");
|
||||
u_free(client);
|
||||
#ifdef _WIN32
|
||||
closesocket(client_fd);
|
||||
#else
|
||||
close(client_fd);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
client->subscribed_nodes = 0;
|
||||
client->write_registered = 0;
|
||||
client->idle_timer = uasync_set_timeout(server->ua, 300000, client, control_client_idle_cb, "ctrl_idle");
|
||||
|
||||
/* Register with uasync */
|
||||
client->socket_id = uasync_add_socket_t(server->ua, client_fd,
|
||||
client_read_callback,
|
||||
NULL, /* write callback - not needed for now */
|
||||
client_write_callback,
|
||||
client_except_callback,
|
||||
client);
|
||||
|
||||
@@ -544,9 +569,45 @@ static void client_read_callback(socket_t fd, void* arg) {
|
||||
}
|
||||
|
||||
static void client_write_callback(socket_t fd, void* arg) {
|
||||
/* Not used for now - writes are immediate */
|
||||
struct control_client* client = (struct control_client*)arg;
|
||||
struct control_server* server = client->server;
|
||||
struct ll_queue* q = client->output_queue;
|
||||
|
||||
while (q->head) {
|
||||
struct ll_entry* entry = q->head;
|
||||
|
||||
#ifdef _WIN32
|
||||
int sent = send(fd, (const char*)entry->dgram, entry->len, 0);
|
||||
if (sent == SOCKET_ERROR) {
|
||||
int err = WSAGetLastError();
|
||||
if (err != WSAEWOULDBLOCK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Client send error: %d, closing", err);
|
||||
close_client(server, client);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
#else
|
||||
ssize_t sent = send(fd, entry->dgram, entry->len, 0);
|
||||
if (sent < 0) {
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Client send error: %s, closing", strerror(errno));
|
||||
close_client(server, client);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if ((size_t)sent < entry->len) { entry->dgram += sent; entry->len -= (uint16_t)sent; q->total_bytes -= (size_t)sent; return; }
|
||||
|
||||
struct ll_entry* e = queue_data_get(q);
|
||||
queue_dgram_free(e); queue_entry_free(e);
|
||||
}
|
||||
|
||||
uasync_set_socket_write(server->ua, client->socket_id, 0);
|
||||
client->write_registered = 0;
|
||||
if (q->count > 128) DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "ctrl_out: large output queue count=%d fd=%d", q->count, (int)client->fd);
|
||||
(void)fd;
|
||||
(void)arg;
|
||||
}
|
||||
|
||||
static void client_except_callback(socket_t fd, void* arg) {
|
||||
@@ -560,10 +621,35 @@ static void client_except_callback(socket_t fd, void* arg) {
|
||||
(void)fd;
|
||||
}
|
||||
|
||||
static int control_client_send(struct control_server* server, struct control_client* client, uint8_t* dgram, uint16_t len) {
|
||||
(void)server;
|
||||
if (!client || !client->output_queue || !dgram || len == 0) return -1;
|
||||
struct ll_entry* entry = queue_entry_new(0);
|
||||
if (!entry) { DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate output queue entry"); return -1; }
|
||||
entry->dgram = dgram;
|
||||
entry->len = len;
|
||||
entry->int_len = len;
|
||||
queue_data_put(client->output_queue, entry);
|
||||
if (!client->write_registered) {
|
||||
uasync_set_socket_write(client->server->ua, client->socket_id, 1);
|
||||
client->write_registered = 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void control_client_idle_cb(void* arg) {
|
||||
struct control_client* client = (struct control_client*)arg;
|
||||
if (!client || !client->server) return;
|
||||
client->idle_timer = NULL;
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Control client idle timeout fd=%d, closing", client->fd);
|
||||
close_client(client->server, client);
|
||||
}
|
||||
|
||||
static void close_client(struct control_server* server, struct control_client* client) {
|
||||
if (!server || !client) return;
|
||||
|
||||
if (client->idle_timer) { uasync_cancel_timeout(server->ua, client->idle_timer); client->idle_timer = NULL; }
|
||||
|
||||
/* Log disconnection */
|
||||
if (server->log_file) {
|
||||
fprintf(server->log_file, "%llu: [LOG] Client connection closed\n",
|
||||
@@ -583,7 +669,9 @@ static void close_client(struct control_server* server, struct control_client* c
|
||||
close(client->fd);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
if (client->output_queue) { queue_free(client->output_queue); client->output_queue = NULL; }
|
||||
|
||||
/* Remove from list */
|
||||
struct control_client** curr = &server->clients;
|
||||
while (*curr) {
|
||||
@@ -614,7 +702,7 @@ static void handle_client_data(struct control_server* server, struct control_cli
|
||||
close_client(server, client);
|
||||
return;
|
||||
}
|
||||
if (hdr->type < ETCPMON_CMD_LIST_CONN || hdr->type > ETCPMON_CMD_SET_DEBUG_CONFIG) {
|
||||
if (hdr->type < ETCPMON_CMD_LIST_CONN || hdr->type > ETCPMON_CMD_SUBSCRIBE_NODES) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Invalid command type from client: 0x%02X", hdr->type);
|
||||
close_client(server, client);
|
||||
return;
|
||||
@@ -691,6 +779,27 @@ static void handle_client_data(struct control_server* server, struct control_cli
|
||||
if (strncmp(cmd->action, "nat", 3) == 0) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Action 'nat' received, requesting NAT check for all links");
|
||||
route_bgp_request_nat_check_all(server->instance->bgp);
|
||||
} else if (strncmp(cmd->action, "nodes", 5) == 0) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Action 'nodes' received, dumping all BGP nodes");
|
||||
uint16_t text_max = (ETCPMON_MAX_MSG_SIZE > 1024) ? ETCPMON_MAX_MSG_SIZE - 512 : 1024;
|
||||
char* text = (char*)u_malloc(text_max);
|
||||
if (text) {
|
||||
int text_len = route_node_format_all(server->instance->bgp, text, text_max);
|
||||
if (text_len < 0) text_len = 0;
|
||||
text[text_len] = '\0';
|
||||
uint16_t rsp_size = ETCPMON_ACTION_RESULT_SIZE(text_len);
|
||||
uint8_t* buffer = (uint8_t*)u_malloc(rsp_size);
|
||||
if (buffer) {
|
||||
struct etcpmon_msg_header* rsp_hdr = (struct etcpmon_msg_header*)buffer;
|
||||
etcpmon_build_header(rsp_hdr, sizeof(struct etcpmon_rsp_action_result) + text_len + 1,
|
||||
ETCPMON_RSP_ACTION_RESULT, req_seq);
|
||||
struct etcpmon_rsp_action_result* rsp = (struct etcpmon_rsp_action_result*)(buffer + sizeof(*rsp_hdr));
|
||||
rsp->code = 0;
|
||||
memcpy(buffer + sizeof(*rsp_hdr) + sizeof(*rsp), text, text_len + 1);
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
}
|
||||
u_free(text);
|
||||
}
|
||||
} else {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Unknown action received: '%s'", cmd->action);
|
||||
}
|
||||
@@ -721,6 +830,17 @@ static void handle_client_data(struct control_server* server, struct control_cli
|
||||
}
|
||||
break;
|
||||
|
||||
case ETCPMON_CMD_SUBSCRIBE_NODES:
|
||||
client->subscribed_nodes = 1;
|
||||
if (server->log_file) {
|
||||
fprintf(server->log_file, "%llu: [LOG] Client subscribed to node updates\n",
|
||||
(unsigned long long)get_timestamp_ms());
|
||||
fflush(server->log_file);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Client subscribed to node updates");
|
||||
control_server_send_node_list(server, client, req_seq);
|
||||
break;
|
||||
|
||||
case ETCPMON_CMD_DISCONNECT:
|
||||
if (server->log_file) {
|
||||
fprintf(server->log_file, "%llu: [LOG] Client requested disconnect\n",
|
||||
@@ -736,6 +856,10 @@ static void handle_client_data(struct control_server* server, struct control_cli
|
||||
return;
|
||||
}
|
||||
|
||||
/* Reset idle timer on each valid command */
|
||||
if (client->idle_timer) { uasync_cancel_timeout(server->ua, client->idle_timer); }
|
||||
client->idle_timer = uasync_set_timeout(server->ua, 300000, client, control_client_idle_cb, "ctrl_idle");
|
||||
|
||||
/* Remove processed message from buffer */
|
||||
uint16_t msg_size = hdr->size;
|
||||
if (client->recv_len > msg_size) {
|
||||
@@ -795,13 +919,7 @@ static void send_conn_list(struct control_server* server, struct control_client*
|
||||
fflush(server->log_file);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
send(client->fd, (const char*)buffer, rsp_size, 0);
|
||||
#else
|
||||
send(client->fd, buffer, rsp_size, 0);
|
||||
#endif
|
||||
|
||||
u_free(buffer);
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
}
|
||||
|
||||
static void send_socket_list(struct control_server* server, struct control_client* client, uint8_t seq_id) {
|
||||
@@ -819,8 +937,7 @@ static void send_socket_list(struct control_server* server, struct control_clien
|
||||
struct etcpmon_rsp_socket_list* rsp = (struct etcpmon_rsp_socket_list*)(buffer + sizeof(*hdr));
|
||||
rsp->count = 0;
|
||||
|
||||
send(client->fd, buffer, rsp_size, 0);
|
||||
u_free(buffer);
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -889,17 +1006,11 @@ static void send_socket_list(struct control_server* server, struct control_clien
|
||||
if (server->log_file) {
|
||||
log_hex_data(server->log_file, "TX", buffer, rsp_size);
|
||||
fprintf(server->log_file, "%llu: [LOG] Sent RSP_SOCKET_LIST count=%d\n",
|
||||
(unsigned long long)get_timestamp_ms(), count);
|
||||
(unsigned long long)get_timestamp_ms(), count);
|
||||
fflush(server->log_file);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
send(client->fd, (const char*)buffer, rsp_size, 0);
|
||||
#else
|
||||
send(client->fd, buffer, rsp_size, 0);
|
||||
#endif
|
||||
|
||||
u_free(buffer);
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
}
|
||||
|
||||
static void send_metrics(struct control_server* server, struct control_client* client, uint8_t seq_id) {
|
||||
@@ -1078,6 +1189,29 @@ static void send_metrics(struct control_server* server, struct control_client* c
|
||||
memset(&rsp->tun, 0, sizeof(rsp->tun));
|
||||
}
|
||||
|
||||
/* Fill router congestion metrics — aggregate over all router_conns */
|
||||
memset(&rsp->router, 0, sizeof(rsp->router));
|
||||
if (instance->router_conns) {
|
||||
struct ll_entry* re = instance->router_conns->head;
|
||||
while (re) {
|
||||
struct ETCP_ROUTER_CONN* rc = (struct ETCP_ROUTER_CONN*)re;
|
||||
rsp->router.total_inflight += (uint32_t)(rc->tx_seq - rc->tx_acked);
|
||||
if (rc->send_q) rsp->router.total_send_q += (uint32_t)queue_entry_count(rc->send_q);
|
||||
if (rc->recv_q) rsp->router.total_recv_q += (uint32_t)queue_entry_count(rc->recv_q);
|
||||
rsp->router.pkts_sent += rc->c_pkts_sent;
|
||||
rsp->router.pkts_send_err += rc->c_pkts_send_err;
|
||||
rsp->router.pkts_rcvd += rc->c_pkts_rcvd;
|
||||
rsp->router.ack_sent += rc->c_ack_sent;
|
||||
rsp->router.ack_recv += rc->c_ack_recv;
|
||||
rsp->router.dup_dropped += rc->c_dup_dropped;
|
||||
rsp->router.oob_dropped += rc->c_oob_dropped;
|
||||
rsp->router.stale_ack += rc->c_stale_ack;
|
||||
rsp->router.sign_fail += rc->c_sign_fail;
|
||||
re = re->next;
|
||||
}
|
||||
rsp->router.total_conns = (uint32_t)queue_entry_count(instance->router_conns);
|
||||
}
|
||||
|
||||
/* Fill link metrics */
|
||||
struct etcpmon_link_metrics* link_info = (struct etcpmon_link_metrics*)(buffer + sizeof(*hdr) + sizeof(*rsp));
|
||||
link = conn->links;
|
||||
@@ -1136,13 +1270,7 @@ static void send_metrics(struct control_server* server, struct control_client* c
|
||||
fflush(server->log_file);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
send(client->fd, (const char*)buffer, rsp_size, 0);
|
||||
#else
|
||||
send(client->fd, buffer, rsp_size, 0);
|
||||
#endif
|
||||
|
||||
u_free(buffer);
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
}
|
||||
|
||||
static void send_error(struct control_client* client, uint8_t error_code, const char* msg, uint8_t seq_id) {
|
||||
@@ -1170,13 +1298,7 @@ static void send_error(struct control_client* client, uint8_t error_code, const
|
||||
fflush(server->log_file);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
send(client->fd, (const char*)buffer, rsp_size, 0);
|
||||
#else
|
||||
send(client->fd, buffer, rsp_size, 0);
|
||||
#endif
|
||||
|
||||
u_free(buffer);
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
}
|
||||
|
||||
static void send_debug_config(struct control_server* server, struct control_client* client, uint8_t seq_id) {
|
||||
@@ -1215,13 +1337,7 @@ static void send_debug_config(struct control_server* server, struct control_clie
|
||||
fflush(server->log_file);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
send(client->fd, (const char*)buffer, rsp_size, 0);
|
||||
#else
|
||||
send(client->fd, buffer, rsp_size, 0);
|
||||
#endif
|
||||
|
||||
u_free(buffer);
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
@@ -1240,6 +1356,87 @@ static struct ETCP_CONN* find_connection_by_peer_id(struct UTUN_INSTANCE* instan
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* Node Info Helpers (send_single_node_info, send_node_list, notify_change)
|
||||
* ============================================================================ */
|
||||
|
||||
static void send_single_node_info(struct control_server* server, struct control_client* client, struct NODEINFO_Q* nq) {
|
||||
int dyn = nodeinfo_dyn_size(&nq->node);
|
||||
size_t np_size = sizeof(struct etcpmon_msg_header) + 2 + sizeof(struct NODEINFO) + dyn;
|
||||
|
||||
uint8_t* buffer = (uint8_t*)u_malloc(np_size);
|
||||
if (!buffer) { DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate node info buffer"); return; }
|
||||
|
||||
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
|
||||
etcpmon_build_header(hdr, 2 + sizeof(struct NODEINFO) + dyn, ETCPMON_RSP_NODE_INFO, 0);
|
||||
|
||||
uint8_t* pay = buffer + sizeof(*hdr);
|
||||
pay[0] = CTRL_BGP_CMD; pay[1] = CTRL_BGP_SUBCMD;
|
||||
memcpy(pay + 2, &nq->node, sizeof(struct NODEINFO));
|
||||
memcpy(pay + 2 + sizeof(struct NODEINFO), (uint8_t*)&nq->node + sizeof(struct NODEINFO), dyn);
|
||||
|
||||
control_client_send(server, client, buffer, (uint16_t)np_size);
|
||||
}
|
||||
|
||||
static void control_server_send_node_list(struct control_server* server, struct control_client* client, uint8_t seq_id) {
|
||||
struct ROUTE_BGP* bgp = server->instance->bgp;
|
||||
if (!bgp) { send_error(client, ETCPMON_ERR_SERVER_BUSY, "BGP not available", seq_id); return; }
|
||||
|
||||
/* Send local node first */
|
||||
if (bgp->local_node) send_single_node_info(server, client, bgp->local_node);
|
||||
|
||||
/* Send all known remote nodes */
|
||||
struct ll_entry* e = bgp->nodes ? bgp->nodes->head : NULL;
|
||||
while (e) {
|
||||
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)e;
|
||||
send_single_node_info(server, client, nq);
|
||||
e = e->next;
|
||||
}
|
||||
|
||||
/* Send end-of-list marker */
|
||||
uint8_t* buffer = (uint8_t*)u_malloc(sizeof(struct etcpmon_msg_header));
|
||||
if (!buffer) return;
|
||||
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
|
||||
etcpmon_build_header(hdr, 0, ETCPMON_RSP_NODE_INFO_END, seq_id);
|
||||
control_client_send(server, client, buffer, sizeof(struct etcpmon_msg_header));
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Sent node list to client (seq=%d)", seq_id);
|
||||
}
|
||||
|
||||
void control_server_notify_node_change(struct control_server* server, struct NODEINFO_Q* node) {
|
||||
if (!server || !node) return;
|
||||
struct control_client* client = server->clients;
|
||||
while (client) {
|
||||
if (client->subscribed_nodes) send_single_node_info(server, client, node);
|
||||
client = client->next;
|
||||
}
|
||||
}
|
||||
|
||||
void control_server_notify_node_removed(struct control_server* server, uint64_t node_id) {
|
||||
if (!server) return;
|
||||
|
||||
uint8_t* buffer = (uint8_t*)u_malloc(sizeof(struct etcpmon_msg_header) + sizeof(uint64_t));
|
||||
if (!buffer) return;
|
||||
|
||||
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
|
||||
etcpmon_build_header(hdr, sizeof(uint64_t), ETCPMON_RSP_NODE_REMOVED, 0);
|
||||
*(uint64_t*)(buffer + sizeof(*hdr)) = node_id;
|
||||
|
||||
struct control_client* client = server->clients;
|
||||
while (client) {
|
||||
if (client->subscribed_nodes) {
|
||||
/* Every client needs its own copy since ownership transfers to the queue */
|
||||
uint8_t* copy = (uint8_t*)u_malloc(sizeof(struct etcpmon_msg_header) + sizeof(uint64_t));
|
||||
if (copy) {
|
||||
memcpy(copy, buffer, sizeof(struct etcpmon_msg_header) + sizeof(uint64_t));
|
||||
control_client_send(server, client, copy, sizeof(struct etcpmon_msg_header) + sizeof(uint64_t));
|
||||
}
|
||||
}
|
||||
client = client->next;
|
||||
}
|
||||
u_free(buffer);
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* Public API Implementation
|
||||
* ============================================================================ */
|
||||
|
||||
+10
-3
@@ -19,6 +19,8 @@ extern "C" {
|
||||
/* Forward declarations */
|
||||
struct UTUN_INSTANCE;
|
||||
struct UASYNC;
|
||||
struct NODEINFO_Q;
|
||||
struct ll_queue;
|
||||
|
||||
/* Forward declaration */
|
||||
struct control_server;
|
||||
@@ -36,9 +38,11 @@ struct control_client {
|
||||
|
||||
/* Selected connection */
|
||||
uint64_t selected_peer_id; /* 0 = none selected */
|
||||
|
||||
/* Client state */
|
||||
// uint8_t connected;
|
||||
|
||||
struct ll_queue* output_queue; /* Output queue for async sends */
|
||||
void* idle_timer; /* Client idle timeout */
|
||||
uint8_t subscribed_nodes; /* 1 = subscribed to node updates */
|
||||
uint8_t write_registered; /* 1 = write events registered in uasync */
|
||||
};
|
||||
|
||||
/* Control server state */
|
||||
@@ -113,6 +117,9 @@ void control_server_process_updates(struct control_server* server);
|
||||
*/
|
||||
uint32_t control_server_get_client_count(const struct control_server* server);
|
||||
|
||||
void control_server_notify_node_change(struct control_server* server, struct NODEINFO_Q* node);
|
||||
void control_server_notify_node_removed(struct control_server* server, uint64_t node_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -577,6 +577,11 @@ struct dummynet_filter {
|
||||
uint32_t jitter_ms;
|
||||
struct ETCP_LINK* link;
|
||||
struct dummynet_stats stats;
|
||||
struct {
|
||||
uint32_t ip;
|
||||
uint16_t port;
|
||||
} block_addrs[4];
|
||||
uint8_t block_count;
|
||||
};
|
||||
|
||||
static ssize_t dummynet_filter_send_hook(socket_t fd, const void* buf, size_t len,
|
||||
@@ -585,6 +590,16 @@ static ssize_t dummynet_filter_send_hook(socket_t fd, const void* buf, size_t le
|
||||
struct dummynet_filter* df = (struct dummynet_filter*)ctx;
|
||||
(void)link;
|
||||
df->stats.recv++;
|
||||
if (addr && addr->sa_family == AF_INET && df->block_count > 0) {
|
||||
const struct sockaddr_in* sin = (const struct sockaddr_in*)addr;
|
||||
for (uint8_t i = 0; i < df->block_count; i++) {
|
||||
if (sin->sin_addr.s_addr == df->block_addrs[i].ip &&
|
||||
sin->sin_port == df->block_addrs[i].port) {
|
||||
df->stats.lost++;
|
||||
return (ssize_t)len;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (df->loss_permille > 0) {
|
||||
uint32_t roll = (uint32_t)rand() % 1000;
|
||||
if (roll < df->loss_permille) {
|
||||
@@ -637,6 +652,26 @@ void dummynet_filter_destroy(struct dummynet_filter* df) {
|
||||
u_free(df);
|
||||
}
|
||||
|
||||
void dummynet_filter_block_addr(struct dummynet_filter* df, uint32_t ip, uint16_t port) {
|
||||
if (!df || df->block_count >= 4) return;
|
||||
for (uint8_t i = 0; i < df->block_count; i++)
|
||||
if (df->block_addrs[i].ip == ip && df->block_addrs[i].port == port) return;
|
||||
df->block_addrs[df->block_count].ip = ip;
|
||||
df->block_addrs[df->block_count].port = port;
|
||||
df->block_count++;
|
||||
}
|
||||
|
||||
void dummynet_filter_unblock_addr(struct dummynet_filter* df, uint32_t ip, uint16_t port) {
|
||||
if (!df) return;
|
||||
for (uint8_t i = 0; i < df->block_count; i++) {
|
||||
if (df->block_addrs[i].ip == ip && df->block_addrs[i].port == port) {
|
||||
df->block_count--;
|
||||
if (i < df->block_count) memmove(&df->block_addrs[i], &df->block_addrs[i+1], (df->block_count - i) * sizeof(df->block_addrs[0]));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const struct dummynet_stats* dummynet_filter_get_stats(struct dummynet_filter* df) {
|
||||
return df ? &df->stats : NULL;
|
||||
}
|
||||
@@ -176,5 +176,7 @@ void dummynet_filter_attach(struct dummynet_filter* df, struct ETCP_LINK* link);
|
||||
void dummynet_filter_detach(struct dummynet_filter* df);
|
||||
void dummynet_filter_destroy(struct dummynet_filter* df);
|
||||
const struct dummynet_stats* dummynet_filter_get_stats(struct dummynet_filter* df);
|
||||
void dummynet_filter_block_addr(struct dummynet_filter* df, uint32_t ip, uint16_t port);
|
||||
void dummynet_filter_unblock_addr(struct dummynet_filter* df, uint32_t ip, uint16_t port);
|
||||
|
||||
#endif // DUMMYNET_H
|
||||
+17
-9
@@ -70,7 +70,7 @@ static int inflight_seq_cmp(const void* a, const void* b) {
|
||||
|
||||
static void feed_dgram_to_asm(struct ETCP_CONN* etcp, struct PKTNORM* pn,
|
||||
uint8_t* dgram, uint16_t dgram_len,
|
||||
uint8_t** asm_buf, uint16_t* asm_len, uint16_t* asm_cap,
|
||||
uint8_t** asm_buf, uint16_t* asm_len, uint32_t* asm_cap,
|
||||
uint32_t* returned) {
|
||||
uint32_t need = (uint32_t)*asm_len + dgram_len;
|
||||
if (need >= ASM_BUF_MAX_SIZE) {
|
||||
@@ -78,9 +78,10 @@ static void feed_dgram_to_asm(struct ETCP_CONN* etcp, struct PKTNORM* pn,
|
||||
return;
|
||||
}
|
||||
if (need > *asm_cap) {
|
||||
uint16_t new_cap = *asm_cap ? *asm_cap : 256;
|
||||
uint32_t new_cap = *asm_cap ? *asm_cap : 256;
|
||||
while (new_cap < need) new_cap *= 2;
|
||||
uint8_t* new_buf = u_realloc(*asm_buf, new_cap);
|
||||
if (new_cap > 0xFFFF) new_cap = 0xFFFF;
|
||||
uint8_t* new_buf = u_realloc(*asm_buf, (uint16_t)new_cap);
|
||||
if (!new_buf) return;
|
||||
*asm_buf = new_buf;
|
||||
*asm_cap = new_cap;
|
||||
@@ -138,7 +139,7 @@ static void etcp_return_inflight_to_normalizer(struct ETCP_CONN* etcp) {
|
||||
|
||||
uint8_t* asm_buf = NULL;
|
||||
uint16_t asm_len = 0;
|
||||
uint16_t asm_cap = 0;
|
||||
uint32_t asm_cap = 0;
|
||||
uint32_t returned_packets = 0;
|
||||
|
||||
for (int i = 0; i < inflight_count; i++) {
|
||||
@@ -374,7 +375,10 @@ void etcp_conn_reset(struct ETCP_CONN* etcp) {
|
||||
etcp->last_rr_link = NULL;
|
||||
|
||||
// Return unconfirmed inflight data to normalizer input (до нормалайзера)
|
||||
if (etcp->normalizer) etcp_return_inflight_to_normalizer(etcp);
|
||||
if (etcp->normalizer) {
|
||||
etcp->normalizer->input->callback_suspended = 1;
|
||||
etcp_return_inflight_to_normalizer(etcp);
|
||||
}
|
||||
|
||||
// Clear queues (keep queue structures)
|
||||
clear_queue(etcp->input_queue);
|
||||
@@ -453,6 +457,11 @@ void etcp_conn_reset(struct ETCP_CONN* etcp) {
|
||||
}
|
||||
}
|
||||
|
||||
clear_queue(etcp->input_send_q);
|
||||
clear_queue(etcp->input_wait_ack);
|
||||
queue_resume_callback(etcp->input_send_q);
|
||||
queue_resume_callback(etcp->input_wait_ack);
|
||||
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "end");
|
||||
}
|
||||
|
||||
@@ -864,7 +873,7 @@ static void etcp_conn_process_send_queue(struct ETCP_CONN* etcp) {// вызыв
|
||||
char l_status[256]={0};
|
||||
struct ETCP_LINK* link = etcp->links;
|
||||
while (link) {
|
||||
snprintf (l_status+strlen(l_status), 256-strlen(l_status), "L%d%d%s,%s ", link->recv_keepalive, link->remote_keepalive, (link->shaper_timer)?"wait":"rdy", link->send_blocked_inflight?"inf_block":"rdy");
|
||||
snprintf (l_status+strlen(l_status), 256-strlen(l_status), "L%d%d%s ", link->recv_keepalive, link->remote_keepalive, (link->shaper_timer)?"wait":"rdy");
|
||||
link = link->next;
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] TX state: %d (link not ready, skip send) %s", etcp->log_name, etcp->tx_state, l_status);
|
||||
@@ -934,8 +943,6 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
|
||||
bbr_note_loss(inf_pkt->last_link->bbr);
|
||||
inf_pkt->last_link->inflight_bytes -= inf_pkt->ll.len;
|
||||
inf_pkt->last_link->inflight_packets--;
|
||||
if (inf_pkt->last_link->send_blocked_inflight && inf_pkt->last_link->inflight_bytes < inf_pkt->last_link->inflight_lim_bytes)
|
||||
loadbalancer_link_ready(inf_pkt->last_link);
|
||||
}
|
||||
|
||||
// Always add to the CURRENT link (first send or retransmission)
|
||||
@@ -1175,8 +1182,9 @@ void etcp_output_try_assembly(struct ETCP_CONN* etcp) {
|
||||
// Add to output_queue using the same ETCP_FRAGMENT structure
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] moving packet id=%u to output_queue (qlen=%d)", etcp->log_name,
|
||||
next_expected_id, etcp->output_queue->count);
|
||||
uint16_t pkt_len = rx_pkt->ll.len;
|
||||
if (queue_data_put(etcp->output_queue, (struct ll_entry*)rx_pkt) == 0) {
|
||||
delivered_bytes += rx_pkt->ll.len;
|
||||
delivered_bytes += pkt_len;
|
||||
delivered_count++;
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to add packet id=%u to output_queue", etcp->log_name,
|
||||
|
||||
+3
-1
@@ -13,6 +13,7 @@ extern "C" {
|
||||
|
||||
#include "pkt_normalizer.h"
|
||||
|
||||
struct stcp_link; // forward declaration
|
||||
// In struct ETCP_CONN, add:
|
||||
//struct pn_pair* normalizer;
|
||||
|
||||
@@ -104,6 +105,7 @@ struct ETCP_CONN {
|
||||
// Links (channels) - linked list
|
||||
struct ETCP_LINK* links;
|
||||
struct ETCP_LINK* last_rr_link; // последний линк, выбранный round-robin
|
||||
struct stcp_link *transport_link; // non-NULL for TCP connections
|
||||
|
||||
// Crypto and state
|
||||
struct secure_channel crypto_ctx;
|
||||
@@ -131,7 +133,7 @@ struct ETCP_CONN {
|
||||
|
||||
|
||||
// IDs and state
|
||||
uint32_t next_tx_id; // Next TX ID
|
||||
uint32_t next_tx_id; // ID для добавления в очередь отправки (с этим id будет добавлен следующий пакет)
|
||||
uint32_t last_rx_id; // Last received ID
|
||||
uint32_t last_delivered_id; // Last delivered to output_queue
|
||||
|
||||
|
||||
+31
-163
@@ -1,197 +1,65 @@
|
||||
/**
|
||||
* @file etcp_api.c
|
||||
* @brief Реализация API для приёма-передачи пакетов через ETCP (per-instance bindings)
|
||||
* @brief ETCP API — send/bind/recv dispatch
|
||||
*/
|
||||
|
||||
#include "etcp_api.h"
|
||||
#include "etcp.h"
|
||||
#include "pkt_normalizer.h"
|
||||
#include "utun_instance.h"
|
||||
#include "stcp_link.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include <string.h>
|
||||
#include "route_lib.h"
|
||||
#include "route_bgp.h"
|
||||
|
||||
#define DEBUG_CATEGORY_ETCP_API 1
|
||||
#define DEBUG_CATEGORY_ETCP_API DEBUG_CATEGORY_ETCP
|
||||
|
||||
#define ETCP_MAX_BINDINGS 256 /**< Максимальное количество bindings (по одному на каждый ID 0-255) */
|
||||
|
||||
// после создания подключения надо выждать conn_ready. только после этого можно передавать сообщения. Иначе сообщения могут потеряться.
|
||||
// conn_ready может вызваться несколько раз (после каждого переподключения - например когда удаленный узел перегрузился или возникла неустранимая ошибка).
|
||||
void etcp_conn_set_ready_cbk(struct ETCP_CONN* etcp, etcp_cbk_fn callback_fn, void* arg) {
|
||||
if (!etcp) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "NULL conn"); return; }
|
||||
etcp->ready_cbk = callback_fn;
|
||||
etcp->ready_arg = arg;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "conn=%s cbk=%p arg=%p", etcp->log_name, (void*)callback_fn, arg);
|
||||
void etcp_conn_set_ready_cbk(struct ETCP_CONN* e, etcp_cbk_fn fn, void* arg) {
|
||||
if (e) { e->ready_cbk = fn; e->ready_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_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_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_conn_set_down_cbk(struct ETCP_CONN* e, etcp_cbk_fn fn, void* arg) {
|
||||
if (e) { e->down_cbk = fn; e->down_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);
|
||||
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; }
|
||||
}
|
||||
|
||||
int etcp_bind(struct UTUN_INSTANCE* inst, uint8_t id, etcp_recv_fn callback) {
|
||||
if (!inst) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_bind: NULL instance");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((unsigned)id >= ETCP_MAX_BINDINGS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Invalid ID %u (max=%d)", id, ETCP_MAX_BINDINGS);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!callback) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_bind: NULL callback for id=%u", id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (inst->api_bindings.callbacks[id] != NULL) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Overwriting existing callback for id=%u", id);
|
||||
}
|
||||
if (!inst || !callback || (unsigned)id >= ETCP_MAX_BINDINGS) return -1;
|
||||
inst->api_bindings.callbacks[id] = callback;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Bound id=%u → callback=%p", id, (void*)callback);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int etcp_unbind(struct UTUN_INSTANCE* inst, uint8_t id) {
|
||||
if (!inst) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: NULL instance");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((unsigned)id >= ETCP_MAX_BINDINGS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: Invalid ID %u", id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (inst->api_bindings.callbacks[id] == NULL) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: ID %u not bound", id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!inst || (unsigned)id >= ETCP_MAX_BINDINGS) return -1;
|
||||
inst->api_bindings.callbacks[id] = NULL;
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: Unbound ID %u for instance %p", id, (void*)inst);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int etcp_send(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "etcp_send enter conn=%p entry=%p", conn, entry);
|
||||
if (!conn) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: NULL connection");
|
||||
return -1;
|
||||
}
|
||||
if (!entry) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: NULL entry");
|
||||
return -1;
|
||||
}
|
||||
if (!conn->normalizer) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: Connection has no normalizer");
|
||||
return -1;
|
||||
}
|
||||
if (conn->initialized==0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: Connection is not initialized");
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct PKTNORM* pn = conn->normalizer;
|
||||
if (!pn || !pn->input) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: pn=%p or input null", pn);
|
||||
return -1;
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_send: queued to [%s]", conn->log_name);
|
||||
int result = queue_data_put(pn->input, entry);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_NORMALIZER, "etcp_send after put result=%d", result);
|
||||
if (result != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_send: queue_data_put failed");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
if (!conn || !entry) 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);
|
||||
}
|
||||
|
||||
void etcp_int_recv(struct ll_queue* queue, void* arg) {
|
||||
struct ETCP_CONN* conn = (struct ETCP_CONN*)arg;
|
||||
|
||||
if (!queue || !conn) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Invalid arguments (queue=%p, conn=%p)",
|
||||
(void*)queue, (void*)conn);
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем элемент из очереди
|
||||
struct ll_entry* entry = queue_data_get(queue);
|
||||
if (!entry) {
|
||||
// Очередь пуста - это нормально, просто возобновляем коллбэк
|
||||
queue_resume_callback(queue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем что есть данные
|
||||
if (!entry->dgram || entry->len == 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Empty packet received");
|
||||
queue_entry_free(entry);
|
||||
queue_dgram_free(entry);
|
||||
queue_resume_callback(queue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем ID пакета (первый байт кодограммы)
|
||||
uint8_t id = entry->dgram[0];
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Received packet with ID %u from conn [%s] %p",
|
||||
id, conn->log_name, (void*)conn);
|
||||
|
||||
// Получаем instance из соединения
|
||||
if (!queue || !conn) return;
|
||||
struct ll_entry* e = queue_data_get(queue);
|
||||
if (!e) { queue_resume_callback(queue); return; }
|
||||
if (!e->dgram || e->len == 0) { queue_dgram_free(e); queue_entry_free(e); queue_resume_callback(queue); return; }
|
||||
uint8_t id = e->dgram[0];
|
||||
struct UTUN_INSTANCE* inst = conn->instance;
|
||||
if (!inst) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Connection has no instance");
|
||||
queue_entry_free(entry);
|
||||
queue_dgram_free(entry);
|
||||
queue_resume_callback(queue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ищем binding для этого ID в instance
|
||||
if (inst->api_bindings.callbacks[id] != NULL) {
|
||||
// Вызываем коллбэк
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Delivering packet ID %u to callback %p",
|
||||
id, (void*)inst->api_bindings.callbacks[id]);
|
||||
inst->api_bindings.callbacks[id](conn, entry);
|
||||
} else {
|
||||
// Нет binding для этого ID - по умолчанию считаем что это данные (ID=0)
|
||||
if (inst->api_bindings.callbacks[0] != NULL) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_recv: No binding for ID %u, routing to default (ID=0)", id);
|
||||
inst->api_bindings.callbacks[0](conn, entry);
|
||||
} else {
|
||||
// Нет default binding - освобождаем пакет
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_recv: No binding for ID %u and no default handler, dropping packet", id);
|
||||
queue_entry_free(entry);
|
||||
queue_dgram_free(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Возобновляем коллбэк для получения следующего пакета
|
||||
if (!inst) { queue_dgram_free(e); queue_entry_free(e); queue_resume_callback(queue); return; }
|
||||
if (inst->api_bindings.callbacks[id])
|
||||
inst->api_bindings.callbacks[id](conn, e);
|
||||
else if (inst->api_bindings.callbacks[0])
|
||||
inst->api_bindings.callbacks[0](conn, e);
|
||||
else { queue_dgram_free(e); queue_entry_free(e); }
|
||||
queue_resume_callback(queue);
|
||||
}
|
||||
+5
-1
@@ -6,6 +6,8 @@
|
||||
* - etcp_send() - отправить пакет в очередь normalizer
|
||||
* - etcp_bind() - подписаться на пакеты с определенным ID
|
||||
* - etcp_int_recv() - коллбэк для сбора пакетов из всех подключений
|
||||
* !!! для обычной отправки-приёма между узлами используем более универсальный модушь etcp_router.
|
||||
* это в первую очередь - апи для использования etcp-router-ом
|
||||
*
|
||||
* Формат кодограмм: <cmd 1 byte> <data ... n bytes>
|
||||
* cmd = 0 - пакет для передачи адресату
|
||||
@@ -28,7 +30,9 @@
|
||||
#define ETCP_ID_TCP_PROXY 0x04 // TCP proxy exit node (server)
|
||||
#define ETCP_ID_UDP_PROXY 0x05 // UDP datagram прокси (client ↔ exit)
|
||||
#define ETCP_ID_ICMP_PROXY 0x06 // ICMP echo прокси (ping через exit)
|
||||
#define ETCP_ID_TCP_PROXY_CLIENT 0x07 // TCP proxy client (клиент)
|
||||
#define ETCP_ID_TCP_PROXY_CLIENT 0x07 // TCP proxy client (клиент)
|
||||
#define ETCP_ID_MSG_TRANSPORT 0x10 // msg_transport — локальный IPC транспорт сообщений
|
||||
#define ETCP_ID_CONN_MGR 0x11 // Connection Manager — management connections
|
||||
|
||||
// Forward declarations
|
||||
struct ETCP_CONN;
|
||||
|
||||
+423
-368
@@ -15,6 +15,7 @@
|
||||
#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"
|
||||
@@ -27,6 +28,22 @@
|
||||
#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);
|
||||
@@ -95,41 +112,27 @@ static void etcp_link_send_init(struct ETCP_LINK* link, uint8_t reset) {
|
||||
|
||||
dgram->link = link;
|
||||
dgram->noencrypt_len = SC_PUBKEY_ENC_SIZE;
|
||||
size_t offset = 0;
|
||||
|
||||
// reset=1: ETCP_INIT_REQUEST (0x02), reset=0: ETCP_INIT_REQUEST_NOINIT (0x04)
|
||||
dgram->data[offset++] = reset ? ETCP_INIT_REQUEST : ETCP_INIT_REQUEST_NOINIT;
|
||||
|
||||
uint64_t node_id = link->etcp->instance->node_id;
|
||||
dgram->data[offset++] = (node_id >> 56) & 0xFF;
|
||||
dgram->data[offset++] = (node_id >> 48) & 0xFF;
|
||||
dgram->data[offset++] = (node_id >> 40) & 0xFF;
|
||||
dgram->data[offset++] = (node_id >> 32) & 0xFF;
|
||||
dgram->data[offset++] = (node_id >> 24) & 0xFF;
|
||||
dgram->data[offset++] = (node_id >> 16) & 0xFF;
|
||||
dgram->data[offset++] = (node_id >> 8) & 0xFF;
|
||||
dgram->data[offset++] = node_id & 0xFF;
|
||||
|
||||
// session_id (4 bytes) - для защиты от ложного reinit
|
||||
uint32_t session_id = link->etcp->session_id;
|
||||
dgram->data[offset++] = (session_id >> 24) & 0xFF;
|
||||
dgram->data[offset++] = (session_id >> 16) & 0xFF;
|
||||
dgram->data[offset++] = (session_id >> 8) & 0xFF;
|
||||
dgram->data[offset++] = session_id & 0xFF;
|
||||
|
||||
dgram->data[offset++] = (link->mtu_local >> 8) & 0xFF;
|
||||
dgram->data[offset++] = link->mtu_local & 0xFF;
|
||||
|
||||
dgram->data[offset++] = (link->keepalive_interval >> 8) & 0xFF;
|
||||
dgram->data[offset++] = link->keepalive_interval & 0xFF;
|
||||
|
||||
dgram->data[offset++] = ((link->recovery_interval/100) >> 8) & 0xFF;
|
||||
dgram->data[offset++] = (link->recovery_interval/100) & 0xFF;
|
||||
|
||||
dgram->data[offset++] = link->local_link_id;
|
||||
dgram->data[offset++] = link->conn ? link->conn->sock_id : 0;
|
||||
dgram->data[offset++] = link->conn ? link->conn->only_local : 0;
|
||||
dgram->data[offset++] = link->conn ? link->conn->type : CFG_SERVER_TYPE_UNKNOWN;
|
||||
struct ETCP_INIT_REQUEST_PKT* req = (struct ETCP_INIT_REQUEST_PKT*)dgram->data;
|
||||
req->code = reset ? ETCP_INIT_REQUEST : ETCP_INIT_REQUEST_NOINIT;
|
||||
*(uint64_t*)req->node_id = htobe64(link->etcp->instance->node_id);
|
||||
*(uint32_t*)req->session_id = htobe32(link->etcp->session_id);
|
||||
*(uint16_t*)req->mtu = htobe16(link->mtu_local);
|
||||
*(uint16_t*)req->keepalive = htobe16(link->keepalive_interval);
|
||||
*(uint16_t*)req->recovery = htobe16(link->recovery_interval / 100);
|
||||
req->link_id = link->local_link_id;
|
||||
req->socket_id = link->conn ? link->conn->sock_id : 0;
|
||||
req->only_local = link->conn ? link->conn->only_local : 0;
|
||||
req->type = link->conn ? link->conn->type : CFG_SERVER_TYPE_UNKNOWN;
|
||||
if (link->conn && link->conn->interface_addr.ss_family == AF_INET) {
|
||||
struct sockaddr_in* sin = (struct sockaddr_in*)&link->conn->interface_addr;
|
||||
memcpy(req->src_ipv4, &sin->sin_addr.s_addr, 4);
|
||||
*(uint16_t*)req->src_port = sin->sin_port;
|
||||
} else {
|
||||
memset(req->src_ipv4, 0, 4);
|
||||
memset(req->src_port, 0, 2);
|
||||
}
|
||||
size_t offset = ETCP_INIT_REQ_V2_SIZE;
|
||||
|
||||
// padding
|
||||
int s = rand() % (link->handshake_maxsize - link->handshake_minsize) + link->handshake_minsize;
|
||||
@@ -162,7 +165,7 @@ static void etcp_link_send_init(struct ETCP_LINK* link, uint8_t reset) {
|
||||
|
||||
dgram->data_len = offset;
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Sending INIT request to link, node_id=%016llx, retry=%d", (unsigned long long)node_id, link->init_retry_count);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Sending INIT request to link, node_id=%016llx, retry=%d", (unsigned long long)link->etcp->instance->node_id, link->init_retry_count);
|
||||
|
||||
// Debug: print remote address before sending
|
||||
if (link->remote_addr.ss_family == AF_INET) {
|
||||
@@ -321,10 +324,11 @@ static void keepalive_timer_cb(void* arg) {
|
||||
}
|
||||
}
|
||||
|
||||
// Adaptive keepalive period
|
||||
// Adaptive keepalive period (only if adaptive enabled)
|
||||
if (link->pkt_sent_since_keepalive)
|
||||
link->ka_period_ms = KA_PERIOD_MIN_MS; // data flowing → reset to 200ms
|
||||
else {
|
||||
link->ka_period_ms = KA_PERIOD_MIN_MS;
|
||||
else if (link->etcp->instance && link->etcp->instance->config &&
|
||||
link->etcp->instance->config->global.keepalive_adaptive) {
|
||||
uint32_t next = (uint32_t)link->ka_period_ms * 105 / 100 + 1;
|
||||
link->ka_period_ms = next > KA_PERIOD_MAX_MS ? KA_PERIOD_MAX_MS : (uint16_t)next;
|
||||
}
|
||||
@@ -736,9 +740,7 @@ void etcp_socket_remove(struct ETCP_SOCKET* conn) {
|
||||
size_t i = 0;
|
||||
while (i < conn->num_channels) {
|
||||
struct ETCP_LINK* l = conn->links[i];
|
||||
etcp_link_close(l);
|
||||
conn->links[i] = NULL; // обнулить после — etcp_link_close обращается к массиву через remove_link
|
||||
i++;
|
||||
etcp_link_close(l); // remove_link inside shifts elements left → next at same i
|
||||
}
|
||||
u_free(conn->links);
|
||||
|
||||
@@ -852,16 +854,8 @@ void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim) {
|
||||
if (new_lim < INFLIGHT_LIM_MIN) new_lim = INFLIGHT_LIM_MIN;
|
||||
if (new_lim > link->etcp->max_inflight) new_lim = link->etcp->max_inflight;
|
||||
|
||||
uint32_t old = link->inflight_lim_bytes;
|
||||
link->inflight_lim_bytes = new_lim;
|
||||
|
||||
if (link->inflight_bytes < new_lim && link->send_blocked_inflight) {// разблокируем линк
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] unblocking link %p (inflight_lim %u -> %u)", link->etcp->log_name, link, old, new_lim);
|
||||
etcp_conn_on_inflight_lim_changed(link->etcp);// сперва обновим inflight в etcp
|
||||
link->send_blocked_inflight = 0;
|
||||
if (!link->shaper_timer) loadbalancer_link_ready(link);// потом вызовем ready -> разблок
|
||||
} else etcp_conn_on_inflight_lim_changed(link->etcp);//
|
||||
|
||||
etcp_conn_on_inflight_lim_changed(link->etcp);
|
||||
}
|
||||
|
||||
void etcp_link_close(struct ETCP_LINK* link) {
|
||||
@@ -1153,6 +1147,306 @@ int etcp_send_ping(struct UTUN_INSTANCE* instance, const uint8_t* peer_pubkey_bi
|
||||
cb, user_arg, user_data, user_data_len);
|
||||
}
|
||||
|
||||
// === Helpers extracted from etcp_connections_read_callback_socket ===
|
||||
|
||||
static int handle_ping(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, const struct sockaddr_storage* addr, const uint8_t* decrypted_pubkey, size_t pkt_len) {
|
||||
if (pkt_len < 22) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "PING too short: pkt_len=%zu from %s", pkt_len, sockaddr_storage_to_str(addr).str);
|
||||
return 7;
|
||||
}
|
||||
uint64_t nonce = be64toh(*(uint64_t*)(pkt->data + 9));
|
||||
uint16_t ulen = be16toh(*(uint16_t*)(pkt->data + 17));
|
||||
const uint8_t* udata = (ulen > 0) ? (pkt->data + 19) : NULL;
|
||||
struct ETCP_DGRAM* resp = u_malloc(PACKET_DATA_SIZE);
|
||||
if (resp) {
|
||||
resp->link = NULL;
|
||||
resp->noencrypt_len = SC_PUBKEY_ENC_SIZE;
|
||||
uint8_t* p = resp->data;
|
||||
*p++ = ETCP_PONG;
|
||||
uint64_t nid = htobe64(e_sock->instance->node_id);
|
||||
memcpy(p, &nid, 8); p += 8;
|
||||
uint64_t n = htobe64(nonce);
|
||||
memcpy(p, &n, 8); p += 8;
|
||||
uint16_t resp_ulen_be = htobe16(ulen);
|
||||
memcpy(p, &resp_ulen_be, 2); p += 2;
|
||||
size_t copied = etcp_build_ping_response_data(udata, ulen, p, PACKET_DATA_SIZE - (p - resp->data) - SC_PUBKEY_ENC_SIZE);
|
||||
p += copied;
|
||||
uint8_t salt[SC_PUBKEY_ENC_SALT_SIZE];
|
||||
random_bytes(salt, sizeof(salt));
|
||||
memcpy(p, salt, SC_PUBKEY_ENC_SALT_SIZE); p += SC_PUBKEY_ENC_SALT_SIZE;
|
||||
uint8_t obfuscated_pubkey[SC_PUBKEY_SIZE];
|
||||
sc_obfuscate_pubkey(salt, decrypted_pubkey, e_sock->instance->my_keys.public_key, obfuscated_pubkey);
|
||||
memcpy(p, obfuscated_pubkey, SC_PUBKEY_SIZE); p += SC_PUBKEY_SIZE;
|
||||
resp->data_len = (uint16_t)(p - resp->data);
|
||||
sc_context_t resp_sc;
|
||||
sc_init_ctx(&resp_sc, &e_sock->instance->my_keys);
|
||||
if (sc_set_peer_public_key(&resp_sc, decrypted_pubkey, SC_PEER_PUBKEY_BIN) == SC_OK) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "PONG send nonce=%016llx to=%s fd=%d",
|
||||
(unsigned long long)nonce, sockaddr_storage_to_str(addr).str, e_sock->fd);
|
||||
etcp_send_ping_raw(resp, e_sock->fd, &resp_sc, addr);
|
||||
}
|
||||
u_free(resp);
|
||||
}
|
||||
memory_pool_free(e_sock->instance->pkt_pool, pkt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_pong(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, const struct sockaddr_storage* addr, size_t pkt_len) {
|
||||
if (pkt_len < 20) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "PONG too short: pkt_len=%zu from %s", pkt_len, sockaddr_storage_to_str(addr).str);
|
||||
return 7;
|
||||
}
|
||||
uint64_t nonce = be64toh(*(uint64_t*)(pkt->data + 9));
|
||||
uint16_t ulen = 0;
|
||||
const uint8_t* udata = NULL;
|
||||
if (pkt->data_len >= 19) {
|
||||
ulen = be16toh(*(uint16_t*)(pkt->data + 17));
|
||||
if (ulen > 0 && pkt->data_len >= 19 + ulen) {
|
||||
udata = pkt->data + 19;
|
||||
}
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "PONG recv nonce=%016llx data_len=%u from=%s socket=%s",
|
||||
(unsigned long long)nonce, (unsigned)pkt->data_len,
|
||||
sockaddr_storage_to_str(addr).str, e_sock->name);
|
||||
struct PING_CONTEXT* ctx = e_sock->instance->pending_pings;
|
||||
struct PING_CONTEXT* prev = NULL;
|
||||
int found = 0;
|
||||
while (ctx) {
|
||||
if (ctx->nonce == nonce) {
|
||||
found = 1;
|
||||
if (prev) prev->next = ctx->next;
|
||||
else e_sock->instance->pending_pings = ctx->next;
|
||||
if (ctx->timeout_timer) {
|
||||
uasync_cancel_timeout(e_sock->instance->ua, ctx->timeout_timer);
|
||||
ctx->timeout_timer = NULL;
|
||||
}
|
||||
uint64_t now = get_time_tb();
|
||||
uint16_t rtt = (now >= ctx->send_time) ? (uint16_t)(now - ctx->send_time) : 0;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "PONG matched nonce=%016llx rtt=%u",
|
||||
(unsigned long long)nonce, (unsigned)rtt);
|
||||
ctx->cb(1, rtt, ctx->arg, nonce, udata, ulen);
|
||||
if (ctx->user_data) u_free(ctx->user_data);
|
||||
u_free(ctx);
|
||||
break;
|
||||
}
|
||||
prev = ctx;
|
||||
ctx = ctx->next;
|
||||
}
|
||||
if (!found) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "PONG nonce=%016llx NOT FOUND in pending (timeout?)",
|
||||
(unsigned long long)nonce);
|
||||
}
|
||||
memory_pool_free(e_sock->instance->pkt_pool, pkt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void send_init_response(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, struct ETCP_LINK* link, struct ETCP_CONN* conn, const struct ETCP_INIT_REQUEST_PKT* req, const struct sockaddr_storage* addr, uint8_t send_reset, uint32_t req_src_ip, uint16_t req_src_port, size_t pkt_len) {
|
||||
struct ETCP_INIT_RESPONSE_PKT* resp = (struct ETCP_INIT_RESPONSE_PKT*)pkt->data;
|
||||
|
||||
// Set response code: 0x03 (with reset) or 0x05 (without reset)
|
||||
// response with init (0x03) only if reinit was actually done on server side
|
||||
if (send_reset != 0) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_CONNECTION, "send init_response with reset");
|
||||
resp->code = ETCP_INIT_RESPONSE; // 0x03 - with reset
|
||||
} else {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_CONNECTION, "send init_response without reset");
|
||||
resp->code = ETCP_INIT_RESPONSE_NOINIT; // 0x05 - without reset
|
||||
}
|
||||
*(uint64_t*)resp->node_id = htobe64(e_sock->instance->node_id);
|
||||
*(uint32_t*)resp->session_id = htobe32(conn->session_id);
|
||||
|
||||
resp->mtu[0]=link->mtu_local>>8;
|
||||
resp->mtu[1]=link->mtu_local;
|
||||
resp->link_id = link->local_link_id;
|
||||
resp->remote_socket_id = req->socket_id;
|
||||
resp->only_local = e_sock->only_local;
|
||||
resp->type = e_sock->type;
|
||||
// Add client's IP:port (so client behind NAT can know its external address)
|
||||
if (addr->ss_family == AF_INET) {
|
||||
struct sockaddr_in *sin = (struct sockaddr_in*)addr;
|
||||
memcpy(resp->peer_ipv4, &sin->sin_addr.s_addr, 4);
|
||||
uint16_t port = ntohs(sin->sin_port);
|
||||
resp->peer_port[0] = port >> 8;
|
||||
resp->peer_port[1] = port & 0xFF;
|
||||
link->nat_ip = sin->sin_addr.s_addr;
|
||||
link->nat_port = port;
|
||||
} else {
|
||||
// For IPv6, set to 0 (not supported for NAT traversal)
|
||||
memset(resp->peer_ipv4, 0, 4);
|
||||
memset(resp->peer_port, 0, 2);
|
||||
link->nat_ip = 0;
|
||||
link->nat_port = 0;
|
||||
}
|
||||
// DIRECT detection: if client reports its own address and it matches observed source → real public IP
|
||||
if (pkt_len >= ETCP_INIT_REQ_V2_SIZE && addr->ss_family == AF_INET && link->nat_ip != 0) {
|
||||
if (req_src_ip != 0 && req_src_ip == link->nat_ip
|
||||
&& req_src_port == link->nat_port
|
||||
&& !is_local_subnet(link->nat_ip))
|
||||
{
|
||||
link->nat_type = NAT_TYPE_DIRECT;
|
||||
link->nat_check_status = NAT_CHECK_EIM;
|
||||
if (link->etcp->instance->bgp) {
|
||||
route_bgp_send_nat_info(link->etcp, link->remote_socket_id,
|
||||
link->nat_ip, link->nat_port, NAT_TYPE_DIRECT);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "DIRECT IP: %s:%u for %s",
|
||||
ip_to_str(&link->nat_ip, AF_INET).str, link->nat_port,
|
||||
link->etcp->log_name);
|
||||
}
|
||||
}
|
||||
pkt->noencrypt_len=0;
|
||||
pkt->link=link;
|
||||
link->recv_keepalive = 1;
|
||||
link->last_recv_local_time = get_time_tb();
|
||||
link->last_recv_timestamp = pkt->timestamp;
|
||||
|
||||
int xoffset=sizeof(struct ETCP_INIT_RESPONSE_PKT);
|
||||
// padding
|
||||
int s = rand() % (link->handshake_maxsize - link->handshake_minsize) + link->handshake_minsize;
|
||||
if (s > (int)(link->mtu)) s = (int)(link->mtu);
|
||||
if (s < 0) s = 0;
|
||||
|
||||
int to_add=s - xoffset - UDP_HDR_SIZE - UDP_SC_HDR_SIZE;
|
||||
if (to_add<0) to_add=0;
|
||||
if (xoffset + to_add > PACKET_DATA_SIZE) { to_add = PACKET_DATA_SIZE - xoffset; if (to_add<0) to_add=0; }
|
||||
|
||||
for (int i=0; i<to_add; i++) pkt->data[xoffset++]=rand();// fill pad
|
||||
// padding end
|
||||
pkt->data_len=xoffset;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "Sending INIT RESPONSE, link=%p, local_link_id=%d, remote_link_id=%d", link, link->local_link_id, link->remote_link_id);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP DEBUG] Send INIT RESPONSE");
|
||||
etcp_encrypt_send(pkt);
|
||||
|
||||
memory_pool_free(e_sock->instance->pkt_pool, pkt);
|
||||
link->initialized = 1;
|
||||
link->link_state = 3;
|
||||
if (link->init_timer) {
|
||||
uasync_cancel_timeout(link->etcp->instance->ua, link->init_timer);
|
||||
link->init_timer = NULL;
|
||||
}
|
||||
if (link->etcp->initialized == 0) {
|
||||
etcp_conn_ready(link->etcp);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "Connection established: log_name=%s socket=%s link_id=%d status=UP", link->etcp->log_name, e_sock->name, link->local_link_id);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] Link %p (local_id=%d) initialized and marked as UP (server)", link->etcp->log_name, link, link->local_link_id);
|
||||
start_keepalive_timer(link);
|
||||
loadbalancer_link_ready(link);
|
||||
// Restart NAT check after link is up (e.g. after address change or reinit)
|
||||
if (link->etcp->instance->bgp) {
|
||||
if (link->nat_check_status < NAT_CHECK_IN_PROGRESS) route_bgp_start_link_nat_check(link->etcp->instance->bgp, link);
|
||||
}
|
||||
}
|
||||
|
||||
static int handle_init_response_client(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, struct ETCP_LINK* link, uint8_t pkt_code, size_t pkt_len) {
|
||||
if (pkt_len < ETCP_INIT_RESP_V1_SIZE) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "INIT_RESPONSE too short: pkt_len=%zu", pkt_len);
|
||||
return 46;
|
||||
}
|
||||
// ETCP_INIT_RESPONSE (0x03) - reset entire ETCP_CONN
|
||||
// ETCP_INIT_RESPONSE_NOINIT (0x05) - no reset
|
||||
struct ETCP_INIT_RESPONSE_PKT* resp = (struct ETCP_INIT_RESPONSE_PKT*)pkt->data;
|
||||
uint64_t server_node_id = be64toh(*(uint64_t*)resp->node_id);
|
||||
uint32_t resp_session_id = be32toh(*(uint32_t*)resp->session_id);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_CONNECTION, "INIT_RESPONSE session_id=%08x", resp_session_id);
|
||||
// Check session_id: ignore response if it doesn't match our session
|
||||
if (resp_session_id != link->etcp->session_id) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONNECTION, "[%s] INIT_RESPONSE session_id mismatch: got %08x, expected %08x, ignoring",
|
||||
link->etcp->log_name, resp_session_id, link->etcp->session_id);
|
||||
memory_pool_free(e_sock->instance->pkt_pool, pkt);
|
||||
return 0;
|
||||
}
|
||||
link->mtu_remote = be16toh(*(uint16_t*)resp->mtu);
|
||||
if (link->mtu_remote > PACKET_DATA_MAX_MTU) link->mtu_remote = PACKET_DATA_MAX_MTU;
|
||||
link->mtu = link->mtu_local < link->mtu_remote ? link->mtu_local : link->mtu_remote;
|
||||
etcp_update_mtu(link->etcp);
|
||||
link->remote_link_id = resp->link_id;
|
||||
link->remote_socket_id = resp->remote_socket_id;
|
||||
link->remote_only_local = resp->only_local;
|
||||
link->remote_type = resp->type;
|
||||
|
||||
// Parse NAT IP:port from response (new format includes 4+2 bytes)
|
||||
if (pkt_len >= ETCP_INIT_RESP_V2_SIZE) {
|
||||
uint32_t new_nat_ip;
|
||||
memcpy(&new_nat_ip, resp->peer_ipv4, 4);
|
||||
uint16_t new_nat_port = be16toh(*(uint16_t*)resp->peer_port);
|
||||
|
||||
// Check if NAT address changed
|
||||
if (link->nat_ip == 0 && link->nat_port == 0) {
|
||||
// First time receiving NAT info
|
||||
link->nat_ip = new_nat_ip;
|
||||
link->nat_port = new_nat_port;
|
||||
struct in_addr addr;
|
||||
addr.s_addr = new_nat_ip;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] NAT address initialized: %s:%u",
|
||||
link->etcp->log_name, ip_to_str(&addr, AF_INET).str, new_nat_port);
|
||||
} else if (link->nat_ip != new_nat_ip || link->nat_port != new_nat_port) {
|
||||
// NAT address changed
|
||||
struct in_addr old_addr, new_addr;
|
||||
old_addr.s_addr = link->nat_ip;
|
||||
new_addr.s_addr = new_nat_ip;
|
||||
|
||||
link->nat_ip = new_nat_ip;
|
||||
link->nat_port = new_nat_port;
|
||||
link->nat_changes_count++;
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] NAT address changed: %s:%u -> %s:%u (change #%u)",
|
||||
link->etcp->log_name, ip_to_str(&old_addr.s_addr, AF_INET).str, link->nat_port, ip_to_str(&new_addr.s_addr, AF_INET).str, new_nat_port,
|
||||
link->nat_changes_count);
|
||||
}
|
||||
|
||||
// Update socket NAT address
|
||||
{
|
||||
struct sockaddr_in* sin = (struct sockaddr_in*)&e_sock->nat_addr;
|
||||
sin->sin_family = AF_INET;
|
||||
sin->sin_addr.s_addr = new_nat_ip;
|
||||
sin->sin_port = htons(new_nat_port);
|
||||
}
|
||||
} else {
|
||||
// Legacy format without NAT info
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "[%s] Received legacy INIT_RESPONSE without NAT info",
|
||||
link->etcp->log_name);
|
||||
}
|
||||
|
||||
|
||||
// DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Received INIT_RESPONSE from server_node_id=%llu, mtu=%d", (unsigned long long)server_node_id, link->mtu);
|
||||
|
||||
link->etcp->peer_node_id = server_node_id; // If not set
|
||||
etcp_update_log_name(link->etcp); // Update log_name with peer_node_id
|
||||
|
||||
link->initialized = 1;// получен init response (client)
|
||||
link->link_state = 3; // connected
|
||||
if (link->init_timer) {
|
||||
uasync_cancel_timeout(link->etcp->instance->ua, link->init_timer);
|
||||
link->init_timer = NULL;
|
||||
}
|
||||
|
||||
if (pkt_code == ETCP_INIT_RESPONSE) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_CONNECTION, "do reinit 3 %p", link->etcp);
|
||||
etcp_conn_reinit(link->etcp);
|
||||
}
|
||||
|
||||
if (link->etcp->initialized == 0) {
|
||||
etcp_conn_ready(link->etcp);
|
||||
}
|
||||
|
||||
loadbalancer_link_ready(link);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] Link %p (local_id=%d) initialized and marked as UP (client): rk=%d lk=%d up=%d ki=%d",
|
||||
link->etcp->log_name, link, link->local_link_id, link->recv_keepalive, link->remote_keepalive, link->link_status, link->keepalive_interval);
|
||||
|
||||
// Start keepalive timer
|
||||
etcp_link_send_keepalive(link);
|
||||
|
||||
start_keepalive_timer(link);
|
||||
|
||||
|
||||
loadbalancer_link_ready(link);
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "etcp client: Link initialized successfully! Server node_id=%016llx, mtu=%d, local_link_id=%d, remote_link_id=%d", (unsigned long long)server_node_id, link->mtu, link->local_link_id, link->remote_link_id);
|
||||
|
||||
memory_pool_free(e_sock->instance->pkt_pool, pkt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_CONNECTION, "");
|
||||
// DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connections_read_callback fd=%d, socket=%p", fd, arg);
|
||||
@@ -1254,116 +1548,28 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
|
||||
|
||||
pkt->data_len=pkt_len-3;
|
||||
pkt->noencrypt_len=0;
|
||||
struct {
|
||||
uint8_t code;
|
||||
uint8_t id[8];
|
||||
uint8_t session_id[4];
|
||||
uint8_t mtu[2];
|
||||
uint8_t keepalive[2];
|
||||
uint8_t recovery[2];
|
||||
uint8_t link_id;
|
||||
uint8_t remote_socket_id;
|
||||
uint8_t only_local;
|
||||
uint8_t type;
|
||||
uint8_t pubkey[SC_PUBKEY_SIZE];
|
||||
} *ack_hdr=(void*)&pkt->data[0];
|
||||
uint64_t peer_id = be64toh(*(uint64_t*)ack_hdr->id);
|
||||
if (ack_hdr->code == ETCP_PING) {
|
||||
if (pkt_len < 22) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "PING too short: pkt_len=%zu from %s", pkt_len, sockaddr_storage_to_str(&addr).str);
|
||||
errorcode=7;
|
||||
goto ec_fr;
|
||||
}
|
||||
uint64_t nonce = be64toh(*(uint64_t*)(pkt->data + 9));
|
||||
uint16_t ulen = be16toh(*(uint16_t*)(pkt->data + 17));
|
||||
const uint8_t* udata = (ulen > 0) ? (pkt->data + 19) : NULL;
|
||||
struct ETCP_DGRAM* resp = u_malloc(PACKET_DATA_SIZE);
|
||||
if (resp) {
|
||||
resp->link = NULL;
|
||||
resp->noencrypt_len = SC_PUBKEY_ENC_SIZE;
|
||||
uint8_t* p = resp->data;
|
||||
*p++ = ETCP_PONG;
|
||||
uint64_t nid = htobe64(e_sock->instance->node_id);
|
||||
memcpy(p, &nid, 8); p += 8;
|
||||
uint64_t n = htobe64(nonce);
|
||||
memcpy(p, &n, 8); p += 8;
|
||||
uint16_t resp_ulen_be = htobe16(ulen);
|
||||
memcpy(p, &resp_ulen_be, 2); p += 2;
|
||||
size_t copied = etcp_build_ping_response_data(udata, ulen, p, PACKET_DATA_SIZE - (p - resp->data) - SC_PUBKEY_ENC_SIZE);
|
||||
p += copied;
|
||||
uint8_t salt[SC_PUBKEY_ENC_SALT_SIZE];
|
||||
random_bytes(salt, sizeof(salt));
|
||||
memcpy(p, salt, SC_PUBKEY_ENC_SALT_SIZE); p += SC_PUBKEY_ENC_SALT_SIZE;
|
||||
uint8_t obfuscated_pubkey[SC_PUBKEY_SIZE];
|
||||
sc_obfuscate_pubkey(salt, decrypted_pubkey, e_sock->instance->my_keys.public_key, obfuscated_pubkey);
|
||||
memcpy(p, obfuscated_pubkey, SC_PUBKEY_SIZE); p += SC_PUBKEY_SIZE;
|
||||
resp->data_len = (uint16_t)(p - resp->data);
|
||||
sc_context_t resp_sc;
|
||||
sc_init_ctx(&resp_sc, &e_sock->instance->my_keys);
|
||||
if (sc_set_peer_public_key(&resp_sc, decrypted_pubkey, SC_PEER_PUBKEY_BIN) == SC_OK) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "PONG send nonce=%016llx to=%s fd=%d",
|
||||
(unsigned long long)nonce, sockaddr_storage_to_str(&addr).str, e_sock->fd);
|
||||
etcp_send_ping_raw(resp, e_sock->fd, &resp_sc, &addr);
|
||||
}
|
||||
u_free(resp);
|
||||
}
|
||||
memory_pool_free(e_sock->instance->pkt_pool, pkt);
|
||||
uint8_t code = pkt->data[0];
|
||||
uint64_t peer_id = be64toh(*(uint64_t*)(pkt->data + 1));
|
||||
if (code == ETCP_PING) {
|
||||
int ret = handle_ping(e_sock, pkt, &addr, decrypted_pubkey, pkt_len);
|
||||
if (ret) { errorcode = ret; goto ec_fr; }
|
||||
return;
|
||||
}
|
||||
if (ack_hdr->code == ETCP_PONG) {
|
||||
if (pkt_len < 20) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "PONG too short: pkt_len=%zu from %s", pkt_len, sockaddr_storage_to_str(&addr).str);
|
||||
errorcode=7;
|
||||
goto ec_fr;
|
||||
}
|
||||
uint64_t nonce = be64toh(*(uint64_t*)(pkt->data + 9));
|
||||
uint16_t ulen = 0;
|
||||
const uint8_t* udata = NULL;
|
||||
if (pkt->data_len >= 19) {
|
||||
ulen = be16toh(*(uint16_t*)(pkt->data + 17));
|
||||
if (ulen > 0 && pkt->data_len >= 19 + ulen) {
|
||||
udata = pkt->data + 19;
|
||||
}
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "PONG recv nonce=%016llx data_len=%u from=%s socket=%s",
|
||||
(unsigned long long)nonce, (unsigned)pkt->data_len,
|
||||
sockaddr_storage_to_str(&addr).str, e_sock->name);
|
||||
struct PING_CONTEXT* ctx = e_sock->instance->pending_pings;
|
||||
struct PING_CONTEXT* prev = NULL;
|
||||
int found = 0;
|
||||
while (ctx) {
|
||||
if (ctx->nonce == nonce) {
|
||||
found = 1;
|
||||
if (prev) prev->next = ctx->next;
|
||||
else e_sock->instance->pending_pings = ctx->next;
|
||||
if (ctx->timeout_timer) {
|
||||
uasync_cancel_timeout(e_sock->instance->ua, ctx->timeout_timer);
|
||||
ctx->timeout_timer = NULL;
|
||||
}
|
||||
uint64_t now = get_time_tb();
|
||||
uint16_t rtt = (now >= ctx->send_time) ? (uint16_t)(now - ctx->send_time) : 0;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "PONG matched nonce=%016llx rtt=%u",
|
||||
(unsigned long long)nonce, (unsigned)rtt);
|
||||
ctx->cb(1, rtt, ctx->arg, nonce, udata, ulen);
|
||||
if (ctx->user_data) u_free(ctx->user_data);
|
||||
u_free(ctx);
|
||||
break;
|
||||
}
|
||||
prev = ctx;
|
||||
ctx = ctx->next;
|
||||
}
|
||||
if (!found) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "PONG nonce=%016llx NOT FOUND in pending (timeout?)",
|
||||
(unsigned long long)nonce);
|
||||
}
|
||||
memory_pool_free(e_sock->instance->pkt_pool, pkt);
|
||||
if (code == ETCP_PONG) {
|
||||
int ret = handle_pong(e_sock, pkt, &addr, pkt_len);
|
||||
if (ret) { errorcode = ret; goto ec_fr; }
|
||||
return;
|
||||
}
|
||||
if (ack_hdr->code!=ETCP_INIT_REQUEST && ack_hdr->code!=ETCP_INIT_REQUEST_NOINIT) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "not an init packet, code=%02x", ack_hdr->code);
|
||||
if (code!=ETCP_INIT_REQUEST && code!=ETCP_INIT_REQUEST_NOINIT) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "not an init packet, code=%02x", code);
|
||||
errorcode=4;
|
||||
goto ec_fr;
|
||||
}// не init
|
||||
if (pkt_len < ETCP_INIT_REQ_V1_SIZE) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "INIT REQUEST too short: pkt_len=%zu from %s", pkt_len, sockaddr_storage_to_str(&addr).str);
|
||||
errorcode=7;
|
||||
goto ec_fr;
|
||||
}
|
||||
|
||||
// Check allowed keys for incoming connections
|
||||
struct global_config *global = &e_sock->instance->config->global;
|
||||
@@ -1389,7 +1595,8 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t session_id = be32toh(*(uint32_t*)ack_hdr->session_id);
|
||||
struct ETCP_INIT_REQUEST_PKT* req = (struct ETCP_INIT_REQUEST_PKT*)pkt->data;
|
||||
uint32_t session_id = be32toh(*(uint32_t*)req->session_id);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "INIT request session_id=%08x", session_id);
|
||||
|
||||
struct ETCP_CONN* conn=e_sock->instance->connections;
|
||||
@@ -1419,15 +1626,15 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
|
||||
|
||||
// Check if link already exists (for CHANNEL_INIT recovery)
|
||||
|
||||
struct ETCP_LINK* existing_link = etcp_link_find_by_remote_id(conn, ack_hdr->link_id);
|
||||
struct ETCP_LINK* existing_link = etcp_link_find_by_remote_id(conn, req->link_id);
|
||||
|
||||
uint8_t send_reset = 0;
|
||||
|
||||
if (existing_link && existing_link->etcp == conn) {// существующий линк
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "[%s] found existing link for id=%d, socket=[%s]", conn->log_name, ack_hdr->link_id, e_sock->name);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "[%s] found existing link for id=%d, socket=[%s]", conn->log_name, req->link_id, e_sock->name);
|
||||
link = existing_link;
|
||||
if (!sockaddr_equal(&link->remote_addr, &addr)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONNECTION, "[%s] IP:port changed for remote_link_id=%d socket:[%s]", conn->log_name, ack_hdr->link_id, e_sock->name);
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONNECTION, "[%s] IP:port changed for remote_link_id=%d socket:[%s]", conn->log_name, req->link_id, e_sock->name);
|
||||
if (link->conn) remove_link(link->conn, link->ip_port_hash); // remove old connection from old socket
|
||||
link->conn=e_sock;
|
||||
memcpy(&link->remote_addr, &addr, sizeof(addr));
|
||||
@@ -1445,10 +1652,10 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
|
||||
}
|
||||
|
||||
// Link exists - reuse it for recovery
|
||||
link->remote_link_id = ack_hdr->link_id;
|
||||
link->remote_socket_id = ack_hdr->remote_socket_id;
|
||||
link->remote_only_local = ack_hdr->only_local;
|
||||
link->remote_type = ack_hdr->type;
|
||||
link->remote_link_id = req->link_id;
|
||||
link->remote_socket_id = req->socket_id;
|
||||
link->remote_only_local = req->only_local;
|
||||
link->remote_type = req->type;
|
||||
|
||||
// For CHANNEL_INIT (0x04): if link already initialized - no reset, otherwise reset
|
||||
// For INIT_REQUEST (0x02): always reset
|
||||
@@ -1469,14 +1676,14 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
|
||||
link->init_timer = NULL;
|
||||
}
|
||||
} else {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "[%s] NO existing link for id=%d, socket=[%s]", conn->log_name, ack_hdr->link_id, e_sock->name);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "[%s] NO existing link for id=%d, socket=[%s]", conn->log_name, req->link_id, e_sock->name);
|
||||
// Create new link
|
||||
link = etcp_link_new(conn, e_sock, &addr, 1);
|
||||
if (!link) { if (new_conn) etcp_connection_close(conn); errorcode=66; DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "etcp_connections_read_callback: failed to create link for connection"); goto ec_fr; }// облом
|
||||
link->remote_link_id = ack_hdr->link_id;
|
||||
link->remote_socket_id = ack_hdr->remote_socket_id;
|
||||
link->remote_only_local = ack_hdr->only_local;
|
||||
link->remote_type = ack_hdr->type;
|
||||
link->remote_link_id = req->link_id;
|
||||
link->remote_socket_id = req->socket_id;
|
||||
link->remote_only_local = req->only_local;
|
||||
link->remote_type = req->type;
|
||||
|
||||
// For new links: check session_id to avoid false reinit
|
||||
if (conn->session_id != session_id) {
|
||||
@@ -1485,103 +1692,21 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
|
||||
conn->session_id = session_id;
|
||||
etcp_conn_reinit(conn);
|
||||
}
|
||||
link->keepalive_interval=(ack_hdr->keepalive[0]<<8) | ack_hdr->keepalive[1];
|
||||
link->recovery_interval=((ack_hdr->recovery[0]<<8) | ack_hdr->recovery[1])*100;// timebase в link, timebase/100 в кодограмме
|
||||
link->keepalive_interval=(req->keepalive[0]<<8) | req->keepalive[1];
|
||||
link->recovery_interval=((req->recovery[0]<<8) | req->recovery[1])*100;// timebase в link, timebase/100 в кодограмме
|
||||
if (link->keepalive_interval < 10) link->keepalive_interval = 10;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_KEEPALIVE, "set keepalive for link=%d", link->keepalive_interval);
|
||||
}
|
||||
|
||||
link->mtu_remote = (ack_hdr->mtu[0] << 8) | ack_hdr->mtu[1];
|
||||
link->mtu_remote = (req->mtu[0] << 8) | req->mtu[1];
|
||||
if (link->mtu_remote > PACKET_DATA_MAX_MTU) link->mtu_remote = PACKET_DATA_MAX_MTU;
|
||||
link->mtu = link->mtu_local < link->mtu_remote ? link->mtu_local : link->mtu_remote;
|
||||
etcp_update_mtu(link->etcp);
|
||||
|
||||
struct {
|
||||
uint8_t code;
|
||||
uint8_t id[8];
|
||||
uint8_t session_id[4];
|
||||
uint8_t mtu[2];
|
||||
uint8_t link_id;
|
||||
uint8_t remote_socket_id;
|
||||
uint8_t only_local;
|
||||
uint8_t type;
|
||||
uint8_t peer_ipv4[4];
|
||||
uint8_t peer_port[2];
|
||||
} *ack_repl_hdr=(void*)&pkt->data[0];
|
||||
uint32_t req_src_ip; memcpy(&req_src_ip, req->src_ipv4, 4);
|
||||
uint16_t req_src_port = be16toh(*(uint16_t*)req->src_port);
|
||||
|
||||
// Set response code: 0x03 (with reset) or 0x05 (without reset)
|
||||
// response with init (0x03) only if reinit was actually done on server side
|
||||
if (send_reset != 0) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_CONNECTION, "send init_response with reset");
|
||||
ack_repl_hdr->code = ETCP_INIT_RESPONSE; // 0x03 - with reset
|
||||
} else {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_CONNECTION, "send init_response without reset");
|
||||
ack_repl_hdr->code = ETCP_INIT_RESPONSE_NOINIT; // 0x05 - without reset
|
||||
}
|
||||
*(uint64_t*)ack_repl_hdr->id = htobe64(e_sock->instance->node_id);
|
||||
*(uint32_t*)ack_repl_hdr->session_id = htobe32(conn->session_id);
|
||||
|
||||
ack_repl_hdr->mtu[0]=link->mtu_local>>8;
|
||||
ack_repl_hdr->mtu[1]=link->mtu_local;
|
||||
ack_repl_hdr->link_id = link->local_link_id;
|
||||
ack_repl_hdr->remote_socket_id = ack_hdr->remote_socket_id;
|
||||
ack_repl_hdr->only_local = e_sock->only_local;
|
||||
ack_repl_hdr->type = e_sock->type;
|
||||
// Add client's IP:port (so client behind NAT can know its external address)
|
||||
if (addr.ss_family == AF_INET) {
|
||||
struct sockaddr_in *sin = (struct sockaddr_in*)&addr;
|
||||
memcpy(ack_repl_hdr->peer_ipv4, &sin->sin_addr.s_addr, 4);
|
||||
uint16_t port = ntohs(sin->sin_port);
|
||||
ack_repl_hdr->peer_port[0] = port >> 8;
|
||||
ack_repl_hdr->peer_port[1] = port & 0xFF;
|
||||
link->nat_ip = sin->sin_addr.s_addr;
|
||||
link->nat_port = port;
|
||||
} else {
|
||||
// For IPv6, set to 0 (not supported for NAT traversal)
|
||||
memset(ack_repl_hdr->peer_ipv4, 0, 4);
|
||||
memset(ack_repl_hdr->peer_port, 0, 2);
|
||||
link->nat_ip = 0;
|
||||
link->nat_port = 0;
|
||||
}
|
||||
pkt->noencrypt_len=0;
|
||||
pkt->link=link;
|
||||
link->recv_keepalive = 1;
|
||||
|
||||
int xoffset=sizeof(*ack_repl_hdr);
|
||||
// padding
|
||||
int s = rand() % (link->handshake_maxsize - link->handshake_minsize) + link->handshake_minsize;
|
||||
if (s > (int)(link->mtu)) s = (int)(link->mtu);
|
||||
if (s < 0) s = 0;
|
||||
|
||||
int to_add=s - xoffset - UDP_HDR_SIZE - UDP_SC_HDR_SIZE;
|
||||
if (to_add<0) to_add=0;
|
||||
if (xoffset + to_add > PACKET_DATA_SIZE) { to_add = PACKET_DATA_SIZE - xoffset; if (to_add<0) to_add=0; }
|
||||
|
||||
for (int i=0; i<to_add; i++) pkt->data[xoffset++]=rand();// fill pad
|
||||
// padding end
|
||||
pkt->data_len=xoffset;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "Sending INIT RESPONSE, link=%p, local_link_id=%d, remote_link_id=%d", link, link->local_link_id, link->remote_link_id);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP DEBUG] Send INIT RESPONSE");
|
||||
etcp_encrypt_send(pkt);
|
||||
|
||||
memory_pool_free(e_sock->instance->pkt_pool, pkt);
|
||||
link->initialized = 1;
|
||||
link->link_state = 3;
|
||||
if (link->init_timer) {
|
||||
uasync_cancel_timeout(link->etcp->instance->ua, link->init_timer);
|
||||
link->init_timer = NULL;
|
||||
}
|
||||
if (link->etcp->initialized == 0) {
|
||||
etcp_conn_ready(link->etcp);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "Connection established: log_name=%s socket=%s link_id=%d status=UP", link->etcp->log_name, e_sock->name, link->local_link_id);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] Link %p (local_id=%d) initialized and marked as UP (server)", link->etcp->log_name, link, link->local_link_id);
|
||||
start_keepalive_timer(link);
|
||||
loadbalancer_link_ready(link);
|
||||
// Restart NAT check after link is up (e.g. after address change or reinit)
|
||||
if (link->etcp->instance->bgp) {
|
||||
if (link->nat_check_status < NAT_CHECK_IN_PROGRESS) route_bgp_start_link_nat_check(link->etcp->instance->bgp, link);
|
||||
}
|
||||
send_init_response(e_sock, pkt, link, conn, req, &addr, send_reset, req_src_ip, req_src_port, pkt_len);
|
||||
return;
|
||||
|
||||
|
||||
@@ -1614,9 +1739,9 @@ process_decrypted:
|
||||
link->total_decrypted += pkt->data_len;
|
||||
|
||||
size_t offset = 0;
|
||||
uint8_t code = pkt->data[offset++];
|
||||
uint8_t pkt_code = pkt->data[offset++];
|
||||
|
||||
if (code == ETCP_KEEPALIVE) {
|
||||
if (pkt_code == ETCP_KEEPALIVE) {
|
||||
if (pkt->data_len >= 3) {
|
||||
uint16_t peer_period = pkt->data[1] | ((uint16_t)pkt->data[2] << 8);
|
||||
link->keepalive_timeout = (uint32_t)peer_period * KA_TIMEOUT_MULT;
|
||||
@@ -1626,118 +1751,10 @@ process_decrypted:
|
||||
return; // KA handled, nothing more to process
|
||||
}
|
||||
|
||||
if (code == ETCP_INIT_RESPONSE || code == ETCP_INIT_RESPONSE_NOINIT) {
|
||||
if (pkt_len < 22) { errorcode = 46; DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "INIT_RESPONSE too short: pkt_len=%zu", pkt_len); goto ec_fr; }
|
||||
// ETCP_INIT_RESPONSE (0x03) - reset entire ETCP_CONN
|
||||
// ETCP_INIT_RESPONSE_NOINIT (0x05) - no reset
|
||||
uint64_t server_node_id = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
server_node_id = (server_node_id << 8) | pkt->data[offset++];
|
||||
}
|
||||
uint32_t resp_session_id = (pkt->data[offset] << 24) | (pkt->data[offset+1] << 16) | (pkt->data[offset+2] << 8) | pkt->data[offset+3];
|
||||
offset += 4;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_CONNECTION, "INIT_RESPONSE session_id=%08x", resp_session_id);
|
||||
// Check session_id: ignore response if it doesn't match our session
|
||||
if (resp_session_id != link->etcp->session_id) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONNECTION, "[%s] INIT_RESPONSE session_id mismatch: got %08x, expected %08x, ignoring",
|
||||
link->etcp->log_name, resp_session_id, link->etcp->session_id);
|
||||
memory_pool_free(e_sock->instance->pkt_pool, pkt);
|
||||
return;
|
||||
}
|
||||
link->mtu_remote = ((uint16_t)pkt->data[offset] << 8) | pkt->data[offset + 1];
|
||||
offset += 2;
|
||||
if (link->mtu_remote > PACKET_DATA_MAX_MTU) link->mtu_remote = PACKET_DATA_MAX_MTU;
|
||||
link->mtu = link->mtu_local < link->mtu_remote ? link->mtu_local : link->mtu_remote;
|
||||
etcp_update_mtu(link->etcp);
|
||||
link->remote_link_id = pkt->data[offset++];
|
||||
link->remote_socket_id = pkt->data[offset++];
|
||||
link->remote_only_local = pkt->data[offset++];
|
||||
link->remote_type = pkt->data[offset++];
|
||||
|
||||
// Parse NAT IP:port from response (new format includes 4+2 bytes)
|
||||
if (pkt_len >= 24) {
|
||||
uint32_t new_nat_ip;
|
||||
memcpy(&new_nat_ip, &pkt->data[offset], 4);
|
||||
offset += 4;
|
||||
uint16_t new_nat_port = (pkt->data[offset] << 8) | pkt->data[offset+1];
|
||||
offset += 2;
|
||||
|
||||
// Check if NAT address changed
|
||||
if (link->nat_ip == 0 && link->nat_port == 0) {
|
||||
// First time receiving NAT info
|
||||
link->nat_ip = new_nat_ip;
|
||||
link->nat_port = new_nat_port;
|
||||
struct in_addr addr;
|
||||
addr.s_addr = new_nat_ip;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] NAT address initialized: %s:%u",
|
||||
link->etcp->log_name, ip_to_str(&addr, AF_INET).str, new_nat_port);
|
||||
} else if (link->nat_ip != new_nat_ip || link->nat_port != new_nat_port) {
|
||||
// NAT address changed
|
||||
struct in_addr old_addr, new_addr;
|
||||
old_addr.s_addr = link->nat_ip;
|
||||
new_addr.s_addr = new_nat_ip;
|
||||
|
||||
link->nat_ip = new_nat_ip;
|
||||
link->nat_port = new_nat_port;
|
||||
link->nat_changes_count++;
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] NAT address changed: %s:%u -> %s:%u (change #%u)",
|
||||
link->etcp->log_name, ip_to_str(&old_addr.s_addr, AF_INET).str, link->nat_port, ip_to_str(&new_addr.s_addr, AF_INET).str, new_nat_port,
|
||||
link->nat_changes_count);
|
||||
}
|
||||
|
||||
// Update socket NAT address
|
||||
{
|
||||
struct sockaddr_in* sin = (struct sockaddr_in*)&e_sock->nat_addr;
|
||||
sin->sin_family = AF_INET;
|
||||
sin->sin_addr.s_addr = new_nat_ip;
|
||||
sin->sin_port = htons(new_nat_port);
|
||||
}
|
||||
} else {
|
||||
// Legacy format without NAT info
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "[%s] Received legacy INIT_RESPONSE without NAT info",
|
||||
link->etcp->log_name);
|
||||
}
|
||||
|
||||
if (offset > pkt_len) { errorcode=13; DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connections_read_callback: packet parsing overflow, offset=%zu, pkt_len=%zu", offset, pkt_len); goto ec_fr; }
|
||||
|
||||
// DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Received INIT_RESPONSE from server_node_id=%llu, mtu=%d", (unsigned long long)server_node_id, link->mtu);
|
||||
|
||||
link->etcp->peer_node_id = server_node_id; // If not set
|
||||
etcp_update_log_name(link->etcp); // Update log_name with peer_node_id
|
||||
|
||||
link->initialized = 1;// получен init response (client)
|
||||
link->link_state = 3; // connected
|
||||
if (link->init_timer) {
|
||||
uasync_cancel_timeout(link->etcp->instance->ua, link->init_timer);
|
||||
link->init_timer = NULL;
|
||||
}
|
||||
|
||||
if (code == ETCP_INIT_RESPONSE) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_CONNECTION, "do reinit 3 %p", link->etcp);
|
||||
etcp_conn_reinit(link->etcp);
|
||||
}
|
||||
|
||||
if (link->etcp->initialized == 0) {
|
||||
etcp_conn_ready(link->etcp);
|
||||
}
|
||||
|
||||
loadbalancer_link_ready(link);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] Link %p (local_id=%d) initialized and marked as UP (client): rk=%d lk=%d up=%d ki=%d",
|
||||
link->etcp->log_name, link, link->local_link_id, link->recv_keepalive, link->remote_keepalive, link->link_status, link->keepalive_interval);
|
||||
|
||||
// Start keepalive timer
|
||||
etcp_link_send_keepalive(link);
|
||||
|
||||
start_keepalive_timer(link);
|
||||
|
||||
|
||||
loadbalancer_link_ready(link);
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "etcp client: Link initialized successfully! Server node_id=%016llx, mtu=%d, local_link_id=%d, remote_link_id=%d", (unsigned long long)server_node_id, link->mtu, link->local_link_id, link->remote_link_id);
|
||||
|
||||
memory_pool_free(e_sock->instance->pkt_pool, pkt);
|
||||
return; // INIT_RESPONSE is handled, no further processing needed
|
||||
if (pkt_code == ETCP_INIT_RESPONSE || pkt_code == ETCP_INIT_RESPONSE_NOINIT) {
|
||||
int ret = handle_init_response_client(e_sock, pkt, link, pkt_code, pkt_len);
|
||||
if (ret) { errorcode = ret; goto ec_fr; }
|
||||
return;
|
||||
}
|
||||
|
||||
if (link->link_state == 2) {// из recovery получен нормальный пакет - восстанавливаем линк в нормальный режим
|
||||
@@ -1829,6 +1846,21 @@ 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;
|
||||
@@ -1971,6 +2003,29 @@ 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;
|
||||
|
||||
+40
-3
@@ -54,6 +54,45 @@ struct ETCP_DGRAM {// пакет (незашифрованный)
|
||||
/* Размер базового ACK: type(1) + count(1) + last_delivered_id(4) + rx_dup_count(2) */
|
||||
#define ETCP_ACK_BASE_SIZE (2 + 4 + 2)
|
||||
|
||||
// --- INIT packet wire formats (inside encrypted payload, before padding) ---
|
||||
|
||||
// INIT REQUEST: client → server
|
||||
struct ETCP_INIT_REQUEST_PKT {
|
||||
uint8_t code; // 0: ETCP_INIT_REQUEST (0x02) или ETCP_INIT_REQUEST_NOINIT (0x04)
|
||||
uint8_t node_id[8]; // 1: sender node_id (big-endian)
|
||||
uint8_t session_id[4]; // 9: session (big-endian)
|
||||
uint8_t mtu[2]; // 13: client MTU (big-endian)
|
||||
uint8_t keepalive[2]; // 15: keepalive interval (big-endian)
|
||||
uint8_t recovery[2]; // 17: recovery interval/100 (big-endian)
|
||||
uint8_t link_id; // 19: client's local link id
|
||||
uint8_t socket_id; // 20: client's socket id
|
||||
uint8_t only_local; // 21: client only_local flag
|
||||
uint8_t type; // 22: client socket type (CFG_SERVER_TYPE_*)
|
||||
// V2 fields (NAT_DIRECT detection):
|
||||
uint8_t src_ipv4[4]; // 23: client interface_addr IPv4 (big-endian, 0 if N/A)
|
||||
uint8_t src_port[2]; // 27: client interface_addr port (big-endian)
|
||||
} __attribute__((packed));
|
||||
#define ETCP_INIT_REQ_V1_SIZE 23 // size without src_ipv4/src_port (backward compat)
|
||||
#define ETCP_INIT_REQ_V2_SIZE 29 // size with src_ipv4/src_port
|
||||
|
||||
// INIT RESPONSE: server → client
|
||||
struct ETCP_INIT_RESPONSE_PKT {
|
||||
uint8_t code; // 0: ETCP_INIT_RESPONSE (0x03) или ETCP_INIT_RESPONSE_NOINIT (0x05)
|
||||
uint8_t node_id[8]; // 1: server node_id (big-endian)
|
||||
uint8_t session_id[4]; // 9: session (big-endian)
|
||||
uint8_t mtu[2]; // 13: server MTU (big-endian)
|
||||
uint8_t link_id; // 15: server's local link id
|
||||
uint8_t remote_socket_id; // 16: echo of client's socket_id
|
||||
uint8_t only_local; // 17: server only_local flag
|
||||
uint8_t type; // 18: server socket type (CFG_SERVER_TYPE_*)
|
||||
// V2 fields:
|
||||
uint8_t peer_ipv4[4]; // 19: client's external NAT address (big-endian)
|
||||
uint8_t peer_port[2]; // 23: client's external NAT port (big-endian)
|
||||
} __attribute__((packed));
|
||||
#define ETCP_INIT_RESP_V1_SIZE 19 // size without peer_ipv4/peer_port
|
||||
#define ETCP_INIT_RESP_V2_SIZE 25 // size with peer_ipv4/peer_port
|
||||
#define ETCP_INIT_RESP_MIN_SIZE ETCP_INIT_RESP_V1_SIZE
|
||||
|
||||
typedef void (*etcp_ping_callback_t)(int success, uint16_t rtt, void* arg, uint64_t nonce,
|
||||
const uint8_t* resp_data, size_t resp_data_len);
|
||||
struct PING_CONTEXT {
|
||||
@@ -106,6 +145,7 @@ struct ETCP_SOCKET {
|
||||
#define NAT_TYPE_UNKNOWN 0
|
||||
#define NAT_TYPE_EIM 1 // Endpoint-Independent Mapping
|
||||
#define NAT_TYPE_STRICT 2 // Address/Restricted or Symmetric
|
||||
#define NAT_TYPE_DIRECT 3 // real public IP, no NAT (detected during INIT handshake)
|
||||
|
||||
// Verified NAT types (published in nodeinfo after server-side NAT detection)
|
||||
// Values above CFG_SERVER_TYPE_PRIVATE to avoid collision with config types
|
||||
@@ -171,9 +211,6 @@ struct ETCP_LINK {
|
||||
uint32_t inflight_packets;
|
||||
uint32_t inflight_lim_bytes;
|
||||
|
||||
/* Биты блокировки отправки по причине (для корректного resume) */
|
||||
unsigned send_blocked_inflight : 1;
|
||||
|
||||
// statistics
|
||||
size_t encrypt_errors;
|
||||
size_t decrypt_errors;
|
||||
|
||||
+1
-2
@@ -161,9 +161,8 @@ void etcp_dump_conn_state(struct ETCP_CONN* conn) {
|
||||
link->keepalive_sent_count > 0 ? "has_sent" : "idle");
|
||||
|
||||
/* inflight (BBR) */
|
||||
DUMP_LINE("BBR: bytes=%u pkts=%u lim=%u blocked=%d mode=%d cycle=%d pacing=%u",
|
||||
DUMP_LINE("BBR: bytes=%u pkts=%u lim=%u mode=%d cycle=%d pacing=%u",
|
||||
link->inflight_bytes, link->inflight_packets, link->inflight_lim_bytes,
|
||||
link->send_blocked_inflight,
|
||||
link->bbr ? link->bbr->mode : -1,
|
||||
link->bbr ? link->bbr->cycle_idx : -1,
|
||||
link->bbr_pacing_rate);
|
||||
|
||||
+6
-18
@@ -57,8 +57,8 @@ struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp) {
|
||||
|
||||
if (available_count == 0) {
|
||||
if (etcp->links)
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] no suitable link found: inf:%s, tmr:%s",
|
||||
etcp->log_name, etcp->links->send_blocked_inflight?"wait":"rdy", etcp->links->shaper_timer?"wait":"rdy");
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] no suitable link found: tmr:%s",
|
||||
etcp->log_name, etcp->links->shaper_timer?"wait":"rdy");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -181,20 +181,8 @@ void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram) {
|
||||
int loadbalancer_link_can_send(struct ETCP_LINK* link) {
|
||||
if (!link) return 0;
|
||||
if (link->burst_active) return 1; // burst bypasses all limits
|
||||
int can = 1;
|
||||
if (link->inflight_bytes >= link->inflight_lim_bytes) {
|
||||
link->send_blocked_inflight = 1;
|
||||
can = 0;
|
||||
} else {
|
||||
link->send_blocked_inflight = 0;
|
||||
}
|
||||
if (link->shaper_timer) {
|
||||
// link->send_blocked_bandwidth = 1;
|
||||
can = 0;
|
||||
} else {
|
||||
// link->send_blocked_bandwidth = 0;
|
||||
}
|
||||
return can;
|
||||
if (link->shaper_timer) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void loadbalancer_link_ready(struct ETCP_LINK* link) {
|
||||
@@ -202,8 +190,8 @@ void loadbalancer_link_ready(struct ETCP_LINK* link) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "invalid link (%p)", link);
|
||||
return;
|
||||
}
|
||||
if (loadbalancer_link_can_send(link) == 0) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "link still blocked");
|
||||
if (link->shaper_timer) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "link still blocked by shaper");
|
||||
return;
|
||||
}
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "link=%p now ready, notifying ETCP_CONN", link);
|
||||
|
||||
+186
-27
@@ -22,7 +22,7 @@ static void router_schedule_ack(struct ETCP_ROUTER_CONN* rconn);
|
||||
static void router_try_assembly(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* conn);
|
||||
static void router_deliver(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* conn, const uint8_t* payload, size_t payload_len);
|
||||
static int router_send_one(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len);
|
||||
static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, uint32_t seq_flags);
|
||||
static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, uint32_t seq_flags, int is_signed);
|
||||
static void router_send_to(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t svc_id, uint32_t seq_flags);
|
||||
static void router_drain_send_q(struct ETCP_ROUTER_CONN* rconn);
|
||||
static void router_send_resume_cb(void* arg);
|
||||
@@ -47,17 +47,18 @@ static struct ETCP_ROUTER_CONN* router_conn_find(struct UTUN_INSTANCE* inst,
|
||||
// ====================================================================
|
||||
|
||||
static int router_send_one(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len) {
|
||||
return router_send_one_flags(rconn, payload, pl_len, 0);
|
||||
return router_send_one_flags(rconn, payload, pl_len, 0, 0);
|
||||
}
|
||||
|
||||
static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, uint32_t seq_flags) {
|
||||
static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, uint32_t seq_flags, int is_signed) {
|
||||
struct UTUN_INSTANCE* inst = rconn->inst;
|
||||
uint32_t seq = (seq_flags & (ROUTER_SEQ_CLOSE_FLAG | ROUTER_SEQ_RST_FLAG))
|
||||
? 0 : rconn->tx_seq++;
|
||||
seq |= seq_flags;
|
||||
size_t total_len = SVC_ROUTE_HDR_SIZE + pl_len;
|
||||
size_t sig_len = is_signed ? SC_SIGN_SIZE : 0;
|
||||
size_t total_len = SVC_ROUTE_HDR_SIZE + pl_len + sig_len;
|
||||
uint8_t* dgram = u_malloc(total_len);
|
||||
if (!dgram) { if (!seq_flags) rconn->tx_seq--; return -1; }
|
||||
if (!dgram) { if (!seq_flags) rconn->tx_seq--; rconn->c_pkts_send_err++; return -1; }
|
||||
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)dgram;
|
||||
hdr->cmd = ETCP_ID_SVC_ROUTE;
|
||||
hdr->dst_node_id = rconn->remote_node_id;
|
||||
@@ -70,27 +71,68 @@ 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 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);
|
||||
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);
|
||||
queue_entry_free(entry); queue_dgram_free(entry); if (!seq_flags) rconn->tx_seq--; return -1;
|
||||
u_free(dgram); if (!seq_flags) rconn->tx_seq--; rconn->c_pkts_send_err++; 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--; rconn->c_pkts_send_err++; 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--; rconn->c_pkts_send_err++; 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--; rconn->c_pkts_send_err++; return -1;
|
||||
}
|
||||
}
|
||||
|
||||
struct ll_entry* entry = queue_entry_new(0);
|
||||
if (!entry) { u_free(dgram); if (!seq_flags) rconn->tx_seq--; rconn->c_pkts_send_err++; return -1; }
|
||||
entry->dgram = dgram;
|
||||
entry->len = total_len;
|
||||
|
||||
rconn->last_dgram_ts = get_current_timestamp();
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "ETCP_SEND: svc_id=%u seq=%u len=%zu inflight=%d → %016llx",
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "ETCP_SEND: svc_id=%u seq=%u len=%zu inflight=%d signed=%d → %016llx",
|
||||
rconn->svc_id, seq, pl_len,
|
||||
(int32_t)(rconn->tx_seq - rconn->tx_acked),
|
||||
is_signed,
|
||||
(unsigned long long)rconn->remote_node_id);
|
||||
return etcp_send(conn, entry);
|
||||
int ret = etcp_send(conn, entry);
|
||||
if (ret != 0) {
|
||||
if (!seq_flags) rconn->tx_seq--;
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
rconn->c_pkts_send_err++;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_send_one: etcp_send failed ret=%d svc_id=%u seq=%u", ret, rconn->svc_id, seq);
|
||||
} else {
|
||||
rconn->c_pkts_sent++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Отправка без conn (RST для неизвестного src)
|
||||
@@ -110,7 +152,11 @@ static void router_send_to(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t svc
|
||||
entry->len = SVC_ROUTE_HDR_SIZE;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router_send_to: seq_flags=0x%08x → %016llx svc_id=%u",
|
||||
seq_flags, (unsigned long long)dst, svc_id);
|
||||
etcp_send(conn, entry);
|
||||
int ret = etcp_send(conn, entry);
|
||||
if (ret != 0) {
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_send_to: etcp_send failed ret=%d", ret);
|
||||
}
|
||||
}
|
||||
|
||||
static void router_send_close_to_service(struct ETCP_ROUTER_CONN* rconn) {
|
||||
@@ -136,7 +182,7 @@ static void router_close_and_notify(struct ETCP_ROUTER_CONN* rconn) {
|
||||
router_send_close_to_service(rconn);
|
||||
// Отправляем CLOSE удалённой стороне
|
||||
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, rconn->remote_node_id);
|
||||
if (conn) router_send_one_flags(rconn, NULL, 0, ROUTER_SEQ_CLOSE_FLAG);
|
||||
if (conn) router_send_one_flags(rconn, NULL, 0, ROUTER_SEQ_CLOSE_FLAG, 0);
|
||||
// Таймеры
|
||||
if (rconn->ack_timer) { uasync_cancel_timeout(inst->ua, rconn->ack_timer); rconn->ack_timer = NULL; }
|
||||
if (rconn->idle_ack_timer) { uasync_cancel_timeout(inst->ua, rconn->idle_ack_timer); rconn->idle_ack_timer = NULL; }
|
||||
@@ -166,8 +212,11 @@ static void router_drain_send_q(struct ETCP_ROUTER_CONN* rconn) {
|
||||
if ((int32_t)(rconn->tx_seq - rconn->tx_acked) >= ROUTER_MAX_INFLIGHT) break;
|
||||
struct ll_entry* e = queue_data_get(rconn->send_q);
|
||||
if (!e) { rconn->send_blocked = 0; break; }
|
||||
router_send_one(rconn, e->dgram, e->len);
|
||||
u_check(e, "drain:after_get", "router");
|
||||
int s_err = router_send_one(rconn, e->dgram, e->len);
|
||||
u_check(e, "drain:after_send", "router");
|
||||
queue_dgram_free(e); queue_entry_free(e);
|
||||
if (s_err != 0 && (int32_t)(rconn->tx_seq - rconn->tx_acked) >= ROUTER_MAX_INFLIGHT) break;
|
||||
}
|
||||
if (rconn->send_blocked && !rconn->send_resume_timer)
|
||||
rconn->send_resume_timer = uasync_set_timeout(rconn->inst->ua,
|
||||
@@ -199,6 +248,7 @@ static int router_enqueue_send(struct ETCP_ROUTER_CONN* rconn, const uint8_t* pa
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router_send_q: queued svc_id=%u inflight=%d send_q=%d",
|
||||
rconn->svc_id, (int32_t)(rconn->tx_seq - rconn->tx_acked), queue_entry_count(rconn->send_q));
|
||||
queue_data_put(rconn->send_q, qe);
|
||||
u_check(qe, "enqueue_send:after_put", "router");
|
||||
rconn->send_blocked = 1;
|
||||
if (!rconn->send_resume_timer)
|
||||
rconn->send_resume_timer = uasync_set_timeout(rconn->inst->ua,
|
||||
@@ -212,8 +262,6 @@ static int router_enqueue_send(struct ETCP_ROUTER_CONN* rconn, const uint8_t* pa
|
||||
|
||||
static void router_ack_do_send(struct ETCP_ROUTER_CONN* rconn) {
|
||||
router_send_ack(rconn);
|
||||
rconn->last_sent_ack_seq = rconn->rx_seq;
|
||||
rconn->last_ack_sent_tb = get_time_tb();
|
||||
}
|
||||
|
||||
static void router_send_ack(struct ETCP_ROUTER_CONN* rconn) {
|
||||
@@ -222,6 +270,7 @@ static void router_send_ack(struct ETCP_ROUTER_CONN* rconn) {
|
||||
if (!conn) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router_ack: no route to %016llx svc_id=%u",
|
||||
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
|
||||
rconn->last_ack_sent_tb = get_time_tb();
|
||||
return;
|
||||
}
|
||||
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)u_malloc(SVC_ROUTE_HDR_SIZE);
|
||||
@@ -240,7 +289,15 @@ static void router_send_ack(struct ETCP_ROUTER_CONN* rconn) {
|
||||
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "ACK_SEND: svc_id=%u rx_seq=%u → %016llx",
|
||||
rconn->svc_id, rconn->rx_seq, (unsigned long long)rconn->remote_node_id);
|
||||
etcp_send(conn, entry);
|
||||
int ret = etcp_send(conn, entry);
|
||||
if (ret != 0) {
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_ack: etcp_send failed ret=%d svc_id=%u rx_seq=%u", ret, rconn->svc_id, rconn->rx_seq);
|
||||
return;
|
||||
}
|
||||
rconn->c_ack_sent++;
|
||||
rconn->last_sent_ack_seq = rconn->rx_seq;
|
||||
rconn->last_ack_sent_tb = get_time_tb();
|
||||
}
|
||||
|
||||
static void router_schedule_ack(struct ETCP_ROUTER_CONN* rconn) {
|
||||
@@ -305,6 +362,7 @@ static void router_deliver(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* con
|
||||
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "router_deliver: svc_id=%u len=%zu from remote=%016llx",
|
||||
rconn->svc_id, payload_len, (unsigned long long)rconn->remote_node_id);
|
||||
rconn->c_pkts_rcvd++;
|
||||
cb(conn, svc_entry);
|
||||
}
|
||||
|
||||
@@ -343,6 +401,56 @@ 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);
|
||||
{
|
||||
struct ETCP_ROUTER_CONN* sr = router_conn_find(inst, hdr->src_node_id, hdr->svc_id);
|
||||
if (sr) sr->c_sign_fail++;
|
||||
}
|
||||
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);
|
||||
|
||||
@@ -361,9 +469,11 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
|
||||
rconn->svc_id, hdr->seq, (int32_t)(rconn->tx_seq - rconn->tx_acked),
|
||||
queue_entry_count(rconn->send_q),
|
||||
(unsigned long long)hdr->src_node_id);
|
||||
rconn->c_ack_recv++;
|
||||
} else {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: stale ACK seq=%u tx_acked=%u from %016llx, ignoring",
|
||||
hdr->seq, rconn->tx_acked, (unsigned long long)hdr->src_node_id);
|
||||
rconn->c_stale_ack++;
|
||||
}
|
||||
rconn->last_dgram_ts = get_current_timestamp();
|
||||
if (rconn->send_blocked) router_drain_send_q(rconn);
|
||||
@@ -376,6 +486,7 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
|
||||
if (!rconn) {
|
||||
rconn = etcp_router_conn_get(inst, hdr->src_node_id, hdr->svc_id);
|
||||
if (!rconn) { queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
rconn->peer_sess_id = (hdr->flags >> ROUTER_SESS_ID_SHIFT) & 0x3;
|
||||
}
|
||||
|
||||
// Обнаружение перезапуска peer'а: новый sess_id или START-флаг
|
||||
@@ -407,12 +518,19 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
|
||||
rconn->last_dgram_ts = get_current_timestamp();
|
||||
uint32_t seq = hdr->seq;
|
||||
|
||||
// Синхронизация после авто-создания/рестарта: принимаем первый пришедший seq
|
||||
if (!rconn->peer_sync_done) {
|
||||
rconn->peer_sync_done = 1;
|
||||
rconn->rx_seq = seq;
|
||||
}
|
||||
|
||||
// Проверка границ (32-bit circular, аналог ETCP MAX_INFLIGHT_SIZE)
|
||||
{
|
||||
int32_t d = (int32_t)(seq - rconn->rx_seq);
|
||||
if (d > ROUTER_MAX_INFLIGHT || d < -ROUTER_MAX_INFLIGHT) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router: seq=%u out of bounds, rx_seq=%u (d=%d), dropping",
|
||||
seq, rconn->rx_seq, d);
|
||||
rconn->c_oob_dropped++;
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
return;
|
||||
}
|
||||
@@ -422,6 +540,7 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
|
||||
if (((int32_t)(rconn->rx_seq - seq) > 0) ||
|
||||
queue_find_data_by_index(rconn->recv_q, &seq)) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router: dup seq=%u rx_seq=%u, dropping", seq, rconn->rx_seq);
|
||||
rconn->c_dup_dropped++;
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
return;
|
||||
}
|
||||
@@ -431,7 +550,7 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
|
||||
if (!qe) { queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
*(uint32_t*)qe->data = seq;
|
||||
qe->dgram = u_malloc(pl_len);
|
||||
if (!qe->dgram) { queue_entry_free(qe); queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
if (!qe->dgram) { queue_dgram_free(qe); queue_entry_free(qe); queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
qe->len = pl_len;
|
||||
memcpy(qe->dgram, pl, pl_len);
|
||||
|
||||
@@ -456,7 +575,11 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
|
||||
}
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "etcp_router: forwarding svc_id=%u → %016llx via %s",
|
||||
hdr->svc_id, (unsigned long long)hdr->dst_node_id, next->log_name);
|
||||
etcp_send(next, entry);
|
||||
int fwd_err = etcp_send(next, entry);
|
||||
if (fwd_err != 0) {
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "etcp_router: transit etcp_send failed ret=%d", fwd_err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -620,13 +743,17 @@ void etcp_router_conn_restart(struct UTUN_INSTANCE* inst, uint64_t remote_node_i
|
||||
}
|
||||
}
|
||||
|
||||
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; }
|
||||
if (rconn->send_resume_timer) { uasync_cancel_timeout(inst->ua, rconn->send_resume_timer); rconn->send_resume_timer = NULL; }
|
||||
|
||||
struct ll_entry* f;
|
||||
if (rconn->send_q) {
|
||||
while ((f = queue_data_get(rconn->send_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
|
||||
while ((f = queue_data_get(rconn->send_q)) != NULL) { u_check(f, "restart:send_q", "router"); queue_dgram_free(f); queue_entry_free(f); }
|
||||
queue_free(rconn->send_q);
|
||||
}
|
||||
if (rconn->recv_q) {
|
||||
while ((f = queue_data_get(rconn->recv_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
|
||||
while ((f = queue_data_get(rconn->recv_q)) != NULL) { u_check(f, "restart:recv_q", "router"); queue_dgram_free(f); queue_entry_free(f); }
|
||||
queue_free(rconn->recv_q);
|
||||
}
|
||||
rconn->send_q = queue_new(inst->ua, 0, 0, 0, "router_send_q");
|
||||
@@ -637,10 +764,19 @@ void etcp_router_conn_restart(struct UTUN_INSTANCE* inst, uint64_t remote_node_i
|
||||
rconn->rx_seq = 0;
|
||||
rconn->tx_acked = 0;
|
||||
rconn->last_sent_ack_seq = 0;
|
||||
rconn->sess_id = (rconn->sess_id + 1) & 0x3;
|
||||
rconn->last_ack_sent_tb = 0;
|
||||
rconn->peer_sess_id = 0;
|
||||
rconn->start_sent = 0;
|
||||
rconn->send_blocked = 0;
|
||||
rconn->peer_sync_done = 0;
|
||||
rconn->c_pkts_sent = 0;
|
||||
rconn->c_pkts_send_err = 0;
|
||||
rconn->c_pkts_rcvd = 0;
|
||||
rconn->c_ack_sent = 0;
|
||||
rconn->c_ack_recv = 0;
|
||||
rconn->c_dup_dropped = 0;
|
||||
rconn->c_oob_dropped = 0;
|
||||
rconn->c_stale_ack = 0;
|
||||
rconn->c_sign_fail = 0;
|
||||
}
|
||||
|
||||
void etcp_router_on_send_ready(struct UTUN_INSTANCE* inst, uint64_t node_id, uint8_t svc_id,
|
||||
@@ -687,8 +823,17 @@ struct ETCP_ROUTER_CONN* etcp_router_conn_get(struct UTUN_INSTANCE* inst,
|
||||
rconn->send_blocked = 0;
|
||||
rconn->send_resume_timer = NULL;
|
||||
rconn->sess_id = 0;
|
||||
rconn->peer_sess_id = 0;
|
||||
rconn->start_sent = 0;
|
||||
rconn->peer_sync_done = 0;
|
||||
rconn->c_pkts_sent = 0;
|
||||
rconn->c_pkts_send_err = 0;
|
||||
rconn->c_pkts_rcvd = 0;
|
||||
rconn->c_ack_sent = 0;
|
||||
rconn->c_ack_recv = 0;
|
||||
rconn->c_dup_dropped = 0;
|
||||
rconn->c_oob_dropped = 0;
|
||||
rconn->c_stale_ack = 0;
|
||||
rconn->c_sign_fail = 0;
|
||||
rconn->recv_q = queue_new(inst->ua, ROUTER_RECVQ_HASH_SIZE, 0, 4, "router_recv_q");
|
||||
if (!rconn->recv_q) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_conn_get: queue_new(recv_q) failed"); queue_entry_free(&rconn->ll); return NULL; }
|
||||
rconn->send_q = queue_new(inst->ua, 0, 0, 0, "router_send_q");
|
||||
@@ -713,6 +858,20 @@ int etcp_router_conn_send(struct ETCP_ROUTER_CONN* rconn,
|
||||
return router_send_one(rconn, data, len);
|
||||
}
|
||||
|
||||
int etcp_router_conn_send_signed(struct ETCP_ROUTER_CONN* rconn,
|
||||
const uint8_t* data, size_t len) {
|
||||
if (!rconn || !data || len == 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_conn_send_signed: invalid args rconn=%p data=%p len=%zu",
|
||||
(void*)rconn, (const void*)data, len);
|
||||
return -1;
|
||||
}
|
||||
if ((int32_t)(rconn->tx_seq - rconn->tx_acked) >= ROUTER_MAX_INFLIGHT) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router_conn_send_signed: inflight full svc_id=%u", rconn->svc_id);
|
||||
return -1;
|
||||
}
|
||||
return router_send_one_flags(rconn, data, len, 0, 1);
|
||||
}
|
||||
|
||||
void etcp_router_conn_close(struct ETCP_ROUTER_CONN* rconn) {
|
||||
router_close_and_notify(rconn);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ struct SVC_ROUTE_HDR {
|
||||
// Биты в flags
|
||||
#define ROUTER_FLAG_START 0x80
|
||||
#define ROUTER_FLAG_RST 0x40
|
||||
#define ROUTER_FLAG_SIGNED 0x08 // пакет содержит Ed25519 подпись (64 байта после payload)
|
||||
#define ROUTER_SESS_ID_SHIFT 4
|
||||
#define ROUTER_SESS_ID_MASK 0x30
|
||||
|
||||
@@ -55,6 +56,17 @@ struct ETCP_ROUTER_CONN {
|
||||
uint8_t sess_id; // наш session id (0-3)
|
||||
uint8_t peer_sess_id; // последний sess_id от peer'а
|
||||
uint8_t start_sent; // 0 = нужно отправить START в первом data
|
||||
uint8_t peer_sync_done; // 0 = rx_seq ещё не синхронизирован с удалённым seq (авто-создание/рестарт)
|
||||
|
||||
uint32_t c_pkts_sent; // успешные отправки данных
|
||||
uint32_t c_pkts_send_err; // ошибки отправки
|
||||
uint32_t c_pkts_rcvd; // получено и доставлено данных
|
||||
uint32_t c_ack_sent; // отправлено ACK
|
||||
uint32_t c_ack_recv; // получено ACK (последовательных)
|
||||
uint32_t c_dup_dropped; // дропнуто дубликатов seq
|
||||
uint32_t c_oob_dropped; // дропнуто out-of-bounds seq
|
||||
uint32_t c_stale_ack; // устаревших ACK
|
||||
uint32_t c_sign_fail; // signature verification failures
|
||||
};
|
||||
|
||||
#define ROUTER_CONN_HASH_SIZE 256
|
||||
@@ -96,6 +108,10 @@ struct ETCP_ROUTER_CONN* etcp_router_conn_get(struct UTUN_INSTANCE* inst,
|
||||
int etcp_router_conn_send(struct ETCP_ROUTER_CONN* rconn,
|
||||
const uint8_t* data, size_t len);
|
||||
|
||||
// Отправить данные с Ed25519 подписью (аналог etcp_router_conn_send с флагом ROUTER_FLAG_SIGNED)
|
||||
int etcp_router_conn_send_signed(struct ETCP_ROUTER_CONN* rconn,
|
||||
const uint8_t* data, size_t len);
|
||||
|
||||
// Закрыть seq-подключение
|
||||
void etcp_router_conn_close(struct ETCP_ROUTER_CONN* rconn);
|
||||
|
||||
|
||||
@@ -0,0 +1,638 @@
|
||||
/*
|
||||
* msg_transport.c — TCP-сервер транспорта сообщений (local IPC → ETCP)
|
||||
*
|
||||
* Принимает TCP-соединения от локальных приложений (протокол msg_transport.h),
|
||||
* привязывает к remote peer и пересылает данные через etcp_router.
|
||||
*/
|
||||
|
||||
#include "msg_transport.h"
|
||||
#include "utun_instance.h"
|
||||
#include "etcp_router.h"
|
||||
#include "etcp.h"
|
||||
#include "secure_channel.h"
|
||||
#include "route_bgp.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
#ifndef DEBUG_CATEGORY_MSGTRANSPORT
|
||||
#define DEBUG_CATEGORY_MSGTRANSPORT DEBUG_CATEGORY_GENERAL
|
||||
#endif
|
||||
|
||||
/* ============================================================================
|
||||
* Внутренние структуры
|
||||
* ============================================================================ */
|
||||
|
||||
struct msg_client {
|
||||
struct msg_client* next;
|
||||
struct msg_transport* transport;
|
||||
socket_t fd;
|
||||
void* socket_id;
|
||||
uint8_t recv_buffer[MSG_MAX_MSG_SIZE];
|
||||
uint16_t recv_len;
|
||||
uint64_t bound_peer_id; /* 0 = не забинден */
|
||||
uint8_t is_localhost; /* 1 = подключение с loopback */
|
||||
};
|
||||
|
||||
struct msg_transport {
|
||||
struct UTUN_INSTANCE* instance;
|
||||
struct UASYNC* ua;
|
||||
socket_t listen_fd;
|
||||
void* listen_socket_id;
|
||||
struct sockaddr_storage bind_addr;
|
||||
struct msg_client* clients;
|
||||
uint32_t client_count;
|
||||
uint32_t max_clients;
|
||||
uint8_t svc_id;
|
||||
struct msg_transport_counters counters;
|
||||
};
|
||||
|
||||
/* ============================================================================
|
||||
* Forward declarations
|
||||
* ============================================================================ */
|
||||
|
||||
static void accept_callback(socket_t fd, void* arg);
|
||||
static void client_read_callback(socket_t fd, void* arg);
|
||||
static void client_except_callback(socket_t fd, void* arg);
|
||||
static void close_client(struct msg_transport* t, struct msg_client* client);
|
||||
|
||||
static void handle_client_data(struct msg_transport* t, struct msg_client* client);
|
||||
static void on_etcp_recv(struct ETCP_CONN* conn, struct ll_entry* entry);
|
||||
|
||||
static int forward_to_peer(struct msg_transport* t, uint64_t dst_peer_id,
|
||||
const uint8_t* data, size_t data_len, int is_signed);
|
||||
static void send_response(struct msg_client* client, uint8_t type, uint64_t id,
|
||||
const uint8_t* data, size_t data_len);
|
||||
static void send_error(struct msg_client* client, uint8_t error_code, const char* msg);
|
||||
|
||||
/* ============================================================================
|
||||
* Инициализация / завершение
|
||||
* ============================================================================ */
|
||||
|
||||
int msg_transport_init(struct msg_transport** out,
|
||||
struct UTUN_INSTANCE* instance,
|
||||
struct UASYNC* ua,
|
||||
struct sockaddr_storage* bind_addr,
|
||||
uint32_t max_clients,
|
||||
uint8_t svc_id) {
|
||||
if (!out || !instance || !ua || !bind_addr) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport_init: invalid args out=%p inst=%p ua=%p addr=%p",
|
||||
(void*)out, (void*)instance, (void*)ua, (void*)bind_addr);
|
||||
return -1;
|
||||
}
|
||||
if (bind_addr->ss_family == 0) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport_init: bind_addr has no family, skipping");
|
||||
*out = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct msg_transport* t = u_calloc(1, sizeof(*t));
|
||||
if (!t) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport_init: u_calloc failed");
|
||||
return -1;
|
||||
}
|
||||
t->instance = instance;
|
||||
t->ua = ua;
|
||||
t->max_clients = max_clients ? max_clients : 8;
|
||||
t->svc_id = svc_id;
|
||||
memcpy(&t->bind_addr, bind_addr, sizeof(*bind_addr));
|
||||
|
||||
int family = bind_addr->ss_family;
|
||||
t->listen_fd = socket(family, SOCK_STREAM, IPPROTO_TCP);
|
||||
|
||||
#ifdef _WIN32
|
||||
if (t->listen_fd == INVALID_SOCKET) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "socket() failed: %d", WSAGetLastError());
|
||||
u_free(t); return -1;
|
||||
}
|
||||
u_long nonblock = 1;
|
||||
ioctlsocket(t->listen_fd, FIONBIO, &nonblock);
|
||||
int reuse = 1;
|
||||
setsockopt(t->listen_fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse));
|
||||
#else
|
||||
if (t->listen_fd < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "socket() failed: %s", strerror(errno));
|
||||
u_free(t); return -1;
|
||||
}
|
||||
int reuse = 1;
|
||||
setsockopt(t->listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
|
||||
int flags = fcntl(t->listen_fd, F_GETFL, 0);
|
||||
if (flags >= 0) fcntl(t->listen_fd, F_SETFL, flags | O_NONBLOCK);
|
||||
#endif
|
||||
|
||||
socklen_t addr_len = (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
|
||||
|
||||
#ifdef _WIN32
|
||||
if (bind(t->listen_fd, (struct sockaddr*)bind_addr, addr_len) == SOCKET_ERROR) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "bind() failed: %d", WSAGetLastError());
|
||||
closesocket(t->listen_fd); u_free(t); return -1;
|
||||
}
|
||||
if (listen(t->listen_fd, 5) == SOCKET_ERROR) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "listen() failed: %d", WSAGetLastError());
|
||||
closesocket(t->listen_fd); u_free(t); return -1;
|
||||
}
|
||||
#else
|
||||
if (bind(t->listen_fd, (struct sockaddr*)bind_addr, addr_len) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "bind() failed: %s", strerror(errno));
|
||||
close(t->listen_fd); u_free(t); return -1;
|
||||
}
|
||||
if (listen(t->listen_fd, 5) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "listen() failed: %s", strerror(errno));
|
||||
close(t->listen_fd); u_free(t); return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
t->listen_socket_id = uasync_add_socket_t(ua, t->listen_fd,
|
||||
accept_callback, NULL, NULL, t);
|
||||
if (!t->listen_socket_id) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "uasync_add_socket_t failed for listen_fd");
|
||||
#ifdef _WIN32
|
||||
closesocket(t->listen_fd);
|
||||
#else
|
||||
close(t->listen_fd);
|
||||
#endif
|
||||
u_free(t); return -1;
|
||||
}
|
||||
|
||||
if (etcp_router_bind(instance, svc_id, on_etcp_recv) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "etcp_router_bind(svc_id=%u) failed", svc_id);
|
||||
uasync_remove_socket_t(ua, t->listen_fd);
|
||||
#ifdef _WIN32
|
||||
closesocket(t->listen_fd);
|
||||
#else
|
||||
close(t->listen_fd);
|
||||
#endif
|
||||
u_free(t); return -1;
|
||||
}
|
||||
|
||||
if (family == AF_INET) {
|
||||
struct sockaddr_in* sin = (struct sockaddr_in*)bind_addr;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport listening on %s:%d svc_id=%u",
|
||||
ip_to_str(&sin->sin_addr, AF_INET).str, ntohs(sin->sin_port), svc_id);
|
||||
} else {
|
||||
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)bind_addr;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport listening on [%s]:%d svc_id=%u",
|
||||
ip_to_str(&sin6->sin6_addr, AF_INET6).str, ntohs(sin6->sin6_port), svc_id);
|
||||
}
|
||||
|
||||
*out = t;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void msg_transport_shutdown(struct msg_transport* t) {
|
||||
if (!t) return;
|
||||
|
||||
while (t->clients) close_client(t, t->clients);
|
||||
|
||||
#ifdef _WIN32
|
||||
if (t->listen_fd != INVALID_SOCKET) {
|
||||
uasync_remove_socket_t(t->ua, t->listen_fd);
|
||||
t->listen_socket_id = NULL;
|
||||
closesocket(t->listen_fd);
|
||||
t->listen_fd = INVALID_SOCKET;
|
||||
}
|
||||
#else
|
||||
if (t->listen_fd >= 0) {
|
||||
uasync_remove_socket_t(t->ua, t->listen_fd);
|
||||
t->listen_socket_id = NULL;
|
||||
close(t->listen_fd);
|
||||
t->listen_fd = -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport shutdown complete svc_id=%u", t->svc_id);
|
||||
memset(t, 0, sizeof(*t));
|
||||
u_free(t);
|
||||
}
|
||||
|
||||
void msg_transport_get_counters(struct msg_transport* t, struct msg_transport_counters* c) {
|
||||
if (t && c) memcpy(c, &t->counters, sizeof(*c));
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* TCP-клиенты: accept / read / close
|
||||
* ============================================================================ */
|
||||
|
||||
static void accept_callback(socket_t fd, void* arg) {
|
||||
struct msg_transport* t = (struct msg_transport*)arg;
|
||||
|
||||
struct sockaddr_storage client_addr;
|
||||
socklen_t addr_len = sizeof(client_addr);
|
||||
|
||||
#ifdef _WIN32
|
||||
socket_t client_fd = accept(fd, (struct sockaddr*)&client_addr, &addr_len);
|
||||
if (client_fd == INVALID_SOCKET) {
|
||||
int err = WSAGetLastError();
|
||||
if (err != WSAEWOULDBLOCK)
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "accept() failed: %d", err);
|
||||
return;
|
||||
}
|
||||
u_long nonblock = 1;
|
||||
ioctlsocket(client_fd, FIONBIO, &nonblock);
|
||||
#else
|
||||
socket_t client_fd = accept(fd, (struct sockaddr*)&client_addr, &addr_len);
|
||||
if (client_fd < 0) {
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK)
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "accept() failed: %s", strerror(errno));
|
||||
return;
|
||||
}
|
||||
int flags = fcntl(client_fd, F_GETFL, 0);
|
||||
if (flags >= 0) fcntl(client_fd, F_SETFL, flags | O_NONBLOCK);
|
||||
#endif
|
||||
|
||||
if (t->client_count >= t->max_clients) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "max_clients=%u reached, rejecting", t->max_clients);
|
||||
#ifdef _WIN32
|
||||
closesocket(client_fd);
|
||||
#else
|
||||
close(client_fd);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
struct msg_client* client = u_calloc(1, sizeof(*client));
|
||||
if (!client) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "u_calloc(msg_client) failed");
|
||||
#ifdef _WIN32
|
||||
closesocket(client_fd);
|
||||
#else
|
||||
close(client_fd);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
client->fd = client_fd;
|
||||
client->transport = t;
|
||||
|
||||
/* Определяем loopback */
|
||||
if (client_addr.ss_family == AF_INET) {
|
||||
struct sockaddr_in* sin = (struct sockaddr_in*)&client_addr;
|
||||
client->is_localhost = (ntohl(sin->sin_addr.s_addr) == 0x7F000001u); /* 127.0.0.1 */
|
||||
} else if (client_addr.ss_family == AF_INET6) {
|
||||
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&client_addr;
|
||||
static const uint8_t loopback_v6[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};
|
||||
client->is_localhost = (memcmp(&sin6->sin6_addr, loopback_v6, 16) == 0);
|
||||
} else {
|
||||
client->is_localhost = 0;
|
||||
}
|
||||
|
||||
client->socket_id = uasync_add_socket_t(t->ua, client_fd,
|
||||
client_read_callback, NULL,
|
||||
client_except_callback, client);
|
||||
if (!client->socket_id) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "uasync_add_socket_t failed for client");
|
||||
u_free(client);
|
||||
#ifdef _WIN32
|
||||
closesocket(client_fd);
|
||||
#else
|
||||
close(client_fd);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
client->next = t->clients;
|
||||
t->clients = client;
|
||||
t->client_count++;
|
||||
t->counters.accepted++;
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport client connected (total=%u)", t->client_count);
|
||||
}
|
||||
|
||||
static void client_read_callback(socket_t fd, void* arg) {
|
||||
struct msg_client* client = (struct msg_client*)arg;
|
||||
struct msg_transport* t = client->transport;
|
||||
|
||||
uint8_t* buf = client->recv_buffer + client->recv_len;
|
||||
size_t buf_space = MSG_MAX_MSG_SIZE - client->recv_len;
|
||||
|
||||
#ifdef _WIN32
|
||||
int received = recv(fd, (char*)buf, (int)buf_space, 0);
|
||||
if (received == SOCKET_ERROR) {
|
||||
int err = WSAGetLastError();
|
||||
if (err != WSAEWOULDBLOCK) {
|
||||
if (err == 10054) { close_client(t, client); return; }
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "recv() error: %d", err);
|
||||
close_client(t, client);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#else
|
||||
ssize_t received = recv(fd, buf, buf_space, 0);
|
||||
if (received < 0) {
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "recv() error: %s", strerror(errno));
|
||||
close_client(t, client);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (received == 0) {
|
||||
close_client(t, client);
|
||||
return;
|
||||
}
|
||||
|
||||
client->recv_len += (uint16_t)received;
|
||||
if (client->recv_len > MSG_MAX_MSG_SIZE) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "recv buffer overflow");
|
||||
close_client(t, client);
|
||||
return;
|
||||
}
|
||||
handle_client_data(t, client);
|
||||
}
|
||||
|
||||
static void client_except_callback(socket_t fd, void* arg) {
|
||||
struct msg_client* client = (struct msg_client*)arg;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "client socket exception");
|
||||
close_client(client->transport, client);
|
||||
(void)fd;
|
||||
}
|
||||
|
||||
static void close_client(struct msg_transport* t, struct msg_client* client) {
|
||||
if (!t || !client) return;
|
||||
|
||||
#ifdef _WIN32
|
||||
if (client->fd != INVALID_SOCKET) {
|
||||
uasync_remove_socket_t(t->ua, client->fd);
|
||||
closesocket(client->fd);
|
||||
}
|
||||
#else
|
||||
if (client->fd >= 0) {
|
||||
uasync_remove_socket_t(t->ua, client->fd);
|
||||
close(client->fd);
|
||||
}
|
||||
#endif
|
||||
|
||||
struct msg_client** curr = &t->clients;
|
||||
while (*curr) {
|
||||
if (*curr == client) { *curr = client->next; break; }
|
||||
curr = &(*curr)->next;
|
||||
}
|
||||
t->client_count--;
|
||||
t->counters.closed++;
|
||||
u_free(client);
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport client disconnected (total=%u)", t->client_count);
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* Обработка входящих сообщений от TCP-клиента
|
||||
* ============================================================================ */
|
||||
|
||||
static void handle_client_data(struct msg_transport* t, struct msg_client* client) {
|
||||
while (client->recv_len >= MSG_HDR_SIZE) {
|
||||
struct msg_header* hdr = (struct msg_header*)client->recv_buffer;
|
||||
|
||||
if (msg_validate_header(hdr) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "invalid header size=%u type=0x%02x", hdr->size, hdr->type);
|
||||
send_error(client, MSG_ERR_INVALID_MSG, "Invalid message header");
|
||||
close_client(t, client);
|
||||
return;
|
||||
}
|
||||
if (client->recv_len < hdr->size) break;
|
||||
|
||||
uint8_t* payload = client->recv_buffer + MSG_HDR_SIZE;
|
||||
uint16_t payload_size = hdr->size - MSG_HDR_SIZE;
|
||||
|
||||
switch (hdr->type) {
|
||||
|
||||
case MSG_CTL_BIND: {
|
||||
uint64_t peer_id = hdr->id;
|
||||
if (peer_id == 0) {
|
||||
send_error(client, MSG_ERR_INVALID_MSG, "BIND with zero peer_id");
|
||||
break;
|
||||
}
|
||||
/* Проверить конфликт: этот peer_id уже занят другим клиентом */
|
||||
struct msg_client* other = t->clients;
|
||||
while (other) {
|
||||
if (other != client && other->bound_peer_id == peer_id) {
|
||||
send_error(client, MSG_ERR_ID_TAKEN, "peer_id already bound by another client");
|
||||
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "BIND peer=%016llx rejected: already bound",
|
||||
(unsigned long long)peer_id);
|
||||
break;
|
||||
}
|
||||
other = other->next;
|
||||
}
|
||||
if (other) break; /* конфликт */
|
||||
client->bound_peer_id = peer_id;
|
||||
send_response(client, MSG_RSP_BIND_OK, peer_id, NULL, 0);
|
||||
t->counters.bind_count++;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "BIND peer=%016llx", (unsigned long long)peer_id);
|
||||
break;
|
||||
}
|
||||
|
||||
case MSG_CTL_UNBIND:
|
||||
client->bound_peer_id = 0;
|
||||
t->counters.unbind_count++;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "UNBIND");
|
||||
break;
|
||||
|
||||
case MSG_CTL_GET_PUBKEY:
|
||||
send_response(client, MSG_RSP_PUBKEY, 0,
|
||||
t->instance->my_keys.public_key, SC_PUBKEY_SIZE);
|
||||
break;
|
||||
|
||||
case MSG_CTL_GET_PRIVKEY:
|
||||
if (!client->is_localhost) {
|
||||
send_error(client, MSG_ERR_ACCESS_DENIED, "private key only available from localhost");
|
||||
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "GET_PRIVKEY denied: not localhost");
|
||||
break;
|
||||
}
|
||||
send_response(client, MSG_RSP_PRIVKEY, 0,
|
||||
t->instance->my_keys.private_key, SC_PRIVKEY_SIZE);
|
||||
break;
|
||||
|
||||
case MSG_CTL_GET_PEER_INFO: {
|
||||
uint64_t peer_id = hdr->id;
|
||||
struct ROUTE_BGP* bgp = t->instance->bgp;
|
||||
if (!bgp) {
|
||||
send_error(client, MSG_ERR_PEER_NOT_FOUND, "BGP not initialized");
|
||||
break;
|
||||
}
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(bgp, peer_id);
|
||||
if (!nq) {
|
||||
send_error(client, MSG_ERR_PEER_NOT_FOUND, "peer not found");
|
||||
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "GET_PEER_INFO: peer=%016llx not found",
|
||||
(unsigned long long)peer_id);
|
||||
break;
|
||||
}
|
||||
uint8_t buf[SC_PUBKEY_SIZE * 2];
|
||||
memcpy(buf, nq->node.public_key, SC_PUBKEY_SIZE);
|
||||
memcpy(buf + SC_PUBKEY_SIZE, nq->node.ed25519_public_key, SC_PUBKEY_SIZE);
|
||||
send_response(client, MSG_RSP_PEER_INFO, peer_id, buf, SC_PUBKEY_SIZE * 2);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "GET_PEER_INFO: peer=%016llx sent pubkey+edpub",
|
||||
(unsigned long long)peer_id);
|
||||
break;
|
||||
}
|
||||
|
||||
case MSG_DATA_SEND: {
|
||||
uint64_t dst = hdr->id ? hdr->id : client->bound_peer_id;
|
||||
if (dst == 0) {
|
||||
send_error(client, MSG_ERR_NOT_BOUND, "Not bound to any peer");
|
||||
break;
|
||||
}
|
||||
if (payload_size == 0) break;
|
||||
int is_signed = (hdr->options & MSG_OPT_SIGNED) ? 1 : 0;
|
||||
if (forward_to_peer(t, dst, payload, payload_size, is_signed) != 0) {
|
||||
send_error(client, MSG_ERR_SEND_FAILED, "Failed to forward message to peer");
|
||||
t->counters.drops_no_route++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "unknown message type 0x%02x from client", hdr->type);
|
||||
send_error(client, MSG_ERR_INVALID_MSG, "Unknown message type");
|
||||
break;
|
||||
}
|
||||
|
||||
uint16_t msg_size = hdr->size;
|
||||
if (client->recv_len > msg_size)
|
||||
memmove(client->recv_buffer, client->recv_buffer + msg_size, client->recv_len - msg_size);
|
||||
client->recv_len -= msg_size;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* Отправка пиру через etcp_router
|
||||
* ============================================================================ */
|
||||
|
||||
static int forward_to_peer(struct msg_transport* t, uint64_t dst_peer_id,
|
||||
const uint8_t* data, size_t data_len, int is_signed) {
|
||||
struct ETCP_ROUTER_CONN* rconn = etcp_router_conn_get(t->instance, dst_peer_id, t->svc_id);
|
||||
if (!rconn) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "forward_to_peer: no route to %016llx", (unsigned long long)dst_peer_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ret;
|
||||
if (is_signed) {
|
||||
ret = etcp_router_conn_send_signed(rconn, data, data_len);
|
||||
} else {
|
||||
ret = etcp_router_conn_send(rconn, data, data_len);
|
||||
}
|
||||
if (ret != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "forward_to_peer: send failed to %016llx (signed=%d)",
|
||||
(unsigned long long)dst_peer_id, is_signed);
|
||||
return -1;
|
||||
}
|
||||
t->counters.msg_sent_to_peer++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* Приём от пира через etcp_router → доставка TCP-клиенту
|
||||
* ============================================================================ */
|
||||
|
||||
static void on_etcp_recv(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
if (!conn || !entry) return;
|
||||
struct UTUN_INSTANCE* inst = conn->instance;
|
||||
if (!inst) { queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
|
||||
struct msg_transport* t = inst->msg_t;
|
||||
if (!t) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "on_etcp_recv: msg_transport not initialized on instance");
|
||||
queue_dgram_free(entry); queue_entry_free(entry); return;
|
||||
}
|
||||
|
||||
uint8_t* payload = entry->dgram + 1;
|
||||
size_t payload_len = entry->len - 1;
|
||||
uint64_t src_node_id = conn->peer_node_id;
|
||||
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_MSGTRANSPORT, "on_etcp_recv: from %016llx len=%zu",
|
||||
(unsigned long long)src_node_id, payload_len);
|
||||
|
||||
/* Ищем клиента привязанного к этому пиру */
|
||||
struct msg_client* client = t->clients;
|
||||
while (client) {
|
||||
if (client->bound_peer_id == src_node_id) break;
|
||||
client = client->next;
|
||||
}
|
||||
|
||||
if (!client) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "on_etcp_recv: no client bound to %016llx, dropping",
|
||||
(unsigned long long)src_node_id);
|
||||
t->counters.drops_no_client++;
|
||||
queue_dgram_free(entry);
|
||||
queue_entry_free(entry);
|
||||
return;
|
||||
}
|
||||
|
||||
t->counters.msg_recv_from_peer++;
|
||||
|
||||
/* Формируем MSG_DATA_RECV */
|
||||
size_t total = MSG_HDR_SIZE + payload_len;
|
||||
uint8_t* buf = u_malloc(total);
|
||||
if (!buf) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "on_etcp_recv: u_malloc failed");
|
||||
queue_dgram_free(entry); queue_entry_free(entry); return;
|
||||
}
|
||||
struct msg_header* hdr = (struct msg_header*)buf;
|
||||
msg_build_header(hdr, (uint16_t)payload_len, MSG_DATA_RECV, src_node_id, 0);
|
||||
memcpy(buf + MSG_HDR_SIZE, payload, payload_len);
|
||||
|
||||
#ifdef _WIN32
|
||||
send(client->fd, (const char*)buf, (int)total, 0);
|
||||
#else
|
||||
send(client->fd, buf, total, 0);
|
||||
#endif
|
||||
u_free(buf);
|
||||
t->counters.msg_sent_to_client++;
|
||||
|
||||
queue_dgram_free(entry);
|
||||
queue_entry_free(entry);
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* Помощники отправки ответов TCP-клиенту
|
||||
* ============================================================================ */
|
||||
|
||||
static void send_response(struct msg_client* client, uint8_t type, uint64_t id,
|
||||
const uint8_t* data, size_t data_len) {
|
||||
size_t total = MSG_HDR_SIZE + data_len;
|
||||
uint8_t* buf = u_malloc(total);
|
||||
if (!buf) return;
|
||||
|
||||
struct msg_header* hdr = (struct msg_header*)buf;
|
||||
msg_build_header(hdr, (uint16_t)data_len, type, id, 0);
|
||||
if (data_len > 0) memcpy(buf + MSG_HDR_SIZE, data, data_len);
|
||||
|
||||
#ifdef _WIN32
|
||||
send(client->fd, (const char*)buf, (int)total, 0);
|
||||
#else
|
||||
send(client->fd, buf, total, 0);
|
||||
#endif
|
||||
u_free(buf);
|
||||
}
|
||||
|
||||
static void send_error(struct msg_client* client, uint8_t error_code, const char* msg) {
|
||||
size_t msg_len = strlen(msg);
|
||||
size_t total = MSG_HDR_SIZE + 1 + msg_len;
|
||||
uint8_t* buf = u_malloc(total);
|
||||
if (!buf) return;
|
||||
|
||||
struct msg_header* hdr = (struct msg_header*)buf;
|
||||
msg_build_header(hdr, (uint16_t)(1 + msg_len), MSG_RSP_ERROR, 0, 0);
|
||||
buf[MSG_HDR_SIZE] = error_code;
|
||||
memcpy(buf + MSG_HDR_SIZE + 1, msg, msg_len);
|
||||
|
||||
#ifdef _WIN32
|
||||
send(client->fd, (const char*)buf, (int)total, 0);
|
||||
#else
|
||||
send(client->fd, buf, total, 0);
|
||||
#endif
|
||||
u_free(buf);
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* msg_transport.h — TCP-сервер транспорта сообщений (local IPC → ETCP)
|
||||
*
|
||||
* Принимает TCP-соединения от локальных приложений.
|
||||
* Каждое соединение привязывается (BIND) к конкретному remote peer node_id.
|
||||
* Сообщения от локального клиента пересылаются пиру через etcp_router,
|
||||
* сообщения от пира доставляются локальному клиенту.
|
||||
*
|
||||
* Формат фрейма на TCP:
|
||||
* [size:2 (uint16_t, native endian)] [type:1] [id:8 (uint64_t)] [options:1] [data...]
|
||||
* size включает само поле size.
|
||||
*/
|
||||
|
||||
#ifndef MSG_TRANSPORT_H
|
||||
#define MSG_TRANSPORT_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "../lib/socket_compat.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ============================================================================
|
||||
* Протокол
|
||||
* ============================================================================ */
|
||||
|
||||
#define MSG_MAX_MSG_SIZE 8192
|
||||
#define MSG_SIGN_SIZE 64 /* Ed25519 */
|
||||
|
||||
/* Options byte bits */
|
||||
#define MSG_OPT_SIGNED 0x01
|
||||
|
||||
/* Типы сообщений */
|
||||
#define MSG_CTL_BIND 0x01 /* GUI→utun: привязать TCP-соединение к пиру id */
|
||||
#define MSG_CTL_UNBIND 0x02 /* GUI→utun: отвязать */
|
||||
#define MSG_CTL_GET_PUBKEY 0x03 /* GUI→utun: запросить публичный ключ */
|
||||
#define MSG_CTL_GET_PRIVKEY 0x04 /* GUI→utun: запросить приватный ключ (только localhost) */
|
||||
#define MSG_CTL_GET_PEER_INFO 0x05 /* GUI→utun: запросить ключи пира id (pubkey + ed25519_pubkey) */
|
||||
#define MSG_DATA_SEND 0x10 /* GUI→utun: отправить сообщение пиру id */
|
||||
#define MSG_DATA_RECV 0x11 /* utun→GUI: получено сообщение от пира id */
|
||||
#define MSG_RSP_BIND_OK 0xE1 /* utun→GUI: BIND успешен */
|
||||
#define MSG_RSP_PUBKEY 0xE2 /* utun→GUI: публичный ключ (32 байта) */
|
||||
#define MSG_RSP_PRIVKEY 0xE3 /* utun→GUI: приватный ключ (32 байта) */
|
||||
#define MSG_RSP_PEER_INFO 0xE4 /* utun→GUI: ключи пира (pubkey+edpub=64 байта) */
|
||||
#define MSG_RSP_ERROR 0xFF /* utun→GUI: ошибка */
|
||||
|
||||
/* Коды ошибок для MSG_RSP_ERROR */
|
||||
#define MSG_ERR_INVALID_MSG 0x01
|
||||
#define MSG_ERR_NOT_BOUND 0x02
|
||||
#define MSG_ERR_NO_ROUTE 0x03
|
||||
#define MSG_ERR_SEND_FAILED 0x04
|
||||
#define MSG_ERR_ACCESS_DENIED 0x07
|
||||
#define MSG_ERR_PEER_NOT_FOUND 0x08
|
||||
#define MSG_ERR_ID_TAKEN 0x06
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct msg_header {
|
||||
uint16_t size; /* полный размер сообщения, включая это поле */
|
||||
uint8_t type; /* тип (MSG_CTL_* / MSG_DATA_* / MSG_RSP_*) */
|
||||
uint64_t id; /* peer node_id */
|
||||
uint8_t options; /* битовые флаги (MSG_OPT_*) */
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
#define MSG_HDR_SIZE sizeof(struct msg_header) /* 12 */
|
||||
|
||||
static inline void msg_build_header(struct msg_header* hdr,
|
||||
uint16_t payload_size,
|
||||
uint8_t msg_type,
|
||||
uint64_t id,
|
||||
uint8_t options) {
|
||||
hdr->size = MSG_HDR_SIZE + payload_size;
|
||||
hdr->type = msg_type;
|
||||
hdr->id = id;
|
||||
hdr->options = options;
|
||||
}
|
||||
|
||||
static inline int msg_validate_header(const struct msg_header* hdr) {
|
||||
if (hdr->size < MSG_HDR_SIZE || hdr->size > MSG_MAX_MSG_SIZE) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* Счётчики (диагностика)
|
||||
* ============================================================================ */
|
||||
|
||||
struct UTUN_INSTANCE;
|
||||
struct UASYNC;
|
||||
|
||||
struct msg_transport;
|
||||
|
||||
struct msg_transport_counters {
|
||||
uint64_t accepted;
|
||||
uint64_t closed;
|
||||
uint64_t bind_count;
|
||||
uint64_t unbind_count;
|
||||
uint64_t msg_sent_to_peer;
|
||||
uint64_t msg_recv_from_peer;
|
||||
uint64_t msg_sent_to_client;
|
||||
uint64_t errors;
|
||||
uint64_t drops_no_client;
|
||||
uint64_t drops_no_route;
|
||||
};
|
||||
|
||||
/* ============================================================================
|
||||
* API
|
||||
* ============================================================================ */
|
||||
|
||||
int msg_transport_init(struct msg_transport** out,
|
||||
struct UTUN_INSTANCE* instance,
|
||||
struct UASYNC* ua,
|
||||
struct sockaddr_storage* bind_addr,
|
||||
uint32_t max_clients,
|
||||
uint8_t svc_id);
|
||||
|
||||
void msg_transport_shutdown(struct msg_transport* t);
|
||||
|
||||
void msg_transport_get_counters(struct msg_transport* t,
|
||||
struct msg_transport_counters* c);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* MSG_TRANSPORT_H */
|
||||
+5
-5
@@ -51,7 +51,7 @@ static void nat_transport_client_tun_out_cb(struct ll_queue* q, void* arg) {
|
||||
if (ret != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT client: etcp_route_send to provider %016llx failed",
|
||||
(unsigned long long)tr->nat_via_node_id);
|
||||
queue_entry_free(new_entry); queue_dgram_free(new_entry);
|
||||
queue_dgram_free(new_entry); queue_entry_free(new_entry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,21 +94,21 @@ static void nat_transport_provider_tun_out_cb(struct ll_queue* q, void* arg) {
|
||||
if (send_ret != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT provider: etcp_route_send back to node %016llx failed",
|
||||
(unsigned long long)entry->src_node_id);
|
||||
queue_entry_free(new_entry); queue_dgram_free(new_entry);
|
||||
queue_dgram_free(new_entry); queue_entry_free(new_entry);
|
||||
}
|
||||
}
|
||||
|
||||
// ETCP_ID_NAT receive via etcp_router: CLIENT gets response, PROVIDER gets request
|
||||
static void nat_transport_etcp_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
if (!conn || !entry || !entry->dgram || entry->len < NAT_SVC_HDR_SIZE) {
|
||||
if (entry) { queue_entry_free(entry); queue_dgram_free(entry); }
|
||||
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
|
||||
return;
|
||||
}
|
||||
struct UTUN_INSTANCE* inst = conn->instance;
|
||||
if (!inst) { queue_entry_free(entry); queue_dgram_free(entry); return; }
|
||||
if (!inst) { queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
struct nat_transport_ctx* tr = &inst->nat_tr;
|
||||
struct eim_nat_ctx* ctx = &inst->nat;
|
||||
if (!ctx->initialized) { queue_entry_free(entry); queue_dgram_free(entry); return; }
|
||||
if (!ctx->initialized) { queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
|
||||
uint64_t src_node_id;
|
||||
memcpy(&src_node_id, entry->dgram + 1, 8);
|
||||
|
||||
@@ -394,11 +394,12 @@ static void pn_unpacker_cb(struct ll_queue* q, void* arg) {
|
||||
ptr += cp;
|
||||
|
||||
if (pn->recvpart->len == pn->recvpart->memlen) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_NORMALIZER, "unpacked dgram (size=%d)", pn->recvpart->len);
|
||||
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP, "NORM->", pn->recvpart->dgram, pn->recvpart->len);
|
||||
uint32_t recv_len = pn->recvpart->len;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_NORMALIZER, "unpacked dgram (size=%d)", recv_len);
|
||||
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP, "NORM->", pn->recvpart->dgram, recv_len);
|
||||
queue_data_put(pn->output, pn->recvpart);
|
||||
pn->out_total_pkts++;
|
||||
pn->out_total_bytes += pn->recvpart->len;
|
||||
pn->out_total_bytes += recv_len;
|
||||
pn->recvpart = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ drop:
|
||||
// Сторона клиента: принять REPLY, доставить echo ответ в TUN
|
||||
// ====================================================================
|
||||
static void client_handle_reply(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
if (entry->len < ICMP_PROXY_HDR_SIZE + 1) { queue_entry_free(entry); queue_dgram_free(entry); return; }
|
||||
if (entry->len < ICMP_PROXY_HDR_SIZE + 1) { queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
uint32_t src_ip;
|
||||
uint32_t orig_src_ip;
|
||||
uint16_t echo_id, echo_seq;
|
||||
@@ -194,7 +194,7 @@ static void client_handle_reply(struct ETCP_CONN* conn, struct ll_entry* entry)
|
||||
// ====================================================================
|
||||
void icmp_proxy_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
if (!entry || !entry->dgram || entry->len < 2) {
|
||||
if (entry) { queue_entry_free(entry); queue_dgram_free(entry); }
|
||||
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
|
||||
return;
|
||||
}
|
||||
uint8_t subcmd = entry->dgram[1];
|
||||
|
||||
@@ -0,0 +1,487 @@
|
||||
// socks_proxy.c — SOCKS5 / HTTP CONNECT proxy (client side)
|
||||
#include "socks_proxy.h"
|
||||
#include "tcp_proxy_server.h"
|
||||
#include "etcp.h"
|
||||
#include "etcp_api.h"
|
||||
#include "etcp_router.h"
|
||||
#include "utun_instance.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/memory_pool.h"
|
||||
#include "../lib/tcp_io.h"
|
||||
#include "../lib/mem.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
static void on_accept_cb(socket_t sock, void* arg);
|
||||
static void on_read_cb(struct ll_queue* q, void* arg);
|
||||
static void on_fin_cb(struct tcp_conn* tc, void* arg);
|
||||
static void on_error_cb(struct tcp_conn* tc, int err, void* arg);
|
||||
static void on_closed_cb(struct tcp_conn* tc, void* arg);
|
||||
static void tx_waiter_cb(struct ll_queue* q, void* arg);
|
||||
|
||||
static int send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd, uint32_t sid, const uint8_t* data, size_t len, int force);
|
||||
|
||||
struct listen_ctx {
|
||||
socket_t listen_sock;
|
||||
void* socket_id;
|
||||
struct UASYNC* ua;
|
||||
struct UTUN_INSTANCE* inst;
|
||||
uint64_t via_node_id;
|
||||
int is_http;
|
||||
struct socks_proxy_conn** conns;
|
||||
int* conn_count;
|
||||
uint32_t* next_stream_id;
|
||||
};
|
||||
|
||||
// ====================================================================
|
||||
// Отправка сообщений через ETCP
|
||||
// ====================================================================
|
||||
static int send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd, uint32_t sid, const uint8_t* data, size_t len, int force) {
|
||||
struct ll_entry* e = queue_entry_new(0);
|
||||
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: queue_entry_new failed subcmd=%02x sid=%08x", subcmd, sid); return -1; }
|
||||
e->dgram = u_malloc(TCP_PROXY_HDR_SIZE + len);
|
||||
if (!e->dgram) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: malloc(%zu) failed", TCP_PROXY_HDR_SIZE + len); queue_entry_free(e); return -1; }
|
||||
e->dgram[0] = ETCP_ID_TCP_PROXY;
|
||||
e->dgram[1] = subcmd;
|
||||
memcpy(e->dgram + 2, &sid, 4);
|
||||
if (len > 0) memcpy(e->dgram + TCP_PROXY_HDR_SIZE, data, len);
|
||||
if (TCP_PROXY_HDR_SIZE + len > UINT16_MAX) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: msg too large len=%zu subcmd=%02x", len, subcmd);
|
||||
queue_dgram_free(e); queue_entry_free(e); return -1;
|
||||
}
|
||||
e->len = (uint16_t)(TCP_PROXY_HDR_SIZE + len);
|
||||
return etcp_route_send(inst, dst, e, force);
|
||||
}
|
||||
|
||||
static int send_connect(struct socks_proxy_conn* c) {
|
||||
uint8_t buf[6];
|
||||
memcpy(buf, c->dest_ip, 4); memcpy(buf + 4, &c->dest_port, 2);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCKS proxy: CONNECT sid=%08x to %d.%d.%d.%d:%d via_node=%016llx %s",
|
||||
c->stream_id, c->dest_ip[0], c->dest_ip[1], c->dest_ip[2], c->dest_ip[3],
|
||||
ntohs(c->dest_port), (unsigned long long)c->via_node_id, c->is_http ? "http" : "socks");
|
||||
return send_msg(c->inst, c->via_node_id, TCP_PROXY_SUBCMD_CONNECT, c->stream_id, buf, 6, 1);
|
||||
}
|
||||
|
||||
static int send_data(struct socks_proxy_conn* c, const uint8_t* data, uint16_t len) {
|
||||
return send_msg(c->inst, c->via_node_id, TCP_PROXY_SUBCMD_DATA, c->stream_id, data, len, 0);
|
||||
}
|
||||
|
||||
static void send_close(struct socks_proxy_conn* c) {
|
||||
if (send_msg(c->inst, c->via_node_id, TCP_PROXY_SUBCMD_CLOSE, c->stream_id, NULL, 0, 1) < 0) c->close_pending = 1;
|
||||
else { c->close_pending = 0; c->close_sent = 1; }
|
||||
}
|
||||
|
||||
static void send_error(struct socks_proxy_conn* c) {
|
||||
if (send_msg(c->inst, c->via_node_id, TCP_PROXY_SUBCMD_ERROR, c->stream_id, NULL, 0, 1) < 0) c->close_pending = 1;
|
||||
else { c->close_pending = 0; c->close_sent = 1; }
|
||||
}
|
||||
|
||||
static void send_fin(struct socks_proxy_conn* c) {
|
||||
send_msg(c->inst, c->via_node_id, TCP_PROXY_SUBCMD_FIN, c->stream_id, NULL, 0, 1);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Запись ответа в TCP клиенту через write_queue
|
||||
// ====================================================================
|
||||
static int write_to_client(struct socks_proxy_conn* c, const uint8_t* data, uint16_t len) {
|
||||
struct ll_entry* e = queue_entry_new_from_pool(c->tc->entry_pool);
|
||||
uint8_t* buf = memory_pool_alloc(c->tc->data_pool);
|
||||
if (!e || !buf) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: write_to_client alloc failed sid=%08x", c->stream_id);
|
||||
if (e) queue_entry_free(e); if (buf) memory_pool_free(c->tc->data_pool, buf);
|
||||
return -1;
|
||||
}
|
||||
if (len > c->tc->data_pool->object_size) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: write_to_client len=%u > pool_sz=%zu sid=%08x",
|
||||
len, c->tc->data_pool->object_size, c->stream_id);
|
||||
queue_entry_free(e); memory_pool_free(c->tc->data_pool, buf);
|
||||
return -1;
|
||||
}
|
||||
memcpy(buf, data, len);
|
||||
e->dgram = buf; e->len = len;
|
||||
queue_data_put(c->tc->write_queue, e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// SOCKS5 handshake
|
||||
// ====================================================================
|
||||
static void process_socks_greeting(struct socks_proxy_conn* c) {
|
||||
if (c->buf_len < 3) return;
|
||||
uint8_t ver = c->buf[0], nmethods = c->buf[1];
|
||||
if (ver != 5 || c->buf_len < (uint16_t)(2 + nmethods)) return;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "socks_proxy: greeting ver=%d nmethods=%d", ver, nmethods);
|
||||
uint8_t reply[] = { 0x05, 0x00 };
|
||||
write_to_client(c, reply, 2);
|
||||
c->buf_len = 0;
|
||||
c->state = SOCKS_STATE_REQUEST;
|
||||
}
|
||||
|
||||
static void process_socks_request(struct socks_proxy_conn* c) {
|
||||
if (c->buf_len < 10) return;
|
||||
uint8_t ver = c->buf[0], cmd = c->buf[1], atyp = c->buf[3];
|
||||
if (ver != 5) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad ver=%d", ver); goto error; }
|
||||
if (cmd != 1) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: unsupported cmd=%d (only CONNECT supported)", cmd); goto error; }
|
||||
|
||||
uint16_t need;
|
||||
if (atyp == 1) need = 10; // IPv4: 4+2=6 more bytes
|
||||
else if (atyp == 3) { // domain: 1+len+2
|
||||
if (c->buf_len < 5) return;
|
||||
need = (uint16_t)(5 + c->buf[4] + 2);
|
||||
}
|
||||
else if (atyp == 4) need = 22; // IPv6: 16+2=18 more
|
||||
else { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: unsupported atyp=%d", atyp); goto error; }
|
||||
|
||||
if (c->buf_len < need) return;
|
||||
|
||||
if (atyp == 1) {
|
||||
memcpy(c->dest_ip, c->buf + 4, 4);
|
||||
memcpy(&c->dest_port, c->buf + 8, 2);
|
||||
} else if (atyp == 4) {
|
||||
// Извлекаем первые 4 байта IPv6 в dest_ip (для упрощения: IPv6 mapped IPv4 или реальный IPv6)
|
||||
memcpy(c->dest_ip, c->buf + 12, 4);
|
||||
memcpy(&c->dest_port, c->buf + 20, 2);
|
||||
// TODO: proper IPv6 support
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: IPv6 unsupported, using last 4 bytes of addr");
|
||||
} else { // atyp == 3 (domain)
|
||||
uint8_t dlen = c->buf[4];
|
||||
char domain[256]; memcpy(domain, c->buf + 5, dlen); domain[dlen] = '\0';
|
||||
memcpy(&c->dest_port, c->buf + 5 + dlen, 2);
|
||||
// resolve domain
|
||||
struct hostent* he = gethostbyname(domain);
|
||||
if (!he || he->h_addrtype != AF_INET) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: DNS failed for %s", domain);
|
||||
goto error;
|
||||
}
|
||||
memcpy(c->dest_ip, he->h_addr, 4);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: resolved %s → %d.%d.%d.%d:%d",
|
||||
domain, c->dest_ip[0], c->dest_ip[1], c->dest_ip[2], c->dest_ip[3], ntohs(c->dest_port));
|
||||
}
|
||||
|
||||
c->buf_len = 0;
|
||||
c->state = SOCKS_STATE_RELAY; // relay immediately, no waiting for first DATA
|
||||
uint8_t reply[] = { 0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
write_to_client(c, reply, 10);
|
||||
if (send_connect(c) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: send_connect failed sid=%08x", c->stream_id);
|
||||
tcp_conn_push_close(c->tc);
|
||||
}
|
||||
return;
|
||||
|
||||
error: {
|
||||
uint8_t err_reply[] = { 0x05, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
write_to_client(c, err_reply, 10);
|
||||
tcp_conn_push_close(c->tc);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// HTTP CONNECT handshake
|
||||
// ====================================================================
|
||||
static void process_http_request(struct socks_proxy_conn* c) {
|
||||
// Ищем "\r\n\r\n" или первую "\r\n" для строки CONNECT
|
||||
char* end = memmem(c->buf, c->buf_len, "\r\n\r\n", 4);
|
||||
if (!end) { end = memmem(c->buf, c->buf_len, "\r\n", 2); if (!end) return; }
|
||||
|
||||
uint16_t line_len = (uint16_t)((uint8_t*)end - c->buf);
|
||||
if (line_len < 8) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: http request too short"); goto error; }
|
||||
|
||||
// Парсим "CONNECT host:port HTTP/1.1"
|
||||
char line[512]; if (line_len > sizeof(line) - 1) line_len = sizeof(line) - 1;
|
||||
memcpy(line, c->buf, line_len); line[line_len] = '\0';
|
||||
|
||||
char host_port[256];
|
||||
if (sscanf(line, "CONNECT %255s HTTP/", host_port) != 1) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad http connect: %s", line);
|
||||
goto error;
|
||||
}
|
||||
|
||||
char* colon = strrchr(host_port, ':');
|
||||
if (!colon) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: no port in %s", host_port); goto error; }
|
||||
*colon = '\0'; int port = atoi(colon + 1);
|
||||
if (port <= 0 || port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad port %d", port); goto error; }
|
||||
|
||||
struct hostent* he = gethostbyname(host_port);
|
||||
if (!he || he->h_addrtype != AF_INET) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: DNS failed for %s", host_port);
|
||||
uint8_t resp[] = "HTTP/1.1 502 Bad Gateway\r\n\r\n";
|
||||
write_to_client(c, resp, (uint16_t)strlen((char*)resp)); tcp_conn_push_close(c->tc); return;
|
||||
}
|
||||
memcpy(c->dest_ip, he->h_addr, 4); c->dest_port = htons((uint16_t)port);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: HTTP CONNECT %s → %d.%d.%d.%d:%d",
|
||||
host_port, c->dest_ip[0], c->dest_ip[1], c->dest_ip[2], c->dest_ip[3], port);
|
||||
|
||||
c->buf_len = 0;
|
||||
c->state = HTTP_STATE_RELAY; // reply immediately, no waiting
|
||||
uint8_t resp[] = "HTTP/1.1 200 Connection Established\r\n\r\n";
|
||||
write_to_client(c, resp, (uint16_t)strlen((char*)resp));
|
||||
if (send_connect(c) < 0) {
|
||||
tcp_conn_push_close(c->tc);
|
||||
}
|
||||
return;
|
||||
|
||||
error: {
|
||||
uint8_t resp[] = "HTTP/1.1 400 Bad Request\r\n\r\n";
|
||||
write_to_client(c, resp, (uint16_t)strlen((char*)resp)); tcp_conn_push_close(c->tc);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// tcp_io read callback — парсинг рукопожатия + релей данных
|
||||
// ====================================================================
|
||||
static void on_read_cb(struct ll_queue* q, void* arg) {
|
||||
struct socks_proxy_conn* c = (struct socks_proxy_conn*)arg;
|
||||
struct ll_entry* e = queue_data_get(q);
|
||||
if (!e) { queue_resume_callback(q); return; }
|
||||
|
||||
if (c->rem_closed) {
|
||||
queue_dgram_free(e); queue_entry_free(e); queue_resume_callback(q); return;
|
||||
}
|
||||
|
||||
if (c->state == SOCKS_STATE_GREETING || c->state == SOCKS_STATE_REQUEST ||
|
||||
c->state == HTTP_STATE_REQUEST) {
|
||||
// накапливаем данные для рукопожатия
|
||||
uint16_t space = sizeof(c->buf) - c->buf_len;
|
||||
if (space < e->len) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: handshake buffer overflow sid=%08x", c->stream_id);
|
||||
memory_pool_free(c->tc->data_pool, e->dgram); queue_entry_free(e);
|
||||
tcp_conn_push_close(c->tc); queue_resume_callback(q); return;
|
||||
}
|
||||
memcpy(c->buf + c->buf_len, e->dgram, e->len);
|
||||
c->buf_len += e->len;
|
||||
memory_pool_free(c->tc->data_pool, e->dgram); queue_entry_free(e);
|
||||
queue_resume_callback(q);
|
||||
|
||||
if (c->is_http) {
|
||||
process_http_request(c);
|
||||
} else {
|
||||
if (c->state == SOCKS_STATE_GREETING) process_socks_greeting(c);
|
||||
if (c->state == SOCKS_STATE_REQUEST) process_socks_request(c);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// RELAY = релей данных в ETCP
|
||||
int ret = send_data(c, e->dgram, e->len);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "SOCKS PROXY SEND sid=%08x len=%u", c->stream_id, e->len);
|
||||
if (ret == 0) {
|
||||
memory_pool_free(c->tc->data_pool, e->dgram); queue_entry_free(e);
|
||||
queue_resume_callback(q);
|
||||
} else {
|
||||
memory_pool_free(c->tc->data_pool, e->dgram); queue_entry_free(e);
|
||||
etcp_router_waiter_register(c->inst, c->via_node_id, &c->tx_waiter, tx_waiter_cb, c);
|
||||
}
|
||||
}
|
||||
|
||||
static void tx_waiter_cb(struct ll_queue* q, void* arg) {
|
||||
(void)q;
|
||||
struct socks_proxy_conn* c = (struct socks_proxy_conn*)arg;
|
||||
if (c->rem_closed || c->close_sent) return;
|
||||
queue_resume_callback(c->tc->read_queue);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// tcp_io: FIN / Error / Closed
|
||||
// ====================================================================
|
||||
static void on_fin_cb(struct tcp_conn* tc, void* arg) {
|
||||
struct socks_proxy_conn* c = (struct socks_proxy_conn*)arg;
|
||||
if (c->rem_closed || c->close_sent) return;
|
||||
if (!tc->fin_local && !tc->write_buf && !tc->write_queue->head) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "socks_proxy: local FIN → relay FIN sid=%08x", c->stream_id);
|
||||
send_fin(c);
|
||||
if (c->fin_remote && !c->close_sent && !c->close_pending) send_close(c);
|
||||
} else {
|
||||
// данные ещё в write_queue, отложим FIN
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "socks_proxy: local FIN deferred (wq=%d wbuf=%s) sid=%08x",
|
||||
tc->write_queue->count, tc->write_buf ? "y" : "n", c->stream_id);
|
||||
tcp_conn_set_flushed(tc, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_error_cb(struct tcp_conn* tc, int err, void* arg) {
|
||||
(void)err;
|
||||
struct socks_proxy_conn* c = (struct socks_proxy_conn*)arg;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: tcp error sid=%08x err=%d", c->stream_id, err);
|
||||
if (!c->close_sent && !c->close_pending) send_error(c);
|
||||
socks_proxy_conn_free(c);
|
||||
}
|
||||
|
||||
static void on_closed_cb(struct tcp_conn* tc, void* arg) {
|
||||
struct socks_proxy_conn* c = (struct socks_proxy_conn*)arg;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "socks_proxy: tcp closed sid=%08x", c->stream_id);
|
||||
if (!c->close_sent && !c->close_pending) send_close(c);
|
||||
socks_proxy_conn_free(c);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Accept callback для слушающего сокета
|
||||
// ====================================================================
|
||||
static void on_accept_cb(socket_t sock, void* arg) {
|
||||
struct listen_ctx* ctx = (struct listen_ctx*)arg;
|
||||
struct sockaddr_in addr; socklen_t alen = sizeof(addr);
|
||||
socket_t csock = accept(sock, (struct sockaddr*)&addr, &alen);
|
||||
if (csock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: accept failed errno=%d", errno); return; }
|
||||
socket_set_nonblocking(csock);
|
||||
|
||||
struct socks_proxy_conn* c = u_calloc(1, sizeof(struct socks_proxy_conn));
|
||||
if (!c) { socket_close_wrapper(csock); DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: u_calloc failed"); return; }
|
||||
c->stream_id = ++(*ctx->next_stream_id);
|
||||
c->is_http = (uint8_t)ctx->is_http;
|
||||
c->ua = ctx->ua; c->inst = ctx->inst; c->via_node_id = ctx->via_node_id;
|
||||
c->state = ctx->is_http ? HTTP_STATE_REQUEST : SOCKS_STATE_GREETING;
|
||||
c->head = ctx->conns; c->count = ctx->conn_count;
|
||||
|
||||
c->tc = tcp_conn_create(ctx->ua, csock, 4096, 4096, 8, 0, 0, on_fin_cb, on_error_cb, c);
|
||||
if (!c->tc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: tcp_conn_create failed"); u_free(c); socket_close_wrapper(csock); return; }
|
||||
c->tc->on_closed = on_closed_cb;
|
||||
queue_set_callback(c->tc->read_queue, on_read_cb, c);
|
||||
queue_set_waiter_defer(c->tc->read_queue, 1);
|
||||
|
||||
c->next = *ctx->conns; *ctx->conns = c; (*ctx->conn_count)++;
|
||||
|
||||
char ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &addr.sin_addr, ip, sizeof(ip));
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: accepted %s conn from %s:%d sid=%08x total=%d",
|
||||
ctx->is_http ? "http" : "socks", ip, ntohs(addr.sin_port), c->stream_id, *ctx->conn_count);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Публичное API для tcp_proxy_client (etcp dispatch)
|
||||
// ====================================================================
|
||||
struct socks_proxy_conn* socks_proxy_find_conn(struct socks_proxy_conn* head, uint32_t stream_id) {
|
||||
struct socks_proxy_conn* c;
|
||||
for (c = head; c; c = c->next) if (c->stream_id == stream_id) return c;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int socks_proxy_handle_etcp(struct socks_proxy_conn** head, int* count,
|
||||
uint32_t stream_id, uint8_t subcmd,
|
||||
const uint8_t* data, size_t data_len) {
|
||||
struct socks_proxy_conn* c = socks_proxy_find_conn(*head, stream_id);
|
||||
if (!c) return 0;
|
||||
|
||||
if (subcmd == TCP_PROXY_SUBCMD_DATA) {
|
||||
if (data_len > 0) {
|
||||
if (data_len > c->tc->data_pool->object_size) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: data_len=%zu > pool_sz=%zu, dropping sid=%08x",
|
||||
data_len, c->tc->data_pool->object_size, stream_id);
|
||||
return 1;
|
||||
}
|
||||
struct ll_entry* e = queue_entry_new_from_pool(c->tc->entry_pool);
|
||||
uint8_t* buf = memory_pool_alloc(c->tc->data_pool);
|
||||
if (e && buf) {
|
||||
memcpy(buf, data, data_len); e->dgram = buf; e->len = (uint16_t)data_len;
|
||||
queue_data_put(c->tc->write_queue, e);
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: handle_data alloc failed sid=%08x", stream_id);
|
||||
if (e) queue_entry_free(e); if (buf) memory_pool_free(c->tc->data_pool, buf);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (subcmd == TCP_PROXY_SUBCMD_CLOSE) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: REM_CLOSED sid=%08x", stream_id);
|
||||
c->rem_closed = 1;
|
||||
etcp_router_waiter_cancel(c->inst, c->via_node_id, &c->tx_waiter);
|
||||
tcp_conn_push_close(c->tc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (subcmd == TCP_PROXY_SUBCMD_ERROR) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: ERROR from exit sid=%08x", stream_id);
|
||||
c->rem_closed = 1;
|
||||
etcp_router_waiter_cancel(c->inst, c->via_node_id, &c->tx_waiter);
|
||||
tcp_conn_push_close(c->tc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (subcmd == TCP_PROXY_SUBCMD_FIN) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: FIN from exit sid=%08x", stream_id);
|
||||
c->fin_remote = 1;
|
||||
tcp_conn_push_fin(c->tc);
|
||||
if (c->tc->fin_local && !c->close_sent && !c->close_pending) send_close(c);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void socks_proxy_conn_free(struct socks_proxy_conn* c) {
|
||||
if (!c) return;
|
||||
if (c->freed) return;
|
||||
c->freed = 1;
|
||||
struct socks_proxy_conn** head = c->head; int* count = c->count;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: FREE sid=%08x state=%d total=%d", c->stream_id, c->state, count ? *count : 0);
|
||||
if (c->close_pending && c->inst) {
|
||||
c->close_pending = 0;
|
||||
uint8_t subcmd = c->tc && c->tc->error ? TCP_PROXY_SUBCMD_ERROR : TCP_PROXY_SUBCMD_CLOSE;
|
||||
send_msg(c->inst, c->via_node_id, subcmd, c->stream_id, NULL, 0, 1);
|
||||
}
|
||||
if (head) { struct socks_proxy_conn** prev = head; while (*prev) { if (*prev == c) { *prev = c->next; if (count) (*count)--; break; } prev = &(*prev)->next; } }
|
||||
if (c->tc) { tcp_conn_destroy(c->tc); c->tc = NULL; }
|
||||
if (c->inst) etcp_router_waiter_cancel(c->inst, c->via_node_id, &c->tx_waiter);
|
||||
u_free(c);
|
||||
}
|
||||
|
||||
void socks_proxy_conn_free_all(struct socks_proxy_conn** head, int* count) {
|
||||
while (*head) { struct socks_proxy_conn* next = (*head)->next; socks_proxy_conn_free(*head); }
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Слушающий сокет
|
||||
// ====================================================================
|
||||
struct listen_ctx* socks_proxy_init_listen(struct UASYNC* ua, const char* addr_str,
|
||||
struct socks_proxy_conn** conns, int* count, uint32_t* next_stream_id,
|
||||
struct UTUN_INSTANCE* inst, uint64_t via_node_id, int is_http) {
|
||||
char ip[64], *colon = strrchr(addr_str, ':');
|
||||
if (!colon) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad addr '%s' (need IP:PORT)", addr_str); return NULL; }
|
||||
size_t ip_len = (size_t)(colon - addr_str);
|
||||
if (ip_len > sizeof(ip) - 1) ip_len = sizeof(ip) - 1;
|
||||
memcpy(ip, addr_str, ip_len); ip[ip_len] = '\0';
|
||||
int port = atoi(colon + 1);
|
||||
if (port <= 0 || port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad port %d", port); return NULL; }
|
||||
|
||||
struct listen_ctx* ctx = u_calloc(1, sizeof(struct listen_ctx));
|
||||
if (!ctx) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: u_calloc failed"); return NULL; }
|
||||
ctx->ua = ua; ctx->inst = inst; ctx->via_node_id = via_node_id;
|
||||
ctx->is_http = is_http; ctx->conns = conns; ctx->conn_count = count;
|
||||
ctx->next_stream_id = next_stream_id;
|
||||
|
||||
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: socket() failed errno=%d", errno); u_free(ctx); return NULL; }
|
||||
socket_set_nonblocking(sock);
|
||||
socket_set_reuseaddr(sock, 1);
|
||||
|
||||
struct sockaddr_in addr; memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET; addr.sin_port = htons((uint16_t)port);
|
||||
if (inet_pton(AF_INET, ip, &addr.sin_addr) != 1) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: inet_pton(%s) failed", ip); socket_close_wrapper(sock); u_free(ctx); return NULL; }
|
||||
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bind(%s:%d) failed errno=%d", ip, port, errno); socket_close_wrapper(sock); u_free(ctx); return NULL; }
|
||||
if (listen(sock, 32) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: listen() failed errno=%d", errno); socket_close_wrapper(sock); u_free(ctx); return NULL; }
|
||||
|
||||
ctx->listen_sock = sock;
|
||||
ctx->socket_id = uasync_add_socket_t(ua, sock, on_accept_cb, NULL, NULL, ctx);
|
||||
if (!ctx->socket_id) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: uasync_add_socket_t failed"); socket_close_wrapper(sock); u_free(ctx); return NULL; }
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: %s listening on %s:%d sock=%d",
|
||||
is_http ? "HTTP" : "SOCKS", ip, port, (int)sock);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void socks_proxy_close_listen(struct UASYNC* ua, struct listen_ctx* ctx, socket_t* sock_out) {
|
||||
if (!ctx) return;
|
||||
if (ctx->socket_id) uasync_remove_socket_t(ua, ctx->listen_sock);
|
||||
socket_close_wrapper(ctx->listen_sock);
|
||||
if (sock_out) *sock_out = SOCKET_INVALID;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: listener closed sock=%d", (int)ctx->listen_sock);
|
||||
u_free(ctx);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// socks_proxy.h — SOCKS5 / HTTP CONNECT proxy (client side)
|
||||
#ifndef SOCKS_PROXY_H
|
||||
#define SOCKS_PROXY_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "../lib/socket_compat.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
|
||||
struct UASYNC;
|
||||
struct UTUN_INSTANCE;
|
||||
struct ll_entry;
|
||||
struct ll_queue;
|
||||
struct tcp_conn;
|
||||
|
||||
enum {
|
||||
SOCKS_STATE_GREETING = 0, // ждём SOCKS5 greeting (ver + methods)
|
||||
SOCKS_STATE_REQUEST, // ждём SOCKS5 CONNECT request
|
||||
SOCKS_STATE_CONNECTING, // отправили ETCP CONNECT, ждём ответ от exit
|
||||
SOCKS_STATE_RELAY // релей данных
|
||||
};
|
||||
|
||||
enum {
|
||||
HTTP_STATE_REQUEST = 0, // ждём "CONNECT host:port HTTP/1.1\r\n"
|
||||
HTTP_STATE_CONNECTING, // отправили ETCP CONNECT, ждём ответ от exit
|
||||
HTTP_STATE_RELAY // релей данных
|
||||
};
|
||||
|
||||
struct socks_proxy_conn {
|
||||
struct socks_proxy_conn* next;
|
||||
struct socks_proxy_conn** head; // указатель на голову списка (для удаления)
|
||||
int* count; // указатель на счётчик списка
|
||||
struct tcp_conn* tc;
|
||||
struct UASYNC* ua;
|
||||
struct UTUN_INSTANCE* inst;
|
||||
uint64_t via_node_id;
|
||||
uint32_t stream_id;
|
||||
uint8_t dest_ip[4];
|
||||
uint16_t dest_port;
|
||||
uint8_t fin_remote;
|
||||
uint8_t rem_closed;
|
||||
uint8_t close_sent;
|
||||
uint8_t close_pending;
|
||||
uint8_t is_http;
|
||||
uint8_t state;
|
||||
uint8_t freed;
|
||||
uint8_t buf[1024];
|
||||
uint16_t buf_len;
|
||||
struct queue_waiter_handle tx_waiter;
|
||||
};
|
||||
|
||||
struct listen_ctx;
|
||||
|
||||
// Создать слушающий сокет для SOCKS/HTTP proxy. addr_str = "IP:PORT".
|
||||
// listen_ctx должен быть освобождён через socks_proxy_close_listen.
|
||||
// Возвращает listen_ctx при успехе, NULL при ошибке.
|
||||
struct listen_ctx* socks_proxy_init_listen(struct UASYNC* ua, const char* addr_str,
|
||||
struct socks_proxy_conn** conns, int* count, uint32_t* next_stream_id,
|
||||
struct UTUN_INSTANCE* inst, uint64_t via_node_id, int is_http);
|
||||
|
||||
// Закрыть слушающий сокет и освободить listen_ctx.
|
||||
// sock_out получает значение сокета (для последующего close).
|
||||
void socks_proxy_close_listen(struct UASYNC* ua, struct listen_ctx* ctx, socket_t* sock_out);
|
||||
|
||||
// Найти соединение по stream_id
|
||||
struct socks_proxy_conn* socks_proxy_find_conn(struct socks_proxy_conn* head, uint32_t stream_id);
|
||||
|
||||
// Обработать входящее ETCP сообщение (DATA/CLOSE/ERROR/FIN)
|
||||
// Возвращает 1 если обработано, 0 если stream_id не найден
|
||||
int socks_proxy_handle_etcp(struct socks_proxy_conn** head, int* count,
|
||||
uint32_t stream_id, uint8_t subcmd,
|
||||
const uint8_t* data, size_t data_len);
|
||||
|
||||
// Освободить одно соединение (удаляет из списка)
|
||||
void socks_proxy_conn_free(struct socks_proxy_conn* c);
|
||||
|
||||
// Освободить все соединения в списке
|
||||
void socks_proxy_conn_free_all(struct socks_proxy_conn** head, int* count);
|
||||
|
||||
#endif // SOCKS_PROXY_H
|
||||
@@ -1,5 +1,6 @@
|
||||
// tcp_proxy_client.c — TCP прокси-клиент: стек lwIP TCP → ETCP → удалённый exit узел
|
||||
#include "tcp_proxy_client.h"
|
||||
#include "socks_proxy.h"
|
||||
#include "lwip_tcp/lwip_tcp.h"
|
||||
#include "lwip_tcp/lwip_tcp_priv.h"
|
||||
#include "lwip_tcp/lwip_tcp_opts.h"
|
||||
@@ -525,6 +526,8 @@ void tcp_proxy_client_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* en
|
||||
struct tcp_proxy_client_conn *pc, *next;
|
||||
for (pc = proxy->conns; pc; pc = next) { next = pc->next; tcp_proxy_client_conn_free(pc); }
|
||||
proxy->conns = NULL; proxy->conn_count = 0;
|
||||
socks_proxy_conn_free_all(&proxy->socks_conns, &proxy->socks_conn_count);
|
||||
socks_proxy_conn_free_all(&proxy->http_conns, &proxy->http_conn_count);
|
||||
}
|
||||
}
|
||||
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
|
||||
@@ -534,6 +537,17 @@ void tcp_proxy_client_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* en
|
||||
uint32_t stream_id; memcpy(&stream_id, entry->dgram + 2, 4);
|
||||
|
||||
if (proxy) {
|
||||
size_t data_len = entry->len > TCP_PROXY_HDR_SIZE ? entry->len - TCP_PROXY_HDR_SIZE : 0;
|
||||
// Пробуем SOCKS/HTTP first (у них приоритет — могут быть без TUN)
|
||||
if (proxy->socks_enabled && socks_proxy_handle_etcp(&proxy->socks_conns, &proxy->socks_conn_count,
|
||||
stream_id, subcmd, entry->dgram + TCP_PROXY_HDR_SIZE, data_len)) {
|
||||
queue_dgram_free(entry); queue_entry_free(entry); return;
|
||||
}
|
||||
if (proxy->http_proxy_enabled && socks_proxy_handle_etcp(&proxy->http_conns, &proxy->http_conn_count,
|
||||
stream_id, subcmd, entry->dgram + TCP_PROXY_HDR_SIZE, data_len)) {
|
||||
queue_dgram_free(entry); queue_entry_free(entry); return;
|
||||
}
|
||||
// Существующие lwIP conns
|
||||
if (subcmd == TCP_PROXY_SUBCMD_DATA) { tcp_proxy_client_handle_data(proxy, conn, stream_id, entry); return; }
|
||||
if (subcmd == TCP_PROXY_SUBCMD_CLOSE) { tcp_proxy_client_handle_close(proxy, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
if (subcmd == TCP_PROXY_SUBCMD_ERROR) { tcp_proxy_client_handle_error(proxy, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
@@ -553,66 +567,90 @@ void tcp_proxy_client_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* en
|
||||
struct tcp_proxy_client* tcp_proxy_client_create(struct UTUN_INSTANCE* inst, struct UASYNC* ua,
|
||||
const char* tun_name, const char* tun_ip, int mtu, int test_mode,
|
||||
struct tcp_proxy_client_mapping_config* mappings, int mapping_count,
|
||||
uint64_t via_node_id)
|
||||
uint64_t via_node_id,
|
||||
int socks_enabled, const char* socks_addr,
|
||||
int http_proxy_enabled, const char* http_proxy_addr)
|
||||
{
|
||||
if (!ua) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: ua is NULL"); return NULL; }
|
||||
if (!tun_name || !tun_ip) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: tun name/ip required"); return NULL; }
|
||||
|
||||
int need_tun = tun_name && tun_name[0] && tun_ip && tun_ip[0];
|
||||
if (!need_tun && !socks_enabled && !http_proxy_enabled) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: no proxy mode enabled"); return NULL;
|
||||
}
|
||||
|
||||
struct tcp_proxy_client* p = u_calloc(1, sizeof(struct tcp_proxy_client));
|
||||
if (!p) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: u_calloc failed"); return NULL; }
|
||||
p->inst = inst; p->ua = ua; p->next_stream_id = 1;
|
||||
p->via_node_id = via_node_id;
|
||||
p->mappings = mappings; p->mapping_count = mapping_count;
|
||||
p->socks_enabled = socks_enabled;
|
||||
p->http_proxy_enabled = http_proxy_enabled;
|
||||
|
||||
p->entry_pool = memory_pool_init(sizeof(struct ll_entry), "entry_pool");
|
||||
if (!p->entry_pool) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: memory_pool_init failed"); u_free(p); return NULL; }
|
||||
|
||||
p->tun = tun_init_nat(ua, tun_name, tun_ip, mtu > 0 ? mtu : 1500, test_mode);
|
||||
if (!p->tun) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "tcp_proxy_client: failed to create TUN %s", tun_name); memory_pool_destroy(p->entry_pool); u_free(p); return NULL; }
|
||||
queue_set_callback(p->tun->output_queue, tcp_proxy_client_tun_input, p);
|
||||
if (need_tun) {
|
||||
p->tun = tun_init_nat(ua, tun_name, tun_ip, mtu > 0 ? mtu : 1500, test_mode);
|
||||
if (!p->tun) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "tcp_proxy_client: failed to create TUN %s", tun_name); memory_pool_destroy(p->entry_pool); u_free(p); return NULL; }
|
||||
queue_set_callback(p->tun->output_queue, tcp_proxy_client_tun_input, p);
|
||||
|
||||
p->lwip = lwip_tcp_init(ua, tcp_proxy_client_output_cb, p);
|
||||
if (!p->lwip) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: lwip_tcp_init failed");
|
||||
tun_close(p->tun);
|
||||
memory_pool_destroy(p->entry_pool); u_free(p); return NULL;
|
||||
}
|
||||
p->lwip = lwip_tcp_init(ua, tcp_proxy_client_output_cb, p);
|
||||
if (!p->lwip) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: lwip_tcp_init failed"); tun_close(p->tun); memory_pool_destroy(p->entry_pool); u_free(p); return NULL; }
|
||||
|
||||
if (mapping_count > 0) {
|
||||
int j; for (j = 0; j < mapping_count; j++) {
|
||||
uint16_t port_net = htons(mappings[j].local_port);
|
||||
struct tcp_pcb *lpcb = tcp_new(p->lwip);
|
||||
if (!lpcb) continue;
|
||||
tcp_bind(lpcb, INADDR_ANY, port_net);
|
||||
struct tcp_pcb *listen_pcb = tcp_listen(lpcb);
|
||||
if (listen_pcb) {
|
||||
tcp_arg(listen_pcb, p);
|
||||
tcp_accept(listen_pcb, tcp_proxy_client_accept_cb);
|
||||
if (mapping_count > 0) {
|
||||
int j; for (j = 0; j < mapping_count; j++) {
|
||||
uint16_t port_net = htons(mappings[j].local_port);
|
||||
struct tcp_pcb *lpcb = tcp_new(p->lwip);
|
||||
if (!lpcb) continue;
|
||||
tcp_bind(lpcb, INADDR_ANY, port_net);
|
||||
struct tcp_pcb *listen_pcb = tcp_listen(lpcb);
|
||||
if (listen_pcb) { tcp_arg(listen_pcb, p); tcp_accept(listen_pcb, tcp_proxy_client_accept_cb); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (socks_enabled && socks_addr && socks_addr[0]) {
|
||||
p->socks_listen = socks_proxy_init_listen(ua, socks_addr, &p->socks_conns, &p->socks_conn_count,
|
||||
&p->next_stream_id, inst, via_node_id, 0);
|
||||
if (!p->socks_listen) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client: SOCKS init failed on %s", socks_addr); }
|
||||
}
|
||||
|
||||
if (http_proxy_enabled && http_proxy_addr && http_proxy_addr[0]) {
|
||||
p->http_listen = socks_proxy_init_listen(ua, http_proxy_addr, &p->http_conns, &p->http_conn_count,
|
||||
&p->next_stream_id, inst, via_node_id, 1);
|
||||
if (!p->http_listen) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client: HTTP proxy init failed on %s", http_proxy_addr); }
|
||||
}
|
||||
|
||||
if (inst) {
|
||||
if (etcp_router_bind(inst, ETCP_ID_TCP_PROXY_CLIENT, tcp_proxy_client_router_recv_cb) != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy client: etcp_router_bind failed");
|
||||
} else {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client: etcp_router bind registered for ID=0x%02x", ETCP_ID_TCP_PROXY_CLIENT);
|
||||
udp_proxy_init(inst, ua);
|
||||
icmp_proxy_init(inst, ua);
|
||||
if (need_tun) { udp_proxy_init(inst, ua); icmp_proxy_init(inst, ua); }
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TCP proxy client created: mappings=%d via_node=%016llx", mapping_count, (unsigned long long)via_node_id);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TCP proxy client created: tun=%s socks=%s(http=%s) mappings=%d via_node=%016llx",
|
||||
need_tun ? "yes" : "no", socks_enabled ? "yes" : "no", http_proxy_enabled ? "yes" : "no",
|
||||
mapping_count, (unsigned long long)via_node_id);
|
||||
return p;
|
||||
}
|
||||
|
||||
void tcp_proxy_client_destroy(struct tcp_proxy_client* p) {
|
||||
if (!p) return;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client destroying: conns=%d", p->conn_count);
|
||||
int total = p->conn_count + p->socks_conn_count + p->http_conn_count;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client destroying: lwip=%d socks=%d http=%d",
|
||||
p->conn_count, p->socks_conn_count, p->http_conn_count);
|
||||
if (p->inst) etcp_router_unbind(p->inst, ETCP_ID_TCP_PROXY_CLIENT);
|
||||
udp_proxy_destroy(p->inst);
|
||||
icmp_proxy_destroy(p->inst);
|
||||
|
||||
if (p->socks_listen) socks_proxy_close_listen(p->ua, p->socks_listen, NULL);
|
||||
socks_proxy_conn_free_all(&p->socks_conns, &p->socks_conn_count);
|
||||
|
||||
if (p->http_listen) socks_proxy_close_listen(p->ua, p->http_listen, NULL);
|
||||
socks_proxy_conn_free_all(&p->http_conns, &p->http_conn_count);
|
||||
|
||||
struct tcp_proxy_client_conn* pc = p->conns;
|
||||
while (pc) {
|
||||
struct tcp_proxy_client_conn* next = pc->next;
|
||||
|
||||
@@ -17,6 +17,8 @@ struct memory_pool;
|
||||
struct tcp_proxy_client_mapping_config;
|
||||
struct tcp_proxy_server;
|
||||
struct lwip_tcp_ctx;
|
||||
struct socks_proxy_conn;
|
||||
struct listen_ctx;
|
||||
|
||||
struct tcp_pcb;
|
||||
|
||||
@@ -57,12 +59,26 @@ struct tcp_proxy_client {
|
||||
|
||||
struct tcp_proxy_client_mapping_config* mappings; // указатель на конфиг
|
||||
int mapping_count;
|
||||
|
||||
// SOCKS proxy
|
||||
int socks_enabled;
|
||||
struct socks_proxy_conn* socks_conns;
|
||||
int socks_conn_count;
|
||||
struct listen_ctx* socks_listen;
|
||||
|
||||
// HTTP CONNECT proxy
|
||||
int http_proxy_enabled;
|
||||
struct socks_proxy_conn* http_conns;
|
||||
int http_conn_count;
|
||||
struct listen_ctx* http_listen;
|
||||
};
|
||||
|
||||
struct tcp_proxy_client* tcp_proxy_client_create(struct UTUN_INSTANCE* inst, struct UASYNC* ua,
|
||||
const char* tun_name, const char* tun_ip, int mtu, int test_mode,
|
||||
struct tcp_proxy_client_mapping_config* mappings, int mapping_count,
|
||||
uint64_t via_node_id);
|
||||
uint64_t via_node_id,
|
||||
int socks_enabled, const char* socks_addr,
|
||||
int http_proxy_enabled, const char* http_proxy_addr);
|
||||
void tcp_proxy_client_destroy(struct tcp_proxy_client* p);
|
||||
void tcp_proxy_client_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry);
|
||||
|
||||
|
||||
@@ -57,6 +57,10 @@ static int send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd,
|
||||
e->dgram[1] = subcmd;
|
||||
memcpy(e->dgram + 2, &sid, 4);
|
||||
if (len > 0) memcpy(e->dgram + TCP_PROXY_HDR_SIZE, data, len);
|
||||
if (TCP_PROXY_HDR_SIZE + len > UINT16_MAX) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_server: msg too large len=%zu subcmd=%02x", len, subcmd);
|
||||
queue_dgram_free(e); queue_entry_free(e); return -1;
|
||||
}
|
||||
e->len = TCP_PROXY_HDR_SIZE + len;
|
||||
return etcp_route_send(inst, dst, e, force);
|
||||
}
|
||||
@@ -253,10 +257,10 @@ void tcp_proxy_server_conn_free(struct tcp_proxy_server_conn* rc) {
|
||||
struct tcp_proxy_server_conn** prev = &rc->ctx->conns;
|
||||
while (*prev) { if (*prev == rc) { *prev = rc->next; rc->ctx->conn_count--; break; } prev = &(*prev)->next; }
|
||||
}
|
||||
if (rc->tc) { tcp_conn_destroy(rc->tc); rc->tc = 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);
|
||||
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);
|
||||
if (rc->tc) { tcp_conn_destroy(rc->tc); rc->tc = NULL; }
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FREE u_free rc=%p", (void*)rc);
|
||||
u_free(rc);
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ drop:
|
||||
// Сторона клиента: принять REPLY, доставить в TUN
|
||||
// ====================================================================
|
||||
static void client_handle_reply(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
if (entry->len < UDP_PROXY_HDR_SIZE + 1) { queue_entry_free(entry); queue_dgram_free(entry); return; }
|
||||
if (entry->len < UDP_PROXY_HDR_SIZE + 1) { queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
// svc_id уже обработан диспетчером etcp_router
|
||||
uint32_t src_ip, dst_ip;
|
||||
uint16_t src_port, dst_port;
|
||||
@@ -136,7 +136,7 @@ static void client_handle_reply(struct ETCP_CONN* conn, struct ll_entry* entry)
|
||||
// ====================================================================
|
||||
void udp_proxy_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
if (!entry || !entry->dgram || entry->len < 2) {
|
||||
if (entry) { queue_entry_free(entry); queue_dgram_free(entry); }
|
||||
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
|
||||
return;
|
||||
}
|
||||
uint8_t subcmd = entry->dgram[1];
|
||||
|
||||
+75
-47
@@ -15,10 +15,12 @@
|
||||
#include "etcp_debug.h"
|
||||
#include "config_parser.h"
|
||||
#include "route_node.h"
|
||||
#include "route_node_lmdb.h"
|
||||
#include "route_lib.h"
|
||||
#include "route_bgp.h"
|
||||
#include "route_ping.h"
|
||||
#include "route_connectivity.h"
|
||||
#include "control_server.h"
|
||||
|
||||
|
||||
// ============================================================================
|
||||
@@ -244,6 +246,16 @@ struct ROUTE_BGP* route_bgp_init(struct UTUN_INSTANCE* instance) {
|
||||
bgp->instance = instance;
|
||||
bgp->ping_pending = NULL;
|
||||
bgp->next_ping_req_id = 1;
|
||||
bgp->nodeinfo_env = NULL;
|
||||
bgp->nodeinfo_dbi = 0;
|
||||
|
||||
sc_derive_ed25519_pubkey(instance->my_keys.private_key, bgp->ed25519_public_key);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Ed25519 pubkey derived from X25519 private key");
|
||||
|
||||
// Создаём/открываем LMDB базу nodeinfo если задан db_path в конфиге
|
||||
if (instance->config && instance->config->global.db_path[0]) {
|
||||
route_node_lmdb_init(bgp, instance->config->global.db_path);
|
||||
}
|
||||
|
||||
// Create nodes queue with hash support for fast lookup by node_id
|
||||
bgp->nodes = queue_new(instance->ua, BGP_NODES_HASH_SIZE, offsetof(struct NODEINFO_Q, node.node_id)-sizeof(struct ll_entry), 8, "bgp_nodes");
|
||||
@@ -324,6 +336,8 @@ void route_bgp_destroy(struct UTUN_INSTANCE* instance) {
|
||||
u_free(instance->bgp->local_node);
|
||||
}
|
||||
|
||||
route_node_lmdb_destroy(instance->bgp);
|
||||
|
||||
if (instance->bgp->nodes) {
|
||||
queue_free(instance->bgp->nodes);
|
||||
}
|
||||
@@ -356,9 +370,16 @@ void route_bgp_new_conn(struct ETCP_CONN* conn) {
|
||||
}
|
||||
|
||||
struct ROUTE_BGP* bgp = conn->instance->bgp;
|
||||
struct NODEINFO_Q* peer_nq = nodeinfo_find_by_id(bgp, conn->peer_node_id);
|
||||
|
||||
route_bgp_add_to_senders(bgp, conn);
|
||||
|
||||
if (peer_nq && peer_nq->alien) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_bgp_new_conn: peer 0x%016llx is alien, skipping route exchange",
|
||||
(unsigned long long)conn->peer_node_id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Scan ALL connections and ALL links in instance - start NAT check if not started
|
||||
struct ETCP_CONN* c = conn->instance->connections;
|
||||
while (c) {
|
||||
@@ -420,15 +441,15 @@ void route_bgp_remove_conn(struct ETCP_CONN* conn) {
|
||||
struct ROUTE_TABLE* rt = conn->instance->rt;
|
||||
|
||||
// Remove this connection from all nodes' path lists
|
||||
bool need_withdraw = false;
|
||||
int nodes_removed = 0;
|
||||
struct ll_entry* node_entry = bgp->nodes ? bgp->nodes->head : NULL;
|
||||
while (node_entry) {
|
||||
struct ll_entry* next = node_entry->next;
|
||||
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)node_entry;
|
||||
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; }
|
||||
@@ -437,6 +458,8 @@ void route_bgp_remove_conn(struct ETCP_CONN* conn) {
|
||||
if (entry) { queue_remove_data(bgp->nodes, entry); queue_entry_free(entry); }
|
||||
nodes_removed++;
|
||||
etcp_router_conn_close_all_for_node(bgp->instance, key);
|
||||
uint64_t wd_src = (key == conn->peer_node_id) ? conn->instance->node_id : conn->peer_node_id;
|
||||
route_bgp_broadcast_withdraw(bgp, key, wd_src, NULL);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Removed node %016llx after link down", (unsigned long long)key);
|
||||
}
|
||||
node_entry = next;
|
||||
@@ -454,12 +477,8 @@ void route_bgp_remove_conn(struct ETCP_CONN* conn) {
|
||||
e = e->next;
|
||||
}
|
||||
|
||||
if (need_withdraw) {
|
||||
route_bgp_broadcast_withdraw(bgp, conn->peer_node_id, conn->instance->node_id, NULL);
|
||||
}
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP peer removed: %s nodes=%d withdraw=%d",
|
||||
conn->log_name, nodes_removed, need_withdraw);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP peer removed: %s nodes=%d",
|
||||
conn->log_name, nodes_removed);
|
||||
}
|
||||
|
||||
|
||||
@@ -467,29 +486,28 @@ void route_bgp_remove_conn(struct ETCP_CONN* conn) {
|
||||
* NEW NODEINFO BASED IMPLEMENTATION
|
||||
* ================================================ */
|
||||
|
||||
struct NODEINFO_Q* route_bgp_get_node(struct ROUTE_BGP* bgp, uint64_t node_id) {
|
||||
if (!bgp || !bgp->nodes) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint64_t key = node_id;
|
||||
struct ll_entry* e = queue_find_data_by_index(bgp->nodes, &key);
|
||||
return e ? (struct NODEINFO_Q*)e : NULL;
|
||||
}
|
||||
|
||||
struct ETCP_CONN* route_bgp_find_conn_for_node(struct ROUTE_BGP* bgp, uint64_t node_id) {
|
||||
if (!bgp) return NULL;
|
||||
struct NODEINFO_Q* nq = route_bgp_get_node(bgp, node_id);
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(bgp, node_id);
|
||||
if (!nq || !nq->paths || !nq->paths->head) return NULL;
|
||||
struct ll_entry* e = nq->paths->head;
|
||||
struct NODEINFO_PATH* best = NULL;
|
||||
uint8_t best_hops = 255;
|
||||
struct NODEINFO_PATH* best_live = NULL;
|
||||
uint8_t best_live_hops = 255;
|
||||
struct NODEINFO_PATH* best_any = NULL;
|
||||
uint8_t best_any_hops = 255;
|
||||
while (e) {
|
||||
struct NODEINFO_PATH* path = (struct NODEINFO_PATH*)e;
|
||||
if (path->conn && path->hop_count < best_hops) { best = path; best_hops = path->hop_count; }
|
||||
if (path->conn) {
|
||||
if (path->conn->links_up && path->hop_count < best_live_hops) { best_live = path; best_live_hops = path->hop_count; }
|
||||
if (path->hop_count < best_any_hops) { best_any = path; best_any_hops = path->hop_count; }
|
||||
}
|
||||
e = e->next;
|
||||
}
|
||||
return best ? best->conn : NULL;
|
||||
if (!best_live && best_any) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "find_conn_for_node %016llx: no live paths, fallback to best (hops=%d links_up=%d)",
|
||||
(unsigned long long)node_id, best_any_hops, best_any->conn->links_up);
|
||||
}
|
||||
return best_live ? best_live->conn : (best_any ? best_any->conn : NULL);
|
||||
}
|
||||
|
||||
int route_bgp_add_path(struct NODEINFO_Q* nq, struct ETCP_CONN* conn, uint64_t* hop_list, uint8_t hop_count) {
|
||||
@@ -650,6 +668,7 @@ static void route_bgp_extract_nat_addr(struct ETCP_CONN* conn, uint32_t* nat_ip,
|
||||
|
||||
void route_bgp_start_link_nat_check(struct ROUTE_BGP* bgp, struct ETCP_LINK* link) {// проверяем тип нат для линка link
|
||||
if (!bgp || !link || !link->conn || !link->etcp) return;
|
||||
if (link->nat_type == NAT_TYPE_DIRECT) return;
|
||||
if (link->nat_check_status == NAT_CHECK_IN_PROGRESS) return;
|
||||
// Skip NAT check for private sockets - they are not reachable from outside
|
||||
if (link->conn->type == CFG_SERVER_TYPE_PRIVATE) {
|
||||
@@ -728,11 +747,22 @@ int route_bgp_process_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, co
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct NODEINFO_Q* nodeinfo1 = route_bgp_get_node(bgp, node_id);
|
||||
struct NODEINFO_Q* nodeinfo1 = nodeinfo_find_by_id(bgp, node_id);
|
||||
uint8_t new_ver = ni->ver;
|
||||
|
||||
if (node_id == bgp->instance->node_id) return 0;
|
||||
|
||||
if (nodeinfo1 && (int8_t)(nodeinfo1->last_ver-new_ver)>=0) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "NODEINFO ignored old ver: node=%016llx ver=%d<=%d from=%s",
|
||||
int new_hops = ni->hop_count + 1;
|
||||
if (new_hops <= MAX_HOPS) {
|
||||
uint64_t hop_list[MAX_HOPS];
|
||||
uint64_t* pkt_hops = (uint64_t*)((uint8_t*)ni + sizeof(struct NODEINFO) + dyn_size);
|
||||
memcpy(hop_list, pkt_hops, ni->hop_count * 8);
|
||||
hop_list[ni->hop_count] = from->peer_node_id;
|
||||
route_bgp_remove_path_by_hop(nodeinfo1, from->peer_node_id);
|
||||
route_bgp_add_path(nodeinfo1, from, hop_list, new_hops);
|
||||
}
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "NODEINFO old ver: node=%016llx ver=%d<=%d from=%s (paths updated)",
|
||||
(unsigned long long)node_id, new_ver, nodeinfo1->last_ver, from->log_name);
|
||||
return 0;
|
||||
}
|
||||
@@ -760,6 +790,7 @@ int route_bgp_process_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, co
|
||||
|
||||
if (need_alloc) {
|
||||
if (nodeinfo1) {
|
||||
if (bgp->instance->rt) route_delete(bgp->instance->rt, nodeinfo1);
|
||||
queue_remove_data(bgp->nodes, &nodeinfo1->ll);
|
||||
queue_free(paths);
|
||||
queue_entry_free(&nodeinfo1->ll);
|
||||
@@ -771,6 +802,8 @@ int route_bgp_process_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, co
|
||||
nodeinfo1->connectivity.interface_status = PROBE_RESULT_UNKNOWN;
|
||||
nodeinfo1->connectivity.nat_status = PROBE_RESULT_UNKNOWN;
|
||||
nodeinfo1->connectivity.real_status = PROBE_RESULT_UNKNOWN;
|
||||
nodeinfo1->connectivity.ping_req_time = 0;
|
||||
nodeinfo1->alien = 0;
|
||||
queue_data_put_with_index(bgp->nodes, &nodeinfo1->ll);
|
||||
} else {
|
||||
socks_changed = (nodeinfo1->node.local_v4_sockets != ni->local_v4_sockets) ||
|
||||
@@ -780,22 +813,6 @@ int route_bgp_process_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, co
|
||||
memcpy(&nodeinfo1->node, ni, sizeof(struct NODEINFO) + dyn_size);
|
||||
}
|
||||
|
||||
/* Если это наш local_node (по node_id), обновляем его данные */
|
||||
if (node_id == bgp->instance->node_id && bgp->local_node) {
|
||||
uint8_t* dyn_src = (uint8_t*)&nodeinfo1->node + sizeof(struct NODEINFO);
|
||||
uint8_t* dyn_dst = (uint8_t*)&bgp->local_node->node + sizeof(struct NODEINFO);
|
||||
int dyn_sz = nodeinfo1->node.node_name_len +
|
||||
nodeinfo1->node.local_v4_sockets * sizeof(struct NODEINFO_IPV4_SOCKET_META) +
|
||||
nodeinfo1->node.local_v4_addrs * sizeof(struct NODEINFO_IPV4_ADDR) +
|
||||
nodeinfo1->node.local_v6_sockets * sizeof(struct NODEINFO_IPV6_SOCKET_META) +
|
||||
nodeinfo1->node.local_v6_addrs * sizeof(struct NODEINFO_IPV6_ADDR) +
|
||||
nodeinfo1->node.local_v4_subnets * sizeof(struct NODEINFO_IPV4_SUBNET) +
|
||||
nodeinfo1->node.local_v6_subnets * sizeof(struct NODEINFO_IPV6_SUBNET);
|
||||
memcpy(dyn_dst, dyn_src, dyn_sz);
|
||||
bgp->local_node->node.ver = nodeinfo1->node.ver;
|
||||
bgp->local_node->last_ver = new_ver;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Updated local_node from remote NODEINFO: node=%016llx", (unsigned long long)node_id);
|
||||
}
|
||||
nodeinfo1->paths = paths;
|
||||
nodeinfo1->last_ver = new_ver;
|
||||
|
||||
@@ -813,6 +830,12 @@ int route_bgp_process_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, co
|
||||
route_insert(bgp->instance->rt, nodeinfo1);
|
||||
}
|
||||
|
||||
if (node_id != bgp->instance->node_id) {
|
||||
route_node_lmdb_put(bgp, nodeinfo1);
|
||||
}
|
||||
|
||||
if (bgp->instance->control_srv) control_server_notify_node_change(bgp->instance->control_srv, nodeinfo1);
|
||||
|
||||
int hop_count=nodeinfo1->node.hop_count;
|
||||
|
||||
// рассылаем по узлам которых нет в hop_list этой ноды (loop prevention)
|
||||
@@ -821,10 +844,12 @@ int route_bgp_process_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, co
|
||||
struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)e->data;
|
||||
if (item->conn) {
|
||||
uint64_t id=item->conn->peer_node_id;
|
||||
int found=0;
|
||||
for (int i=0; i<hop_count; i++) if (hop_list[i]==id) found=1;
|
||||
if (found==0) route_bgp_send_nodeinfo(nodeinfo1, item->conn);
|
||||
else DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Skip send NODEINFO to node %016llx (loop prevention)",id);
|
||||
if (id != node_id) {
|
||||
int found=0;
|
||||
for (int i=0; i<hop_count; i++) if (hop_list[i]==id) found=1;
|
||||
if (found==0) route_bgp_send_nodeinfo(nodeinfo1, item->conn);
|
||||
else DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Skip send NODEINFO to node %016llx (loop prevention)",id);
|
||||
}
|
||||
}
|
||||
e = e->next;
|
||||
}
|
||||
@@ -850,14 +875,15 @@ int route_bgp_process_withdraw(struct ROUTE_BGP* bgp, struct ETCP_CONN* sender,
|
||||
uint64_t node_id = wp->node_id;
|
||||
uint64_t wd_source = wp->wd_source;
|
||||
|
||||
struct NODEINFO_Q* nq = route_bgp_get_node(bgp, node_id);
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(bgp, node_id);
|
||||
if (!nq) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "node not found");
|
||||
return 0;
|
||||
}
|
||||
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 (ret == 1) {
|
||||
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; }
|
||||
@@ -931,6 +957,7 @@ static void route_bgp_add_to_senders(struct ROUTE_BGP* bgp, struct ETCP_CONN* co
|
||||
|
||||
static bool route_bgp_should_send_to(const struct NODEINFO_Q* nq, uint64_t target_id) {
|
||||
if (!nq || !nq->paths) return false;
|
||||
if (target_id == nq->node.node_id) return false;
|
||||
struct ll_entry* e = nq->paths->head;
|
||||
while (e) {
|
||||
struct NODEINFO_PATH* path = (struct NODEINFO_PATH*)e;
|
||||
@@ -990,6 +1017,7 @@ static void route_bgp_handle_nat_info(struct ROUTE_BGP* bgp, struct ETCP_CONN* f
|
||||
uint8_t verified_type;
|
||||
if (info->nat_type == NAT_TYPE_EIM) verified_type = NAT_VERIFIED_EIM;
|
||||
else if (info->nat_type == NAT_TYPE_STRICT) verified_type = NAT_VERIFIED_STRICT;
|
||||
else if (info->nat_type == NAT_TYPE_DIRECT) verified_type = NAT_VERIFIED_DIRECT;
|
||||
else verified_type = NAT_VERIFIED_UNKNOWN;
|
||||
|
||||
int data_changed = 0;
|
||||
|
||||
+5
-6
@@ -4,6 +4,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/liblmdb/lmdb.h"
|
||||
#include "route_lib.h"
|
||||
#include "route_node.h"
|
||||
#include "secure_channel.h"
|
||||
@@ -88,6 +89,9 @@ struct ROUTE_BGP {
|
||||
struct route_ping_pending* ping_pending;
|
||||
uint32_t next_ping_req_id;
|
||||
uint8_t allow_nat_check_local; // 1 = разрешить NAT check для локальных подсетей (для тестов)
|
||||
uint8_t ed25519_public_key[SC_PUBKEY_SIZE]; // свой Ed25519 pubkey (для подписи сообщений)
|
||||
MDB_env* nodeinfo_env; // LMDB environment handle
|
||||
MDB_dbi nodeinfo_dbi; // LMDB database handle for nodeinfo table
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -151,12 +155,7 @@ void route_bgp_send_nodeinfo(struct NODEINFO_Q* node, struct ETCP_CONN* conn);
|
||||
*/
|
||||
void route_bgp_send_withdraw(struct ROUTE_BGP* bgp, uint64_t node_id);
|
||||
|
||||
/**
|
||||
* @brief Поиск NODEINFO_Q по node_id через hash в nodes queue.
|
||||
*
|
||||
* @return node или NULL
|
||||
*/
|
||||
struct NODEINFO_Q* route_bgp_get_node(struct ROUTE_BGP* bgp, uint64_t node_id);
|
||||
int nodeinfo_dyn_size(struct NODEINFO* node);
|
||||
|
||||
/**
|
||||
* @brief Поиск оптимального ETCP соединения для указанного node_id.
|
||||
|
||||
+37
-25
@@ -20,6 +20,7 @@
|
||||
#define CONN_MAX_SOCKET_CANDIDATES 8
|
||||
|
||||
struct conn_probe_ctx {
|
||||
struct conn_probe_ctx* next; /* linked list in nq->connectivity.probe_list */
|
||||
struct UTUN_INSTANCE* instance;
|
||||
struct NODEINFO_Q* nq;
|
||||
uint8_t addr_type; // ADDR_TYPE_*
|
||||
@@ -245,33 +246,37 @@ static void conn_probe_single_cb(int success, uint16_t rtt, void* arg,
|
||||
}
|
||||
|
||||
static void conn_probe_finish(struct conn_probe_ctx* ctx, int ok) {
|
||||
if (!ctx || !ctx->nq) return;
|
||||
struct NODE_CONNECTIVITY* c = &ctx->nq->connectivity;
|
||||
if (!ctx) return;
|
||||
if (ctx->nq) {
|
||||
struct NODE_CONNECTIVITY* c = &ctx->nq->connectivity;
|
||||
struct conn_probe_ctx** p = (struct conn_probe_ctx**)&c->probe_list;
|
||||
while (*p) { if (*p == ctx) { *p = ctx->next; break; } p = &(*p)->next; }
|
||||
|
||||
switch (ctx->addr_type) {
|
||||
case ADDR_TYPE_INTERFACE:
|
||||
c->interface_status = ok ? PROBE_RESULT_REACHABLE : (c->interface_status == PROBE_RESULT_UNKNOWN ? PROBE_RESULT_UNREACHABLE : c->interface_status);
|
||||
if (ok && ctx->best_across_sockets < c->interface_min_rtt) c->interface_min_rtt = ctx->best_across_sockets;
|
||||
c->interface_probe_time = get_time_tb();
|
||||
break;
|
||||
case ADDR_TYPE_NAT:
|
||||
c->nat_status = ok ? PROBE_RESULT_REACHABLE : (c->nat_status == PROBE_RESULT_UNKNOWN ? PROBE_RESULT_UNREACHABLE : c->nat_status);
|
||||
if (ok && ctx->best_across_sockets < c->nat_min_rtt) c->nat_min_rtt = ctx->best_across_sockets;
|
||||
c->nat_probe_time = get_time_tb();
|
||||
break;
|
||||
case ADDR_TYPE_REAL:
|
||||
c->real_status = ok ? PROBE_RESULT_REACHABLE : (c->real_status == PROBE_RESULT_UNKNOWN ? PROBE_RESULT_UNREACHABLE : c->real_status);
|
||||
if (ok && ctx->best_across_sockets < c->real_min_rtt) c->real_min_rtt = ctx->best_across_sockets;
|
||||
c->real_probe_time = get_time_tb();
|
||||
break;
|
||||
}
|
||||
switch (ctx->addr_type) {
|
||||
case ADDR_TYPE_INTERFACE:
|
||||
c->interface_status = ok ? PROBE_RESULT_REACHABLE : (c->interface_status == PROBE_RESULT_UNKNOWN ? PROBE_RESULT_UNREACHABLE : c->interface_status);
|
||||
if (ok && ctx->best_across_sockets < c->interface_min_rtt) c->interface_min_rtt = ctx->best_across_sockets;
|
||||
c->interface_probe_time = get_time_tb();
|
||||
break;
|
||||
case ADDR_TYPE_NAT:
|
||||
c->nat_status = ok ? PROBE_RESULT_REACHABLE : (c->nat_status == PROBE_RESULT_UNKNOWN ? PROBE_RESULT_UNREACHABLE : c->nat_status);
|
||||
if (ok && ctx->best_across_sockets < c->nat_min_rtt) c->nat_min_rtt = ctx->best_across_sockets;
|
||||
c->nat_probe_time = get_time_tb();
|
||||
break;
|
||||
case ADDR_TYPE_REAL:
|
||||
c->real_status = ok ? PROBE_RESULT_REACHABLE : (c->real_status == PROBE_RESULT_UNKNOWN ? PROBE_RESULT_UNREACHABLE : c->real_status);
|
||||
if (ok && ctx->best_across_sockets < c->real_min_rtt) c->real_min_rtt = ctx->best_across_sockets;
|
||||
c->real_probe_time = get_time_tb();
|
||||
break;
|
||||
}
|
||||
|
||||
if (c->pending_count > 0) c->pending_count--;
|
||||
if (c->pending_count == 0) {
|
||||
c->probe_status = PROBE_STATUS_DONE;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "connectivity probe DONE for node %016llx: intf=%d nat=%d real=%d",
|
||||
(unsigned long long)ctx->nq->node.node_id,
|
||||
c->interface_status, c->nat_status, c->real_status);
|
||||
if (c->pending_count > 0) c->pending_count--;
|
||||
if (c->pending_count == 0) {
|
||||
c->probe_status = PROBE_STATUS_DONE;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "connectivity probe DONE for node %016llx: intf=%d nat=%d real=%d",
|
||||
(unsigned long long)ctx->nq->node.node_id,
|
||||
c->interface_status, c->nat_status, c->real_status);
|
||||
}
|
||||
}
|
||||
|
||||
u_free(ctx);
|
||||
@@ -349,6 +354,8 @@ void route_connectivity_probe_node(struct UTUN_INSTANCE* instance, struct NODEIN
|
||||
ctx->timeout_ms = CONN_PROBE_TIMEOUT_MS;
|
||||
ctx->best_across_sockets = 65535;
|
||||
|
||||
ctx->next = (struct conn_probe_ctx*)nq->connectivity.probe_list;
|
||||
nq->connectivity.probe_list = ctx;
|
||||
pend++;
|
||||
conn_probe_start_series(ctx);
|
||||
}
|
||||
@@ -407,6 +414,8 @@ void route_connectivity_probe_node(struct UTUN_INSTANCE* instance, struct NODEIN
|
||||
ctx->timeout_ms = CONN_PROBE_TIMEOUT_MS;
|
||||
ctx->best_across_sockets = 65535;
|
||||
|
||||
ctx->next = (struct conn_probe_ctx*)nq->connectivity.probe_list;
|
||||
nq->connectivity.probe_list = ctx;
|
||||
pend++;
|
||||
conn_probe_start_series(ctx);
|
||||
}
|
||||
@@ -428,6 +437,9 @@ void route_connectivity_cancel_node(struct UTUN_INSTANCE* instance, struct NODEI
|
||||
if (!instance || !nq) return;
|
||||
nq->connectivity.probe_status = PROBE_STATUS_NONE;
|
||||
nq->connectivity.pending_count = 0;
|
||||
struct conn_probe_ctx* ctx = (struct conn_probe_ctx*)nq->connectivity.probe_list;
|
||||
while (ctx) { ctx->nq = NULL; ctx = ctx->next; }
|
||||
nq->connectivity.probe_list = NULL;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "connectivity probe cancelled for node %016llx", (unsigned long long)nq->node.node_id);
|
||||
}
|
||||
|
||||
|
||||
+1
-4
@@ -21,10 +21,7 @@ static uint32_t prefix_to_mask(uint8_t prefix) {
|
||||
}
|
||||
|
||||
static bool routes_overlap(uint32_t net1, uint8_t pre1, uint32_t net2, uint8_t pre2) {
|
||||
uint32_t mask1 = prefix_to_mask(pre1);
|
||||
uint32_t mask2 = prefix_to_mask(pre2);
|
||||
if ((net1 & mask2) == net2 && pre1 >= pre2) return true;
|
||||
if ((net2 & mask1) == net1 && pre2 >= pre1) return true;
|
||||
if (pre1 == pre2 && net1 == net2) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+318
-2
@@ -10,6 +10,7 @@
|
||||
#include "config_parser.h"
|
||||
#include "route_node.h"
|
||||
#include "route_bgp.h"
|
||||
#include "route_lib.h"
|
||||
#include "etcp_debug.h"
|
||||
|
||||
static const uint8_t* node_dyn_start(const struct NODEINFO* info) {
|
||||
@@ -121,6 +122,310 @@ 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;
|
||||
}
|
||||
|
||||
static int is_node_connectivity_active(struct NODE_CONNECTIVITY* c) {
|
||||
return c->probe_status != PROBE_STATUS_NONE
|
||||
|| c->interface_status != PROBE_RESULT_UNKNOWN
|
||||
|| c->nat_status != PROBE_RESULT_UNKNOWN
|
||||
|| c->real_status != PROBE_RESULT_UNKNOWN
|
||||
|| c->probe_start_time != 0
|
||||
|| c->ping_req_time != 0;
|
||||
}
|
||||
|
||||
static const char* probe_status_str(uint8_t s) {
|
||||
switch (s) { case 0: return "NONE"; case 1: return "IN_PROGRESS"; case 2: return "DONE"; default: return "?"; }
|
||||
}
|
||||
static const char* probe_result_str(uint8_t s) {
|
||||
switch (s) { case 0: return "UNKNOWN"; case 1: return "REACHABLE"; case 2: return "UNREACHABLE"; default: return "?"; }
|
||||
}
|
||||
static const char* conn_mgr_type_str(uint8_t t) {
|
||||
switch (t) { case 1: return "DIRECT"; case 2: return "REVERSE"; case 3: return "INDIRECT"; default: return "NONE"; }
|
||||
}
|
||||
|
||||
void route_node_dump_all(struct ROUTE_BGP* bgp) {
|
||||
if (!bgp || !bgp->nodes) return;
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "=== BGP Nodes dump: %d nodes ===", queue_entry_count(bgp->nodes));
|
||||
|
||||
int idx = 0;
|
||||
struct ll_entry* e = bgp->nodes->head;
|
||||
while (e) {
|
||||
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)e;
|
||||
struct NODEINFO* ni = &nq->node;
|
||||
int is_local = (bgp->local_node == nq);
|
||||
const char* name_s = (ni->node_name_len > 0) ? (const char*)((const uint8_t*)ni + sizeof(struct NODEINFO)) : "";
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "--- Node #%d: id=%016llx name=\"%.*s\" ver=%u dirty=%u alien=%u%s ---",
|
||||
idx, (unsigned long long)ni->node_id, ni->node_name_len, name_s,
|
||||
ni->ver, nq->dirty, nq->alien, is_local ? " [LOCAL]" : "");
|
||||
|
||||
if (nq->best_socket) DEBUG_INFO(DEBUG_CATEGORY_BGP, " best_socket=%p", (void*)nq->best_socket);
|
||||
|
||||
log_dump(DEBUG_LEVEL_INFO, DEBUG_CATEGORY_BGP, " pubkey ", ni->public_key, SC_PUBKEY_SIZE);
|
||||
log_dump(DEBUG_LEVEL_INFO, DEBUG_CATEGORY_BGP, " ed25519", ni->ed25519_public_key, SC_PUBKEY_SIZE);
|
||||
|
||||
const struct NODEINFO_IPV4_SOCKET_META* v4sm;
|
||||
int v4sm_cnt = get_node_v4_sockets_meta(nq, &v4sm);
|
||||
if (v4sm_cnt > 0) for (int i = 0; i < v4sm_cnt; i++) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, " v4sock[%d]: id=%u type=%u", i, v4sm[i].id, v4sm[i].type);
|
||||
}
|
||||
|
||||
const struct NODEINFO_IPV4_ADDR* v4a;
|
||||
int v4a_cnt = get_node_v4_addrs(nq, &v4a);
|
||||
if (v4a_cnt > 0) {
|
||||
const char* atn[] = {"INTERFACE","NAT","REAL"};
|
||||
for (int i = 0; i < v4a_cnt; i++) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, " v4addr[%d]: %s:%u %s sock=%u",
|
||||
i, ip_to_str(v4a[i].addr, AF_INET).str, v4a[i].port,
|
||||
(v4a[i].type<=2)?atn[v4a[i].type]:"?", v4a[i].socket_id);
|
||||
}
|
||||
}
|
||||
|
||||
const struct NODEINFO_IPV6_SOCKET_META* v6sm;
|
||||
int v6sm_cnt = get_node_v6_sockets_meta(nq, &v6sm);
|
||||
if (v6sm_cnt > 0) for (int i = 0; i < v6sm_cnt; i++) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, " v6sock[%d]: id=%u type=%u", i, v6sm[i].id, v6sm[i].type);
|
||||
}
|
||||
|
||||
const struct NODEINFO_IPV6_ADDR* v6a;
|
||||
int v6a_cnt = get_node_v6_addrs(nq, &v6a);
|
||||
if (v6a_cnt > 0) {
|
||||
for (int i = 0; i < v6a_cnt; i++) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, " v6addr[%d]: %s:%u type=%u sock=%u",
|
||||
i, ip_to_str(v6a[i].addr, AF_INET6).str, v6a[i].port,
|
||||
v6a[i].type, v6a[i].socket_id);
|
||||
}
|
||||
}
|
||||
|
||||
const struct NODEINFO_IPV4_SUBNET* sub4;
|
||||
int sub4_cnt = get_node_routes(nq, &sub4);
|
||||
if (sub4_cnt > 0) for (int i = 0; i < sub4_cnt; i++) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, " v4sub[%d]: %s/%d", i,
|
||||
ip_to_str(sub4[i].addr, AF_INET).str, sub4[i].prefix_length);
|
||||
}
|
||||
|
||||
const struct NODEINFO_IPV6_SUBNET* sub6;
|
||||
int sub6_cnt = get_node_v6_routes(nq, &sub6);
|
||||
if (sub6_cnt > 0) for (int i = 0; i < sub6_cnt; i++) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, " v6sub[%d]: %s/%d", i,
|
||||
ip_to_str(sub6[i].addr, AF_INET6).str, sub6[i].prefix_length);
|
||||
}
|
||||
|
||||
if (ni->tranzit_nodes > 0) {
|
||||
const uint8_t* dyn = node_dyn_start(ni);
|
||||
dyn = skip_name(dyn, ni->node_name_len);
|
||||
dyn = skip_v4_sockets_meta(dyn, ni->local_v4_sockets);
|
||||
dyn = skip_v4_addrs(dyn, ni->local_v4_addrs);
|
||||
dyn = skip_v6_sockets_meta(dyn, ni->local_v6_sockets);
|
||||
dyn = skip_v6_addrs(dyn, ni->local_v6_addrs);
|
||||
dyn = skip_v4_subnets(dyn, ni->local_v4_subnets);
|
||||
const struct NODEINFO_IPV6_SUBNET* sub6_dyn = (const struct NODEINFO_IPV6_SUBNET*)dyn;
|
||||
dyn += ni->local_v6_subnets * sizeof(struct NODEINFO_IPV6_SUBNET);
|
||||
const struct NODEINFO_TRANZIT_NODE* tr = (const struct NODEINFO_TRANZIT_NODE*)dyn;
|
||||
for (uint8_t i = 0; i < ni->tranzit_nodes; i++) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, " tranzit[%d]: node=%016llx rtt=%u linkq=%u",
|
||||
i, (unsigned long long)tr[i].node_id, tr[i].rtt, tr[i].link_q);
|
||||
}
|
||||
}
|
||||
|
||||
int path_idx = 0;
|
||||
if (nq->paths) {
|
||||
struct ll_entry* pe = nq->paths->head;
|
||||
while (pe) {
|
||||
struct NODEINFO_PATH* path = (struct NODEINFO_PATH*)pe;
|
||||
uint64_t* hop = (uint64_t*)((uint8_t*)path + sizeof(struct NODEINFO_PATH));
|
||||
char hop_buf[256] = "";
|
||||
int hop_pos = 0;
|
||||
for (uint8_t hi = 0; hi < path->hop_count && hop_pos < (int)sizeof(hop_buf)-20; hi++) {
|
||||
hop_pos += snprintf(hop_buf + hop_pos, sizeof(hop_buf) - hop_pos,
|
||||
"%s%016llx", hi ? "->" : "", (unsigned long long)hop[hi]);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, " path[%d]: conn=%s hop_count=%u hops=[%s]",
|
||||
path_idx, path->conn ? path->conn->log_name : "NULL",
|
||||
path->hop_count, hop_buf);
|
||||
path_idx++;
|
||||
pe = pe->next;
|
||||
}
|
||||
}
|
||||
|
||||
struct NODE_CONNECTIVITY* c = &nq->connectivity;
|
||||
if (is_node_connectivity_active(c)) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, " connectivity: probe=%s iface=%s(%u) nat=%s(%u) real=%s(%u) pend=%u iface_rtt=%u nat_rtt=%u real_rtt=%u start_tb=%llu ping_req_tb=%llu",
|
||||
probe_status_str(c->probe_status),
|
||||
probe_result_str(c->interface_status), c->interface_min_rtt,
|
||||
probe_result_str(c->nat_status), c->nat_min_rtt,
|
||||
probe_result_str(c->real_status), c->real_min_rtt,
|
||||
c->pending_count, c->interface_min_rtt, c->nat_min_rtt, c->real_min_rtt,
|
||||
(unsigned long long)c->probe_start_time, (unsigned long long)c->ping_req_time);
|
||||
}
|
||||
|
||||
if (nq->conn_mgr_type != CONN_TYPE_NONE) {
|
||||
char interm_buf[200] = "";
|
||||
int ipos = 0;
|
||||
for (uint8_t i = 0; i < nq->conn_mgr_intermediariy_count && ipos < (int)sizeof(interm_buf)-19; i++) {
|
||||
ipos += snprintf(interm_buf + ipos, sizeof(interm_buf) - ipos,
|
||||
"%s%016llx", i ? "," : "", (unsigned long long)nq->conn_mgr_intermediaries[i]);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, " conn_mgr: type=%s intermediates=[%s]",
|
||||
conn_mgr_type_str(nq->conn_mgr_type), interm_buf);
|
||||
}
|
||||
|
||||
e = e->next;
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
int route_node_format_all(struct ROUTE_BGP* bgp, char* buf, size_t buf_size) {
|
||||
if (!bgp || !bgp->nodes || !buf || buf_size == 0) return 0;
|
||||
int pos = 0;
|
||||
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0,
|
||||
"=== BGP Nodes dump: %d nodes ===\n", queue_entry_count(bgp->nodes));
|
||||
|
||||
int idx = 0;
|
||||
struct ll_entry* e = bgp->nodes->head;
|
||||
while (e && (size_t)pos < buf_size) {
|
||||
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)e;
|
||||
struct NODEINFO* ni = &nq->node;
|
||||
int is_local = (bgp->local_node == nq);
|
||||
const char* name_s = (ni->node_name_len > 0) ? (const char*)((const uint8_t*)ni + sizeof(struct NODEINFO)) : "";
|
||||
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0,
|
||||
"--- Node #%d: id=%016llx name=\"%.*s\" ver=%u dirty=%u alien=%u%s ---\n",
|
||||
idx, (unsigned long long)ni->node_id, ni->node_name_len, name_s,
|
||||
ni->ver, nq->dirty, nq->alien, is_local ? " [LOCAL]" : "");
|
||||
|
||||
if (nq->best_socket)
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0,
|
||||
" best_socket=%p\n", (void*)nq->best_socket);
|
||||
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, " pubkey: ");
|
||||
for (int k = 0; k < SC_PUBKEY_SIZE; k++)
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "%02x", ni->public_key[k]);
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "\n");
|
||||
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, " ed25519: ");
|
||||
for (int k = 0; k < SC_PUBKEY_SIZE; k++)
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "%02x", ni->ed25519_public_key[k]);
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "\n");
|
||||
|
||||
#define FMT_ADD(fmt, ...) \
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, fmt, ##__VA_ARGS__)
|
||||
|
||||
const struct NODEINFO_IPV4_SOCKET_META* v4sm;
|
||||
int v4sm_cnt = get_node_v4_sockets_meta(nq, &v4sm);
|
||||
for (int i = 0; i < v4sm_cnt; i++)
|
||||
FMT_ADD(" v4sock[%d]: id=%u type=%u\n", i, v4sm[i].id, v4sm[i].type);
|
||||
|
||||
const struct NODEINFO_IPV4_ADDR* v4a;
|
||||
int v4a_cnt = get_node_v4_addrs(nq, &v4a);
|
||||
if (v4a_cnt > 0) {
|
||||
const char* atn[] = {"INTERFACE","NAT","REAL"};
|
||||
for (int i = 0; i < v4a_cnt; i++)
|
||||
FMT_ADD(" v4addr[%d]: %s:%u %s sock=%u\n",
|
||||
i, ip_to_str(v4a[i].addr, AF_INET).str, v4a[i].port,
|
||||
(v4a[i].type<=2)?atn[v4a[i].type]:"?", v4a[i].socket_id);
|
||||
}
|
||||
|
||||
const struct NODEINFO_IPV6_SOCKET_META* v6sm;
|
||||
int v6sm_cnt = get_node_v6_sockets_meta(nq, &v6sm);
|
||||
for (int i = 0; i < v6sm_cnt; i++)
|
||||
FMT_ADD(" v6sock[%d]: id=%u type=%u\n", i, v6sm[i].id, v6sm[i].type);
|
||||
|
||||
const struct NODEINFO_IPV6_ADDR* v6a;
|
||||
int v6a_cnt = get_node_v6_addrs(nq, &v6a);
|
||||
for (int i = 0; i < v6a_cnt; i++)
|
||||
FMT_ADD(" v6addr[%d]: %s:%u type=%u sock=%u\n",
|
||||
i, ip_to_str(v6a[i].addr, AF_INET6).str, v6a[i].port,
|
||||
v6a[i].type, v6a[i].socket_id);
|
||||
|
||||
const struct NODEINFO_IPV4_SUBNET* sub4;
|
||||
int sub4_cnt = get_node_routes(nq, &sub4);
|
||||
for (int i = 0; i < sub4_cnt; i++)
|
||||
FMT_ADD(" v4sub[%d]: %s/%d\n", i,
|
||||
ip_to_str(sub4[i].addr, AF_INET).str, sub4[i].prefix_length);
|
||||
|
||||
const struct NODEINFO_IPV6_SUBNET* sub6;
|
||||
int sub6_cnt = get_node_v6_routes(nq, &sub6);
|
||||
for (int i = 0; i < sub6_cnt; i++)
|
||||
FMT_ADD(" v6sub[%d]: %s/%d\n", i,
|
||||
ip_to_str(sub6[i].addr, AF_INET6).str, sub6[i].prefix_length);
|
||||
|
||||
if (ni->tranzit_nodes > 0) {
|
||||
const uint8_t* dyn = node_dyn_start(ni);
|
||||
dyn = skip_name(dyn, ni->node_name_len);
|
||||
dyn = skip_v4_sockets_meta(dyn, ni->local_v4_sockets);
|
||||
dyn = skip_v4_addrs(dyn, ni->local_v4_addrs);
|
||||
dyn = skip_v6_sockets_meta(dyn, ni->local_v6_sockets);
|
||||
dyn = skip_v6_addrs(dyn, ni->local_v6_addrs);
|
||||
dyn = skip_v4_subnets(dyn, ni->local_v4_subnets);
|
||||
dyn += ni->local_v6_subnets * sizeof(struct NODEINFO_IPV6_SUBNET);
|
||||
const struct NODEINFO_TRANZIT_NODE* tr = (const struct NODEINFO_TRANZIT_NODE*)dyn;
|
||||
for (uint8_t i = 0; i < ni->tranzit_nodes; i++)
|
||||
FMT_ADD(" tranzit[%d]: node=%016llx rtt=%u linkq=%u\n",
|
||||
i, (unsigned long long)tr[i].node_id, tr[i].rtt, tr[i].link_q);
|
||||
}
|
||||
|
||||
if (nq->paths) {
|
||||
int path_idx = 0;
|
||||
struct ll_entry* pe = nq->paths->head;
|
||||
while (pe) {
|
||||
struct NODEINFO_PATH* path = (struct NODEINFO_PATH*)pe;
|
||||
uint64_t* hop = (uint64_t*)((uint8_t*)path + sizeof(struct NODEINFO_PATH));
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0,
|
||||
" path[%d]: conn=%s hop_count=%u hops=[",
|
||||
path_idx, path->conn ? path->conn->log_name : "NULL", path->hop_count);
|
||||
for (uint8_t hi = 0; hi < path->hop_count; hi++)
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0,
|
||||
"%s%016llx", hi ? "->" : "", (unsigned long long)hop[hi]);
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "]\n");
|
||||
path_idx++;
|
||||
pe = pe->next;
|
||||
}
|
||||
}
|
||||
|
||||
struct NODE_CONNECTIVITY* c = &nq->connectivity;
|
||||
if (is_node_connectivity_active(c)) {
|
||||
FMT_ADD(" connectivity: probe=%s iface=%s(%u) nat=%s(%u) real=%s(%u) pend=%u iface_rtt=%u nat_rtt=%u real_rtt=%u start_tb=%llu ping_req_tb=%llu\n",
|
||||
probe_status_str(c->probe_status),
|
||||
probe_result_str(c->interface_status), c->interface_min_rtt,
|
||||
probe_result_str(c->nat_status), c->nat_min_rtt,
|
||||
probe_result_str(c->real_status), c->real_min_rtt,
|
||||
c->pending_count, c->interface_min_rtt, c->nat_min_rtt, c->real_min_rtt,
|
||||
(unsigned long long)c->probe_start_time, (unsigned long long)c->ping_req_time);
|
||||
}
|
||||
|
||||
if (nq->conn_mgr_type != CONN_TYPE_NONE) {
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0,
|
||||
" conn_mgr: type=%s intermediates=[",
|
||||
conn_mgr_type_str(nq->conn_mgr_type));
|
||||
for (uint8_t i = 0; i < nq->conn_mgr_intermediariy_count; i++)
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0,
|
||||
"%s%016llx", i ? "," : "",
|
||||
(unsigned long long)nq->conn_mgr_intermediaries[i]);
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "]\n");
|
||||
}
|
||||
|
||||
#undef FMT_ADD
|
||||
e = e->next;
|
||||
idx++;
|
||||
}
|
||||
|
||||
if ((size_t)pos >= buf_size) {
|
||||
pos = (int)buf_size - 14;
|
||||
if (pos < 0) pos = 0;
|
||||
pos += snprintf(buf + pos, buf_size - pos, "\n[TRUNCATED]\n");
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BGP* bgp) {
|
||||
if (!instance || !bgp) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_update_my_nodeinfo: invalid args");
|
||||
@@ -176,10 +481,16 @@ 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 (bgp->local_node) u_free(bgp->local_node);
|
||||
if (changed) {
|
||||
if (bgp->local_node) {
|
||||
if (instance->rt) route_delete(instance->rt, 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;
|
||||
bgp->local_node->node.node_id = instance->node_id;
|
||||
@@ -193,6 +504,7 @@ int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BG
|
||||
bgp->local_node->node.local_v6_subnets = vc6;
|
||||
bgp->local_node->node.node_name_len = name_len;
|
||||
memcpy(bgp->local_node->node.public_key, instance->my_keys.public_key, SC_PUBKEY_SIZE);
|
||||
memcpy(bgp->local_node->node.ed25519_public_key, bgp->ed25519_public_key, SC_PUBKEY_SIZE);
|
||||
bgp->local_node->dirty = 1;
|
||||
bgp->local_node->last_ver = bgp->local_node->node.ver;
|
||||
|
||||
@@ -300,6 +612,10 @@ int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BG
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "my_nodeinfo updated: v4s=%d v4a=%d v6s=%d v6a=%d v4sub=%d v6sub=%d ver=%d",
|
||||
sock_count, addr_count, sock6_count, addr6_count, vc, vc6, bgp->local_node->node.ver);
|
||||
if (instance->rt) {
|
||||
if (!route_insert(instance->rt, bgp->local_node))
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_bgp_update_my_nodeinfo: failed to re-insert local routes after update");
|
||||
}
|
||||
} else {
|
||||
bgp->local_node->last_ver = bgp->local_node->node.ver;
|
||||
}
|
||||
|
||||
+19
-1
@@ -33,6 +33,12 @@ struct UTUN_INSTANCE;
|
||||
#define CONN_PROBE_COUNT 3
|
||||
#define CONN_PROBE_TIMEOUT_MS 1000
|
||||
|
||||
#define CONN_TYPE_NONE 0
|
||||
#define CONN_TYPE_DIRECT 1
|
||||
#define CONN_TYPE_REVERSE 2
|
||||
#define CONN_TYPE_INDIRECT 3
|
||||
#define CONN_MGR_MAX_INTERMEDIARIES 3
|
||||
|
||||
// ---- состояние связности с удалённым узлом (локальное, не передаётся по BGP) ----
|
||||
|
||||
struct NODE_CONNECTIVITY {
|
||||
@@ -49,6 +55,8 @@ 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)
|
||||
void* probe_list; // linked list of active conn_probe_ctx (для cancel)
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -57,7 +65,8 @@ struct NODE_CONNECTIVITY {
|
||||
struct NODEINFO {
|
||||
uint64_t node_id; // (big-endian)
|
||||
uint8_t ver; // версия пакета (циклический счетчик чтобы быстро сравнивать с локальной копией - были ли обновления)
|
||||
uint8_t public_key[SC_PUBKEY_SIZE]; // node pubkey
|
||||
uint8_t public_key[SC_PUBKEY_SIZE]; // X25519 pubkey
|
||||
uint8_t ed25519_public_key[SC_PUBKEY_SIZE]; // Ed25519 pubkey for signature verification
|
||||
uint8_t node_name_len; // размер в байтах (без null терминации)
|
||||
uint8_t local_v4_sockets; // NODEINFO_IPV4_SOCKET_META число метаданных ipv4 сокетов
|
||||
uint8_t local_v4_addrs; // NODEINFO_IPV4_ADDR число типизированных ipv4 адресов узла (плоский список)
|
||||
@@ -132,6 +141,10 @@ struct NODEINFO_Q {
|
||||
struct ll_queue* paths; // сюда помещаем struct NODEINFO_PATH
|
||||
uint8_t dirty;
|
||||
uint8_t last_ver;
|
||||
uint8_t alien; // 1 = чужой узел (не запрашиваем роутинг при connect)
|
||||
uint8_t conn_mgr_type; // CONN_TYPE_* — тип managed-подключения
|
||||
uint64_t conn_mgr_intermediaries[CONN_MGR_MAX_INTERMEDIARIES]; // посредники для INDIRECT
|
||||
uint8_t conn_mgr_intermediariy_count;
|
||||
struct NODE_CONNECTIVITY connectivity; // состояние связности (локальное)
|
||||
struct ETCP_SOCKET* best_socket;
|
||||
struct NODEINFO node; // Всегда в конце структуры - динамически расширяемый блок
|
||||
@@ -173,4 +186,9 @@ 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);
|
||||
|
||||
void route_node_dump_all(struct ROUTE_BGP* bgp);
|
||||
int route_node_format_all(struct ROUTE_BGP* bgp, char* buf, size_t buf_size);
|
||||
|
||||
#endif // ROUTE_NODE_H
|
||||
@@ -0,0 +1,150 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/stat.h>
|
||||
#include "route_node_lmdb.h"
|
||||
#include "route_bgp.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
|
||||
#define NODEINFO_DB_MAPSIZE (10UL * 1024 * 1024)
|
||||
|
||||
int route_node_lmdb_init(struct ROUTE_BGP* bgp, const char* db_path) {
|
||||
if (!bgp || !db_path || !db_path[0]) return -1;
|
||||
|
||||
int rc = mdb_env_create(&bgp->nodeinfo_env);
|
||||
if (rc != MDB_SUCCESS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_env_create failed: %s", mdb_strerror(rc));
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = mdb_env_set_mapsize(bgp->nodeinfo_env, NODEINFO_DB_MAPSIZE);
|
||||
if (rc != MDB_SUCCESS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_env_set_mapsize failed: %s", mdb_strerror(rc));
|
||||
mdb_env_close(bgp->nodeinfo_env);
|
||||
bgp->nodeinfo_env = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = mdb_env_set_maxdbs(bgp->nodeinfo_env, 2);
|
||||
if (rc != MDB_SUCCESS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_env_set_maxdbs failed: %s", mdb_strerror(rc));
|
||||
mdb_env_close(bgp->nodeinfo_env);
|
||||
bgp->nodeinfo_env = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = mdb_env_open(bgp->nodeinfo_env, db_path, 0, 0644);
|
||||
if (rc != MDB_SUCCESS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_env_open(%s) failed: %s", db_path, mdb_strerror(rc));
|
||||
mdb_env_close(bgp->nodeinfo_env);
|
||||
bgp->nodeinfo_env = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
MDB_txn* txn = NULL;
|
||||
rc = mdb_txn_begin(bgp->nodeinfo_env, NULL, 0, &txn);
|
||||
if (rc != MDB_SUCCESS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_txn_begin failed: %s", mdb_strerror(rc));
|
||||
mdb_env_close(bgp->nodeinfo_env);
|
||||
bgp->nodeinfo_env = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = mdb_dbi_open(txn, "nodeinfo", MDB_CREATE, &bgp->nodeinfo_dbi);
|
||||
if (rc != MDB_SUCCESS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_dbi_open(nodeinfo) failed: %s", mdb_strerror(rc));
|
||||
mdb_txn_abort(txn);
|
||||
mdb_env_close(bgp->nodeinfo_env);
|
||||
bgp->nodeinfo_env = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = mdb_txn_commit(txn);
|
||||
if (rc != MDB_SUCCESS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_txn_commit failed: %s", mdb_strerror(rc));
|
||||
mdb_env_close(bgp->nodeinfo_env);
|
||||
bgp->nodeinfo_env = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "LMDB nodeinfo database opened: %s", db_path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void route_node_lmdb_destroy(struct ROUTE_BGP* bgp) {
|
||||
if (!bgp) return;
|
||||
if (bgp->nodeinfo_env) {
|
||||
mdb_env_close(bgp->nodeinfo_env);
|
||||
bgp->nodeinfo_env = NULL;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "LMDB nodeinfo database closed");
|
||||
}
|
||||
}
|
||||
|
||||
int route_node_lmdb_put(struct ROUTE_BGP* bgp, struct NODEINFO_Q* nq) {
|
||||
if (!bgp || !bgp->nodeinfo_env || !nq) return -1;
|
||||
|
||||
int dyn_size = nodeinfo_dyn_size(&nq->node);
|
||||
size_t val_size = sizeof(struct NODEINFO) + dyn_size;
|
||||
|
||||
MDB_val key, data;
|
||||
key.mv_size = 8;
|
||||
key.mv_data = &nq->node.node_id;
|
||||
|
||||
data.mv_size = val_size;
|
||||
data.mv_data = (void*)&nq->node;
|
||||
|
||||
MDB_txn* txn = NULL;
|
||||
int rc = mdb_txn_begin(bgp->nodeinfo_env, NULL, 0, &txn);
|
||||
if (rc != MDB_SUCCESS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB put mdb_txn_begin failed: %s", mdb_strerror(rc));
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = mdb_put(txn, bgp->nodeinfo_dbi, &key, &data, 0);
|
||||
if (rc != MDB_SUCCESS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_put node %016llx failed: %s",
|
||||
(unsigned long long)nq->node.node_id, mdb_strerror(rc));
|
||||
mdb_txn_abort(txn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = mdb_txn_commit(txn);
|
||||
if (rc != MDB_SUCCESS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB put mdb_txn_commit failed: %s", mdb_strerror(rc));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int route_node_lmdb_del(struct ROUTE_BGP* bgp, uint64_t node_id) {
|
||||
if (!bgp || !bgp->nodeinfo_env) return -1;
|
||||
|
||||
MDB_val key;
|
||||
key.mv_size = 8;
|
||||
key.mv_data = &node_id;
|
||||
|
||||
MDB_txn* txn = NULL;
|
||||
int rc = mdb_txn_begin(bgp->nodeinfo_env, NULL, 0, &txn);
|
||||
if (rc != MDB_SUCCESS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB del mdb_txn_begin failed: %s", mdb_strerror(rc));
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = mdb_del(txn, bgp->nodeinfo_dbi, &key, NULL);
|
||||
if (rc != MDB_SUCCESS && rc != MDB_NOTFOUND) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB mdb_del node %016llx failed: %s",
|
||||
(unsigned long long)node_id, mdb_strerror(rc));
|
||||
mdb_txn_abort(txn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = mdb_txn_commit(txn);
|
||||
if (rc != MDB_SUCCESS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "LMDB del mdb_txn_commit failed: %s", mdb_strerror(rc));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef ROUTE_NODE_LMDB_H
|
||||
#define ROUTE_NODE_LMDB_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "route_node.h"
|
||||
|
||||
struct ROUTE_BGP;
|
||||
|
||||
int route_node_lmdb_init(struct ROUTE_BGP* bgp, const char* db_path);
|
||||
void route_node_lmdb_destroy(struct ROUTE_BGP* bgp);
|
||||
int route_node_lmdb_put(struct ROUTE_BGP* bgp, struct NODEINFO_Q* nq);
|
||||
int route_node_lmdb_del(struct ROUTE_BGP* bgp, uint64_t node_id);
|
||||
|
||||
#endif
|
||||
+6
-1
@@ -325,6 +325,11 @@ void route_ping_handle_req(struct ROUTE_BGP* bgp,
|
||||
ctx->request_id = req_pkt->request_id;
|
||||
ctx->count_total = req_pkt->count;
|
||||
ctx->timeout_ms = req_pkt->timeout_ms;
|
||||
|
||||
{
|
||||
struct NODEINFO_Q* req_nq = nodeinfo_find_by_id(bgp, from_conn->peer_node_id);
|
||||
if (req_nq) req_nq->connectivity.ping_req_time = get_time_tb();
|
||||
}
|
||||
if (len >= sizeof(struct BGP_PING_REQUEST)) {
|
||||
memcpy(ctx->pubkey, req_pkt->pubkey, SC_PUBKEY_SIZE);
|
||||
}
|
||||
@@ -338,7 +343,7 @@ void route_ping_handle_req(struct ROUTE_BGP* bgp,
|
||||
/* Если target не указан — можно разрешить из nodeinfo */
|
||||
/*
|
||||
if (sin->sin_addr.s_addr == 0 && sin->sin_port == 0) {
|
||||
struct NODEINFO_Q* nq = route_bgp_get_node(bgp, req_pkt->node_id);
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(bgp, req_pkt->node_id);
|
||||
if (nq) {
|
||||
const struct NODEINFO_IPV4_ADDR* addrs;
|
||||
int count = get_node_v4_addrs(nq, &addrs);
|
||||
|
||||
+204
-632
File diff suppressed because it is too large.
Load diff
+26
-14
@@ -7,7 +7,7 @@
|
||||
|
||||
// Размеры ключей
|
||||
#define SC_PRIVKEY_SIZE 32
|
||||
#define SC_PUBKEY_SIZE 64
|
||||
#define SC_PUBKEY_SIZE 32
|
||||
#define SC_HASH_SIZE 32
|
||||
#define SC_NONCE_SIZE 13 // CCM requires exactly 13 bytes
|
||||
#define SC_SHARED_SECRET_SIZE SC_HASH_SIZE
|
||||
@@ -15,7 +15,7 @@
|
||||
#define SC_TAG_SIZE 16
|
||||
#define SC_CRC32_SIZE 4
|
||||
|
||||
// Шифрование pubkey при передаче (salt + double SHA256 XOR)
|
||||
// Обфускация pubkey при передаче (salt + double SHA256 XOR)
|
||||
#define SC_PUBKEY_ENC_SALT_SIZE 8
|
||||
#define SC_PUBKEY_ENC_SIZE (SC_PUBKEY_SIZE + SC_PUBKEY_ENC_SALT_SIZE)
|
||||
|
||||
@@ -43,14 +43,10 @@ struct SC_MYKEYS {
|
||||
// Контекст защищенного канала
|
||||
struct secure_channel {
|
||||
struct SC_MYKEYS* pk;
|
||||
/* Ключи пира (после key exchange) */
|
||||
/* Ключ пира (после key exchange) */
|
||||
uint8_t peer_public_key[SC_PUBKEY_SIZE];
|
||||
uint8_t session_key[SC_SESSION_KEY_SIZE]; /* Derived session key */
|
||||
|
||||
/* Nonces для отправки и приема (теперь генерируются динамически, не используются counters для nonce) */
|
||||
uint8_t send_nonce[SC_NONCE_SIZE];
|
||||
uint8_t recv_nonce[SC_NONCE_SIZE];
|
||||
|
||||
uint8_t initialized;
|
||||
uint8_t peer_key_set;
|
||||
uint8_t session_ready;
|
||||
@@ -78,15 +74,9 @@ sc_status_t sc_obfuscate_pubkey(const uint8_t *salt, const uint8_t *peer_pubkey,
|
||||
// --- Streaming cipher (AES-128-CTR, confidentiality only) ---
|
||||
|
||||
#define SC_STREAM_NONCE_SIZE 12
|
||||
#define SC_STREAM_AES_SCHED_SIZE 176 // sizeof(struct tc_aes_key_sched_struct) = Nb*(Nr+1)*4 = 4*11*4
|
||||
|
||||
struct sc_stream_state {
|
||||
#ifdef USE_OPENSSL
|
||||
void *ectx; // EVP_CIPHER_CTX* (opaque, created/destroyed in .c)
|
||||
#else
|
||||
uint8_t sched_buf[SC_STREAM_AES_SCHED_SIZE]; // AES-128 key schedule
|
||||
uint8_t ctr_block[16]; // nonce(12) + counter(4, BE)
|
||||
#endif
|
||||
void *ectx; // EVP_CIPHER_CTX*
|
||||
uint8_t initialized;
|
||||
};
|
||||
|
||||
@@ -94,4 +84,26 @@ sc_status_t sc_stream_init(sc_context_t *ctx, struct sc_stream_state *state, uin
|
||||
sc_status_t sc_stream_xor(struct sc_stream_state *state, uint8_t *data, size_t data_len);
|
||||
void sc_stream_cleanup(struct sc_stream_state *state);
|
||||
|
||||
// --- Streaming Ed25519 signature ---
|
||||
// SHA-512 инкрементально (EVP_DigestUpdate) + Ed25519 подпись хеша (one-shot EVP_DigestSign).
|
||||
// Память O(1), не зависит от размера данных.
|
||||
|
||||
#define SC_SIGN_SIZE 64 // Ed25519 signature size
|
||||
|
||||
struct sc_stream_sign_state {
|
||||
void *md_ctx; // EVP_MD_CTX* — SHA-512 аккумулятор
|
||||
void *pkey; // EVP_PKEY* — Ed25519 ключ
|
||||
uint8_t initialized;
|
||||
uint8_t is_sign; // 1=sign, 0=verify
|
||||
};
|
||||
|
||||
sc_status_t sc_stream_sign_init(sc_context_t *ctx, struct sc_stream_sign_state *state);
|
||||
sc_status_t sc_stream_sign_verify_init(struct sc_stream_sign_state *state, const uint8_t *ed25519_pubkey);
|
||||
sc_status_t sc_stream_sign_update(struct sc_stream_sign_state *state, const uint8_t *data, size_t len);
|
||||
sc_status_t sc_stream_sign_final(struct sc_stream_sign_state *state, uint8_t *sig_out, size_t *sig_len);
|
||||
sc_status_t sc_stream_sign_verify(struct sc_stream_sign_state *state, const uint8_t *sig, size_t sig_len);
|
||||
void sc_stream_sign_cleanup(struct sc_stream_sign_state *state);
|
||||
|
||||
sc_status_t sc_derive_ed25519_pubkey(const uint8_t *x25519_privkey, uint8_t *ed25519_pubkey_out);
|
||||
|
||||
#endif // SECURE_CHANNEL_H
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// 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
|
||||
+5
-1
@@ -72,6 +72,10 @@ static int stcp_decrypt_and_check(uint8_t *plaintext, size_t plaintext_len,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stcp_conn_deferred_free(void* arg) {
|
||||
u_free((struct stcp_conn*)arg);
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -82,7 +86,7 @@ static void stcp_conn_do_close(struct stcp_conn *c, int err) {
|
||||
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); }
|
||||
else if (prev == STCP_STATE_HS_SERVER_WAIT) { sc_stream_cleanup(&c->stream_send); sc_stream_cleanup(&c->stream_recv); if (c->allocated) uasync_call_soon(c->ua, c, stcp_conn_deferred_free); }
|
||||
}
|
||||
|
||||
static void stcp_conn_send_message(struct stcp_conn *c, const uint8_t *data, size_t len) {
|
||||
|
||||
+4
-3
@@ -142,7 +142,7 @@ struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int mtu = config->global.mtu > 0 ? config->global.mtu : TUN_MTU_DEFAULT;
|
||||
int mtu = TUN_MTU_DEFAULT;
|
||||
|
||||
if (!test_mode) {
|
||||
if (tun_platform_init(tun, tun->ifname, ip_str, mtu) != 0) {
|
||||
@@ -223,7 +223,8 @@ fail:
|
||||
struct tun_if* tun_init_nat(struct UASYNC* ua, const char* ifname, const char* ip_addr_str, int mtu, int test_mode)
|
||||
{
|
||||
if (!ua || !ifname || !ip_addr_str) return NULL;
|
||||
if (mtu <= 0) mtu = TUN_MTU_DEFAULT;
|
||||
(void)mtu;
|
||||
int tun_mtu = TUN_MTU_DEFAULT;
|
||||
|
||||
struct tun_if* tun = u_calloc(1, sizeof(struct tun_if));
|
||||
if (!tun) return NULL;
|
||||
@@ -238,7 +239,7 @@ struct tun_if* tun_init_nat(struct UASYNC* ua, const char* ifname, const char* i
|
||||
snprintf(ip_str, sizeof(ip_str), "%s/32", ip_addr_str);
|
||||
|
||||
if (!test_mode) {
|
||||
if (tun_platform_init(tun, tun->ifname, ip_str, mtu) != 0) {
|
||||
if (tun_platform_init(tun, tun->ifname, ip_str, tun_mtu) != 0) {
|
||||
u_free(tun);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
+43
-10
@@ -11,7 +11,9 @@
|
||||
#include "route_bgp.h"
|
||||
#include "etcp_connections.h"
|
||||
#include "etcp.h"
|
||||
#include "conn_mgr.h"
|
||||
#include "control_server.h"
|
||||
#include "msg_transport.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include <stdlib.h>
|
||||
@@ -93,7 +95,7 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
|
||||
return -1;
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Routing module created");
|
||||
if (g_tun_init_enabled) {
|
||||
if (g_tun_init_enabled && config->global.tun_enabled) {
|
||||
instance->tun = tun_init(ua, config);
|
||||
if (!instance->tun) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to initialize TUN device");
|
||||
@@ -124,10 +126,12 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
|
||||
} else {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP module initialized");
|
||||
if (instance->rt && instance->bgp->local_node) {
|
||||
route_bgp_update_my_nodeinfo(instance,instance->bgp);
|
||||
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");
|
||||
route_bgp_update_my_nodeinfo(instance, instance->bgp);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -157,16 +161,21 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
|
||||
}
|
||||
|
||||
// TCP proxy client (from [tcp_proxy] config section)
|
||||
if (config->global.tcp_proxy_client_enabled) {
|
||||
const char* tun_name = config->global.tcp_proxy_client_tun_name[0] ? config->global.tcp_proxy_client_tun_name : "tun_tcp";
|
||||
const char* tun_ip = config->global.tcp_proxy_client_tun_ip[0] ? config->global.tcp_proxy_client_tun_ip : "10.99.0.1";
|
||||
int mtu = config->global.tcp_proxy_client_mtu > 0 ? config->global.tcp_proxy_client_mtu : 1500;
|
||||
if (config->global.tcp_proxy_client_enabled || config->global.tcp_proxy_client_socks_enabled || config->global.tcp_proxy_client_http_proxy_enabled) {
|
||||
const char* tun_name = config->global.tcp_proxy_client_tun_name;
|
||||
const char* tun_ip = config->global.tcp_proxy_client_tun_ip;
|
||||
int mtu = config->global.tcp_proxy_client_mtu;
|
||||
instance->tcp_proxy_client = tcp_proxy_client_create(instance, ua, tun_name, tun_ip, mtu, g_tun_init_enabled ? 0 : 1,
|
||||
config->global.tcp_proxy_client_mappings, config->global.tcp_proxy_client_mapping_count,
|
||||
config->global.tcp_proxy_client_via_node_id);
|
||||
config->global.tcp_proxy_client_via_node_id,
|
||||
config->global.tcp_proxy_client_socks_enabled, config->global.tcp_proxy_client_socks_addr,
|
||||
config->global.tcp_proxy_client_http_proxy_enabled, config->global.tcp_proxy_client_http_proxy_addr);
|
||||
if (instance->tcp_proxy_client) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TCP proxy client enabled: TUN=%s IP=%s MTU=%d mappings=%d",
|
||||
tun_name, tun_ip, mtu, config->global.tcp_proxy_client_mapping_count);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TCP proxy client enabled: TUN=%s IP=%s MTU=%d socks=%s http=%s mappings=%d",
|
||||
tun_name, tun_ip, mtu,
|
||||
config->global.tcp_proxy_client_socks_enabled ? config->global.tcp_proxy_client_socks_addr : "off",
|
||||
config->global.tcp_proxy_client_http_proxy_enabled ? config->global.tcp_proxy_client_http_proxy_addr : "off",
|
||||
config->global.tcp_proxy_client_mapping_count);
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create TCP proxy client");
|
||||
return -1;
|
||||
@@ -289,6 +298,13 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
|
||||
// Stop running if not already
|
||||
instance->running = 0;
|
||||
|
||||
// Shutdown message transport server
|
||||
if (instance->msg_t) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "Shutting down message transport server");
|
||||
msg_transport_shutdown(instance->msg_t);
|
||||
instance->msg_t = NULL;
|
||||
}
|
||||
|
||||
// Shutdown control server first
|
||||
if (instance->control_srv) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Shutting down control server");
|
||||
@@ -365,6 +381,8 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
|
||||
|
||||
// Cleanup etcp_router
|
||||
etcp_router_destroy(instance);
|
||||
|
||||
if (instance->conn_mgr) { conn_mgr_destroy(instance->conn_mgr); instance->conn_mgr = NULL; }
|
||||
|
||||
// Cleanup BGP module
|
||||
if (instance->bgp) {
|
||||
@@ -482,6 +500,21 @@ int utun_instance_init(struct UTUN_INSTANCE *instance) {
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize message transport server if configured
|
||||
if (instance->config->global.msg_transport_sock.ss_family != 0) {
|
||||
int mt_ret = msg_transport_init(&instance->msg_t, instance, instance->ua,
|
||||
&instance->config->global.msg_transport_sock,
|
||||
8, ETCP_ID_MSG_TRANSPORT);
|
||||
if (mt_ret != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "Failed to initialize msg_transport, continuing without IPC transport");
|
||||
instance->msg_t = NULL;
|
||||
} else {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "Message transport server initialized");
|
||||
}
|
||||
} else {
|
||||
instance->msg_t = NULL;
|
||||
}
|
||||
|
||||
// Start the main loop
|
||||
instance->running = 1;
|
||||
|
||||
|
||||
+12
-1
@@ -8,6 +8,7 @@
|
||||
#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"
|
||||
@@ -15,6 +16,9 @@
|
||||
#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;
|
||||
@@ -27,7 +31,9 @@ struct ETCP_BINDINGS;
|
||||
struct ETCP_ROUTER_BINDINGS;
|
||||
struct ROUTE_BGP;
|
||||
struct control_server;
|
||||
struct msg_transport;
|
||||
struct PING_CONTEXT;
|
||||
struct CONN_MGR;
|
||||
|
||||
// uTun instance configuration
|
||||
struct UTUN_INSTANCE {
|
||||
@@ -71,8 +77,9 @@ struct UTUN_INSTANCE {
|
||||
struct memory_pool* pkt_pool;
|
||||
struct memory_pool* ack_pool;
|
||||
|
||||
// Active sockets
|
||||
// Active sockets (UDP)
|
||||
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;
|
||||
@@ -87,6 +94,9 @@ struct UTUN_INSTANCE {
|
||||
// Control server for monitoring
|
||||
struct control_server* control_srv;
|
||||
|
||||
// Message transport server (local IPC → ETCP)
|
||||
struct msg_transport* msg_t;
|
||||
|
||||
// Firewall
|
||||
struct firewall_ctx fw;
|
||||
|
||||
@@ -103,6 +113,7 @@ struct UTUN_INSTANCE {
|
||||
// etcp_router bindings и seq-connections (per-instance service routing)
|
||||
struct ETCP_ROUTER_BINDINGS router_bindings;
|
||||
struct ll_queue* router_conns;
|
||||
struct CONN_MGR* conn_mgr; // Connection Manager (может быть NULL)
|
||||
|
||||
// TCP proxy server (exit node)
|
||||
struct tcp_proxy_server tcp_proxy_server;
|
||||
|
||||
+64
-102
@@ -5,6 +5,9 @@ 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 \
|
||||
@@ -40,47 +43,29 @@ check_PROGRAMS = \
|
||||
test_udp_proxy \
|
||||
test_icmp_proxy \
|
||||
test_tcp_proxy_client \
|
||||
test_socks_http_proxy \
|
||||
test_bgp_route_exchange \
|
||||
test_bgp_triangle \
|
||||
test_conn_mgr \
|
||||
test_bbr_integration \
|
||||
test_intensive_memory_pool \
|
||||
test_tcp_io \
|
||||
test_uasync_socket_race \
|
||||
bench_timeout_heap \
|
||||
bench_uasync_timeouts
|
||||
|
||||
# Долгие тесты: запускаются только вручную, не включаются в make check
|
||||
# test_etcp_congestion — DISABLED: старый congestion control удалён, ждёт новых BBR тестов
|
||||
# test_tcp_proxy_remote — 2-node TCP через etcp, требует fix tcp_proxy_client singleton
|
||||
# Долгие тесты: запускаются только вручную
|
||||
noinst_PROGRAMS =
|
||||
|
||||
# test_crypto and test_ecc_encrypt only needed for TinyCrypt (not when using OpenSSL)
|
||||
if USE_OPENSSL
|
||||
else
|
||||
check_PROGRAMS += test_crypto test_ecc_encrypt
|
||||
endif
|
||||
|
||||
# Basic includes
|
||||
AM_CFLAGS = -g -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
|
||||
# TinyCrypt source files
|
||||
TINYCRYPT_SRCDIR = $(top_srcdir)/tinycrypt/lib/source
|
||||
|
||||
# TinyCrypt object files (built by src/Makefile.am with utun- prefix)
|
||||
TINYCRYPT_OBJS = \
|
||||
$(top_builddir)/src/utun-aes_encrypt.o \
|
||||
$(top_builddir)/src/utun-aes_decrypt.o \
|
||||
$(top_builddir)/src/utun-ccm_mode.o \
|
||||
$(top_builddir)/src/utun-cmac_mode.o \
|
||||
$(top_builddir)/src/utun-ctr_mode.o \
|
||||
$(top_builddir)/src/utun-ecc.o \
|
||||
$(top_builddir)/src/utun-ecc_dh.o \
|
||||
$(top_builddir)/src/utun-ecc_dsa.o \
|
||||
$(top_builddir)/src/utun-sha256.o \
|
||||
$(top_builddir)/src/utun-ecc_platform_specific.o \
|
||||
$(top_builddir)/src/utun-utils.o
|
||||
AM_CFLAGS = -g -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
|
||||
# Secure channel and CRC objects (built in src directory)
|
||||
SECURE_CHANNEL_OBJS = $(top_builddir)/src/utun-secure_channel.o $(top_builddir)/src/utun-crc32.o
|
||||
|
||||
# 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 \
|
||||
@@ -111,7 +96,9 @@ ETCP_FULL_OBJS = \
|
||||
$(top_builddir)/src/utun-route_bgp.o \
|
||||
$(top_builddir)/src/utun-route_ping.o \
|
||||
$(top_builddir)/src/utun-route_node.o \
|
||||
$(top_builddir)/src/utun-route_node_lmdb.o \
|
||||
$(top_builddir)/src/utun-route_connectivity.o \
|
||||
$(top_builddir)/src/utun-conn_mgr.o \
|
||||
$(top_builddir)/src/utun-routing.o \
|
||||
$(top_builddir)/src/utun-tun_if.o \
|
||||
$(top_builddir)/src/utun-tun_route.o \
|
||||
@@ -120,54 +107,34 @@ 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 \
|
||||
$(top_builddir)/src/utun-etcp_router.o \
|
||||
$(top_builddir)/src/proxy/utun-tcp_proxy_server.o \
|
||||
$(top_builddir)/src/proxy/utun-udp_proxy.o \
|
||||
$(top_builddir)/src/proxy/utun-socks_proxy.o \
|
||||
$(top_builddir)/src/proxy/utun-icmp_proxy.o \
|
||||
$(top_builddir)/src/lwip_tcp/utun-lwip_pbuf.o \
|
||||
$(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 (advapi32 for CryptGenRandom, ws2_32 for sockets)
|
||||
# Windows-specific libraries
|
||||
if OS_WINDOWS
|
||||
WIN_LIBS = -lws2_32 -liphlpapi -ladvapi32 -lbcrypt -ldbghelp
|
||||
else
|
||||
WIN_LIBS =
|
||||
endif
|
||||
|
||||
# Common libraries (libuasync.a from lib directory)
|
||||
# Common libraries
|
||||
COMMON_LIBS = $(top_builddir)/lib/libuasync.a -lpthread $(WIN_LIBS)
|
||||
|
||||
# Crypto libraries (conditional)
|
||||
if USE_OPENSSL
|
||||
# Crypto always uses OpenSSL
|
||||
CRYPTO_LIBS = -lcrypto
|
||||
TINYCRYPT_BUILT =
|
||||
else
|
||||
CRYPTO_LIBS = $(TINYCRYPT_OBJS)
|
||||
TINYCRYPT_BUILT = tinycrypt-objects
|
||||
endif
|
||||
|
||||
# Tests run via check-local (with timing and colored output), not TESTS
|
||||
# Build TinyCrypt objects as a group (only when not using OpenSSL)
|
||||
if USE_OPENSSL
|
||||
else
|
||||
tinycrypt-objects: $(TINYCRYPT_OBJS)
|
||||
endif
|
||||
|
||||
# Ensure TinyCrypt objects are built before tests that need them
|
||||
if USE_OPENSSL
|
||||
BUILT_SOURCES =
|
||||
else
|
||||
BUILT_SOURCES = $(TINYCRYPT_OBJS)
|
||||
|
||||
$(TINYCRYPT_OBJS): $(top_builddir)/src/utun
|
||||
@$(MAKE) -C $(top_builddir)/src tinycrypt-objects
|
||||
endif
|
||||
|
||||
# Test definitions
|
||||
test_etcp_bbr_SOURCES = test_etcp_bbr.c
|
||||
@@ -175,49 +142,47 @@ test_etcp_bbr_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_etcp_bbr_LDADD = $(top_builddir)/src/utun-etcp_bbr.o $(COMMON_LIBS)
|
||||
|
||||
test_etcp_crypto_SOURCES = test_etcp_crypto.c
|
||||
test_etcp_crypto_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
test_etcp_crypto_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_etcp_crypto_LDADD = $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_stream_cipher_SOURCES = test_stream_cipher.c
|
||||
test_stream_cipher_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
test_stream_cipher_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_stream_cipher_LDADD = $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_stream_sign_SOURCES = test_stream_sign.c
|
||||
test_stream_sign_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_stream_sign_LDADD = $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_transport_SOURCES = test_stcp_link.c
|
||||
test_transport_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
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 -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
test_stcp_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_stcp_LDADD = $(top_builddir)/src/utun-stcp.o $(top_builddir)/src/utun-stcp_server.o $(top_builddir)/src/utun-stcp_client.o $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
if USE_OPENSSL
|
||||
else
|
||||
test_crypto_SOURCES = test_crypto.c
|
||||
test_crypto_CFLAGS = -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source -I$(top_srcdir)/lib
|
||||
test_crypto_LDADD = $(TINYCRYPT_OBJS) $(COMMON_LIBS)
|
||||
endif
|
||||
|
||||
test_etcp_two_instances_SOURCES = test_etcp_two_instances.c
|
||||
test_etcp_two_instances_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
test_etcp_two_instances_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_etcp_two_instances_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_etcp_simple_traffic_SOURCES = test_etcp_simple_traffic.c
|
||||
test_etcp_simple_traffic_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
test_etcp_simple_traffic_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_etcp_simple_traffic_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_ipv6_sockets_SOURCES = test_ipv6_sockets.c
|
||||
test_ipv6_sockets_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
test_ipv6_sockets_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_ipv6_sockets_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
# test_etcp_congestion — отключён (старый congestion control удалён)
|
||||
#test_etcp_congestion_SOURCES = test_etcp_congestion.c
|
||||
#test_etcp_congestion_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
#test_etcp_congestion_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
# test_etcp_reinit_inflight — отключён (старый congestion control удалён)
|
||||
#test_etcp_reinit_inflight_SOURCES = test_etcp_reinit_inflight.c
|
||||
#test_etcp_reinit_inflight_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
#test_etcp_reinit_inflight_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_tcp_proxy_client_SOURCES = test_tcp_proxy_client.c
|
||||
test_tcp_proxy_client_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_socks_http_proxy_SOURCES = test_socks_http_proxy.c
|
||||
test_socks_http_proxy_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_lwip_tcp_SOURCES = test_lwip_tcp.c
|
||||
test_lwip_tcp_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_lwip_tcp_LDADD = \
|
||||
@@ -250,6 +215,7 @@ test_radix_LDADD = $(COMMON_LIBS)
|
||||
test_route6_lib_SOURCES = test_route6_lib.c
|
||||
test_route6_lib_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_route6_lib_LDADD = $(top_builddir)/src/utun-route6_lib.o \
|
||||
$(top_builddir)/src/utun-route_lib.o \
|
||||
$(top_builddir)/src/utun-route_node.o \
|
||||
$(top_builddir)/src/utun-etcp_debug.o \
|
||||
$(COMMON_LIBS)
|
||||
@@ -257,35 +223,35 @@ test_route6_lib_LDADD = $(top_builddir)/src/utun-route6_lib.o \
|
||||
test_etcp_minimal_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_etcp_100_packets_SOURCES = test_etcp_100_packets.c
|
||||
test_etcp_100_packets_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
test_etcp_100_packets_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_etcp_100_packets_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_etcp_reconnect_SOURCES = test_etcp_reconnect.c
|
||||
test_etcp_reconnect_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
test_etcp_reconnect_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_etcp_reconnect_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_pkt_normalizer_etcp_SOURCES = test_pkt_normalizer_etcp.c
|
||||
test_pkt_normalizer_etcp_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
test_pkt_normalizer_etcp_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_pkt_normalizer_etcp_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_pkt_normalizer_standalone_SOURCES = test_pkt_normalizer_standalone.c
|
||||
test_pkt_normalizer_standalone_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
test_pkt_normalizer_standalone_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_pkt_normalizer_standalone_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_etcp_api_SOURCES = test_etcp_api.c
|
||||
test_etcp_api_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
test_etcp_api_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_etcp_api_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_etcp_ping_SOURCES = test_etcp_ping.c
|
||||
test_etcp_ping_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
test_etcp_ping_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_etcp_ping_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_route_ping_SOURCES = test_route_ping.c
|
||||
test_route_ping_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
test_route_ping_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_route_ping_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_nat_detection_SOURCES = test_nat_detection.c
|
||||
test_nat_detection_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
test_nat_detection_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_nat_detection_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_nat_engine_SOURCES = test_nat_engine.c
|
||||
@@ -293,7 +259,7 @@ test_nat_engine_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_nat_engine_LDADD = $(top_builddir)/src/utun-eim_nat.o $(top_builddir)/src/utun-config_parser.o $(COMMON_LIBS)
|
||||
|
||||
test_nat_transport_SOURCES = test_nat_transport.c
|
||||
test_nat_transport_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
test_nat_transport_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_nat_transport_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_nat_stress_SOURCES = test_nat_stress.c
|
||||
@@ -308,13 +274,6 @@ test_serialize_SOURCES = test_serialize.c
|
||||
test_serialize_CFLAGS = -I$(top_srcdir)/lib
|
||||
test_serialize_LDADD = $(COMMON_LIBS)
|
||||
|
||||
if USE_OPENSSL
|
||||
else
|
||||
test_ecc_encrypt_SOURCES = test_ecc_encrypt.c
|
||||
test_ecc_encrypt_CFLAGS = -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source -I$(top_srcdir)/lib
|
||||
test_ecc_encrypt_LDADD = $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS) -lcrypto
|
||||
endif
|
||||
|
||||
test_intensive_memory_pool_SOURCES = test_intensive_memory_pool.c
|
||||
test_intensive_memory_pool_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_intensive_memory_pool_LDADD = $(COMMON_LIBS)
|
||||
@@ -323,6 +282,10 @@ test_tcp_io_SOURCES = test_tcp_io.c
|
||||
test_tcp_io_CFLAGS = -I$(top_srcdir)/lib
|
||||
test_tcp_io_LDADD = $(COMMON_LIBS)
|
||||
|
||||
test_uasync_socket_race_SOURCES = test_uasync_socket_race.c
|
||||
test_uasync_socket_race_CFLAGS = -I$(top_srcdir)/lib
|
||||
test_uasync_socket_race_LDADD = $(COMMON_LIBS)
|
||||
|
||||
test_memory_pool_and_config_SOURCES = test_memory_pool_and_config.c
|
||||
test_memory_pool_and_config_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_memory_pool_and_config_LDADD = $(COMMON_LIBS)
|
||||
@@ -352,21 +315,23 @@ test_config_debug_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_config_debug_LDADD = $(top_builddir)/src/utun-config_parser.o $(COMMON_LIBS)
|
||||
|
||||
test_route_lib_SOURCES = test_route_lib.c
|
||||
test_route_lib_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/src -I$(top_srcdir)/tinycrypt/lib/include
|
||||
test_route_lib_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_route_lib_LDADD = $(top_builddir)/src/utun-route_lib.o $(top_builddir)/src/utun-route_node.o $(top_builddir)/src/utun-etcp_debug.o $(COMMON_LIBS)
|
||||
|
||||
test_bgp_route_exchange_SOURCES = test_bgp_route_exchange.c
|
||||
test_bgp_route_exchange_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
test_bgp_route_exchange_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_bgp_route_exchange_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_bgp_triangle_SOURCES = test_bgp_triangle.c
|
||||
test_bgp_triangle_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_bgp_triangle_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
# test_dummynet временно исключен (медленный)
|
||||
# test_dummynet_SOURCES = test_dummynet.c
|
||||
# test_dummynet_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
# test_dummynet_LDADD = $(top_builddir)/src/utun-dummynet.o $(COMMON_LIBS)
|
||||
test_conn_mgr_SOURCES = test_conn_mgr.c
|
||||
test_conn_mgr_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_conn_mgr_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_bbr_integration_SOURCES = bbr_integration/test_bbr_integration.c
|
||||
test_bbr_integration_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
test_bbr_integration_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_bbr_integration_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
bench_timeout_heap_SOURCES = bench_timeout_heap.c
|
||||
@@ -377,9 +342,6 @@ bench_uasync_timeouts_SOURCES = bench_uasync_timeouts.c
|
||||
bench_uasync_timeouts_CFLAGS = -I$(top_srcdir)/lib
|
||||
bench_uasync_timeouts_LDADD = $(COMMON_LIBS)
|
||||
|
||||
# Build tinycrypt objects before tests that need them
|
||||
BUILT_SOURCES = $(TINYCRYPT_BUILT)
|
||||
|
||||
# Copy test configs to build directory (tests run from build/tests/)
|
||||
all-local: copy-test-configs
|
||||
|
||||
|
||||
@@ -334,15 +334,11 @@ int main(void) {
|
||||
if (!ctx.ua) { printf("ERROR: uasync_create failed\n"); return 1; }
|
||||
|
||||
const char* s_priv =
|
||||
"67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb";
|
||||
const char* s_pub =
|
||||
"1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17"
|
||||
"c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9";
|
||||
"38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68";
|
||||
const char* s_pub = "ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a";
|
||||
const char* c_priv =
|
||||
"4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2";
|
||||
const char* c_pub =
|
||||
"c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3"
|
||||
"c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71";
|
||||
"704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f";
|
||||
const char* c_pub = "b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01";
|
||||
|
||||
printf("Creating instances...\n");
|
||||
ctx.sender = create_instance(ctx.ua, 0x1111111111111111ULL, c_priv, c_pub);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[global]
|
||||
my_node_id=0xAAAA000000000002
|
||||
my_private_key=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2
|
||||
my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71
|
||||
my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f
|
||||
my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01
|
||||
tun_ip=10.200.20.1/24
|
||||
tun_ifname=tun_test_cli
|
||||
debug_level=error
|
||||
@@ -13,7 +13,7 @@ type=public
|
||||
[client: c1]
|
||||
keepalive=1
|
||||
link=s1:127.0.0.1:15001
|
||||
peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9
|
||||
peer_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a
|
||||
|
||||
[tcp_proxy_client]
|
||||
enabled=yes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[global]
|
||||
my_node_id=0xAAAA000000000001
|
||||
my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb
|
||||
my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9
|
||||
my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68
|
||||
my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a
|
||||
tun_ip=10.200.10.1/24
|
||||
tun_ifname=tun_test_exit
|
||||
debug_level=error
|
||||
|
||||
@@ -107,6 +107,7 @@ static int create_temp_configs(void) {
|
||||
"my_node_id=0x3333333333333333\n"
|
||||
"tun_ip=10.100.0.3/24\n"
|
||||
"tun_ifname=tun102\n"
|
||||
"keepalive_adaptive=0\n"
|
||||
"\n"
|
||||
"[routing]\n"
|
||||
"my_subnet=192.168.30.0/24\n"
|
||||
@@ -128,6 +129,7 @@ static int create_temp_configs(void) {
|
||||
"my_node_id=0x2222222222222222\n"
|
||||
"tun_ip=10.100.0.2/24\n"
|
||||
"tun_ifname=tun101\n"
|
||||
"keepalive_adaptive=0\n"
|
||||
"\n"
|
||||
"[routing]\n"
|
||||
"my_subnet=192.168.20.0/24\n"
|
||||
@@ -159,6 +161,7 @@ static int create_temp_configs(void) {
|
||||
"my_node_id=0x1111111111111111\n"
|
||||
"tun_ip=10.100.0.1/24\n"
|
||||
"tun_ifname=tun100\n"
|
||||
"keepalive_adaptive=0\n"
|
||||
"\n"
|
||||
"[routing]\n"
|
||||
"my_subnet=192.168.10.0/24\n"
|
||||
@@ -288,8 +291,8 @@ static int cond_bgp_c_to_a_gone(void) {
|
||||
// -------------------- Main --------------------
|
||||
int main(void) {
|
||||
debug_config_init();
|
||||
debug_set_level(DEBUG_LEVEL_INFO);
|
||||
debug_set_categories(DEBUG_CATEGORY_BGP | DEBUG_CATEGORY_ETCP | DEBUG_CATEGORY_CONNECTION);
|
||||
debug_set_level(DEBUG_LEVEL_ERROR);
|
||||
debug_set_categories(DEBUG_CATEGORY_BGP);
|
||||
utun_instance_set_tun_init_enabled(0);
|
||||
|
||||
if (create_temp_configs() != 0) return 1;
|
||||
|
||||
@@ -0,0 +1,485 @@
|
||||
/**
|
||||
* @file test_bgp_triangle.c
|
||||
* @brief Тест треугольной BGP-маршрутизации с цепочкой D и листом E (5 узлов)
|
||||
*
|
||||
* Топология:
|
||||
* E
|
||||
* │
|
||||
* A ── B ── C ── D
|
||||
* │ │
|
||||
* └─────┘
|
||||
*
|
||||
* A↔B, A↔C, B↔C — треугольник (проверка fix#1: альтернативные пути при старой версии)
|
||||
* C↔D — транзитивная цепочка
|
||||
* B↔E — лист (проверка каскадного удаления)
|
||||
*
|
||||
* Фазы:
|
||||
* 1. Все линки up — BGP-обмен, проверка полного состояния
|
||||
* 2. Kill A–C — C/D/E только через B
|
||||
* 3. Restore A–C — C шлёт NODEINFO напрямую (та же версия). Fix#1: путь [C] добавлен
|
||||
* 4. Kill A–B — C/D выживают через A–C, B/E живут через C
|
||||
* 5. Kill A–C — A изолирован, все узлы удалены
|
||||
* 6. Restore A–C + A–B — полное восстановление
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <time.h>
|
||||
#include "../lib/platform_compat.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#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/routing.h"
|
||||
#include "../src/route_lib.h"
|
||||
#include "../src/route_bgp.h"
|
||||
#include "../src/route_node.h"
|
||||
#include "../src/dummynet.h"
|
||||
#include "../src/tun_if.h"
|
||||
#include "../src/secure_channel.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
|
||||
#define TEST_TIMEOUT_TB 200000
|
||||
#define PHASE_TIMEOUT_TB 100000
|
||||
#define POLL_INTERVAL_MS 5
|
||||
|
||||
#define NODE_ID_A 0x1111111111111111ULL
|
||||
#define NODE_ID_B 0x2222222222222222ULL
|
||||
#define NODE_ID_C 0x3333333333333333ULL
|
||||
#define NODE_ID_D 0x4444444444444444ULL
|
||||
#define NODE_ID_E 0x5555555555555555ULL
|
||||
|
||||
static struct UTUN_INSTANCE* inst_a = NULL;
|
||||
static struct UTUN_INSTANCE* inst_b = NULL;
|
||||
static struct UTUN_INSTANCE* inst_c = NULL;
|
||||
static struct UTUN_INSTANCE* inst_d = NULL;
|
||||
static struct UTUN_INSTANCE* inst_e = NULL;
|
||||
static struct UASYNC* ua = NULL;
|
||||
static int test_phase = 0;
|
||||
static void* timeout_id = NULL;
|
||||
|
||||
static struct dummynet_filter* df_ab = NULL;
|
||||
static struct dummynet_filter* df_ac = NULL;
|
||||
|
||||
/* ================================================================
|
||||
* Config generator
|
||||
* ================================================================ */
|
||||
|
||||
struct lnk { const char* local_srv; int remote_port; };
|
||||
struct cli { const char* name; const char* peer_pubkey_hex; int lnk_cnt; struct lnk lnks[2]; };
|
||||
struct srv { const char* name; int port; };
|
||||
struct ncfg {
|
||||
uint64_t node_id; const char* tun_ip; const char* my_subnet;
|
||||
const char* priv_hex; const char* pub_hex;
|
||||
int srv_cnt; struct srv srvs[4];
|
||||
int cli_cnt; struct cli clis[3];
|
||||
};
|
||||
|
||||
static void build_node_config(char* buf, size_t size, const struct ncfg* c) {
|
||||
int off = snprintf(buf, size,
|
||||
"[global]\n"
|
||||
"my_node_id=0x%llx\n"
|
||||
"my_private_key=%s\n"
|
||||
"my_public_key=%s\n"
|
||||
"tun_ip=%s\n"
|
||||
"tun_ifname=tun99\n"
|
||||
"keepalive_adaptive=0\n"
|
||||
"\n"
|
||||
"[routing]\n"
|
||||
"my_subnet=%s\n"
|
||||
"\n",
|
||||
(unsigned long long)c->node_id, c->priv_hex, c->pub_hex, c->tun_ip, c->my_subnet);
|
||||
for (int i = 0; i < c->srv_cnt; i++)
|
||||
off += snprintf(buf + off, size - off,
|
||||
"[server: %s]\naddr=127.0.0.1:%d\ntype=public\n\n", c->srvs[i].name, c->srvs[i].port);
|
||||
for (int i = 0; i < c->cli_cnt; i++) {
|
||||
off += snprintf(buf + off, size - off,
|
||||
"[client: %s]\nkeepalive=1\npeer_public_key=%s\n", c->clis[i].name, c->clis[i].peer_pubkey_hex);
|
||||
for (int j = 0; j < c->clis[i].lnk_cnt; j++)
|
||||
off += snprintf(buf + off, size - off,
|
||||
"link=%s:127.0.0.1:%d\n", c->clis[i].lnks[j].local_srv, c->clis[i].lnks[j].remote_port);
|
||||
off += snprintf(buf + off, size - off, "\n");
|
||||
}
|
||||
off += snprintf(buf + off, size - off, "[allowed_keys]\nallow_all=1\n");
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Verification helpers — проверка внутренних структур
|
||||
* ================================================================ */
|
||||
|
||||
static int peer_in_nodes(struct UTUN_INSTANCE* inst, uint64_t node_id) {
|
||||
if (!inst || !inst->bgp || !inst->bgp->nodes) return 0;
|
||||
return nodeinfo_find_by_id(inst->bgp, node_id) != NULL;
|
||||
}
|
||||
|
||||
static int node_path_count(struct UTUN_INSTANCE* inst, uint64_t node_id) {
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(inst->bgp, node_id);
|
||||
if (!nq || !nq->paths) return 0;
|
||||
return queue_entry_count(nq->paths);
|
||||
}
|
||||
|
||||
static int node_path_has_peer(struct UTUN_INSTANCE* inst, uint64_t node_id, uint64_t peer_id) {
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(inst->bgp, node_id);
|
||||
if (!nq || !nq->paths) return 0;
|
||||
struct ll_entry* e = nq->paths->head;
|
||||
while (e) {
|
||||
struct NODEINFO_PATH* path = (struct NODEINFO_PATH*)e;
|
||||
uint64_t* hop = (uint64_t*)((uint8_t*)path + sizeof(struct NODEINFO_PATH));
|
||||
for (uint8_t i = 0; i < path->hop_count; i++)
|
||||
if (hop[i] == peer_id) return 1;
|
||||
e = e->next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int node_version(struct UTUN_INSTANCE* inst, uint64_t node_id) {
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(inst->bgp, node_id);
|
||||
return nq ? nq->last_ver : -1;
|
||||
}
|
||||
|
||||
static int route_count_for_node(struct UTUN_INSTANCE* inst, uint64_t node_id) {
|
||||
if (!inst || !inst->rt) return 0;
|
||||
uint64_t expected_be = htobe64(node_id);
|
||||
int cnt = 0;
|
||||
for (size_t i = 0; i < inst->rt->count; i++)
|
||||
if (inst->rt->entries[i].v_node_info &&
|
||||
inst->rt->entries[i].v_node_info->node.node_id == expected_be) cnt++;
|
||||
return cnt;
|
||||
}
|
||||
|
||||
static int count_initialized_links(struct UTUN_INSTANCE* inst) {
|
||||
int n = 0;
|
||||
if (!inst) return 0;
|
||||
struct ETCP_CONN* conn = inst->connections;
|
||||
while (conn) {
|
||||
struct ETCP_LINK* l = conn->links;
|
||||
while (l) { if (l->initialized) n++; l = l->next; }
|
||||
conn = conn->next;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static struct ETCP_LINK* find_client_link(struct UTUN_INSTANCE* inst, int idx) {
|
||||
if (!inst) return NULL;
|
||||
struct ETCP_CONN* conn = inst->connections;
|
||||
while (conn) {
|
||||
struct ETCP_LINK* l = conn->links;
|
||||
while (l) {
|
||||
if (l->is_server == 0) { if (idx == 0) return l; idx--; }
|
||||
l = l->next;
|
||||
}
|
||||
conn = conn->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void test_timeout_cb(void* arg) {
|
||||
(void)arg;
|
||||
test_phase = 2;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "test_bgp_triangle: overall timeout");
|
||||
}
|
||||
|
||||
static void fail(const char* msg) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: %s", msg);
|
||||
test_phase = 2;
|
||||
}
|
||||
|
||||
#define PHASE(name) do { \
|
||||
if (test_phase != 0) break; \
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "=== PHASE: %s ===", name); \
|
||||
} while(0)
|
||||
|
||||
#define ASSERT(cond, msg) do { if (!(cond)) { fail(msg); goto cleanup; } } while(0)
|
||||
|
||||
static int wait_for(const char* desc, int (*cond)(void), int timeout_tb) {
|
||||
uint64_t start = get_time_tb();
|
||||
while (!cond() && (get_time_tb() - start) < (uint64_t)timeout_tb && test_phase == 0)
|
||||
uasync_poll(ua, POLL_INTERVAL_MS);
|
||||
if (!cond() && test_phase == 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "timeout waiting for: %s", desc);
|
||||
test_phase = 2;
|
||||
return 0;
|
||||
}
|
||||
return test_phase == 0;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Condition callbacks for wait_for
|
||||
* ================================================================ */
|
||||
|
||||
static int cond_links_init(void) {
|
||||
return count_initialized_links(inst_a) >= 2 &&
|
||||
count_initialized_links(inst_b) >= 2 &&
|
||||
count_initialized_links(inst_c) >= 1 &&
|
||||
count_initialized_links(inst_e) >= 1;
|
||||
}
|
||||
|
||||
static int cond_d_in_a(void) { return peer_in_nodes(inst_a, NODE_ID_D); }
|
||||
static int cond_d_gone_a(void) { return !peer_in_nodes(inst_a, NODE_ID_D); }
|
||||
static int cond_e_in_a(void) { return peer_in_nodes(inst_a, NODE_ID_E); }
|
||||
static int cond_e_gone_a(void) { return !peer_in_nodes(inst_a, NODE_ID_E); }
|
||||
static int cond_c_in_a(void) { return peer_in_nodes(inst_a, NODE_ID_C); }
|
||||
static int cond_c_gone_a(void) { return !peer_in_nodes(inst_a, NODE_ID_C); }
|
||||
static int cond_c_two_paths(void) {
|
||||
return node_path_count(inst_a, NODE_ID_C) >= 2 &&
|
||||
node_path_has_peer(inst_a, NODE_ID_C, NODE_ID_C) &&
|
||||
node_path_has_peer(inst_a, NODE_ID_C, NODE_ID_B);
|
||||
}
|
||||
|
||||
static int cond_c_one_path_B(void) {
|
||||
return node_path_count(inst_a, NODE_ID_C) == 1 &&
|
||||
node_path_has_peer(inst_a, NODE_ID_C, NODE_ID_B);
|
||||
}
|
||||
|
||||
static int cond_b_gone_a(void) { return !peer_in_nodes(inst_a, NODE_ID_B); }
|
||||
|
||||
static int cond_c_one_path(void) { return node_path_count(inst_a, NODE_ID_C) == 1; }
|
||||
|
||||
/* ================================================================
|
||||
* Main
|
||||
* ================================================================ */
|
||||
|
||||
int main(void) {
|
||||
debug_config_init();
|
||||
debug_set_level(DEBUG_LEVEL_ERROR);
|
||||
debug_set_categories(DEBUG_CATEGORY_BGP);
|
||||
utun_instance_set_tun_init_enabled(0);
|
||||
|
||||
/* ---------- Pre-generate 5 keypairs ---------- */
|
||||
struct SC_MYKEYS keys[5];
|
||||
char pub_hex[5][SC_PUBKEY_SIZE * 2 + 1];
|
||||
char priv_hex[5][SC_PRIVKEY_SIZE * 2 + 1];
|
||||
for (int i = 0; i < 5; i++) {
|
||||
sc_generate_keypair(&keys[i]);
|
||||
bytes_to_hex(keys[i].public_key, SC_PUBKEY_SIZE, pub_hex[i], sizeof(pub_hex[i]));
|
||||
bytes_to_hex(keys[i].private_key, SC_PRIVKEY_SIZE, priv_hex[i], sizeof(priv_hex[i]));
|
||||
}
|
||||
|
||||
/* ---------- Ports ---------- */
|
||||
int base = 41000 + (getpid() % 15000);
|
||||
int p_ab_a = base++, p_ab_b = base++; // A↔B
|
||||
int p_ac_a = base++, p_ac_c = base++; // A↔C
|
||||
int p_bc_b = base++, p_bc_c = base++; // B↔C (C's second server for B)
|
||||
int p_be_b = base++, p_be_e = base++; // B↔E
|
||||
int p_cd_c = base++, p_cd_d = base++; // C↔D
|
||||
|
||||
/* ---------- Build config strings ---------- */
|
||||
char cfg_a[2048], cfg_b[2048], cfg_c[2048], cfg_d[2048], cfg_e[2048];
|
||||
|
||||
build_node_config(cfg_a, sizeof(cfg_a), &(struct ncfg){
|
||||
.node_id = NODE_ID_A, .tun_ip = "10.100.0.1/24", .my_subnet = "192.168.10.0/24",
|
||||
.priv_hex = priv_hex[0], .pub_hex = pub_hex[0],
|
||||
.srv_cnt = 2, .srvs = {{"a_srv_b", p_ab_a}, {"a_srv_c", p_ac_a}},
|
||||
.cli_cnt = 2, .clis = {
|
||||
{"to_b", pub_hex[1], 1, {{"a_srv_b", p_ab_b}}},
|
||||
{"to_c", pub_hex[2], 1, {{"a_srv_c", p_ac_c}}},
|
||||
}
|
||||
});
|
||||
|
||||
build_node_config(cfg_b, sizeof(cfg_b), &(struct ncfg){
|
||||
.node_id = NODE_ID_B, .tun_ip = "10.100.0.2/24", .my_subnet = "192.168.20.0/24",
|
||||
.priv_hex = priv_hex[1], .pub_hex = pub_hex[1],
|
||||
.srv_cnt = 3, .srvs = {{"b_srv", p_ab_b}, {"b_cli_c", p_bc_b}, {"b_cli_e", p_be_b}},
|
||||
.cli_cnt = 2, .clis = {
|
||||
{"to_c", pub_hex[2], 1, {{"b_cli_c", p_bc_c}}},
|
||||
{"to_e", pub_hex[4], 1, {{"b_cli_e", p_be_e}}},
|
||||
}
|
||||
});
|
||||
|
||||
build_node_config(cfg_c, sizeof(cfg_c), &(struct ncfg){
|
||||
.node_id = NODE_ID_C, .tun_ip = "10.100.0.3/24", .my_subnet = "192.168.30.0/24",
|
||||
.priv_hex = priv_hex[2], .pub_hex = pub_hex[2],
|
||||
.srv_cnt = 3, .srvs = {{"c_srv_a", p_ac_c}, {"c_srv_b", p_bc_c}, {"c_cli_d", p_cd_c}},
|
||||
.cli_cnt = 1, .clis = {
|
||||
{"to_d", pub_hex[3], 1, {{"c_cli_d", p_cd_d}}},
|
||||
}
|
||||
});
|
||||
|
||||
build_node_config(cfg_d, sizeof(cfg_d), &(struct ncfg){
|
||||
.node_id = NODE_ID_D, .tun_ip = "10.100.0.4/24", .my_subnet = "192.168.40.0/24",
|
||||
.priv_hex = priv_hex[3], .pub_hex = pub_hex[3],
|
||||
.srv_cnt = 1, .srvs = {{"d_srv", p_cd_d}},
|
||||
.cli_cnt = 0, .clis = {},
|
||||
});
|
||||
|
||||
build_node_config(cfg_e, sizeof(cfg_e), &(struct ncfg){
|
||||
.node_id = NODE_ID_E, .tun_ip = "10.100.0.5/24", .my_subnet = "192.168.50.0/24",
|
||||
.priv_hex = priv_hex[4], .pub_hex = pub_hex[4],
|
||||
.srv_cnt = 1, .srvs = {{"e_srv", p_be_e}},
|
||||
.cli_cnt = 0, .clis = {},
|
||||
});
|
||||
|
||||
/* ---------- Create instances ---------- */
|
||||
ua = uasync_create();
|
||||
ASSERT(ua, "uasync_create");
|
||||
|
||||
inst_a = utun_instance_create_from_str(ua, cfg_a); ASSERT(inst_a, "inst_a");
|
||||
inst_b = utun_instance_create_from_str(ua, cfg_b); ASSERT(inst_b, "inst_b");
|
||||
inst_c = utun_instance_create_from_str(ua, cfg_c); ASSERT(inst_c, "inst_c");
|
||||
inst_d = utun_instance_create_from_str(ua, cfg_d); ASSERT(inst_d, "inst_d");
|
||||
inst_e = utun_instance_create_from_str(ua, cfg_e); ASSERT(inst_e, "inst_e");
|
||||
|
||||
ASSERT(utun_instance_init(inst_a) == 0, "init a");
|
||||
ASSERT(utun_instance_init(inst_b) == 0, "init b");
|
||||
ASSERT(utun_instance_init(inst_c) == 0, "init c");
|
||||
ASSERT(utun_instance_init(inst_d) == 0, "init d");
|
||||
ASSERT(utun_instance_init(inst_e) == 0, "init e");
|
||||
|
||||
/* ---------- Dummynet filters (2 managed links) ---------- */
|
||||
struct ETCP_LINK* ab_link = find_client_link(inst_a, 0);
|
||||
struct ETCP_LINK* ac_link = find_client_link(inst_a, 1);
|
||||
ASSERT(ab_link && ac_link, "A links not found");
|
||||
|
||||
df_ab = dummynet_filter_create(ua); ASSERT(df_ab, "df_ab");
|
||||
df_ac = dummynet_filter_create(ua); ASSERT(df_ac, "df_ac");
|
||||
|
||||
timeout_id = uasync_set_timeout(ua, TEST_TIMEOUT_TB, NULL, test_timeout_cb, "test_timeout");
|
||||
|
||||
/* ================================================================
|
||||
* Phase 1: Initial BGP exchange — all 5 nodes visible
|
||||
* ================================================================ */
|
||||
PHASE("1: Initial BGP — all nodes visible");
|
||||
ASSERT(wait_for("links init", cond_links_init, PHASE_TIMEOUT_TB), "links init timeout");
|
||||
ASSERT(wait_for("D in A nodes", cond_d_in_a, PHASE_TIMEOUT_TB), "D not in A");
|
||||
ASSERT(wait_for("E in A nodes", cond_e_in_a, PHASE_TIMEOUT_TB), "E not in A");
|
||||
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_B), "B missing");
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_C), "C missing");
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_D), "D missing");
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_E), "E missing");
|
||||
{
|
||||
int pb = node_path_count(inst_a, NODE_ID_B);
|
||||
int pc = node_path_count(inst_a, NODE_ID_C);
|
||||
int pd = node_path_count(inst_a, NODE_ID_D);
|
||||
int pe = node_path_count(inst_a, NODE_ID_E);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 1: B_paths=%d C_paths=%d D_paths=%d E_paths=%d", pb, pc, pd, pe);
|
||||
ASSERT(pb >= 1, "B: expected >=1");
|
||||
ASSERT(pc >= 1, "C: expected >=1");
|
||||
ASSERT(pd >= 1, "D: expected >=1");
|
||||
ASSERT(pe >= 1, "E: expected >=1");
|
||||
}
|
||||
ASSERT(route_count_for_node(inst_a, NODE_ID_B) > 0, "B routes missing");
|
||||
ASSERT(route_count_for_node(inst_a, NODE_ID_C) > 0, "C routes missing");
|
||||
ASSERT(route_count_for_node(inst_a, NODE_ID_D) > 0, "D routes missing");
|
||||
ASSERT(route_count_for_node(inst_a, NODE_ID_E) > 0, "E routes missing");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 1 PASSED");
|
||||
|
||||
/* ================================================================
|
||||
* Phase 2: Kill A–C — C/D/E only through B
|
||||
* ================================================================ */
|
||||
PHASE("2: Kill A–C — C/D/E via B only");
|
||||
dummynet_filter_attach(df_ac, ac_link);
|
||||
dummynet_filter_set_loss(df_ac, 1000);
|
||||
ASSERT(wait_for("C: 1 path via B after A-C kill", cond_c_one_path_B, PHASE_TIMEOUT_TB), "C paths not 1");
|
||||
{
|
||||
int pc = node_path_count(inst_a, NODE_ID_C);
|
||||
int pd = node_path_count(inst_a, NODE_ID_D);
|
||||
int pe = node_path_count(inst_a, NODE_ID_E);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 2: C_paths=%d D_paths=%d E_paths=%d", pc, pd, pe);
|
||||
ASSERT(cond_c_in_a(), "C missing after A-C kill");
|
||||
ASSERT(cond_d_in_a(), "D missing after A-C kill");
|
||||
ASSERT(cond_e_in_a(), "E missing after A-C kill");
|
||||
ASSERT(pd == 1, "D: expected 1 path");
|
||||
ASSERT(pe >= 1, "E: expected >=1");
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 2 PASSED");
|
||||
|
||||
/* ================================================================
|
||||
* Phase 3: Restore A–C — C sends NODEINFO directly (same ver).
|
||||
* Fix#1: path [C] added despite old version.
|
||||
* ================================================================ */
|
||||
PHASE("3: Restore A–C");
|
||||
dummynet_filter_set_loss(df_ac, 0);
|
||||
ASSERT(wait_for("C: >=2 paths after A-C restore", cond_c_two_paths, PHASE_TIMEOUT_TB), "C paths not >=2");
|
||||
{
|
||||
int cp = node_path_count(inst_a, NODE_ID_C);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 3 PASSED (C paths=%d)", cp);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Phase 4: Kill A–B — B/E survive via C relay, C/D via A-C
|
||||
* ================================================================ */
|
||||
PHASE("4: Kill A–B — B/E survive via C");
|
||||
dummynet_filter_attach(df_ab, ab_link);
|
||||
dummynet_filter_set_loss(df_ab, 1000);
|
||||
/* After A-B kill: direct paths removed, B/C/E survive via C relay */
|
||||
ASSERT(wait_for("A-B paths removed", cond_c_one_path, PHASE_TIMEOUT_TB), "A-B paths not removed");
|
||||
{
|
||||
int pb = node_path_count(inst_a, NODE_ID_B);
|
||||
int pe = node_path_count(inst_a, NODE_ID_E);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 4: B_paths=%d E_paths=%d", pb, pe);
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_B), "B missing — should survive via C");
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_E), "E missing — should survive via C");
|
||||
ASSERT(cond_c_in_a(), "C missing");
|
||||
ASSERT(cond_d_in_a(), "D missing");
|
||||
ASSERT(node_path_count(inst_a, NODE_ID_C) == 1, "C: expected 1 path via A-C");
|
||||
ASSERT(node_path_has_peer(inst_a, NODE_ID_C, NODE_ID_C), "C: missing direct path");
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 4 PASSED");
|
||||
|
||||
/* ================================================================
|
||||
* Phase 5: Kill A–C — A fully isolated (A-B already dead)
|
||||
* ================================================================ */
|
||||
PHASE("5: Kill A–C — A isolated");
|
||||
dummynet_filter_set_loss(df_ac, 1000);
|
||||
ASSERT(wait_for("C gone from A", cond_c_gone_a, PHASE_TIMEOUT_TB), "C still in A");
|
||||
ASSERT(wait_for("D gone from A", cond_d_gone_a, PHASE_TIMEOUT_TB), "D still in A");
|
||||
ASSERT(!peer_in_nodes(inst_a, NODE_ID_B), "B should be gone");
|
||||
ASSERT(!peer_in_nodes(inst_a, NODE_ID_C), "C should be gone");
|
||||
ASSERT(!peer_in_nodes(inst_a, NODE_ID_D), "D should be gone");
|
||||
ASSERT(!peer_in_nodes(inst_a, NODE_ID_E), "E should be gone");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 5 PASSED");
|
||||
|
||||
/* ================================================================
|
||||
* Phase 6: Restore A–C + A–B — full recovery
|
||||
* ================================================================ */
|
||||
PHASE("6: Full restore — both links back");
|
||||
dummynet_filter_set_loss(df_ac, 0);
|
||||
dummynet_filter_set_loss(df_ab, 0);
|
||||
ASSERT(wait_for("D in A after restore", cond_d_in_a, 2 * PHASE_TIMEOUT_TB), "D not back");
|
||||
ASSERT(wait_for("E in A after restore", cond_e_in_a, PHASE_TIMEOUT_TB), "E not back");
|
||||
ASSERT(wait_for("C: >=2 paths after restore", cond_c_two_paths, PHASE_TIMEOUT_TB), "C paths not >=2");
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_B), "B missing after restore");
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_C), "C missing after restore");
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_D), "D missing after restore");
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_E), "E missing after restore");
|
||||
ASSERT(node_path_count(inst_a, NODE_ID_C) >= 2, "C: expected >=2 paths");
|
||||
ASSERT(node_path_count(inst_a, NODE_ID_D) >= 1, "D: expected >=1");
|
||||
ASSERT(node_path_count(inst_a, NODE_ID_B) >= 1, "B: expected >=1");
|
||||
ASSERT(node_path_count(inst_a, NODE_ID_E) >= 1, "E: expected >=1");
|
||||
ASSERT(route_count_for_node(inst_a, NODE_ID_B) > 0, "B routes");
|
||||
ASSERT(route_count_for_node(inst_a, NODE_ID_C) > 0, "C routes");
|
||||
ASSERT(route_count_for_node(inst_a, NODE_ID_D) > 0, "D routes");
|
||||
ASSERT(route_count_for_node(inst_a, NODE_ID_E) > 0, "E routes");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 6 PASSED");
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "=== ALL PHASES PASSED ===");
|
||||
test_phase = 1;
|
||||
|
||||
cleanup:
|
||||
if (df_ab) { dummynet_filter_detach(df_ab); dummynet_filter_destroy(df_ab); }
|
||||
if (df_ac) { dummynet_filter_detach(df_ac); dummynet_filter_destroy(df_ac); }
|
||||
if (timeout_id) uasync_cancel_timeout(ua, timeout_id);
|
||||
if (inst_a) { inst_a->running = 0; utun_instance_destroy(inst_a); }
|
||||
if (inst_b) { inst_b->running = 0; utun_instance_destroy(inst_b); }
|
||||
if (inst_c) { inst_c->running = 0; utun_instance_destroy(inst_c); }
|
||||
if (inst_d) { inst_d->running = 0; utun_instance_destroy(inst_d); }
|
||||
if (inst_e) { inst_e->running = 0; utun_instance_destroy(inst_e); }
|
||||
if (ua) { uasync_destroy(ua, 0); ua = NULL; }
|
||||
|
||||
printf("=== %s ===\n", test_phase == 1 ? "TEST PASSED" : "TEST FAILED");
|
||||
return test_phase == 1 ? 0 : 1;
|
||||
}
|
||||
@@ -26,8 +26,8 @@ static char config_path[256];
|
||||
static const char* config_content =
|
||||
"[global]\n"
|
||||
"my_node_id=0x1111111111111111\n"
|
||||
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n"
|
||||
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
|
||||
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
|
||||
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
|
||||
"tun_ip=10.99.0.1/24\n"
|
||||
"tun_ifname=tun99\n"
|
||||
"# Debug and logging configuration\n"
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* @file test_conn_mgr.c
|
||||
* @brief Connection Manager — smoke test
|
||||
*
|
||||
* Test 1: Direct via existing ETCP conn → CONN_TYPE_DIRECT
|
||||
* Test 2: Status check → state/type
|
||||
* Test 3: Alien node → NODEINFO_Q.alien=1
|
||||
*
|
||||
* Pending:
|
||||
* Test 4: New link UP callback (INIT to new peer via conn_mgr)
|
||||
* Test 5: Indirect through intermediary (needs full Phase 3 exchange)
|
||||
* Test 6: Idle timeout
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include "../lib/platform_compat.h"
|
||||
#include "test_utils.h"
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "../src/etcp.h"
|
||||
#include "../src/etcp_connections.h"
|
||||
#include "../src/config_parser.h"
|
||||
#include "../src/config_updater.h"
|
||||
#include "../src/utun_instance.h"
|
||||
#include "../src/route_bgp.h"
|
||||
#include "../src/conn_mgr.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
|
||||
#define TIMEOUT_TB 300000
|
||||
#define POLL_MS 5
|
||||
#define NID_A 0xAAAA000000000001ULL
|
||||
#define NID_B 0xBBBB000000000002ULL
|
||||
|
||||
static struct UTUN_INSTANCE* g_a = NULL, *g_b = NULL;
|
||||
static struct UASYNC* ua = NULL;
|
||||
static volatile int result = 0;
|
||||
static void* ttimer = NULL;
|
||||
static char tdir[] = "/tmp/utun_cm_XXXXXX";
|
||||
static char ca[256], cb[256];
|
||||
static int pa = 0, pb = 0;
|
||||
static volatile int cdone = 0, cresult = 0;
|
||||
|
||||
static int wf(const char* p, const char* f, ...) {
|
||||
va_list ap; FILE* fp = fopen(p, "w"); if (!fp) return -1;
|
||||
va_start(ap, f); vfprintf(fp, f, ap); va_end(ap); fclose(fp); return 0;
|
||||
}
|
||||
static char* gv(const char* p, const char* k) {
|
||||
struct utun_config* c = parse_config(p); if (!c) return NULL;
|
||||
char* r = (strcmp(k, "pub") == 0) ? u_strdup(c->global.my_public_key_hex) : u_strdup(c->global.my_private_key_hex);
|
||||
free_config(c); return r;
|
||||
}
|
||||
static int lks(struct UTUN_INSTANCE* i) {
|
||||
int n = 0; struct ETCP_CONN* c = i->connections;
|
||||
while (c) { struct ETCP_LINK* l = c->links; while (l) { if (l->initialized && l->link_status) n++; l = l->next; } c = c->next; }
|
||||
return n;
|
||||
}
|
||||
static void fail(const char* msg) { fprintf(stderr, "FAIL: %s\n", msg); fflush(stderr); result = 2; }
|
||||
static void test1(void* arg);
|
||||
static void test2(void* arg);
|
||||
static void test3(void* arg);
|
||||
|
||||
static void to_cb(void* arg) { (void)arg; fprintf(stderr, "TIMEOUT\n"); result = 2; }
|
||||
static void ccb(int r, uint64_t id, void* arg) {
|
||||
(void)arg; fprintf(stderr, "connect_cb: result=%d node=0x%llx\n", r, (unsigned long long)id); fflush(stderr);
|
||||
cdone = 1; cresult = r;
|
||||
}
|
||||
|
||||
static void test1(void* arg) {
|
||||
(void)arg; if (result) return;
|
||||
if (!nodeinfo_find_by_id(g_a->bgp, NID_B)) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test1, "t1"); return; }
|
||||
fprintf(stderr, "Test 1: direct — conn_mgr_connect_node(B)\n"); fflush(stderr);
|
||||
conn_mgr_connect_node(g_a->conn_mgr, NID_B, 0, ccb, NULL);
|
||||
uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test2, "t2a");
|
||||
}
|
||||
static void test2(void* arg) {
|
||||
(void)arg; if (result) return;
|
||||
if (!cdone) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test2, "t2b"); return; }
|
||||
if (cresult != CONN_MGR_OK) { fail("connect failed"); return; }
|
||||
uint8_t st, ty; conn_mgr_get_status(g_a->conn_mgr, NID_B, &st, &ty);
|
||||
fprintf(stderr, "Test 2: status state=%d type=%d\n", st, ty); fflush(stderr);
|
||||
if (st == CONN_MGR_STATE_CONNECTED && ty == CONN_TYPE_DIRECT) fprintf(stderr, " OK: DIRECT\n");
|
||||
else { fail("status mismatch"); return; }
|
||||
test3(NULL);
|
||||
}
|
||||
static void test3(void* arg) {
|
||||
(void)arg;
|
||||
uint64_t aid = 0xEEEE000000000001ULL;
|
||||
struct NODEINFO ni; memset(&ni, 0, sizeof(ni)); ni.node_id = aid; ni.ver = 1;
|
||||
conn_mgr_add_alien_node(g_a->conn_mgr, (uint8_t*)&ni, sizeof(ni));
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(g_a->bgp, aid);
|
||||
fprintf(stderr, "Test 3: alien alien=%d\n", nq ? nq->alien : -1); fflush(stderr);
|
||||
if (nq && nq->alien == 1) fprintf(stderr, " OK: alien flag set\n");
|
||||
else { fail("alien flag not set"); return; }
|
||||
fprintf(stderr, "=== ALL DONE ===\n"); fflush(stderr);
|
||||
result = (result == 0) ? 1 : 2;
|
||||
}
|
||||
|
||||
static void setup(void) {
|
||||
test_mkdtemp(tdir);
|
||||
int base = 47000 + (getpid() % 15000); pa = base; pb = base + 1;
|
||||
snprintf(ca, sizeof(ca), "%s/a.conf", tdir); snprintf(cb, sizeof(cb), "%s/b.conf", tdir);
|
||||
wf(ca, "[global]\nmy_node_id=0x%llx\ntun_ip=10.99.0.1/24\ntun_ifname=tun99\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n", NID_A, pa);
|
||||
wf(cb, "[global]\nmy_node_id=0x%llx\ntun_ip=10.99.0.2/24\ntun_ifname=tun98\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n", NID_B, pb);
|
||||
config_ensure_keys_and_node_id(ca); config_ensure_keys_and_node_id(cb);
|
||||
char *p0 = gv(ca,"pub"), *r0 = gv(ca,"priv"), *p1 = gv(cb,"pub"), *r1 = gv(cb,"priv");
|
||||
wf(ca, "[global]\nmy_node_id=0x%llx\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.1/24\ntun_ifname=tun99\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[client: to_b]\nkeepalive=1\npeer_public_key=%s\nlink=s1:127.0.0.1:%d\n[allowed_keys]\nallow_all=1\n", NID_A, r0, p0, pa, p1, pb);
|
||||
wf(cb, "[global]\nmy_node_id=0x%llx\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.2/24\ntun_ifname=tun98\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n", NID_B, r1, p1, pb);
|
||||
u_free(p0); u_free(r0); u_free(p1); u_free(r1);
|
||||
}
|
||||
static void cleanup(void) { test_unlink(ca); test_unlink(cb); test_rmdir(tdir); }
|
||||
|
||||
int main(void) {
|
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN);
|
||||
utun_instance_set_tun_init_enabled(0); setup();
|
||||
ua = uasync_create();
|
||||
g_a = utun_instance_create(ua, ca); g_b = utun_instance_create(ua, cb);
|
||||
if (!g_a || !g_b) goto done;
|
||||
utun_instance_init(g_a); utun_instance_init(g_b);
|
||||
uasync_call_soon(ua, NULL, test1);
|
||||
ttimer = uasync_set_timeout(ua, TIMEOUT_TB, NULL, to_cb, "to");
|
||||
{ int el = 0; while (!result && el < TIMEOUT_TB + 5000) { uasync_poll(ua, POLL_MS); el += POLL_MS; } }
|
||||
fprintf(stderr, "final result=%d\n", result); fflush(stderr);
|
||||
done:
|
||||
if (ttimer) uasync_cancel_timeout(ua, ttimer);
|
||||
if (g_a) { g_a->running = 0; utun_instance_destroy(g_a); }
|
||||
if (g_b) { g_b->running = 0; utun_instance_destroy(g_b); }
|
||||
if (ua) { uasync_destroy(ua, 0); ua = NULL; }
|
||||
cleanup();
|
||||
return (result == 1) ? 0 : 1;
|
||||
}
|
||||
@@ -41,8 +41,8 @@ static char client_config_path[256];
|
||||
static const char* server_config_content =
|
||||
"[global]\n"
|
||||
"my_node_id=0x1111111111111111\n"
|
||||
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n"
|
||||
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
|
||||
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
|
||||
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
|
||||
"tun_ip=10.99.0.1/24\n"
|
||||
"tun_ifname=tun99\n"
|
||||
"\n"
|
||||
@@ -57,8 +57,8 @@ static const char* server_config_content =
|
||||
static const char* client_config_content =
|
||||
"[global]\n"
|
||||
"my_node_id=0x2222222222222222\n"
|
||||
"my_private_key=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2\n"
|
||||
"my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n"
|
||||
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
|
||||
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
|
||||
"tun_ip=10.99.0.2/24\n"
|
||||
"tun_ifname=tun98\n"
|
||||
"\n"
|
||||
@@ -68,7 +68,7 @@ static const char* client_config_content =
|
||||
"\n"
|
||||
"[client: test_client]\n"
|
||||
"keepalive=1\n"
|
||||
"peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
|
||||
"peer_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
|
||||
"link=test:127.0.0.1:9021\n";
|
||||
|
||||
// Create temp config files
|
||||
|
||||
@@ -48,8 +48,8 @@ static char client_config_path[256];
|
||||
static const char* server_config_content =
|
||||
"[global]\n"
|
||||
"my_node_id=0x1111111111111111\n"
|
||||
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n"
|
||||
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
|
||||
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
|
||||
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
|
||||
"tun_ip=10.99.0.1/24\n"
|
||||
"tun_ifname=tun99\n"
|
||||
"\n"
|
||||
@@ -64,8 +64,8 @@ static const char* server_config_content =
|
||||
static const char* client_config_content =
|
||||
"[global]\n"
|
||||
"my_node_id=0x2222222222222222\n"
|
||||
"my_private_key=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2\n"
|
||||
"my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n"
|
||||
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
|
||||
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
|
||||
"tun_ip=10.99.0.2/24\n"
|
||||
"tun_ifname=tun98\n"
|
||||
"\n"
|
||||
@@ -75,7 +75,7 @@ static const char* client_config_content =
|
||||
"\n"
|
||||
"[client: test_client]\n"
|
||||
"keepalive=1\n"
|
||||
"peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
|
||||
"peer_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
|
||||
"link=test:127.0.0.1:9031\n";
|
||||
|
||||
// Create temp config files
|
||||
|
||||
@@ -298,10 +298,10 @@ int main(void) {
|
||||
if (!ctx.ua) { printf("uasync failed\n"); return 1; }
|
||||
|
||||
/* Ключи */
|
||||
const char* s_priv = "67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb";
|
||||
const char* s_pub = "1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9";
|
||||
const char* c_priv = "4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2";
|
||||
const char* c_pub = "c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71";
|
||||
const char* s_priv = "38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68";
|
||||
const char* s_pub = "ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a";
|
||||
const char* c_priv = "704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f";
|
||||
const char* c_pub = "b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01";
|
||||
|
||||
printf("Creating instances...\n");
|
||||
ctx.sender = create_instance(ctx.ua, 0x1111111111111111ULL, c_priv, c_pub);
|
||||
|
||||
Loaded 100 of 530 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user