Compare commits
110
Commits
tmo
...
v0.1.0-rc2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55fa5c7759 | ||
|
|
0b5a710fe6 | ||
|
|
f1f533a552 | ||
|
|
03490cfc00 | ||
|
|
ba14e31e09 | ||
|
|
5b850ea5da | ||
|
|
2fba5c31c4 | ||
|
|
4f168c93f6 | ||
|
|
6c938ed2ba | ||
|
|
2cb85cbc3d | ||
|
|
754c08e9c2 | ||
|
|
71ccccc2d5 | ||
|
|
c6d1ebeeaf | ||
|
|
eea0ed0a48 | ||
|
|
0738dcfd5a | ||
|
|
4862c30261 | ||
|
|
3c5d82a6ee | ||
|
|
389ef00da6 | ||
|
|
487cb8718e | ||
|
|
42d96f118d | ||
|
|
8962f58ea4 | ||
|
|
1f541d6432 | ||
|
|
e66156eb49 | ||
|
|
2cd38988e6 | ||
|
|
d58b532741 | ||
|
|
4716e54069 | ||
|
|
aa73e9d8cb | ||
|
|
993c265125 | ||
|
|
5ca0bbf9e7 | ||
|
|
0ef7ee597c | ||
|
|
7d460ca156 | ||
|
|
b738b2ff1b | ||
|
|
cc5289e37c | ||
|
|
0c5f25c96d | ||
|
|
b53758bdbe | ||
|
|
f5b1c3e789 | ||
|
|
0edd21e335 | ||
|
|
e0c38676e7 | ||
|
|
fefd2bcc52 | ||
|
|
8427e18c98 | ||
|
|
7329fa65f1 | ||
|
|
de8cce521a | ||
|
|
f06ebd2ab4 | ||
|
|
a97a38d867 | ||
|
|
aff3b2d00f | ||
|
|
ccdbab95a8 | ||
|
|
a01e2f0899 | ||
|
|
dfdec06a17 | ||
|
|
393a641e18 | ||
|
|
53d41d5c62 | ||
|
|
c60ba6eaec | ||
|
|
c5123e4baf | ||
|
|
928c2fe057 | ||
|
|
b5b6875bc5 | ||
|
|
7c953c1929 | ||
|
|
1c09ad2e5a | ||
|
|
3284dffaaf | ||
|
|
c893285d71 | ||
|
|
75028f278a | ||
|
|
7fc1b5419f | ||
|
|
dee9889423 | ||
|
|
35871891db | ||
|
|
373e4b33c7 | ||
|
|
090e2f28e4 | ||
|
|
a2c7f1e6cb | ||
|
|
21da640ebe | ||
|
|
80177c9b3e | ||
|
|
8f13f0ec4d | ||
|
|
5420e414ce | ||
|
|
69112ffde1 | ||
|
|
89d675f15d | ||
|
|
914f252b12 | ||
|
|
a7b20f268f | ||
|
|
c0207ad41d | ||
|
|
8051dfe4e5 | ||
|
|
22c24d135e | ||
|
|
d8c8c1c5b0 | ||
|
|
231081dd2f | ||
|
|
62b3695a77 | ||
|
|
0e2b0e28d8 | ||
|
|
e50ca9978e | ||
|
|
b667d6f352 | ||
|
|
6717c863e7 | ||
|
|
2bbdf8195f | ||
|
|
73f707ef9b | ||
|
|
34683f0500 | ||
|
|
150e9977df | ||
|
|
f913bdf351 | ||
|
|
0dd942be63 | ||
|
|
474bb8bd6b | ||
|
|
3419ff3a85 | ||
|
|
6b2b0f1360 | ||
|
|
20e81e493d | ||
|
|
23732039de | ||
|
|
f883c8da75 | ||
|
|
4211e32f51 | ||
|
|
7c840c8cfd | ||
|
|
1a1e305b6b | ||
|
|
974c6770e0 | ||
|
|
075c4f4583 | ||
|
|
9e133699bf | ||
|
|
81cad40e3b | ||
|
|
baca018387 | ||
|
|
48a4f182aa | ||
|
|
7c456a1652 | ||
|
|
a0ea19acb8 | ||
|
|
1f34629753 | ||
|
|
9a3eb1ce80 | ||
|
|
5982b011b4 | ||
|
|
e55c4ec7ce |
No files matched your search
+8
-1
@@ -1,5 +1,7 @@
|
||||
# Build directory (out-of-tree build)
|
||||
/build/
|
||||
build-win/
|
||||
tools/chatgui/build/
|
||||
|
||||
# Build outputs
|
||||
*.o
|
||||
@@ -59,7 +61,7 @@ Makefile.in
|
||||
.cache/
|
||||
|
||||
# Debug outputs
|
||||
core
|
||||
/core
|
||||
vgcore.*
|
||||
callgrind.out.*
|
||||
|
||||
@@ -96,6 +98,9 @@ utun.conf
|
||||
tools/bping/bping
|
||||
tools/proxy/udp_proxy
|
||||
|
||||
# Reference projects (not part of utun)
|
||||
tools/tdesktop-dev/
|
||||
|
||||
# Debug/temp artifacts
|
||||
*.bak
|
||||
*.debug
|
||||
@@ -108,3 +113,5 @@ cl
|
||||
build_win.log
|
||||
/wintun.dll
|
||||
src/lwip_orig/
|
||||
lib/timeout/
|
||||
utun.pid
|
||||
@@ -15,7 +15,7 @@ This file contains essential information for AI coding agents working in the uTu
|
||||
**Repository:** uTun - Secure VPN tunnel with ETCP protocol
|
||||
**Language:** C (C99)
|
||||
**Build System:** GNU Autotools (autoconf/automake)
|
||||
**Cryptography:** TinyCrypt + OpenSSL (AES-CCM, ECC, SHA256)
|
||||
**Cryptography:** OpenSSL (AES-CCM, X25519, SHA256)
|
||||
|
||||
## Build Commands
|
||||
|
||||
@@ -56,6 +56,16 @@ cd tests && make # Build only tests
|
||||
./build_direct.sh # Компиляция вручную с x86_64-w64-mingw32-gcc
|
||||
```
|
||||
|
||||
### ASAN Build (AddressSanitizer, Linux)
|
||||
```bash
|
||||
./build.sh --asan -j4 # lib/ + src/ only (tests not ASAN-compatible)
|
||||
```
|
||||
Запуск с ASAN (detect_leaks отключены чтобы не спамить при shutdown):
|
||||
```bash
|
||||
ASAN_OPTIONS=detect_leaks=0:halt_on_error=0 ./src/utun -f -p /tmp/utun.pid -l /tmp/utun.log
|
||||
```
|
||||
Краш-логи ASAN пишутся в `/tmp/utun_asan.<pid>`.
|
||||
|
||||
## Test Commands
|
||||
|
||||
### Run All Tests
|
||||
@@ -142,14 +152,14 @@ gcc -I../src -I../lib -I../tinycrypt/lib/include \
|
||||
### Key Sizes
|
||||
| Constant | Value | Description |
|
||||
|----------|-------|-------------|
|
||||
| `SC_PRIVKEY_SIZE` | 32 | ECC private key |
|
||||
| `SC_PUBKEY_SIZE` | 64 | ECC public key |
|
||||
| `SC_PRIVKEY_SIZE` | 32 | X25519 private key |
|
||||
| `SC_PUBKEY_SIZE` | 32 | X25519 public key |
|
||||
| `SC_NONCE_SIZE` | 13 | CCM nonce (exactly 13 bytes) |
|
||||
| `SC_SESSION_KEY_SIZE` | 16 | AES-128 session key |
|
||||
| `SC_TAG_SIZE` | 16 | CCM auth tag |
|
||||
| `SC_CRC32_SIZE` | 4 | CRC32 checksum |
|
||||
| `SC_PUBKEY_ENC_SALT_SIZE` | 8 | Salt for pubkey obfuscation |
|
||||
| `SC_PUBKEY_ENC_SIZE` | 72 | Total pubkey+salt block sent unencrypted |
|
||||
| `SC_PUBKEY_ENC_SIZE` | 40 | Total pubkey+salt block sent unencrypted |
|
||||
|
||||
### Using Secure Channel (secure_channel.h)
|
||||
```c
|
||||
@@ -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
|
||||
|
||||
@@ -321,6 +333,34 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
|
||||
Всегда когда начинаешь диагностику ознакомься со скиллом (используй skills)
|
||||
Диагностика и поиск ошибок - это в первую очередь продумывание отладочных механизмов которые покажут понятную картину и точное представление об ошибке. Рассуждать надо как диагностикой добиться точной картины. И как приавльнее добавить диагностику чтобы не спамила лишними сообщениями и была понятной, логичной и информативной.
|
||||
|
||||
Важное правило отладки: вся отладка сводится к тому чтобы сделать удобные логи которые наглядно показывают поведение и проблемы. без лишнего мусора, компактно и по существу. очень желательно с деталями которые сильно повышают качество отладки.
|
||||
при отладке нельзя: гадать и долго пытаться разбирать код пытаяясь найти причину. гораздо надёжнее с помощью логов понято точное поведение.
|
||||
Если логов много - записывай в файл и потом анализируй.
|
||||
Если видишь спам-логи - подумай как их выборочно отключить чтобы не мешали.
|
||||
|
||||
Для отладки добавляй в DEBUG_CATEGORY_DEBUG диагностические сообщения где надо. И включи эту категорию в настройках. Убирай только после проверки (путём запуска) когда все ошибки устранены.
|
||||
сообщение выводится по или - либо debug_set_level(DEBUG_LEVEL_TRACE) - выводится ВСЁ независимо от настроек по категориям. Тоесть берется max(global level, category lavel)
|
||||
|
||||
Твой бич - ты постоянно гадаешь и анализируешь код. Что приводит к снежному кобу ошибок и неверных гипотез. 10 раз повторяю - только логи логи логи и никакого гадания. Лоооги!!! правильные логи покажут всё с предельной точностью. Вся суть отладки - информативные логи
|
||||
И смотри логи. не задавливай их grep-ом. лучше больше. единственное с чем борись - это бесполезные спам логи.
|
||||
Но полезные смотри всегда, и всегда оставляй логи которые выводятся нечасто.
|
||||
Частые логи - это трафик которые >100 раз повторяются. Но которые мало раз обязательно оставлять и выводить.
|
||||
можешь в тесте включить логи в нужный интервал чтобы не спамить. debug_set_level(DEBUG_LEVEL_TRACE) и выключить debug_set_level(DEBUG_LEVEL_NONE)
|
||||
Если видишь проблему и ее решение неочевидно то выстраивай диагностику вокруг неё пока не будет очевидно где и что происходит не так. Это базовое и обязательное требование к отладке.
|
||||
Подробные логи ты должен выводить не обрезая всегда и анализировать. Если лог большой - выведи в файл и анализируй файл.
|
||||
Рассуждение должно быть примерно таким:
|
||||
- данные повреждаются при отправке.
|
||||
- где и почему непонятно. надо продумать ключевые точки где получим максимум полезной информации и не было большого объёма вывода лога.
|
||||
- где лучше? каждый пакет логировать - много, но будет предельно точная картина
|
||||
- Еще хорошо бы время - так мы заодно сможем найти проблемы с производительностью, найти проблемные места застревания кода.
|
||||
- Но это большой объём. Можно ли без него? можно но это будет малоэффективно и хороших альтернатив пока не видно.
|
||||
- Значит логируем весь трафик, а заодно включим полную трассировку функций.
|
||||
- Запустили. Записали весь процесс в файл. Теперь можно анализировать. Давай сперва посчитаю количество строк с дампом: ... 50000.
|
||||
- давай сделаю простой скрипт который дамп прочитает и сохранит в файл. И сверю с оригинальным файлом
|
||||
- Давай заодно возьму произвольный фрагмент лога и изучу еа предмет явных проблем. Особенно инициализацию и освобождение ресурсов.
|
||||
Сколько времени занимает, нет ли заклиниваний, нет ли ошибок или странностей
|
||||
|
||||
|
||||
### Прочие правила:
|
||||
- sed для редактирования исходников - запрещено
|
||||
- Проверяй на дублирование кода - не сделано ли это уже в другом месте
|
||||
@@ -337,6 +377,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 +528,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"
|
||||
Executable
+284
@@ -0,0 +1,284 @@
|
||||
#!/bin/bash
|
||||
# cross-build-win.sh — кросс-компиляция uTun для Windows 10 из Linux
|
||||
# Использование: ./cross-build-win.sh [--clean]
|
||||
set -euo pipefail
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
step() { echo -e "${CYAN}===${NC} $*"; }
|
||||
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARNING]${NC} $*"; }
|
||||
fatal() { echo -e "${RED}FATAL:${NC} $*" >&2; exit 1; }
|
||||
|
||||
trap 'fatal "script failed at line $LINENO (exit code: $?)"' ERR
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
BUILD_DIR="$SCRIPT_DIR/build-win"
|
||||
PREFIX="$BUILD_DIR/prefix"
|
||||
|
||||
MINGW_CC="x86_64-w64-mingw32-gcc"
|
||||
MINGW_AR="x86_64-w64-mingw32-ar"
|
||||
MINGW_RANLIB="x86_64-w64-mingw32-ranlib"
|
||||
|
||||
MINGW_PKGS="gcc-mingw-w64-x86-64-posix g++-mingw-w64-x86-64-posix"
|
||||
OPENSSL_VER="3.0.16"
|
||||
OPENSSL_URL="https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_VER}/openssl-${OPENSSL_VER}.tar.gz"
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 0. Аргументы
|
||||
# -------------------------------------------------------------------
|
||||
CLEAN=0
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--clean) CLEAN=1; shift ;;
|
||||
*) fatal "unknown option: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "$CLEAN" -eq 1 ]; then
|
||||
echo -e "${YELLOW}Cleaning build directory: $BUILD_DIR${NC}"
|
||||
rm -rf "$BUILD_DIR"
|
||||
fi
|
||||
|
||||
echo -e "${CYAN}==============================================${NC}"
|
||||
echo -e "${CYAN} uTun Cross-Compilation for Windows 10${NC}"
|
||||
echo -e "${CYAN}==============================================${NC}"
|
||||
echo ""
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 1. Проверка системы
|
||||
# -------------------------------------------------------------------
|
||||
step "Checking system..."
|
||||
if [ "$(uname -s)" != "Linux" ]; then
|
||||
fatal "This script only runs on Linux. Detected: $(uname -s)"
|
||||
fi
|
||||
if [ "$(uname -m)" != "x86_64" ]; then
|
||||
fatal "This script requires x86_64 architecture. Detected: $(uname -m)"
|
||||
fi
|
||||
|
||||
if ! command -v apt-get &>/dev/null; then
|
||||
fatal "apt-get not found. Only Debian/Ubuntu are supported."
|
||||
fi
|
||||
ok "System: Linux x86_64 with apt"
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 2. Установка пакетов
|
||||
# -------------------------------------------------------------------
|
||||
step "Installing required packages..."
|
||||
|
||||
need_install=()
|
||||
for pkg in $MINGW_PKGS; do
|
||||
if ! dpkg -s "$pkg" &>/dev/null 2>&1; then
|
||||
need_install+=("$pkg")
|
||||
fi
|
||||
done
|
||||
if ! command -v wget &>/dev/null && ! command -v curl &>/dev/null; then
|
||||
need_install+=("wget")
|
||||
fi
|
||||
|
||||
if [ ${#need_install[@]} -gt 0 ]; then
|
||||
echo " Installing: ${need_install[*]}"
|
||||
sudo apt-get update -qq || fatal "apt-get update failed"
|
||||
sudo apt-get install -y -qq "${need_install[@]}" || fatal "apt-get install failed"
|
||||
ok "Packages installed: ${need_install[*]}"
|
||||
else
|
||||
ok "All packages already installed"
|
||||
fi
|
||||
|
||||
# Проверка компилятора
|
||||
for tool in "$MINGW_CC" "$MINGW_AR" "$MINGW_RANLIB"; do
|
||||
if ! command -v "$tool" &>/dev/null; then
|
||||
fatal "$tool not found after package installation"
|
||||
fi
|
||||
done
|
||||
ok "Cross-compiler: $($MINGW_CC --version | head -1)"
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 3. Кросс-компиляция OpenSSL
|
||||
# -------------------------------------------------------------------
|
||||
OPENSSL_SRC="$BUILD_DIR/openssl-${OPENSSL_VER}"
|
||||
LIBCRYPTO="$PREFIX/lib/libcrypto.a"
|
||||
|
||||
if [ -f "$LIBCRYPTO" ]; then
|
||||
ok "OpenSSL already built: $LIBCRYPTO"
|
||||
else
|
||||
step "Building OpenSSL ${OPENSSL_VER} for Windows..."
|
||||
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
TARBALL="$BUILD_DIR/openssl-${OPENSSL_VER}.tar.gz"
|
||||
if [ ! -f "$TARBALL" ]; then
|
||||
echo " Downloading OpenSSL ${OPENSSL_VER}..."
|
||||
if command -v wget &>/dev/null; then
|
||||
wget -q --show-progress -O "$TARBALL" "$OPENSSL_URL" || fatal "wget download failed for $OPENSSL_URL"
|
||||
else
|
||||
curl -L --progress-bar -o "$TARBALL" "$OPENSSL_URL" || fatal "curl download failed for $OPENSSL_URL"
|
||||
fi
|
||||
ok "Downloaded: $TARBALL"
|
||||
fi
|
||||
|
||||
if [ ! -d "$OPENSSL_SRC" ]; then
|
||||
echo " Extracting..."
|
||||
tar xzf "$TARBALL" -C "$BUILD_DIR" || fatal "tar extract failed"
|
||||
fi
|
||||
|
||||
echo " Configuring OpenSSL for mingw64..."
|
||||
cd "$OPENSSL_SRC"
|
||||
./Configure --cross-compile-prefix=x86_64-w64-mingw32- \
|
||||
mingw64 no-shared no-tests \
|
||||
--prefix="$PREFIX" --libdir="$PREFIX/lib" --openssldir="$PREFIX/ssl" \
|
||||
|| fatal "OpenSSL configure failed (see output above)"
|
||||
ok "OpenSSL configured"
|
||||
|
||||
echo " Compiling OpenSSL..."
|
||||
make -j"$(nproc)" || fatal "OpenSSL make failed"
|
||||
echo " Installing OpenSSL..."
|
||||
make install_sw >/dev/null || fatal "OpenSSL make install_sw failed"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# OpenSSL может установить в lib64/ вместо lib/ — поправим
|
||||
if [ ! -f "$LIBCRYPTO" ] && [ -f "$PREFIX/lib64/libcrypto.a" ]; then
|
||||
ln -sf lib64 "$PREFIX/lib" 2>/dev/null || cp -r "$PREFIX/lib64" "$PREFIX/lib" 2>/dev/null || true
|
||||
fi
|
||||
if [ ! -f "$LIBCRYPTO" ]; then
|
||||
fatal "libcrypto.a was not found at $PREFIX/lib/ or $PREFIX/lib64/"
|
||||
fi
|
||||
ok "OpenSSL built: $LIBCRYPTO"
|
||||
fi
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 4. Сборка libuasync.a
|
||||
# -------------------------------------------------------------------
|
||||
step "Building libuasync.a..."
|
||||
|
||||
LIB_OBJDIR="$BUILD_DIR/lib-objs"
|
||||
mkdir -p "$LIB_OBJDIR"
|
||||
|
||||
# Все .c файлы из lib/Makefile.am + myip.c (реализация get_my_ip)
|
||||
LIB_SOURCES=(
|
||||
u_async.c ll_queue.c memory_pool.c debug_config.c timeout_heap.c
|
||||
sha256.c mem.c serialize.c swm_min.c getmyip.c radix.c tcp_io.c
|
||||
myip.c platform_compat.c socket_compat.c
|
||||
liblmdb/mdb.c liblmdb/midl.c
|
||||
)
|
||||
|
||||
LIB_CFLAGS="-D_ISOC99_SOURCE -DDEBUG_OUTPUT_STDERR -DUSE_OPENSSL"
|
||||
LIB_CFLAGS+=" -I$SCRIPT_DIR/lib -I$SCRIPT_DIR/src -I$SCRIPT_DIR/lib/liblmdb"
|
||||
LIB_CFLAGS+=" -I$PREFIX/include"
|
||||
LIB_CFLAGS+=" -g -O2 -Wno-deprecated-declarations"
|
||||
|
||||
for src in "${LIB_SOURCES[@]}"; do
|
||||
obj="$LIB_OBJDIR/$(basename "${src%.c}.o")"
|
||||
echo " CC lib/$src"
|
||||
"$MINGW_CC" $LIB_CFLAGS -c "$SCRIPT_DIR/lib/$src" -o "$obj" \
|
||||
|| fatal "compilation failed: lib/$src"
|
||||
done
|
||||
|
||||
"$MINGW_AR" crs "$BUILD_DIR/libuasync.a" "$LIB_OBJDIR"/*.o \
|
||||
|| fatal "ar failed for libuasync.a"
|
||||
"$MINGW_RANLIB" "$BUILD_DIR/libuasync.a" 2>/dev/null || true
|
||||
ok "libuasync.a ($(wc -c < "$BUILD_DIR/libuasync.a") bytes)"
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 5. Сборка объектных файлов для utun.exe
|
||||
# -------------------------------------------------------------------
|
||||
step "Compiling utun source files..."
|
||||
|
||||
SRC_OBJDIR="$BUILD_DIR/src-objs"
|
||||
mkdir -p "$SRC_OBJDIR"
|
||||
|
||||
# Все .c файлы из src/Makefile.am (utun_CORE_SOURCES)
|
||||
SRC_SOURCES=(
|
||||
utun.c utun_instance.c config_parser.c config_updater.c
|
||||
route_lib.c route6_lib.c 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 tun_linux.c tun_freebsd.c tun_windows.c
|
||||
etcp.c etcp_connections.c etcp_bbr.c etcp_loadbalancer.c
|
||||
etcp_debug.c etcp_dump.c secure_channel.c crc32.c
|
||||
stcp_link.c stcp.c stcp_server.c stcp_client.c
|
||||
pkt_normalizer.c packet_dump.c etcp_api.c
|
||||
control_server.c msg_transport.c
|
||||
firewall.c eim_nat.c nat_transport.c dummynet.c
|
||||
etcp_router.c
|
||||
proxy/udp_proxy.c proxy/socks_proxy.c
|
||||
lwip_tcp/lwip_pbuf.c
|
||||
)
|
||||
|
||||
SRC_CFLAGS="-DUSE_OPENSSL"
|
||||
SRC_CFLAGS+=" -I$SCRIPT_DIR/src -I$SCRIPT_DIR/lib -I$SCRIPT_DIR/lib/liblmdb"
|
||||
SRC_CFLAGS+=" -I$SCRIPT_DIR/tinycrypt/lib/include -I$SCRIPT_DIR/tinycrypt/lib/source"
|
||||
SRC_CFLAGS+=" -I$PREFIX/include"
|
||||
SRC_CFLAGS+=" -g -O2 -Wno-deprecated-declarations"
|
||||
|
||||
TOTAL=${#SRC_SOURCES[@]}
|
||||
CURRENT=0
|
||||
FAILED=()
|
||||
|
||||
for src in "${SRC_SOURCES[@]}"; do
|
||||
CURRENT=$((CURRENT + 1))
|
||||
# Имя объектного файла: заменяем / на _ (для proxy/ и lwip_tcp/)
|
||||
obj_name="$(echo "$src" | tr '/' '_')"
|
||||
obj="$SRC_OBJDIR/${obj_name%.c}.o"
|
||||
printf " [%3d/%3d] CC src/%s\n" "$CURRENT" "$TOTAL" "$src"
|
||||
if ! "$MINGW_CC" $SRC_CFLAGS -c "$SCRIPT_DIR/src/$src" -o "$obj" 2>&1; then
|
||||
FAILED+=("src/$src")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#FAILED[@]} -gt 0 ]; then
|
||||
echo ""
|
||||
fatal "compilation failed for: ${FAILED[*]}"
|
||||
fi
|
||||
ok "All $TOTAL source files compiled"
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 6. Линковка utun.exe
|
||||
# -------------------------------------------------------------------
|
||||
step "Linking utun.exe..."
|
||||
|
||||
"$MINGW_CC" -o "$SCRIPT_DIR/utun.exe" \
|
||||
"$SRC_OBJDIR"/*.o \
|
||||
"$BUILD_DIR/libuasync.a" \
|
||||
"$LIBCRYPTO" \
|
||||
-lws2_32 -liphlpapi -ladvapi32 -lbcrypt -lcrypt32 -lpthread -lm \
|
||||
-static \
|
||||
|| fatal "linking failed (see undefined references above)"
|
||||
|
||||
ok "utun.exe ($(wc -c < "$SCRIPT_DIR/utun.exe") bytes)"
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 7. Копирование wintun.dll и финал
|
||||
# -------------------------------------------------------------------
|
||||
echo ""
|
||||
if [ -f "$SCRIPT_DIR/lib/wintun.dll" ]; then
|
||||
cp "$SCRIPT_DIR/lib/wintun.dll" "$SCRIPT_DIR/"
|
||||
ok "wintun.dll copied to project root"
|
||||
else
|
||||
warn "wintun.dll not found in lib/ — place it manually alongside utun.exe"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}==============================================${NC}"
|
||||
echo -e "${GREEN} Build successful!${NC}"
|
||||
echo -e "${GREEN} Binary: utun.exe${NC}"
|
||||
echo -e "${GREEN}==============================================${NC}"
|
||||
echo ""
|
||||
|
||||
if [ -f "$SCRIPT_DIR/lib/wintun.dll" ]; then
|
||||
echo "Run on Windows:"
|
||||
echo " 1. Install Wintun driver (https://www.wintun.net)"
|
||||
echo " 2. Copy utun.exe + wintun.dll to the same directory"
|
||||
echo " 3. Run: utun.exe -f -p utun.pid -l utun.log"
|
||||
else
|
||||
echo "Run on Windows:"
|
||||
echo " 1. Install Wintun driver (https://www.wintun.net)"
|
||||
echo " 2. Copy utun.exe + wintun.dll to the same directory"
|
||||
echo " 3. Run: utun.exe -f -p utun.pid -l utun.log"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
exit 0
|
||||
+31
-7
@@ -22,12 +22,9 @@ utun поддерживает встроенный ipv4 роутинг, може
|
||||
Есть статические подключения (которые прописаны в конфиге) и есть динамические - которые устанавливаются в процессе по необходимости.
|
||||
Обмен маршрутами происходит только через статические подключения.
|
||||
|
||||
toto:
|
||||
- задать ограничение bandwidth для каждого сокета (общее).
|
||||
|
||||
|
||||
## etcp_connections:
|
||||
- обслуживает encrypt/decrypt и установку защищенного подключения.
|
||||
- обслуживает encrypt/decrypt и установку защищенного подключения per-link.
|
||||
- для одного etcp соединения может испольоваться несколько подключений одновременно (load balancing / filover)
|
||||
|
||||
## etcp_loadbalancer:
|
||||
@@ -50,9 +47,36 @@ toto:
|
||||
- обеспечивает ретрансмиссии при передаче и сборку в правильной последовательности при приёме
|
||||
- взаимодействует с pkt_normalizer и с udp сокетами для отправки-получения пакетов
|
||||
|
||||
## etcp_metric:
|
||||
- обеспечивает обновление метрик каналов etcp_connections
|
||||
- вызывается при получении ack
|
||||
## stcp_* :
|
||||
- аналогично etcp только упрощенный вариант, используя tcp в качестве транспорта.
|
||||
|
||||
## etcp_router:
|
||||
- устанавливает связь между узлами, маршрутизирует трафик через цепочку узлов
|
||||
|
||||
## route_lib, route6_lib :
|
||||
- управляет таблицами маршрутизации (ipv4/ipv6 lookup)
|
||||
|
||||
## route_node:
|
||||
- хранит список доступных узлов (полученные от соседей) и их адреса/pubkeys для подключения.
|
||||
- содержит апи для поиска-чтения-обновления узла
|
||||
|
||||
## route_bgp:
|
||||
- составляет карту маршрутизации между узлами и обновляет ее при подключении-отключении узлов
|
||||
|
||||
## route_ping:
|
||||
- модуль для определения типа nat (strict или eim) используя вспомогательный узел
|
||||
|
||||
Библиотеки:
|
||||
## uasync
|
||||
- модуль для асинхронной работы.
|
||||
реализует сокеты, таймауты и сигналы из других потоков
|
||||
- быстро работает с большим числом таймаутов (timeout_heap)
|
||||
|
||||
## ll_queue
|
||||
- модуль работы с очередями, использует uasync.
|
||||
- реализует fifo/lifo очереди, быстрый поиск по индексу, различные callback - сигнализация о состояниях очереди,
|
||||
- congestion control: если много желающих записать -> очередь round-robin
|
||||
|
||||
|
||||
Тесты:
|
||||
# Правила работы с очередями в тестах:
|
||||
|
||||
@@ -136,7 +136,7 @@ link_state: 0=just init, 1=handshake, 2=reconnect, 3=connected.
|
||||
- local_link_id: uint8_t (0-255), назначается отправителем
|
||||
- sock_id: uint8_t, уникальный ID сокета отправителя
|
||||
- only_local: uint8_t, флаг "только локальные подключения"
|
||||
- type: uint8_t, тип сокета (CFG_SERVER_TYPE_PUBLIC/NAT/PRIVATE)
|
||||
- type: uint8_t, тип сокета (CFG_SERVER_TYPE_UNKNOWN=0/PUBLIC=1/NAT=2/PRIVATE=3/LOCAL=4)
|
||||
- random_padding: случайные байты до размера handshake (защита от fingerprinting)
|
||||
- salt + obfuscated_pubkey: публичный ключ с обфускацией (не шифруется AES,
|
||||
но скрывается XOR с salt и собственным публичным ключом). Передаётся в конце пакета
|
||||
|
||||
+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:
|
||||
|
||||
+19
-41
@@ -13,30 +13,29 @@
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
void log_dump(const char* prefix, const uint8_t* data, size_t len) {
|
||||
void log_dump(int level, int category, const char* prefix, const uint8_t* data, size_t len) {
|
||||
if (!debug_should_output(level, category)) return;
|
||||
|
||||
char hex_buf[513] = {'\0'};
|
||||
size_t hex_len = 0;
|
||||
size_t show_len = (len > 128) ? 128 : len;
|
||||
|
||||
|
||||
for (size_t i = 0; i < show_len && hex_len < 512 - 3; i++) {
|
||||
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "%02x", data[i]);
|
||||
if (i < show_len - 1 && (i + 1) % 32 == 0) { // Add space every 32 bytes
|
||||
if (i < show_len - 1 && (i + 1) % 32 == 0)
|
||||
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, " ");
|
||||
}
|
||||
}
|
||||
|
||||
if (len > 128) {
|
||||
|
||||
if (len > 128)
|
||||
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "...");
|
||||
|
||||
switch (level) {
|
||||
case DEBUG_LEVEL_ERROR: DEBUG_ERROR(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
|
||||
case DEBUG_LEVEL_WARN: DEBUG_WARN(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
|
||||
case DEBUG_LEVEL_INFO: DEBUG_INFO(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
|
||||
case DEBUG_LEVEL_DEBUG: DEBUG_DEBUG(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
|
||||
case DEBUG_LEVEL_TRACE: DEBUG_TRACE(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
|
||||
}
|
||||
|
||||
// Single-line debug output with packet info
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DUMP, "%s: len=%zu hex=%s", prefix, len, hex_buf);
|
||||
|
||||
// Additional debug info for first few bytes
|
||||
// if (len >= 2) {
|
||||
// DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "%s: first_bytes=%02x%02x last_bytes=%02x%02x",
|
||||
// prefix, data[0], data[1], data[len-2], data[len-1]);
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
@@ -128,6 +127,8 @@ static const struct {
|
||||
{"nat", DEBUG_CATEGORY_NAT},
|
||||
{"keepalive", DEBUG_CATEGORY_KEEPALIVE},
|
||||
{"etcp_route", DEBUG_CATEGORY_ETCPROUTE},
|
||||
{"bbr", DEBUG_CATEGORY_BBR},
|
||||
{"etcp_dump", DEBUG_CATEGORY_ETCP_DUMP},
|
||||
{"all", DEBUG_CATEGORY_ALL},
|
||||
{NULL, DEBUG_CATEGORY_NONE}
|
||||
};
|
||||
@@ -199,32 +200,9 @@ void debug_apply_global_level(const char* level_name) {
|
||||
|
||||
/* Get category name for logging */
|
||||
const char* debug_get_category_name(debug_category_t category_idx) {
|
||||
switch (category_idx) {
|
||||
case DEBUG_CATEGORY_UASYNC: return "UASYNC";
|
||||
case DEBUG_CATEGORY_LL_QUEUE: return "LL_QUEUE";
|
||||
case DEBUG_CATEGORY_CONNECTION: return "CONNECTION";
|
||||
case DEBUG_CATEGORY_ETCP: return "ETCP";
|
||||
case DEBUG_CATEGORY_CRYPTO: return "CRYPTO";
|
||||
case DEBUG_CATEGORY_MEMORY: return "MEMORY";
|
||||
case DEBUG_CATEGORY_TIMING: return "TIMING";
|
||||
case DEBUG_CATEGORY_CONFIG: return "CONFIG";
|
||||
case DEBUG_CATEGORY_TUN: return "TUN";
|
||||
case DEBUG_CATEGORY_ROUTING: return "ROUTING";
|
||||
case DEBUG_CATEGORY_TIMERS: return "TIMERS";
|
||||
case DEBUG_CATEGORY_NORMALIZER: return "NORMALIZER";
|
||||
case DEBUG_CATEGORY_BGP: return "BGP";
|
||||
case DEBUG_CATEGORY_SOCKET: return "SOCKET";
|
||||
case DEBUG_CATEGORY_CONTROL: return "CONTROL";
|
||||
case DEBUG_CATEGORY_DUMP: return "DUMP";
|
||||
case DEBUG_CATEGORY_TRAFFIC: return "TRAFFIC";
|
||||
case DEBUG_CATEGORY_DEBUG: return "DEBUG";
|
||||
case DEBUG_CATEGORY_GENERAL: return "GENERAL";
|
||||
case DEBUG_CATEGORY_NAT: return "NAT";
|
||||
case DEBUG_CATEGORY_KEEPALIVE: return "KEEPALIVE";
|
||||
case DEBUG_CATEGORY_ETCPROUTE: return "ETCPROUTE";
|
||||
case DEBUG_CATEGORY_NONE: return "NONE";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
for (int i = 0; g_debug_categories[i].name; i++)
|
||||
if (g_debug_categories[i].category == category_idx) return g_debug_categories[i].name;
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
/* Global debug configuration */
|
||||
|
||||
+5
-2
@@ -60,7 +60,9 @@ typedef int debug_category_t;
|
||||
#define DEBUG_CATEGORY_NAT 20 // EIM NAT module
|
||||
#define DEBUG_CATEGORY_KEEPALIVE 21 // Link keepalive logic
|
||||
#define DEBUG_CATEGORY_ETCPROUTE 22 // ETCP routing
|
||||
#define DEBUG_CATEGORY_COUNT 23 // Total number of categories
|
||||
#define DEBUG_CATEGORY_BBR 23 // BBR congestion control
|
||||
#define DEBUG_CATEGORY_ETCP_DUMP 24 // ETCP packet dump
|
||||
#define DEBUG_CATEGORY_COUNT 25 // Total number of categories
|
||||
#define DEBUG_CATEGORY_ALL (-1) // special value for all categories
|
||||
|
||||
/* Debug configuration structure */
|
||||
@@ -86,6 +88,7 @@ extern debug_config_t g_debug_config;
|
||||
void debug_config_init(void);
|
||||
|
||||
/* Set debug level */
|
||||
// итоговый level = max (global level - здесь задается, category level)
|
||||
void debug_set_level(debug_level_t level);
|
||||
|
||||
/* Set debug level for specific category (0 = disabled, otherwise uses that level) */
|
||||
@@ -123,7 +126,7 @@ ip_str_t ip_to_str(const void *addr, int family);// big endian addr
|
||||
ip_str_t sockaddr_storage_to_str(const struct sockaddr_storage *addr);// автоопределение v4 или v6
|
||||
|
||||
// hex dump в лог
|
||||
void log_dump(const char* prefix, const uint8_t* data, size_t len);
|
||||
void log_dump(int level, int category, const char* prefix, const uint8_t* data, size_t len);
|
||||
|
||||
/* Check if debug output should be shown for given level and category */
|
||||
int debug_should_output(debug_level_t level, debug_category_t category_idx);
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
mtest
|
||||
mtest[23456]
|
||||
testdb
|
||||
mdb_copy
|
||||
mdb_stat
|
||||
mdb_dump
|
||||
mdb_load
|
||||
*.lo
|
||||
*.[ao]
|
||||
*.so
|
||||
*.exe
|
||||
*[~#]
|
||||
*.bak
|
||||
*.orig
|
||||
*.rej
|
||||
*.gcov
|
||||
*.gcda
|
||||
*.gcno
|
||||
core
|
||||
core.*
|
||||
valgrind.*
|
||||
man/
|
||||
html/
|
||||
@@ -0,0 +1,317 @@
|
||||
LMDB 0.9 Change Log
|
||||
|
||||
LMDB 0.9.36 Engineering
|
||||
ITS#7772 - limit sub-page growth when re-adding existing data
|
||||
ITS#8335 - more input validation for put(MDB_MULTIPLE)
|
||||
ITS#8386 - be a bit more precise that mdb_get retrieves data in intro.doc
|
||||
ITS#8590 - suppress SIGPIPE on Mac OSX for mdb_copy/compact
|
||||
ITS#8739 - don't use fdatasync on FreeBSD 11.0 and older
|
||||
ITS#8824 - mdb_dump: cleanup check for MDB_SUCCESS
|
||||
ITS#9388 - fix mdb_stat page counts for dupsort DBs
|
||||
ITS#10054 - fix write of 2GB values
|
||||
ITS#10108 - mdb_dump: fix comment
|
||||
ITS#10454 - fix metapage sync on Mac OSX
|
||||
ITS#10504 - silence linker waarning on Mac OSX
|
||||
ITS#10522 - reject 0-length values in DUPSORT DBs
|
||||
|
||||
LMDB 0.9.35 Release (2026/01/27)
|
||||
ITS#10434 - Fix typo affecting Mac OSX
|
||||
|
||||
LMDB 0.9.34 Release (2026/01/26)
|
||||
ITS#9564 - fix race condition freeing spilled pages at end of transaction
|
||||
ITS#10222 - Update mdb_dump(1) and mdb_load(1) man pages for append (-a) option
|
||||
ITS#10275 - mdb_load: add -Q option to use NOSYNC
|
||||
ITS#10296 - fix fdatasync on MacOS
|
||||
ITS#10342 - fix memleak in mdb_txn_begin for nested txns
|
||||
ITS#10346 - fix mdb_env_copy2 with values > (2GB-16)
|
||||
ITS#10355 - fix mplay build on musl
|
||||
ITS#10396 - fix mdb_cursor_del0 with multiple DUPSORT cursors
|
||||
ITS#10419 - add support for NetBSD
|
||||
ITS#10420 - add support for Haiku
|
||||
ITS#10421 - mdb_load: check for malicious input
|
||||
|
||||
LMDB 0.9.33 Release (2024/05/21)
|
||||
ITS#9037 mdb_page_search: fix error code when DBI record is missing
|
||||
ITS#10198 For win32, stop passing ignored parameter
|
||||
ITS#10212 Fix meta page usage by read only tools
|
||||
|
||||
LMDB 0.9.32 Release (2024/01/29)
|
||||
ITS#9378 - Add ability to replay log and replay log tool
|
||||
ITS#10095 - partial revert of ITS#9278. The patch was incorrect and introduced numerous race conditions.
|
||||
ITS#10125 - mdb_load: fix cursor reinit in Append mode
|
||||
ITS#10137 - Allow users to define MDB_IDL_LOGN
|
||||
|
||||
LMDB 0.9.31 Release (2023/07/10)
|
||||
ITS#8447 - Fix cursor_put(MDB_CURRENT) on DUPSORT DB with different sized data
|
||||
|
||||
LMDB 0.9.30 Release (2023/02/08)
|
||||
ITS#9806 - LMDB page_split: key threshold depends on page size
|
||||
ITS#9916 - avoid gcc optimization bug on sparc64 linux
|
||||
ITS#9919 - Mark infrequently used functions as cold
|
||||
ITS#9723 - clear C_EOF on cursor with MDB_FIRST_DUP
|
||||
ITS#9030 - Use sys/cachectl.h rather than asm/cachectl.h on mips
|
||||
|
||||
LMDB 0.9.29 Release (2021/03/16)
|
||||
ITS#9461 refix ITS#9376
|
||||
ITS#9500 fix regression from ITS#8662
|
||||
|
||||
LMDB 0.9.28 Release (2021/02/04)
|
||||
ITS#8662 add -a append option to mdb_load
|
||||
|
||||
LMDB 0.9.27 Release (2020/10/26)
|
||||
ITS#9376 fix repeated DUPSORT cursor deletes
|
||||
|
||||
LMDB 0.9.26 Release (2020/08/11)
|
||||
ITS#9278 fix robust mutex cleanup for FreeBSD
|
||||
|
||||
LMDB 0.9.25 Release (2020/01/30)
|
||||
ITS#9068 fix mdb_dump/load backslashes in printable content
|
||||
ITS#9118 add MAP_NOSYNC for FreeBSD
|
||||
ITS#9155 free mt_spill_pgs in non-nested txn on end
|
||||
|
||||
LMDB 0.9.24 Release (2019/07/24)
|
||||
ITS#8969 Tweak mdb_page_split
|
||||
ITS#8975 WIN32 fix writemap set_mapsize crash
|
||||
ITS#9007 Fix loose pages in WRITEMAP
|
||||
|
||||
LMDB 0.9.23 Release (2018/12/19)
|
||||
ITS#8756 Fix loose pages in dirty list
|
||||
ITS#8831 Fix mdb_load flag init
|
||||
ITS#8844 Fix mdb_env_close in forked process
|
||||
Documentation
|
||||
ITS#8857 mdb_cursor_del doesn't invalidate cursor
|
||||
ITS#8908 GET_MULTIPLE etc don't change passed in key
|
||||
|
||||
LMDB 0.9.22 Release (2018/03/22)
|
||||
Fix MDB_DUPSORT alignment bug (ITS#8819)
|
||||
Fix regression with new db from 0.9.19 (ITS#8760)
|
||||
Fix liblmdb to build on Solaris (ITS#8612)
|
||||
Fix delete behavior with DUPSORT DB (ITS#8622)
|
||||
Fix mdb_cursor_get/mdb_cursor_del behavior (ITS#8722)
|
||||
|
||||
LMDB 0.9.21 Release (2017/06/01)
|
||||
Fix xcursor after cursor_del (ITS#8622)
|
||||
|
||||
LMDB 0.9.20 (Withdrawn)
|
||||
Fix mdb_load with escaped plaintext (ITS#8558)
|
||||
Fix mdb_cursor_last / mdb_put interaction (ITS#8557)
|
||||
|
||||
LMDB 0.9.19 Release (2016/12/28)
|
||||
Fix mdb_env_cwalk cursor init (ITS#8424)
|
||||
Fix robust mutexes on Solaris 10/11 (ITS#8339)
|
||||
Tweak Win32 error message buffer
|
||||
Fix MDB_GET_BOTH on non-dup record (ITS#8393)
|
||||
Optimize mdb_drop
|
||||
Fix xcursors after mdb_cursor_del (ITS#8406)
|
||||
Fix MDB_NEXT_DUP after mdb_cursor_del (ITS#8412)
|
||||
Fix mdb_cursor_put resetting C_EOF (ITS#8489)
|
||||
Fix mdb_env_copyfd2 to return EPIPE on SIGPIPE (ITS#8504)
|
||||
Fix mdb_env_copy with empty DB (ITS#8209)
|
||||
Fix behaviors with fork (ITS#8505)
|
||||
Fix mdb_dbi_open with mainDB cursors (ITS#8542)
|
||||
Fix robust mutexes on kFreeBSD (ITS#8554)
|
||||
Fix utf8_to_utf16 error checks (ITS#7992)
|
||||
Fix F_NOCACHE on MacOS, error is non-fatal (ITS#7682)
|
||||
Build
|
||||
Make shared lib suffix overridable (ITS#8481)
|
||||
Documentation
|
||||
Cleanup doxygen nits
|
||||
Note reserved vs actual mem/disk usage
|
||||
|
||||
|
||||
LMDB 0.9.18 Release (2016/02/05)
|
||||
Fix robust mutex detection on glibc 2.10-11 (ITS#8330)
|
||||
Fix page_search_root assert on FreeDB (ITS#8336)
|
||||
Fix MDB_APPENDDUP vs. rewrite(single item) (ITS#8334)
|
||||
Fix mdb_copy of large files on Windows
|
||||
Fix subcursor move after delete (ITS#8355)
|
||||
Fix mdb_midl_shirnk off-by-one (ITS#8363)
|
||||
Check for utf8_to_utf16 failures (ITS#7992)
|
||||
Catch strdup failure in mdb_dbi_open
|
||||
Build
|
||||
Additional makefile var tweaks (ITS#8169)
|
||||
Documentation
|
||||
Add Getting Started page
|
||||
Update WRITEMAP description
|
||||
|
||||
|
||||
LMDB 0.9.17 Release (2015/11/30)
|
||||
Fix ITS#7377 catch calloc failure
|
||||
Fix ITS#8237 regression from ITS#7589
|
||||
Fix ITS#8238 page_split for DUPFIXED pages
|
||||
Fix ITS#8221 MDB_PAGE_FULL on delete/rebalance
|
||||
Fix ITS#8258 rebalance/split assert
|
||||
Fix ITS#8263 cursor_put cursor tracking
|
||||
Fix ITS#8264 cursor_del cursor tracking
|
||||
Fix ITS#8310 cursor_del cursor tracking
|
||||
Fix ITS#8299 mdb_del cursor tracking
|
||||
Fix ITS#8300 mdb_del cursor tracking
|
||||
Fix ITS#8304 mdb_del cursor tracking
|
||||
Fix ITS#7771 fakepage cursor tracking
|
||||
Fix ITS#7789 ensure mapsize >= pages in use
|
||||
Fix ITS#7971 mdb_txn_renew0() new reader slots
|
||||
Fix ITS#7969 use __sync_synchronize on non-x86
|
||||
Fix ITS#8311 page_split from update_key
|
||||
Fix ITS#8312 loose pages in nested txn
|
||||
Fix ITS#8313 mdb_rebalance dummy cursor
|
||||
Fix ITS#8315 dirty_room in nested txn
|
||||
Fix ITS#8323 dirty_list in nested txn
|
||||
Fix ITS#8316 page_merge cursor tracking
|
||||
Fix ITS#8321 cursor tracking
|
||||
Fix ITS#8319 mdb_load error messages
|
||||
Fix ITS#8320 mdb_load plaintext input
|
||||
Added mdb_txn_id() (ITS#7994)
|
||||
Added robust mutex support
|
||||
Miscellaneous cleanup/simplification
|
||||
Build
|
||||
Create install dirs if needed (ITS#8256)
|
||||
Fix ThreadProc decl on Win32/MSVC (ITS#8270)
|
||||
Added ssize_t typedef for MSVC (ITS#8067)
|
||||
Use ANSI apis on Windows (ITS#8069)
|
||||
Use O_SYNC if O_DSYNC,MDB_DSYNC are not defined (ITS#7209)
|
||||
Allow passing AR to make (ITS#8168)
|
||||
Allow passing mandir to make install (ITS#8169)
|
||||
|
||||
LMDB 0.9.16 Release (2015/08/14)
|
||||
Fix cursor EOF bug (ITS#8190)
|
||||
Fix handling of subDB records (ITS#8181)
|
||||
Fix mdb_midl_shrink() usage (ITS#8200)
|
||||
|
||||
LMDB 0.9.15 Release (2015/06/19)
|
||||
Fix txn init (ITS#7961,#7987)
|
||||
Fix MDB_PREV_DUP (ITS#7955,#7671)
|
||||
Fix compact of empty env (ITS#7956)
|
||||
Fix mdb_copy file mode
|
||||
Fix mdb_env_close() after failed mdb_env_open()
|
||||
Fix mdb_rebalance collapsing root (ITS#8062)
|
||||
Fix mdb_load with large values (ITS#8066)
|
||||
Fix to retry writes on EINTR (ITS#8106)
|
||||
Fix mdb_cursor_del on empty DB (ITS#8109)
|
||||
Fix MDB_INTEGERDUP key compare (ITS#8117)
|
||||
Fix error handling (ITS#7959,#8157,etc.)
|
||||
Fix race conditions (ITS#7969,7970)
|
||||
Added workaround for fdatasync bug in ext3fs
|
||||
Build
|
||||
Don't use -fPIC for static lib
|
||||
Update .gitignore (ITS#7952,#7953)
|
||||
Cleanup for "make test" (ITS#7841), "make clean", mtest*.c
|
||||
Misc. Android/Windows cleanup
|
||||
Documentation
|
||||
Fix MDB_APPEND doc
|
||||
Fix MDB_MAXKEYSIZE doc (ITS#8156)
|
||||
Fix mdb_cursor_put,mdb_cursor_del EACCES description
|
||||
Fix mdb_env_sync(MDB_RDONLY env) doc (ITS#8021)
|
||||
Clarify MDB_WRITEMAP doc (ITS#8021)
|
||||
Clarify mdb_env_open doc
|
||||
Clarify mdb_dbi_open doc
|
||||
|
||||
LMDB 0.9.14 Release (2014/09/20)
|
||||
Fix to support 64K page size (ITS#7713)
|
||||
Fix to persist decreased as well as increased mapsizes (ITS#7789)
|
||||
Fix cursor bug when deleting last node of a DUPSORT key
|
||||
Fix mdb_env_info to return FIXEDMAP address
|
||||
Fix ambiguous error code from writing to closed DBI (ITS#7825)
|
||||
Fix mdb_copy copying past end of file (ITS#7886)
|
||||
Fix cursor bugs from page_merge/rebalance
|
||||
Fix to dirty fewer pages in deletes (mdb_page_loose())
|
||||
Fix mdb_dbi_open creating subDBs (ITS#7917)
|
||||
Fix mdb_cursor_get(_DUP) with single value (ITS#7913)
|
||||
Fix Windows compat issues in mtests (ITS#7879)
|
||||
Add compacting variant of mdb_copy
|
||||
Add BigEndian integer key compare code
|
||||
Add mdb_dump/mdb_load utilities
|
||||
|
||||
LMDB 0.9.13 Release (2014/06/18)
|
||||
Fix mdb_page_alloc unlimited overflow page search
|
||||
Documentation
|
||||
Re-fix MDB_CURRENT doc (ITS#7793)
|
||||
Fix MDB_GET_MULTIPLE/MDB_NEXT_MULTIPLE doc
|
||||
|
||||
LMDB 0.9.12 Release (2014/06/13)
|
||||
Fix MDB_GET_BOTH regression (ITS#7875,#7681)
|
||||
Fix MDB_MULTIPLE writing multiple keys (ITS#7834)
|
||||
Fix mdb_rebalance (ITS#7829)
|
||||
Fix mdb_page_split (ITS#7815)
|
||||
Fix md_entries count (ITS#7861,#7828,#7793)
|
||||
Fix MDB_CURRENT (ITS#7793)
|
||||
Fix possible crash on Windows DLL detach
|
||||
Misc code cleanup
|
||||
Documentation
|
||||
mdb_cursor_put: cursor moves on error (ITS#7771)
|
||||
|
||||
|
||||
LMDB 0.9.11 Release (2014/01/15)
|
||||
Add mdb_env_set_assert() (ITS#7775)
|
||||
Fix: invalidate txn on page allocation errors (ITS#7377)
|
||||
Fix xcursor tracking in mdb_cursor_del0() (ITS#7771)
|
||||
Fix corruption from deletes (ITS#7756)
|
||||
Fix Windows/MSVC build issues
|
||||
Raise safe limit of max MDB_MAXKEYSIZE
|
||||
Misc code cleanup
|
||||
Documentation
|
||||
Remove spurious note about non-overlapping flags (ITS#7665)
|
||||
|
||||
LMDB 0.9.10 Release (2013/11/12)
|
||||
Add MDB_NOMEMINIT option
|
||||
Fix mdb_page_split() again (ITS#7589)
|
||||
Fix MDB_NORDAHEAD definition (ITS#7734)
|
||||
Fix mdb_cursor_del() positioning (ITS#7733)
|
||||
Partial fix for larger page sizes (ITS#7713)
|
||||
Fix Windows64/MSVC build issues
|
||||
|
||||
LMDB 0.9.9 Release (2013/10/24)
|
||||
Add mdb_env_get_fd()
|
||||
Add MDB_NORDAHEAD option
|
||||
Add MDB_NOLOCK option
|
||||
Avoid wasting space in mdb_page_split() (ITS#7589)
|
||||
Fix mdb_page_merge() cursor fixup (ITS#7722)
|
||||
Fix mdb_cursor_del() on last delete (ITS#7718)
|
||||
Fix adding WRITEMAP on existing env (ITS#7715)
|
||||
Fix nested txns (ITS#7515)
|
||||
Fix mdb_env_copy() O_DIRECT bug (ITS#7682)
|
||||
Fix mdb_cursor_set(SET_RANGE) return code (ITS#7681)
|
||||
Fix mdb_rebalance() cursor fixup (ITS#7701)
|
||||
Misc code cleanup
|
||||
Documentation
|
||||
Note that by default, readers need write access
|
||||
|
||||
|
||||
LMDB 0.9.8 Release (2013/09/09)
|
||||
Allow mdb_env_set_mapsize() on an open environment
|
||||
Fix mdb_dbi_flags() (ITS#7672)
|
||||
Fix mdb_page_unspill() in nested txns
|
||||
Fix mdb_cursor_get(CURRENT|NEXT) after a delete
|
||||
Fix mdb_cursor_get(DUP) to always return key (ITS#7671)
|
||||
Fix mdb_cursor_del() to always advance to next item (ITS#7670)
|
||||
Fix mdb_cursor_set(SET_RANGE) for tree with single page (ITS#7681)
|
||||
Fix mdb_env_copy() retry open if O_DIRECT fails (ITS#7682)
|
||||
Tweak mdb_page_spill() to be less aggressive
|
||||
Documentation
|
||||
Update caveats since mdb_reader_check() added in 0.9.7
|
||||
|
||||
LMDB 0.9.7 Release (2013/08/17)
|
||||
Don't leave stale lockfile on failed RDONLY open (ITS#7664)
|
||||
Fix mdb_page_split() ref beyond cursor depth
|
||||
Fix read txn data race (ITS#7635)
|
||||
Fix mdb_rebalance (ITS#7536, #7538)
|
||||
Fix mdb_drop() (ITS#7561)
|
||||
Misc DEBUG macro fixes
|
||||
Add MDB_NOTLS envflag
|
||||
Add mdb_env_copyfd()
|
||||
Add mdb_txn_env() (ITS#7660)
|
||||
Add mdb_dbi_flags() (ITS#7661)
|
||||
Add mdb_env_get_maxkeysize()
|
||||
Add mdb_env_reader_list()/mdb_env_reader_check()
|
||||
Add mdb_page_spill/unspill, remove hard txn size limit
|
||||
Use shorter names for semaphores (ITS#7615)
|
||||
Build
|
||||
Fix install target (ITS#7656)
|
||||
Documentation
|
||||
Misc updates for cursors, DB handles, data lifetime
|
||||
|
||||
LMDB 0.9.6 Release (2013/02/25)
|
||||
Many fixes/enhancements
|
||||
|
||||
LMDB 0.9.5 Release (2012/11/30)
|
||||
Renamed from libmdb to liblmdb
|
||||
Many fixes/enhancements
|
||||
@@ -0,0 +1,20 @@
|
||||
Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted only as authorized by the OpenLDAP
|
||||
Public License.
|
||||
|
||||
A copy of this license is available in the file LICENSE in the
|
||||
top-level directory of the distribution or, alternatively, at
|
||||
<http://www.OpenLDAP.org/license.html>.
|
||||
|
||||
OpenLDAP is a registered trademark of the OpenLDAP Foundation.
|
||||
|
||||
Individual files and/or contributed packages may be copyright by
|
||||
other parties and/or subject to additional restrictions.
|
||||
|
||||
This work also contains materials derived from public sources.
|
||||
|
||||
Additional information about OpenLDAP can be obtained at
|
||||
<http://www.openldap.org/>.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,47 @@
|
||||
The OpenLDAP Public License
|
||||
Version 2.8, 17 August 2003
|
||||
|
||||
Redistribution and use of this software and associated documentation
|
||||
("Software"), with or without modification, are permitted provided
|
||||
that the following conditions are met:
|
||||
|
||||
1. Redistributions in source form must retain copyright statements
|
||||
and notices,
|
||||
|
||||
2. Redistributions in binary form must reproduce applicable copyright
|
||||
statements and notices, this list of conditions, and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution, and
|
||||
|
||||
3. Redistributions must contain a verbatim copy of this document.
|
||||
|
||||
The OpenLDAP Foundation may revise this license from time to time.
|
||||
Each revision is distinguished by a version number. You may use
|
||||
this Software under terms of this license revision or under the
|
||||
terms of any subsequent revision of the license.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE OPENLDAP FOUNDATION AND ITS
|
||||
CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
||||
SHALL THE OPENLDAP FOUNDATION, ITS CONTRIBUTORS, OR THE AUTHOR(S)
|
||||
OR OWNER(S) OF THE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The names of the authors and copyright holders must not be used in
|
||||
advertising or otherwise to promote the sale, use or other dealing
|
||||
in this Software without specific, written prior permission. Title
|
||||
to copyright in this Software shall at all times remain with copyright
|
||||
holders.
|
||||
|
||||
OpenLDAP is a registered trademark of the OpenLDAP Foundation.
|
||||
|
||||
Copyright 1999-2003 The OpenLDAP Foundation, Redwood City,
|
||||
California, USA. All Rights Reserved. Permission to copy and
|
||||
distribute verbatim copies of this document is granted.
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright 2015-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
/** @page starting Getting Started
|
||||
|
||||
LMDB is compact, fast, powerful, and robust and implements a simplified
|
||||
variant of the BerkeleyDB (BDB) API. (BDB is also very powerful, and verbosely
|
||||
documented in its own right.) After reading this page, the main
|
||||
\ref mdb documentation should make sense. Thanks to Bert Hubert
|
||||
for creating the
|
||||
<a href="https://github.com/ahupowerdns/ahutils/blob/master/lmdb-semantics.md">
|
||||
initial version</a> of this writeup.
|
||||
|
||||
Everything starts with an environment, created by #mdb_env_create().
|
||||
Once created, this environment must also be opened with #mdb_env_open().
|
||||
|
||||
#mdb_env_open() gets passed a name which is interpreted as a directory
|
||||
path. Note that this directory must exist already, it is not created
|
||||
for you. Within that directory, a lock file and a storage file will be
|
||||
generated. If you don't want to use a directory, you can pass the
|
||||
#MDB_NOSUBDIR option, in which case the path you provided is used
|
||||
directly as the data file, and another file with a "-lock" suffix
|
||||
added will be used for the lock file.
|
||||
|
||||
Once the environment is open, a transaction can be created within it
|
||||
using #mdb_txn_begin(). Transactions may be read-write or read-only,
|
||||
and read-write transactions may be nested. A transaction must only
|
||||
be used by one thread at a time. Transactions are always required,
|
||||
even for read-only access. The transaction provides a consistent
|
||||
view of the data.
|
||||
|
||||
Once a transaction has been created, a database can be opened within it
|
||||
using #mdb_dbi_open(). If only one database will ever be used in the
|
||||
environment, a NULL can be passed as the database name. For named
|
||||
databases, the #MDB_CREATE flag must be used to create the database
|
||||
if it doesn't already exist. Also, #mdb_env_set_maxdbs() must be
|
||||
called after #mdb_env_create() and before #mdb_env_open() to set the
|
||||
maximum number of named databases you want to support.
|
||||
|
||||
Note: a single transaction can open multiple databases. Generally
|
||||
databases should only be opened once, by the first transaction in
|
||||
the process. After the first transaction completes, the database
|
||||
handles can freely be used by all subsequent transactions.
|
||||
|
||||
Within a transaction, #mdb_get() can retrieve and #mdb_put() can store single
|
||||
key/value pairs if that is all you need to do (but see \ref Cursors
|
||||
below if you want to do more).
|
||||
|
||||
A key/value pair is expressed as two #MDB_val structures. This struct
|
||||
has two fields, \c mv_size and \c mv_data. The data is a \c void pointer to
|
||||
an array of \c mv_size bytes.
|
||||
|
||||
Because LMDB is very efficient (and usually zero-copy), the data returned
|
||||
in an #MDB_val structure may be memory-mapped straight from disk. In
|
||||
other words <b>look but do not touch</b> (or free() for that matter).
|
||||
Once a transaction is closed, the values can no longer be used, so
|
||||
make a copy if you need to keep them after that.
|
||||
|
||||
@section Cursors Cursors
|
||||
|
||||
To do more powerful things, we must use a cursor.
|
||||
|
||||
Within the transaction, a cursor can be created with #mdb_cursor_open().
|
||||
With this cursor we can store/retrieve/delete (multiple) values using
|
||||
#mdb_cursor_get(), #mdb_cursor_put(), and #mdb_cursor_del().
|
||||
|
||||
#mdb_cursor_get() positions itself depending on the cursor operation
|
||||
requested, and for some operations, on the supplied key. For example,
|
||||
to list all key/value pairs in a database, use operation #MDB_FIRST for
|
||||
the first call to #mdb_cursor_get(), and #MDB_NEXT on subsequent calls,
|
||||
until the end is hit.
|
||||
|
||||
To retrieve all keys starting from a specified key value, use #MDB_SET.
|
||||
For more cursor operations, see the \ref mdb docs.
|
||||
|
||||
When using #mdb_cursor_put(), either the function will position the
|
||||
cursor for you based on the \b key, or you can use operation
|
||||
#MDB_CURRENT to use the current position of the cursor. Note that
|
||||
\b key must then match the current position's key.
|
||||
|
||||
@subsection summary Summarizing the Opening
|
||||
|
||||
So we have a cursor in a transaction which opened a database in an
|
||||
environment which is opened from a filesystem after it was
|
||||
separately created.
|
||||
|
||||
Or, we create an environment, open it from a filesystem, create a
|
||||
transaction within it, open a database within that transaction,
|
||||
and create a cursor within all of the above.
|
||||
|
||||
Got it?
|
||||
|
||||
@section thrproc Threads and Processes
|
||||
|
||||
LMDB uses POSIX locks on files, and these locks have issues if one
|
||||
process opens a file multiple times. Because of this, do not
|
||||
#mdb_env_open() a file multiple times from a single process. Instead,
|
||||
share the LMDB environment that has opened the file across all threads.
|
||||
Otherwise, if a single process opens the same environment multiple times,
|
||||
closing it once will remove all the locks held on it, and the other
|
||||
instances will be vulnerable to corruption from other processes.
|
||||
|
||||
Also note that a transaction is tied to one thread by default using
|
||||
Thread Local Storage. If you want to pass read-only transactions across
|
||||
threads, you can use the #MDB_NOTLS option on the environment.
|
||||
|
||||
@section txns Transactions, Rollbacks, etc.
|
||||
|
||||
To actually get anything done, a transaction must be committed using
|
||||
#mdb_txn_commit(). Alternatively, all of a transaction's operations
|
||||
can be discarded using #mdb_txn_abort(). In a read-only transaction,
|
||||
any cursors will \b not automatically be freed. In a read-write
|
||||
transaction, all cursors will be freed and must not be used again.
|
||||
|
||||
For read-only transactions, obviously there is nothing to commit to
|
||||
storage. The transaction still must eventually be aborted to close
|
||||
any database handle(s) opened in it, or committed to keep the
|
||||
database handles around for reuse in new transactions.
|
||||
|
||||
In addition, as long as a transaction is open, a consistent view of
|
||||
the database is kept alive, which requires storage. A read-only
|
||||
transaction that no longer requires this consistent view should
|
||||
be terminated (committed or aborted) when the view is no longer
|
||||
needed (but see below for an optimization).
|
||||
|
||||
There can be multiple simultaneously active read-only transactions
|
||||
but only one that can write. Once a single read-write transaction
|
||||
is opened, all further attempts to begin one will block until the
|
||||
first one is committed or aborted. This has no effect on read-only
|
||||
transactions, however, and they may continue to be opened at any time.
|
||||
|
||||
@section dupkeys Duplicate Keys
|
||||
|
||||
#mdb_get() and #mdb_put() respectively have no and only some support
|
||||
for multiple key/value pairs with identical keys. If there are multiple
|
||||
values for a key, #mdb_get() will only return the first value.
|
||||
|
||||
When multiple values for one key are required, pass the #MDB_DUPSORT
|
||||
flag to #mdb_dbi_open(). In an #MDB_DUPSORT database, by default
|
||||
#mdb_put() will not replace the value for a key if the key existed
|
||||
already. Instead it will add the new value to the key. In addition,
|
||||
#mdb_del() will pay attention to the value field too, allowing for
|
||||
specific values of a key to be deleted.
|
||||
|
||||
Finally, additional cursor operations become available for
|
||||
traversing through and retrieving duplicate values.
|
||||
|
||||
@section optim Some Optimization
|
||||
|
||||
If you frequently begin and abort read-only transactions, as an
|
||||
optimization, it is possible to only reset and renew a transaction.
|
||||
|
||||
#mdb_txn_reset() releases any old copies of data kept around for
|
||||
a read-only transaction. To reuse this reset transaction, call
|
||||
#mdb_txn_renew() on it. Any cursors in this transaction must also
|
||||
be renewed using #mdb_cursor_renew().
|
||||
|
||||
Note that #mdb_txn_reset() is similar to #mdb_txn_abort() and will
|
||||
close any databases you opened within the transaction.
|
||||
|
||||
To permanently free a transaction, reset or not, use #mdb_txn_abort().
|
||||
|
||||
@section cleanup Cleaning Up
|
||||
|
||||
For read-only transactions, any cursors created within it must
|
||||
be closed using #mdb_cursor_close().
|
||||
|
||||
It is very rarely necessary to close a database handle, and in
|
||||
general they should just be left open.
|
||||
|
||||
@section onward The Full API
|
||||
|
||||
The full \ref mdb documentation lists further details, like how to:
|
||||
|
||||
\li size a database (the default limits are intentionally small)
|
||||
\li drop and clean a database
|
||||
\li detect and report errors
|
||||
\li optimize (bulk) loading speed
|
||||
\li (temporarily) reduce robustness to gain even more speed
|
||||
\li gather statistics about the database
|
||||
\li define custom sort orders
|
||||
|
||||
*/
|
||||
+1608
File diff suppressed because it is too large.
Load diff
+10504
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,55 @@
|
||||
.TH MDB_COPY 1 "2014/07/01" "LMDB 0.9.14"
|
||||
.\" Copyright 2012-2021 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.SH NAME
|
||||
mdb_copy \- LMDB environment copy tool
|
||||
.SH SYNOPSIS
|
||||
.B mdb_copy
|
||||
[\c
|
||||
.BR \-V ]
|
||||
[\c
|
||||
.BR \-c ]
|
||||
[\c
|
||||
.BR \-n ]
|
||||
.B srcpath
|
||||
[\c
|
||||
.BR dstpath ]
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mdb_copy
|
||||
utility copies an LMDB environment. The environment can
|
||||
be copied regardless of whether it is currently in use.
|
||||
No lockfile is created, since it gets recreated at need.
|
||||
|
||||
If
|
||||
.I dstpath
|
||||
is specified it must be the path of an empty directory
|
||||
for storing the backup. Otherwise, the backup will be
|
||||
written to stdout.
|
||||
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-V
|
||||
Write the library version number to the standard output, and exit.
|
||||
.TP
|
||||
.BR \-c
|
||||
Compact while copying. Only current data pages will be copied; freed
|
||||
or unused pages will be omitted from the copy. This option will
|
||||
slow down the backup process as it is more CPU-intensive.
|
||||
Currently it fails if the environment has suffered a page leak.
|
||||
.TP
|
||||
.BR \-n
|
||||
Open LDMB environment(s) which do not use subdirectories.
|
||||
|
||||
.SH DIAGNOSTICS
|
||||
Exit status is zero if no errors occur.
|
||||
Errors result in a non-zero exit status and
|
||||
a diagnostic message being written to standard error.
|
||||
.SH CAVEATS
|
||||
This utility can trigger significant file size growth if run
|
||||
in parallel with write transactions, because pages which they
|
||||
free during copying cannot be reused until the copy is done.
|
||||
.SH "SEE ALSO"
|
||||
.BR mdb_stat (1)
|
||||
.SH AUTHOR
|
||||
Howard Chu of Symas Corporation <http://www.symas.com>
|
||||
@@ -0,0 +1,82 @@
|
||||
/* mdb_copy.c - memory-mapped database backup tool */
|
||||
/*
|
||||
* Copyright 2012-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#define MDB_STDOUT GetStdHandle(STD_OUTPUT_HANDLE)
|
||||
#else
|
||||
#define MDB_STDOUT 1
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
static void
|
||||
sighandle(int sig)
|
||||
{
|
||||
}
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int rc;
|
||||
MDB_env *env;
|
||||
const char *progname = argv[0], *act;
|
||||
unsigned flags = MDB_RDONLY;
|
||||
unsigned cpflags = 0;
|
||||
|
||||
for (; argc > 1 && argv[1][0] == '-'; argc--, argv++) {
|
||||
if (argv[1][1] == 'n' && argv[1][2] == '\0')
|
||||
flags |= MDB_NOSUBDIR;
|
||||
else if (argv[1][1] == 'c' && argv[1][2] == '\0')
|
||||
cpflags |= MDB_CP_COMPACT;
|
||||
else if (argv[1][1] == 'V' && argv[1][2] == '\0') {
|
||||
printf("%s\n", MDB_VERSION_STRING);
|
||||
exit(0);
|
||||
} else
|
||||
argc = 0;
|
||||
}
|
||||
|
||||
if (argc<2 || argc>3) {
|
||||
fprintf(stderr, "usage: %s [-V] [-c] [-n] srcpath [dstpath]\n", progname);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
#ifdef SIGPIPE
|
||||
signal(SIGPIPE, sighandle);
|
||||
#endif
|
||||
#ifdef SIGHUP
|
||||
signal(SIGHUP, sighandle);
|
||||
#endif
|
||||
signal(SIGINT, sighandle);
|
||||
signal(SIGTERM, sighandle);
|
||||
|
||||
act = "opening environment";
|
||||
rc = mdb_env_create(&env);
|
||||
if (rc == MDB_SUCCESS) {
|
||||
rc = mdb_env_open(env, argv[1], flags, 0600);
|
||||
}
|
||||
if (rc == MDB_SUCCESS) {
|
||||
act = "copying";
|
||||
if (argc == 2)
|
||||
rc = mdb_env_copyfd2(env, MDB_STDOUT, cpflags);
|
||||
else
|
||||
rc = mdb_env_copy2(env, argv[2], cpflags);
|
||||
}
|
||||
if (rc)
|
||||
fprintf(stderr, "%s: %s failed, error %d (%s)\n",
|
||||
progname, act, rc, mdb_strerror(rc));
|
||||
mdb_env_close(env);
|
||||
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
.TH MDB_DUMP 1 "2015/09/30" "LMDB 0.9.17"
|
||||
.\" Copyright 2014-2021 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.SH NAME
|
||||
mdb_dump \- LMDB environment export tool
|
||||
.SH SYNOPSIS
|
||||
.B mdb_dump
|
||||
[\c
|
||||
.BR \-V ]
|
||||
[\c
|
||||
.BI \-f \ file\fR]
|
||||
[\c
|
||||
.BR \-l ]
|
||||
[\c
|
||||
.BR \-n ]
|
||||
[\c
|
||||
.BR \-p ]
|
||||
[\c
|
||||
.BR \-a \ |
|
||||
.BI \-s \ subdb\fR]
|
||||
.BR \ envpath
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mdb_dump
|
||||
utility reads a database and writes its contents to the
|
||||
standard output using a portable flat-text format
|
||||
understood by the
|
||||
.BR mdb_load (1)
|
||||
utility.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-V
|
||||
Write the library version number to the standard output, and exit.
|
||||
.TP
|
||||
.BR \-f \ file
|
||||
Write to the specified file instead of to the standard output.
|
||||
.TP
|
||||
.BR \-l
|
||||
List the databases stored in the environment. Just the
|
||||
names will be listed, no data will be output.
|
||||
.TP
|
||||
.BR \-n
|
||||
Dump an LMDB database which does not use subdirectories.
|
||||
.TP
|
||||
.BR \-p
|
||||
If characters in either the key or data items are printing characters (as
|
||||
defined by isprint(3)), output them directly. This option permits users to
|
||||
use standard text editors and tools to modify the contents of databases.
|
||||
|
||||
Note: different systems may have different notions about what characters
|
||||
are considered printing characters, and databases dumped in this manner may
|
||||
be less portable to external systems.
|
||||
.TP
|
||||
.BR \-a
|
||||
Dump all of the subdatabases in the environment.
|
||||
.TP
|
||||
.BR \-s \ subdb
|
||||
Dump a specific subdatabase. If no database is specified, only the main database is dumped.
|
||||
.SH DIAGNOSTICS
|
||||
Exit status is zero if no errors occur.
|
||||
Errors result in a non-zero exit status and
|
||||
a diagnostic message being written to standard error.
|
||||
|
||||
Dumping databases that use user-defined comparison functions will output
|
||||
records with the ordering imposed by those comparison functions. If
|
||||
.B mdb_load
|
||||
is invoked without including the
|
||||
.B -a
|
||||
option when reloading those records, the new databases will likely be
|
||||
damaged beyond repair, permitting neither record storage nor retrieval.
|
||||
|
||||
.SH "SEE ALSO"
|
||||
.BR mdb_load (1)
|
||||
.SH AUTHOR
|
||||
Howard Chu of Symas Corporation <http://www.symas.com>
|
||||
@@ -0,0 +1,319 @@
|
||||
/* mdb_dump.c - memory-mapped database dump tool */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define Z "I"
|
||||
#else
|
||||
#define Z "z"
|
||||
#endif
|
||||
|
||||
#define PRINT 1
|
||||
static int mode;
|
||||
|
||||
typedef struct flagbit {
|
||||
int bit;
|
||||
char *name;
|
||||
} flagbit;
|
||||
|
||||
flagbit dbflags[] = {
|
||||
{ MDB_REVERSEKEY, "reversekey" },
|
||||
{ MDB_DUPSORT, "dupsort" },
|
||||
{ MDB_INTEGERKEY, "integerkey" },
|
||||
{ MDB_DUPFIXED, "dupfixed" },
|
||||
{ MDB_INTEGERDUP, "integerdup" },
|
||||
{ MDB_REVERSEDUP, "reversedup" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
static volatile sig_atomic_t gotsig;
|
||||
|
||||
static void dumpsig( int sig )
|
||||
{
|
||||
gotsig=1;
|
||||
}
|
||||
|
||||
static const char hexc[] = "0123456789abcdef";
|
||||
|
||||
static void hex(unsigned char c)
|
||||
{
|
||||
putchar(hexc[c >> 4]);
|
||||
putchar(hexc[c & 0xf]);
|
||||
}
|
||||
|
||||
static void text(MDB_val *v)
|
||||
{
|
||||
unsigned char *c, *end;
|
||||
|
||||
putchar(' ');
|
||||
c = v->mv_data;
|
||||
end = c + v->mv_size;
|
||||
while (c < end) {
|
||||
if (isprint(*c)) {
|
||||
if (*c == '\\')
|
||||
putchar('\\');
|
||||
putchar(*c);
|
||||
} else {
|
||||
putchar('\\');
|
||||
hex(*c);
|
||||
}
|
||||
c++;
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
static void byte(MDB_val *v)
|
||||
{
|
||||
unsigned char *c, *end;
|
||||
|
||||
putchar(' ');
|
||||
c = v->mv_data;
|
||||
end = c + v->mv_size;
|
||||
while (c < end) {
|
||||
hex(*c++);
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
/* Dump in BDB-compatible format */
|
||||
static int dumpit(MDB_txn *txn, MDB_dbi dbi, char *name)
|
||||
{
|
||||
MDB_cursor *mc;
|
||||
MDB_stat ms;
|
||||
MDB_val key, data;
|
||||
MDB_envinfo info;
|
||||
unsigned int flags;
|
||||
int rc, i;
|
||||
|
||||
rc = mdb_dbi_flags(txn, dbi, &flags);
|
||||
if (rc) return rc;
|
||||
|
||||
rc = mdb_stat(txn, dbi, &ms);
|
||||
if (rc) return rc;
|
||||
|
||||
rc = mdb_env_info(mdb_txn_env(txn), &info);
|
||||
if (rc) return rc;
|
||||
|
||||
printf("VERSION=3\n");
|
||||
printf("format=%s\n", mode & PRINT ? "print" : "bytevalue");
|
||||
if (name)
|
||||
printf("database=%s\n", name);
|
||||
printf("type=btree\n");
|
||||
printf("mapsize=%" Z "u\n", info.me_mapsize);
|
||||
if (info.me_mapaddr)
|
||||
printf("mapaddr=%p\n", info.me_mapaddr);
|
||||
printf("maxreaders=%u\n", info.me_maxreaders);
|
||||
|
||||
if (flags & MDB_DUPSORT)
|
||||
printf("duplicates=1\n");
|
||||
|
||||
for (i=0; dbflags[i].bit; i++)
|
||||
if (flags & dbflags[i].bit)
|
||||
printf("%s=1\n", dbflags[i].name);
|
||||
|
||||
printf("db_pagesize=%d\n", ms.ms_psize);
|
||||
printf("HEADER=END\n");
|
||||
|
||||
rc = mdb_cursor_open(txn, dbi, &mc);
|
||||
if (rc) return rc;
|
||||
|
||||
while ((rc = mdb_cursor_get(mc, &key, &data, MDB_NEXT)) == MDB_SUCCESS) {
|
||||
if (gotsig) {
|
||||
rc = EINTR;
|
||||
break;
|
||||
}
|
||||
if (mode & PRINT) {
|
||||
text(&key);
|
||||
text(&data);
|
||||
} else {
|
||||
byte(&key);
|
||||
byte(&data);
|
||||
}
|
||||
}
|
||||
printf("DATA=END\n");
|
||||
if (rc == MDB_NOTFOUND)
|
||||
rc = MDB_SUCCESS;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void usage(char *prog)
|
||||
{
|
||||
fprintf(stderr, "usage: %s [-V] [-f output] [-l] [-n] [-p] [-a|-s subdb] dbpath\n", prog);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int i, rc;
|
||||
MDB_env *env;
|
||||
MDB_txn *txn;
|
||||
MDB_dbi dbi;
|
||||
char *prog = argv[0];
|
||||
char *envname;
|
||||
char *subname = NULL;
|
||||
int alldbs = 0, envflags = 0, list = 0;
|
||||
|
||||
if (argc < 2) {
|
||||
usage(prog);
|
||||
}
|
||||
|
||||
/* -a: dump all subDBs
|
||||
* -s: dump only the named subDB
|
||||
* -n: use NOSUBDIR flag on env_open
|
||||
* -p: use printable characters
|
||||
* -f: write to file instead of stdout
|
||||
* -V: print version and exit
|
||||
* (default) dump only the main DB
|
||||
*/
|
||||
while ((i = getopt(argc, argv, "af:lnps:V")) != EOF) {
|
||||
switch(i) {
|
||||
case 'V':
|
||||
printf("%s\n", MDB_VERSION_STRING);
|
||||
exit(0);
|
||||
break;
|
||||
case 'l':
|
||||
list = 1;
|
||||
/*FALLTHROUGH*/
|
||||
case 'a':
|
||||
if (subname)
|
||||
usage(prog);
|
||||
alldbs++;
|
||||
break;
|
||||
case 'f':
|
||||
if (freopen(optarg, "w", stdout) == NULL) {
|
||||
fprintf(stderr, "%s: %s: reopen: %s\n",
|
||||
prog, optarg, strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
case 'n':
|
||||
envflags |= MDB_NOSUBDIR;
|
||||
break;
|
||||
case 'p':
|
||||
mode |= PRINT;
|
||||
break;
|
||||
case 's':
|
||||
if (alldbs)
|
||||
usage(prog);
|
||||
subname = optarg;
|
||||
break;
|
||||
default:
|
||||
usage(prog);
|
||||
}
|
||||
}
|
||||
|
||||
if (optind != argc - 1)
|
||||
usage(prog);
|
||||
|
||||
#ifdef SIGPIPE
|
||||
signal(SIGPIPE, dumpsig);
|
||||
#endif
|
||||
#ifdef SIGHUP
|
||||
signal(SIGHUP, dumpsig);
|
||||
#endif
|
||||
signal(SIGINT, dumpsig);
|
||||
signal(SIGTERM, dumpsig);
|
||||
|
||||
envname = argv[optind];
|
||||
rc = mdb_env_create(&env);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (alldbs || subname) {
|
||||
mdb_env_set_maxdbs(env, 2);
|
||||
}
|
||||
|
||||
rc = mdb_env_open(env, envname, envflags | MDB_RDONLY, 0664);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
rc = mdb_open(txn, subname, 0, &dbi);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
|
||||
if (alldbs) {
|
||||
MDB_cursor *cursor;
|
||||
MDB_val key;
|
||||
int count = 0;
|
||||
|
||||
rc = mdb_cursor_open(txn, dbi, &cursor);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
while ((rc = mdb_cursor_get(cursor, &key, NULL, MDB_NEXT_NODUP)) == 0) {
|
||||
char *str;
|
||||
MDB_dbi db2;
|
||||
if (memchr(key.mv_data, '\0', key.mv_size))
|
||||
continue;
|
||||
count++;
|
||||
str = malloc(key.mv_size+1);
|
||||
memcpy(str, key.mv_data, key.mv_size);
|
||||
str[key.mv_size] = '\0';
|
||||
rc = mdb_open(txn, str, 0, &db2);
|
||||
if (rc == MDB_SUCCESS) {
|
||||
if (list) {
|
||||
printf("%s\n", str);
|
||||
list++;
|
||||
} else {
|
||||
rc = dumpit(txn, db2, str);
|
||||
if (rc)
|
||||
break;
|
||||
}
|
||||
mdb_close(env, db2);
|
||||
}
|
||||
free(str);
|
||||
if (rc) continue;
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
if (!count) {
|
||||
fprintf(stderr, "%s: %s does not contain multiple databases\n", prog, envname);
|
||||
rc = MDB_NOTFOUND;
|
||||
} else if (rc == MDB_NOTFOUND) {
|
||||
rc = MDB_SUCCESS;
|
||||
}
|
||||
} else {
|
||||
rc = dumpit(txn, dbi, subname);
|
||||
}
|
||||
if (rc && rc != MDB_NOTFOUND)
|
||||
fprintf(stderr, "%s: %s: %s\n", prog, envname, mdb_strerror(rc));
|
||||
|
||||
mdb_close(env, dbi);
|
||||
txn_abort:
|
||||
mdb_txn_abort(txn);
|
||||
env_close:
|
||||
mdb_env_close(env);
|
||||
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
.TH MDB_LOAD 1 "2015/09/30" "LMDB 0.9.17"
|
||||
.\" Copyright 2014-2021 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.SH NAME
|
||||
mdb_load \- LMDB environment import tool
|
||||
.SH SYNOPSIS
|
||||
.B mdb_load
|
||||
[\c
|
||||
.BR \-V ]
|
||||
[\c
|
||||
.BR \-a ]
|
||||
[\c
|
||||
.BI \-f \ file\fR]
|
||||
[\c
|
||||
.BR \-n ]
|
||||
[\c
|
||||
.BI \-s \ subdb\fR]
|
||||
[\c
|
||||
.BR \-N ]
|
||||
[\c
|
||||
.BR \-Q ]
|
||||
[\c
|
||||
.BR \-T ]
|
||||
.BR \ envpath
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mdb_load
|
||||
utility reads from the standard input and loads it into the
|
||||
LMDB environment
|
||||
.BR envpath .
|
||||
|
||||
The input to
|
||||
.B mdb_load
|
||||
must be in the output format specified by the
|
||||
.BR mdb_dump (1)
|
||||
utility or as specified by the
|
||||
.B -T
|
||||
option below.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-V
|
||||
Write the library version number to the standard output, and exit.
|
||||
.TP
|
||||
.BR \-a
|
||||
Append all records in the order they appear in the input. The input is assumed to already be
|
||||
in correctly sorted order and no sorting or checking for redundant values will be performed.
|
||||
This option must be used to reload data that was produced by running
|
||||
.B mdb_dump
|
||||
on a database that uses custom compare functions.
|
||||
.TP
|
||||
.BR \-f \ file
|
||||
Read from the specified file instead of from the standard input.
|
||||
.TP
|
||||
.BR \-n
|
||||
Load an LMDB database which does not use subdirectories.
|
||||
.TP
|
||||
.BR \-s \ subdb
|
||||
Load a specific subdatabase. If no database is specified, data is loaded into the main database.
|
||||
.TP
|
||||
.BR \-N
|
||||
Don't overwrite existing records when loading into an already existing database; just skip them.
|
||||
.TP
|
||||
.BR \-Q
|
||||
Quick mode, uses MDB_NOSYNC for faster loading. Forces sync with mdb_env_sync() before exiting.
|
||||
.TP
|
||||
.BR \-T
|
||||
Load data from simple text files. The input must be paired lines of text, where the first
|
||||
line of the pair is the key item, and the second line of the pair is its corresponding
|
||||
data item.
|
||||
|
||||
A simple escape mechanism, where newline and backslash (\\) characters are special, is
|
||||
applied to the text input. Newline characters are interpreted as record separators.
|
||||
Backslash characters in the text will be interpreted in one of two ways: If the backslash
|
||||
character precedes another backslash character, the pair will be interpreted as a literal
|
||||
backslash. If the backslash character precedes any other character, the two characters
|
||||
following the backslash will be interpreted as a hexadecimal specification of a single
|
||||
character; for example, \\0a is a newline character in the ASCII character set.
|
||||
|
||||
For this reason, any backslash or newline characters that naturally occur in the text
|
||||
input must be escaped to avoid misinterpretation by
|
||||
.BR mdb_load .
|
||||
|
||||
.SH DIAGNOSTICS
|
||||
Exit status is zero if no errors occur.
|
||||
Errors result in a non-zero exit status and
|
||||
a diagnostic message being written to standard error.
|
||||
|
||||
.SH "SEE ALSO"
|
||||
.BR mdb_dump (1)
|
||||
.SH AUTHOR
|
||||
Howard Chu of Symas Corporation <http://www.symas.com>
|
||||
@@ -0,0 +1,515 @@
|
||||
/* mdb_load.c - memory-mapped database load tool */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <unistd.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define PRINT 1
|
||||
#define NOHDR 2
|
||||
static int mode;
|
||||
|
||||
static char *subname = NULL;
|
||||
|
||||
static size_t lineno;
|
||||
static int version;
|
||||
|
||||
static int flags;
|
||||
|
||||
static char *prog;
|
||||
|
||||
static int Eof;
|
||||
|
||||
static MDB_envinfo info;
|
||||
|
||||
static MDB_val kbuf, dbuf;
|
||||
static MDB_val k0buf;
|
||||
|
||||
#ifdef _WIN32
|
||||
#define Z "I"
|
||||
#else
|
||||
#define Z "z"
|
||||
#endif
|
||||
|
||||
#define STRLENOF(s) (sizeof(s)-1)
|
||||
|
||||
typedef struct flagbit {
|
||||
int bit;
|
||||
char *name;
|
||||
int len;
|
||||
} flagbit;
|
||||
|
||||
#define S(s) s, STRLENOF(s)
|
||||
|
||||
flagbit dbflags[] = {
|
||||
{ MDB_REVERSEKEY, S("reversekey") },
|
||||
{ MDB_DUPSORT, S("dupsort") },
|
||||
{ MDB_INTEGERKEY, S("integerkey") },
|
||||
{ MDB_DUPFIXED, S("dupfixed") },
|
||||
{ MDB_INTEGERDUP, S("integerdup") },
|
||||
{ MDB_REVERSEDUP, S("reversedup") },
|
||||
{ 0, NULL, 0 }
|
||||
};
|
||||
|
||||
static void readhdr(void)
|
||||
{
|
||||
char *ptr;
|
||||
|
||||
flags = 0;
|
||||
while (fgets(dbuf.mv_data, dbuf.mv_size, stdin) != NULL) {
|
||||
lineno++;
|
||||
if (!strncmp(dbuf.mv_data, "VERSION=", STRLENOF("VERSION="))) {
|
||||
version=atoi((char *)dbuf.mv_data+STRLENOF("VERSION="));
|
||||
if (version > 3) {
|
||||
fprintf(stderr, "%s: line %" Z "d: unsupported VERSION %d\n",
|
||||
prog, lineno, version);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "HEADER=END", STRLENOF("HEADER=END"))) {
|
||||
break;
|
||||
} else if (!strncmp(dbuf.mv_data, "format=", STRLENOF("format="))) {
|
||||
if (!strncmp((char *)dbuf.mv_data+STRLENOF("FORMAT="), "print", STRLENOF("print")))
|
||||
mode |= PRINT;
|
||||
else if (strncmp((char *)dbuf.mv_data+STRLENOF("FORMAT="), "bytevalue", STRLENOF("bytevalue"))) {
|
||||
fprintf(stderr, "%s: line %" Z "d: unsupported FORMAT %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("FORMAT="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "database=", STRLENOF("database="))) {
|
||||
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
|
||||
if (ptr) *ptr = '\0';
|
||||
if (subname) free(subname);
|
||||
subname = strdup((char *)dbuf.mv_data+STRLENOF("database="));
|
||||
} else if (!strncmp(dbuf.mv_data, "type=", STRLENOF("type="))) {
|
||||
if (strncmp((char *)dbuf.mv_data+STRLENOF("type="), "btree", STRLENOF("btree"))) {
|
||||
fprintf(stderr, "%s: line %" Z "d: unsupported type %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("type="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "mapaddr=", STRLENOF("mapaddr="))) {
|
||||
int i;
|
||||
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
|
||||
if (ptr) *ptr = '\0';
|
||||
i = sscanf((char *)dbuf.mv_data+STRLENOF("mapaddr="), "%p", &info.me_mapaddr);
|
||||
if (i != 1) {
|
||||
fprintf(stderr, "%s: line %" Z "d: invalid mapaddr %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("mapaddr="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "mapsize=", STRLENOF("mapsize="))) {
|
||||
int i;
|
||||
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
|
||||
if (ptr) *ptr = '\0';
|
||||
i = sscanf((char *)dbuf.mv_data+STRLENOF("mapsize="), "%" Z "u", &info.me_mapsize);
|
||||
if (i != 1) {
|
||||
fprintf(stderr, "%s: line %" Z "d: invalid mapsize %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("mapsize="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "maxreaders=", STRLENOF("maxreaders="))) {
|
||||
int i;
|
||||
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
|
||||
if (ptr) *ptr = '\0';
|
||||
i = sscanf((char *)dbuf.mv_data+STRLENOF("maxreaders="), "%u", &info.me_maxreaders);
|
||||
if (i != 1) {
|
||||
fprintf(stderr, "%s: line %" Z "d: invalid maxreaders %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("maxreaders="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else {
|
||||
int i;
|
||||
for (i=0; dbflags[i].bit; i++) {
|
||||
if (!strncmp(dbuf.mv_data, dbflags[i].name, dbflags[i].len) &&
|
||||
((char *)dbuf.mv_data)[dbflags[i].len] == '=') {
|
||||
flags |= dbflags[i].bit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dbflags[i].bit) {
|
||||
ptr = memchr(dbuf.mv_data, '=', dbuf.mv_size);
|
||||
if (!ptr) {
|
||||
fprintf(stderr, "%s: line %" Z "d: unexpected format\n",
|
||||
prog, lineno);
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
*ptr = '\0';
|
||||
fprintf(stderr, "%s: line %" Z "d: unrecognized keyword ignored: %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void badend(void)
|
||||
{
|
||||
fprintf(stderr, "%s: line %" Z "d: unexpected end of input\n",
|
||||
prog, lineno);
|
||||
}
|
||||
|
||||
static int unhex(unsigned char *c2)
|
||||
{
|
||||
int x, c;
|
||||
x = *c2++ & 0x4f;
|
||||
if (x & 0x40)
|
||||
x -= 55;
|
||||
c = x << 4;
|
||||
x = *c2 & 0x4f;
|
||||
if (x & 0x40)
|
||||
x -= 55;
|
||||
c |= x;
|
||||
return c;
|
||||
}
|
||||
|
||||
static int readline(MDB_val *out, MDB_val *buf)
|
||||
{
|
||||
unsigned char *c1, *c2, *end;
|
||||
size_t len, l2;
|
||||
int c;
|
||||
|
||||
if (!(mode & NOHDR)) {
|
||||
c = fgetc(stdin);
|
||||
if (c == EOF) {
|
||||
Eof = 1;
|
||||
return EOF;
|
||||
}
|
||||
if (c != ' ') {
|
||||
lineno++;
|
||||
if (fgets(buf->mv_data, buf->mv_size, stdin) == NULL) {
|
||||
badend:
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
if (c == 'D' && !strncmp(buf->mv_data, "ATA=END", STRLENOF("ATA=END")))
|
||||
return EOF;
|
||||
goto badend;
|
||||
}
|
||||
}
|
||||
if (fgets(buf->mv_data, buf->mv_size, stdin) == NULL) {
|
||||
Eof = 1;
|
||||
return EOF;
|
||||
}
|
||||
lineno++;
|
||||
|
||||
c1 = buf->mv_data;
|
||||
len = strlen((char *)c1);
|
||||
if (!len) {
|
||||
/* This can only happen with an intentionally invalid input
|
||||
* with a NUL byte after the leading SPACE
|
||||
*/
|
||||
goto badend;
|
||||
}
|
||||
l2 = len;
|
||||
|
||||
/* Is buffer too short? */
|
||||
while (c1[len-1] != '\n') {
|
||||
buf->mv_data = realloc(buf->mv_data, buf->mv_size*2);
|
||||
if (!buf->mv_data) {
|
||||
Eof = 1;
|
||||
fprintf(stderr, "%s: line %" Z "d: out of memory, line too long\n",
|
||||
prog, lineno);
|
||||
return EOF;
|
||||
}
|
||||
c1 = buf->mv_data;
|
||||
c1 += l2;
|
||||
if (fgets((char *)c1, buf->mv_size+1, stdin) == NULL) {
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
buf->mv_size *= 2;
|
||||
len = strlen((char *)c1);
|
||||
l2 += len;
|
||||
}
|
||||
c1 = c2 = buf->mv_data;
|
||||
len = l2;
|
||||
c1[--len] = '\0';
|
||||
end = c1 + len;
|
||||
|
||||
if (mode & PRINT) {
|
||||
while (c2 < end) {
|
||||
if (*c2 == '\\') {
|
||||
if (c2[1] == '\\') {
|
||||
*c1++ = *c2;
|
||||
} else {
|
||||
if (c2+3 > end || !isxdigit(c2[1]) || !isxdigit(c2[2])) {
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
*c1++ = unhex(++c2);
|
||||
}
|
||||
c2 += 2;
|
||||
} else {
|
||||
/* copies are redundant when no escapes were used */
|
||||
*c1++ = *c2++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* odd length not allowed */
|
||||
if (len & 1) {
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
while (c2 < end) {
|
||||
if (!isxdigit(*c2) || !isxdigit(c2[1])) {
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
*c1++ = unhex(c2);
|
||||
c2 += 2;
|
||||
}
|
||||
}
|
||||
c2 = out->mv_data = buf->mv_data;
|
||||
out->mv_size = c1 - c2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void usage(void)
|
||||
{
|
||||
fprintf(stderr, "usage: %s [-V] [-a] [-f input] [-n] [-s name] [-N] [-T] dbpath\n", prog);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static int greater(const MDB_val *a, const MDB_val *b)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int i, rc;
|
||||
MDB_env *env;
|
||||
MDB_txn *txn;
|
||||
MDB_cursor *mc;
|
||||
MDB_dbi dbi;
|
||||
char *envname;
|
||||
int envflags = MDB_NOSYNC, putflags = 0;
|
||||
int dohdr = 0, append = 0;
|
||||
MDB_val prevk;
|
||||
|
||||
prog = argv[0];
|
||||
|
||||
if (argc < 2) {
|
||||
usage();
|
||||
}
|
||||
|
||||
/* -a: append records in input order
|
||||
* -f: load file instead of stdin
|
||||
* -n: use NOSUBDIR flag on env_open
|
||||
* -s: load into named subDB
|
||||
* -N: use NOOVERWRITE on puts
|
||||
* -Q: quick mode using NOSYNC
|
||||
* -T: read plaintext
|
||||
* -V: print version and exit
|
||||
*/
|
||||
while ((i = getopt(argc, argv, "af:ns:NQTV")) != EOF) {
|
||||
switch(i) {
|
||||
case 'V':
|
||||
printf("%s\n", MDB_VERSION_STRING);
|
||||
exit(0);
|
||||
break;
|
||||
case 'a':
|
||||
append = 1;
|
||||
break;
|
||||
case 'f':
|
||||
if (freopen(optarg, "r", stdin) == NULL) {
|
||||
fprintf(stderr, "%s: %s: reopen: %s\n",
|
||||
prog, optarg, strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
case 'n':
|
||||
envflags |= MDB_NOSUBDIR;
|
||||
break;
|
||||
case 's':
|
||||
subname = strdup(optarg);
|
||||
break;
|
||||
case 'N':
|
||||
putflags = MDB_NOOVERWRITE|MDB_NODUPDATA;
|
||||
break;
|
||||
case 'Q':
|
||||
envflags |= MDB_NOSYNC;
|
||||
break;
|
||||
case 'T':
|
||||
mode |= NOHDR | PRINT;
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
}
|
||||
}
|
||||
|
||||
if (optind != argc - 1)
|
||||
usage();
|
||||
|
||||
dbuf.mv_size = 4096;
|
||||
dbuf.mv_data = malloc(dbuf.mv_size);
|
||||
|
||||
if (!(mode & NOHDR))
|
||||
readhdr();
|
||||
|
||||
envname = argv[optind];
|
||||
rc = mdb_env_create(&env);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
mdb_env_set_maxdbs(env, 2);
|
||||
|
||||
if (info.me_maxreaders)
|
||||
mdb_env_set_maxreaders(env, info.me_maxreaders);
|
||||
|
||||
if (info.me_mapsize)
|
||||
mdb_env_set_mapsize(env, info.me_mapsize);
|
||||
|
||||
if (info.me_mapaddr)
|
||||
envflags |= MDB_FIXEDMAP;
|
||||
|
||||
rc = mdb_env_open(env, envname, envflags, 0664);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
kbuf.mv_size = mdb_env_get_maxkeysize(env) * 2 + 2;
|
||||
kbuf.mv_data = malloc(kbuf.mv_size * 2);
|
||||
k0buf.mv_size = kbuf.mv_size;
|
||||
k0buf.mv_data = (char *)kbuf.mv_data + kbuf.mv_size;
|
||||
prevk.mv_data = k0buf.mv_data;
|
||||
|
||||
while(!Eof) {
|
||||
MDB_val key, data;
|
||||
int batch = 0;
|
||||
flags = 0;
|
||||
int appflag;
|
||||
|
||||
if (!dohdr) {
|
||||
dohdr = 1;
|
||||
} else if (!(mode & NOHDR))
|
||||
readhdr();
|
||||
|
||||
rc = mdb_txn_begin(env, NULL, 0, &txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
rc = mdb_dbi_open(txn, subname, flags|MDB_CREATE, &dbi);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_dbi_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
prevk.mv_size = 0;
|
||||
if (append) {
|
||||
mdb_set_compare(txn, dbi, greater);
|
||||
if (flags & MDB_DUPSORT)
|
||||
mdb_set_dupsort(txn, dbi, greater);
|
||||
}
|
||||
|
||||
rc = mdb_cursor_open(txn, dbi, &mc);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
|
||||
while(1) {
|
||||
rc = readline(&key, &kbuf);
|
||||
if (rc) /* rc == EOF */
|
||||
break;
|
||||
|
||||
rc = readline(&data, &dbuf);
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: line %" Z "d: failed to read key value\n", prog, lineno);
|
||||
goto txn_abort;
|
||||
}
|
||||
|
||||
if (append) {
|
||||
appflag = MDB_APPEND;
|
||||
if (flags & MDB_DUPSORT) {
|
||||
if (prevk.mv_size == key.mv_size && !memcmp(prevk.mv_data, key.mv_data, key.mv_size))
|
||||
appflag = MDB_CURRENT|MDB_APPENDDUP;
|
||||
else {
|
||||
memcpy(prevk.mv_data, key.mv_data, key.mv_size);
|
||||
prevk.mv_size = key.mv_size;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
appflag = 0;
|
||||
}
|
||||
rc = mdb_cursor_put(mc, &key, &data, putflags|appflag);
|
||||
if (rc == MDB_KEYEXIST && putflags)
|
||||
continue;
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: line %" Z "d: mdb_cursor_put failed, error %d %s\n", prog, lineno, rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
batch++;
|
||||
if (batch == 100) {
|
||||
rc = mdb_txn_commit(txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: line %" Z "d: txn_commit: %s\n",
|
||||
prog, lineno, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
rc = mdb_txn_begin(env, NULL, 0, &txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
rc = mdb_cursor_open(txn, dbi, &mc);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
if (append) {
|
||||
MDB_val k, d;
|
||||
mdb_cursor_get(mc, &k, &d, MDB_LAST);
|
||||
memcpy(prevk.mv_data, k.mv_data, k.mv_size);
|
||||
prevk.mv_size = k.mv_size;
|
||||
}
|
||||
batch = 0;
|
||||
}
|
||||
}
|
||||
rc = mdb_txn_commit(txn);
|
||||
txn = NULL;
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: line %" Z "d: txn_commit: %s\n",
|
||||
prog, lineno, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
if (envflags & MDB_NOSYNC) {
|
||||
rc = mdb_env_sync(env, 1);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_sync failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
}
|
||||
mdb_dbi_close(env, dbi);
|
||||
}
|
||||
|
||||
txn_abort:
|
||||
mdb_txn_abort(txn);
|
||||
env_close:
|
||||
mdb_env_close(env);
|
||||
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
.TH MDB_STAT 1 "2015/09/30" "LMDB 0.9.17"
|
||||
.\" Copyright 2012-2021 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.SH NAME
|
||||
mdb_stat \- LMDB environment status tool
|
||||
.SH SYNOPSIS
|
||||
.B mdb_stat
|
||||
[\c
|
||||
.BR \-V ]
|
||||
[\c
|
||||
.BR \-e ]
|
||||
[\c
|
||||
.BR \-f [ f [ f ]]]
|
||||
[\c
|
||||
.BR \-n ]
|
||||
[\c
|
||||
.BR \-r [ r ]]
|
||||
[\c
|
||||
.BR \-a \ |
|
||||
.BI \-s \ subdb\fR]
|
||||
.BR \ envpath
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mdb_stat
|
||||
utility displays the status of an LMDB environment.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-V
|
||||
Write the library version number to the standard output, and exit.
|
||||
.TP
|
||||
.BR \-e
|
||||
Display information about the database environment.
|
||||
.TP
|
||||
.BR \-f
|
||||
Display information about the environment freelist.
|
||||
If \fB\-ff\fP is given, summarize each freelist entry.
|
||||
If \fB\-fff\fP is given, display the full list of page IDs in the freelist.
|
||||
.TP
|
||||
.BR \-n
|
||||
Display the status of an LMDB database which does not use subdirectories.
|
||||
.TP
|
||||
.BR \-r
|
||||
Display information about the environment reader table.
|
||||
Shows the process ID, thread ID, and transaction ID for each active
|
||||
reader slot. The process ID and transaction ID are in decimal, the
|
||||
thread ID is in hexadecimal. The transaction ID is displayed as "-"
|
||||
if the reader does not currently have a read transaction open.
|
||||
If \fB\-rr\fP is given, check for stale entries in the reader
|
||||
table and clear them. The reader table will be printed again
|
||||
after the check is performed.
|
||||
.TP
|
||||
.BR \-a
|
||||
Display the status of all of the subdatabases in the environment.
|
||||
.TP
|
||||
.BR \-s \ subdb
|
||||
Display the status of a specific subdatabase.
|
||||
.SH DIAGNOSTICS
|
||||
Exit status is zero if no errors occur.
|
||||
Errors result in a non-zero exit status and
|
||||
a diagnostic message being written to standard error.
|
||||
.SH "SEE ALSO"
|
||||
.BR mdb_copy (1)
|
||||
.SH AUTHOR
|
||||
Howard Chu of Symas Corporation <http://www.symas.com>
|
||||
@@ -0,0 +1,263 @@
|
||||
/* mdb_stat.c - memory-mapped database status tool */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define Z "I"
|
||||
#else
|
||||
#define Z "z"
|
||||
#endif
|
||||
|
||||
static void prstat(MDB_stat *ms)
|
||||
{
|
||||
#if 0
|
||||
printf(" Page size: %u\n", ms->ms_psize);
|
||||
#endif
|
||||
printf(" Tree depth: %u\n", ms->ms_depth);
|
||||
printf(" Branch pages: %"Z"u\n", ms->ms_branch_pages);
|
||||
printf(" Leaf pages: %"Z"u\n", ms->ms_leaf_pages);
|
||||
printf(" Overflow pages: %"Z"u\n", ms->ms_overflow_pages);
|
||||
printf(" Entries: %"Z"u\n", ms->ms_entries);
|
||||
}
|
||||
|
||||
static void usage(char *prog)
|
||||
{
|
||||
fprintf(stderr, "usage: %s [-V] [-n] [-e] [-r[r]] [-f[f[f]]] [-a|-s subdb] dbpath\n", prog);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int i, rc;
|
||||
MDB_env *env;
|
||||
MDB_txn *txn;
|
||||
MDB_dbi dbi;
|
||||
MDB_stat mst;
|
||||
MDB_envinfo mei;
|
||||
char *prog = argv[0];
|
||||
char *envname;
|
||||
char *subname = NULL;
|
||||
int alldbs = 0, envinfo = 0, envflags = 0, freinfo = 0, rdrinfo = 0;
|
||||
|
||||
if (argc < 2) {
|
||||
usage(prog);
|
||||
}
|
||||
|
||||
/* -a: print stat of main DB and all subDBs
|
||||
* -s: print stat of only the named subDB
|
||||
* -e: print env info
|
||||
* -f: print freelist info
|
||||
* -r: print reader info
|
||||
* -n: use NOSUBDIR flag on env_open
|
||||
* -V: print version and exit
|
||||
* (default) print stat of only the main DB
|
||||
*/
|
||||
while ((i = getopt(argc, argv, "Vaefnrs:")) != EOF) {
|
||||
switch(i) {
|
||||
case 'V':
|
||||
printf("%s\n", MDB_VERSION_STRING);
|
||||
exit(0);
|
||||
break;
|
||||
case 'a':
|
||||
if (subname)
|
||||
usage(prog);
|
||||
alldbs++;
|
||||
break;
|
||||
case 'e':
|
||||
envinfo++;
|
||||
break;
|
||||
case 'f':
|
||||
freinfo++;
|
||||
break;
|
||||
case 'n':
|
||||
envflags |= MDB_NOSUBDIR;
|
||||
break;
|
||||
case 'r':
|
||||
rdrinfo++;
|
||||
break;
|
||||
case 's':
|
||||
if (alldbs)
|
||||
usage(prog);
|
||||
subname = optarg;
|
||||
break;
|
||||
default:
|
||||
usage(prog);
|
||||
}
|
||||
}
|
||||
|
||||
if (optind != argc - 1)
|
||||
usage(prog);
|
||||
|
||||
envname = argv[optind];
|
||||
rc = mdb_env_create(&env);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (alldbs || subname) {
|
||||
mdb_env_set_maxdbs(env, 4);
|
||||
}
|
||||
|
||||
rc = mdb_env_open(env, envname, envflags | MDB_RDONLY, 0664);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
if (envinfo) {
|
||||
(void)mdb_env_stat(env, &mst);
|
||||
(void)mdb_env_info(env, &mei);
|
||||
printf("Environment Info\n");
|
||||
printf(" Map address: %p\n", mei.me_mapaddr);
|
||||
printf(" Map size: %"Z"u\n", mei.me_mapsize);
|
||||
printf(" Page size: %u\n", mst.ms_psize);
|
||||
printf(" Max pages: %"Z"u\n", mei.me_mapsize / mst.ms_psize);
|
||||
printf(" Number of pages used: %"Z"u\n", mei.me_last_pgno+1);
|
||||
printf(" Last transaction ID: %"Z"u\n", mei.me_last_txnid);
|
||||
printf(" Max readers: %u\n", mei.me_maxreaders);
|
||||
printf(" Number of readers used: %u\n", mei.me_numreaders);
|
||||
}
|
||||
|
||||
if (rdrinfo) {
|
||||
printf("Reader Table Status\n");
|
||||
rc = mdb_reader_list(env, (MDB_msg_func *)fputs, stdout);
|
||||
if (rdrinfo > 1) {
|
||||
int dead;
|
||||
mdb_reader_check(env, &dead);
|
||||
printf(" %d stale readers cleared.\n", dead);
|
||||
rc = mdb_reader_list(env, (MDB_msg_func *)fputs, stdout);
|
||||
}
|
||||
if (!(subname || alldbs || freinfo))
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
if (freinfo) {
|
||||
MDB_cursor *cursor;
|
||||
MDB_val key, data;
|
||||
size_t pages = 0, *iptr;
|
||||
|
||||
printf("Freelist Status\n");
|
||||
dbi = 0;
|
||||
rc = mdb_cursor_open(txn, dbi, &cursor);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
rc = mdb_stat(txn, dbi, &mst);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
prstat(&mst);
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
iptr = data.mv_data;
|
||||
pages += *iptr;
|
||||
if (freinfo > 1) {
|
||||
char *bad = "";
|
||||
size_t pg, prev;
|
||||
ssize_t i, j, span = 0;
|
||||
j = *iptr++;
|
||||
for (i = j, prev = 1; --i >= 0; ) {
|
||||
pg = iptr[i];
|
||||
if (pg <= prev)
|
||||
bad = " [bad sequence]";
|
||||
prev = pg;
|
||||
pg += span;
|
||||
for (; i >= span && iptr[i-span] == pg; span++, pg++) ;
|
||||
}
|
||||
printf(" Transaction %"Z"u, %"Z"d pages, maxspan %"Z"d%s\n",
|
||||
*(size_t *)key.mv_data, j, span, bad);
|
||||
if (freinfo > 2) {
|
||||
for (--j; j >= 0; ) {
|
||||
pg = iptr[j];
|
||||
for (span=1; --j >= 0 && iptr[j] == pg+span; span++) ;
|
||||
printf(span>1 ? " %9"Z"u[%"Z"d]\n" : " %9"Z"u\n",
|
||||
pg, span);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
printf(" Free pages: %"Z"u\n", pages);
|
||||
}
|
||||
|
||||
rc = mdb_open(txn, subname, 0, &dbi);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
|
||||
rc = mdb_stat(txn, dbi, &mst);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
printf("Status of %s\n", subname ? subname : "Main DB");
|
||||
prstat(&mst);
|
||||
|
||||
if (alldbs) {
|
||||
MDB_cursor *cursor;
|
||||
MDB_val key;
|
||||
|
||||
rc = mdb_cursor_open(txn, dbi, &cursor);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
while ((rc = mdb_cursor_get(cursor, &key, NULL, MDB_NEXT_NODUP)) == 0) {
|
||||
char *str;
|
||||
MDB_dbi db2;
|
||||
if (memchr(key.mv_data, '\0', key.mv_size))
|
||||
continue;
|
||||
str = malloc(key.mv_size+1);
|
||||
memcpy(str, key.mv_data, key.mv_size);
|
||||
str[key.mv_size] = '\0';
|
||||
rc = mdb_open(txn, str, 0, &db2);
|
||||
if (rc == MDB_SUCCESS)
|
||||
printf("Status of %s\n", str);
|
||||
free(str);
|
||||
if (rc) continue;
|
||||
rc = mdb_stat(txn, db2, &mst);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
prstat(&mst);
|
||||
mdb_close(env, db2);
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
}
|
||||
|
||||
if (rc == MDB_NOTFOUND)
|
||||
rc = MDB_SUCCESS;
|
||||
|
||||
mdb_close(env, dbi);
|
||||
txn_abort:
|
||||
mdb_txn_abort(txn);
|
||||
env_close:
|
||||
mdb_env_close(env);
|
||||
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
/** @file midl.c
|
||||
* @brief ldap bdb back-end ID List functions */
|
||||
/* $OpenLDAP$ */
|
||||
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
|
||||
*
|
||||
* Copyright 2000-2021 The OpenLDAP Foundation.
|
||||
* Portions Copyright 2001-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include "midl.h"
|
||||
|
||||
/** @defgroup internal LMDB Internals
|
||||
* @{
|
||||
*/
|
||||
/** @defgroup idls ID List Management
|
||||
* @{
|
||||
*/
|
||||
#define CMP(x,y) ( (x) < (y) ? -1 : (x) > (y) )
|
||||
|
||||
unsigned mdb_midl_search( MDB_IDL ids, MDB_ID id )
|
||||
{
|
||||
/*
|
||||
* binary search of id in ids
|
||||
* if found, returns position of id
|
||||
* if not found, returns first position greater than id
|
||||
*/
|
||||
unsigned base = 0;
|
||||
unsigned cursor = 1;
|
||||
int val = 0;
|
||||
unsigned n = ids[0];
|
||||
|
||||
while( 0 < n ) {
|
||||
unsigned pivot = n >> 1;
|
||||
cursor = base + pivot + 1;
|
||||
val = CMP( ids[cursor], id );
|
||||
|
||||
if( val < 0 ) {
|
||||
n = pivot;
|
||||
|
||||
} else if ( val > 0 ) {
|
||||
base = cursor;
|
||||
n -= pivot + 1;
|
||||
|
||||
} else {
|
||||
return cursor;
|
||||
}
|
||||
}
|
||||
|
||||
if( val > 0 ) {
|
||||
++cursor;
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
#if 0 /* superseded by append/sort */
|
||||
int mdb_midl_insert( MDB_IDL ids, MDB_ID id )
|
||||
{
|
||||
unsigned x, i;
|
||||
|
||||
x = mdb_midl_search( ids, id );
|
||||
assert( x > 0 );
|
||||
|
||||
if( x < 1 ) {
|
||||
/* internal error */
|
||||
return -2;
|
||||
}
|
||||
|
||||
if ( x <= ids[0] && ids[x] == id ) {
|
||||
/* duplicate */
|
||||
assert(0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( ++ids[0] >= MDB_IDL_DB_MAX ) {
|
||||
/* no room */
|
||||
--ids[0];
|
||||
return -2;
|
||||
|
||||
} else {
|
||||
/* insert id */
|
||||
for (i=ids[0]; i>x; i--)
|
||||
ids[i] = ids[i-1];
|
||||
ids[x] = id;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
MDB_IDL mdb_midl_alloc(int num)
|
||||
{
|
||||
MDB_IDL ids = malloc((num+2) * sizeof(MDB_ID));
|
||||
if (ids) {
|
||||
*ids++ = num;
|
||||
*ids = 0;
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
void mdb_midl_free(MDB_IDL ids)
|
||||
{
|
||||
if (ids)
|
||||
free(ids-1);
|
||||
}
|
||||
|
||||
void mdb_midl_shrink( MDB_IDL *idp )
|
||||
{
|
||||
MDB_IDL ids = *idp;
|
||||
if (*(--ids) > MDB_IDL_UM_MAX &&
|
||||
(ids = realloc(ids, (MDB_IDL_UM_MAX+2) * sizeof(MDB_ID))))
|
||||
{
|
||||
*ids++ = MDB_IDL_UM_MAX;
|
||||
*idp = ids;
|
||||
}
|
||||
}
|
||||
|
||||
static int mdb_midl_grow( MDB_IDL *idp, int num )
|
||||
{
|
||||
MDB_IDL idn = *idp-1;
|
||||
/* grow it */
|
||||
idn = realloc(idn, (*idn + num + 2) * sizeof(MDB_ID));
|
||||
if (!idn)
|
||||
return ENOMEM;
|
||||
*idn++ += num;
|
||||
*idp = idn;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_midl_need( MDB_IDL *idp, unsigned num )
|
||||
{
|
||||
MDB_IDL ids = *idp;
|
||||
num += ids[0];
|
||||
if (num > ids[-1]) {
|
||||
num = (num + num/4 + (256 + 2)) & -256;
|
||||
if (!(ids = realloc(ids-1, num * sizeof(MDB_ID))))
|
||||
return ENOMEM;
|
||||
*ids++ = num - 2;
|
||||
*idp = ids;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_midl_append( MDB_IDL *idp, MDB_ID id )
|
||||
{
|
||||
MDB_IDL ids = *idp;
|
||||
/* Too big? */
|
||||
if (ids[0] >= ids[-1]) {
|
||||
if (mdb_midl_grow(idp, MDB_IDL_UM_MAX))
|
||||
return ENOMEM;
|
||||
ids = *idp;
|
||||
}
|
||||
ids[0]++;
|
||||
ids[ids[0]] = id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app )
|
||||
{
|
||||
MDB_IDL ids = *idp;
|
||||
/* Too big? */
|
||||
if (ids[0] + app[0] >= ids[-1]) {
|
||||
if (mdb_midl_grow(idp, app[0]))
|
||||
return ENOMEM;
|
||||
ids = *idp;
|
||||
}
|
||||
memcpy(&ids[ids[0]+1], &app[1], app[0] * sizeof(MDB_ID));
|
||||
ids[0] += app[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_midl_append_range( MDB_IDL *idp, MDB_ID id, unsigned n )
|
||||
{
|
||||
MDB_ID *ids = *idp, len = ids[0];
|
||||
/* Too big? */
|
||||
if (len + n > ids[-1]) {
|
||||
if (mdb_midl_grow(idp, n | MDB_IDL_UM_MAX))
|
||||
return ENOMEM;
|
||||
ids = *idp;
|
||||
}
|
||||
ids[0] = len + n;
|
||||
ids += len;
|
||||
while (n)
|
||||
ids[n--] = id++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mdb_midl_xmerge( MDB_IDL idl, MDB_IDL merge )
|
||||
{
|
||||
MDB_ID old_id, merge_id, i = merge[0], j = idl[0], k = i+j, total = k;
|
||||
idl[0] = (MDB_ID)-1; /* delimiter for idl scan below */
|
||||
old_id = idl[j];
|
||||
while (i) {
|
||||
merge_id = merge[i--];
|
||||
for (; old_id < merge_id; old_id = idl[--j])
|
||||
idl[k--] = old_id;
|
||||
idl[k--] = merge_id;
|
||||
}
|
||||
idl[0] = total;
|
||||
}
|
||||
|
||||
/* Quicksort + Insertion sort for small arrays */
|
||||
|
||||
#define SMALL 8
|
||||
#define MIDL_SWAP(a,b) { itmp=(a); (a)=(b); (b)=itmp; }
|
||||
|
||||
void
|
||||
mdb_midl_sort( MDB_IDL ids )
|
||||
{
|
||||
/* Max possible depth of int-indexed tree * 2 items/level */
|
||||
int istack[sizeof(int)*CHAR_BIT * 2];
|
||||
int i,j,k,l,ir,jstack;
|
||||
MDB_ID a, itmp;
|
||||
|
||||
ir = (int)ids[0];
|
||||
l = 1;
|
||||
jstack = 0;
|
||||
for(;;) {
|
||||
if (ir - l < SMALL) { /* Insertion sort */
|
||||
for (j=l+1;j<=ir;j++) {
|
||||
a = ids[j];
|
||||
for (i=j-1;i>=1;i--) {
|
||||
if (ids[i] >= a) break;
|
||||
ids[i+1] = ids[i];
|
||||
}
|
||||
ids[i+1] = a;
|
||||
}
|
||||
if (jstack == 0) break;
|
||||
ir = istack[jstack--];
|
||||
l = istack[jstack--];
|
||||
} else {
|
||||
k = (l + ir) >> 1; /* Choose median of left, center, right */
|
||||
MIDL_SWAP(ids[k], ids[l+1]);
|
||||
if (ids[l] < ids[ir]) {
|
||||
MIDL_SWAP(ids[l], ids[ir]);
|
||||
}
|
||||
if (ids[l+1] < ids[ir]) {
|
||||
MIDL_SWAP(ids[l+1], ids[ir]);
|
||||
}
|
||||
if (ids[l] < ids[l+1]) {
|
||||
MIDL_SWAP(ids[l], ids[l+1]);
|
||||
}
|
||||
i = l+1;
|
||||
j = ir;
|
||||
a = ids[l+1];
|
||||
for(;;) {
|
||||
do i++; while(ids[i] > a);
|
||||
do j--; while(ids[j] < a);
|
||||
if (j < i) break;
|
||||
MIDL_SWAP(ids[i],ids[j]);
|
||||
}
|
||||
ids[l+1] = ids[j];
|
||||
ids[j] = a;
|
||||
jstack += 2;
|
||||
if (ir-i+1 >= j-l) {
|
||||
istack[jstack] = ir;
|
||||
istack[jstack-1] = i;
|
||||
ir = j-1;
|
||||
} else {
|
||||
istack[jstack] = j-1;
|
||||
istack[jstack-1] = l;
|
||||
l = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id )
|
||||
{
|
||||
/*
|
||||
* binary search of id in ids
|
||||
* if found, returns position of id
|
||||
* if not found, returns first position greater than id
|
||||
*/
|
||||
unsigned base = 0;
|
||||
unsigned cursor = 1;
|
||||
int val = 0;
|
||||
unsigned n = (unsigned)ids[0].mid;
|
||||
|
||||
while( 0 < n ) {
|
||||
unsigned pivot = n >> 1;
|
||||
cursor = base + pivot + 1;
|
||||
val = CMP( id, ids[cursor].mid );
|
||||
|
||||
if( val < 0 ) {
|
||||
n = pivot;
|
||||
|
||||
} else if ( val > 0 ) {
|
||||
base = cursor;
|
||||
n -= pivot + 1;
|
||||
|
||||
} else {
|
||||
return cursor;
|
||||
}
|
||||
}
|
||||
|
||||
if( val > 0 ) {
|
||||
++cursor;
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id )
|
||||
{
|
||||
unsigned x, i;
|
||||
|
||||
x = mdb_mid2l_search( ids, id->mid );
|
||||
|
||||
if( x < 1 ) {
|
||||
/* internal error */
|
||||
return -2;
|
||||
}
|
||||
|
||||
if ( x <= ids[0].mid && ids[x].mid == id->mid ) {
|
||||
/* duplicate */
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( ids[0].mid >= MDB_IDL_UM_MAX ) {
|
||||
/* too big */
|
||||
return -2;
|
||||
|
||||
} else {
|
||||
/* insert id */
|
||||
ids[0].mid++;
|
||||
for (i=(unsigned)ids[0].mid; i>x; i--)
|
||||
ids[i] = ids[i-1];
|
||||
ids[x] = *id;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id )
|
||||
{
|
||||
/* Too big? */
|
||||
if (ids[0].mid >= MDB_IDL_UM_MAX) {
|
||||
return -2;
|
||||
}
|
||||
ids[0].mid++;
|
||||
ids[ids[0].mid] = *id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
/** @} */
|
||||
@@ -0,0 +1,188 @@
|
||||
/** @file midl.h
|
||||
* @brief LMDB ID List header file.
|
||||
*
|
||||
* This file was originally part of back-bdb but has been
|
||||
* modified for use in libmdb. Most of the macros defined
|
||||
* in this file are unused, just left over from the original.
|
||||
*
|
||||
* This file is only used internally in libmdb and its definitions
|
||||
* are not exposed publicly.
|
||||
*/
|
||||
/* $OpenLDAP$ */
|
||||
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
|
||||
*
|
||||
* Copyright 2000-2021 The OpenLDAP Foundation.
|
||||
* Portions Copyright 2001-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
#ifndef _MDB_MIDL_H_
|
||||
#define _MDB_MIDL_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** @defgroup internal LMDB Internals
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** @defgroup idls ID List Management
|
||||
* @{
|
||||
*/
|
||||
/** A generic unsigned ID number. These were entryIDs in back-bdb.
|
||||
* Preferably it should have the same size as a pointer.
|
||||
*/
|
||||
typedef size_t MDB_ID;
|
||||
|
||||
/** An IDL is an ID List, a sorted array of IDs. The first
|
||||
* element of the array is a counter for how many actual
|
||||
* IDs are in the list. In the original back-bdb code, IDLs are
|
||||
* sorted in ascending order. For libmdb IDLs are sorted in
|
||||
* descending order.
|
||||
*/
|
||||
typedef MDB_ID *MDB_IDL;
|
||||
|
||||
/* IDL sizes - likely should be even bigger
|
||||
* limiting factors: sizeof(ID), thread stack size
|
||||
*/
|
||||
#ifndef MDB_IDL_LOGN
|
||||
#define MDB_IDL_LOGN 16 /* DB_SIZE is 2^16, UM_SIZE is 2^17 */
|
||||
#endif
|
||||
#define MDB_IDL_DB_SIZE (1<<MDB_IDL_LOGN)
|
||||
#define MDB_IDL_UM_SIZE (1<<(MDB_IDL_LOGN+1))
|
||||
|
||||
#define MDB_IDL_DB_MAX (MDB_IDL_DB_SIZE-1)
|
||||
#define MDB_IDL_UM_MAX (MDB_IDL_UM_SIZE-1)
|
||||
|
||||
#define MDB_IDL_SIZEOF(ids) (((ids)[0]+1) * sizeof(MDB_ID))
|
||||
#define MDB_IDL_IS_ZERO(ids) ( (ids)[0] == 0 )
|
||||
#define MDB_IDL_CPY( dst, src ) (memcpy( dst, src, MDB_IDL_SIZEOF( src ) ))
|
||||
#define MDB_IDL_FIRST( ids ) ( (ids)[1] )
|
||||
#define MDB_IDL_LAST( ids ) ( (ids)[(ids)[0]] )
|
||||
|
||||
/** Current max length of an #mdb_midl_alloc()ed IDL */
|
||||
#define MDB_IDL_ALLOCLEN( ids ) ( (ids)[-1] )
|
||||
|
||||
/** Append ID to IDL. The IDL must be big enough. */
|
||||
#define mdb_midl_xappend(idl, id) do { \
|
||||
MDB_ID *xidl = (idl), xlen = ++(xidl[0]); \
|
||||
xidl[xlen] = (id); \
|
||||
} while (0)
|
||||
|
||||
/** Search for an ID in an IDL.
|
||||
* @param[in] ids The IDL to search.
|
||||
* @param[in] id The ID to search for.
|
||||
* @return The index of the first ID greater than or equal to \b id.
|
||||
*/
|
||||
unsigned mdb_midl_search( MDB_IDL ids, MDB_ID id );
|
||||
|
||||
/** Allocate an IDL.
|
||||
* Allocates memory for an IDL of the given size.
|
||||
* @return IDL on success, NULL on failure.
|
||||
*/
|
||||
MDB_IDL mdb_midl_alloc(int num);
|
||||
|
||||
/** Free an IDL.
|
||||
* @param[in] ids The IDL to free.
|
||||
*/
|
||||
void mdb_midl_free(MDB_IDL ids);
|
||||
|
||||
/** Shrink an IDL.
|
||||
* Return the IDL to the default size if it has grown larger.
|
||||
* @param[in,out] idp Address of the IDL to shrink.
|
||||
*/
|
||||
void mdb_midl_shrink(MDB_IDL *idp);
|
||||
|
||||
/** Make room for num additional elements in an IDL.
|
||||
* @param[in,out] idp Address of the IDL.
|
||||
* @param[in] num Number of elements to make room for.
|
||||
* @return 0 on success, ENOMEM on failure.
|
||||
*/
|
||||
int mdb_midl_need(MDB_IDL *idp, unsigned num);
|
||||
|
||||
/** Append an ID onto an IDL.
|
||||
* @param[in,out] idp Address of the IDL to append to.
|
||||
* @param[in] id The ID to append.
|
||||
* @return 0 on success, ENOMEM if the IDL is too large.
|
||||
*/
|
||||
int mdb_midl_append( MDB_IDL *idp, MDB_ID id );
|
||||
|
||||
/** Append an IDL onto an IDL.
|
||||
* @param[in,out] idp Address of the IDL to append to.
|
||||
* @param[in] app The IDL to append.
|
||||
* @return 0 on success, ENOMEM if the IDL is too large.
|
||||
*/
|
||||
int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app );
|
||||
|
||||
/** Append an ID range onto an IDL.
|
||||
* @param[in,out] idp Address of the IDL to append to.
|
||||
* @param[in] id The lowest ID to append.
|
||||
* @param[in] n Number of IDs to append.
|
||||
* @return 0 on success, ENOMEM if the IDL is too large.
|
||||
*/
|
||||
int mdb_midl_append_range( MDB_IDL *idp, MDB_ID id, unsigned n );
|
||||
|
||||
/** Merge an IDL onto an IDL. The destination IDL must be big enough.
|
||||
* @param[in] idl The IDL to merge into.
|
||||
* @param[in] merge The IDL to merge.
|
||||
*/
|
||||
void mdb_midl_xmerge( MDB_IDL idl, MDB_IDL merge );
|
||||
|
||||
/** Sort an IDL.
|
||||
* @param[in,out] ids The IDL to sort.
|
||||
*/
|
||||
void mdb_midl_sort( MDB_IDL ids );
|
||||
|
||||
/** An ID2 is an ID/pointer pair.
|
||||
*/
|
||||
typedef struct MDB_ID2 {
|
||||
MDB_ID mid; /**< The ID */
|
||||
void *mptr; /**< The pointer */
|
||||
} MDB_ID2;
|
||||
|
||||
/** An ID2L is an ID2 List, a sorted array of ID2s.
|
||||
* The first element's \b mid member is a count of how many actual
|
||||
* elements are in the array. The \b mptr member of the first element is unused.
|
||||
* The array is sorted in ascending order by \b mid.
|
||||
*/
|
||||
typedef MDB_ID2 *MDB_ID2L;
|
||||
|
||||
/** Search for an ID in an ID2L.
|
||||
* @param[in] ids The ID2L to search.
|
||||
* @param[in] id The ID to search for.
|
||||
* @return The index of the first ID2 whose \b mid member is greater than or equal to \b id.
|
||||
*/
|
||||
unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id );
|
||||
|
||||
|
||||
/** Insert an ID2 into a ID2L.
|
||||
* @param[in,out] ids The ID2L to insert into.
|
||||
* @param[in] id The ID2 to insert.
|
||||
* @return 0 on success, -1 if the ID was already present in the ID2L.
|
||||
*/
|
||||
int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id );
|
||||
|
||||
/** Append an ID2 into a ID2L.
|
||||
* @param[in,out] ids The ID2L to append into.
|
||||
* @param[in] id The ID2 to append.
|
||||
* @return 0 on success, -2 if the ID2L is too big.
|
||||
*/
|
||||
int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id );
|
||||
|
||||
/** @} */
|
||||
/** @} */
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* _MDB_MIDL_H_ */
|
||||
@@ -0,0 +1,580 @@
|
||||
/* mplay.c - memory-mapped database log replay */
|
||||
/*
|
||||
* Copyright 2011-2023 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
#define MDB_SCNy(t) "z" #t
|
||||
|
||||
#define SCMP(s) s, (sizeof(s)-1)
|
||||
char inbuf[8192];
|
||||
char *dbuf, *kbuf;
|
||||
size_t dbufsize;
|
||||
int maxkey;
|
||||
|
||||
#define SOFF(s) (sizeof(s)+1)
|
||||
|
||||
#define MAXENVS 16
|
||||
#define MAXTXNS 16
|
||||
#define MAXCRSS 16
|
||||
|
||||
#define MAXPIDS 16
|
||||
|
||||
typedef struct crspair {
|
||||
void *tcrs; /* scanned text pointer */
|
||||
MDB_cursor *rcrs;
|
||||
} crspair;
|
||||
|
||||
typedef struct txnpair {
|
||||
void *ttxn; /* scanned text pointer */
|
||||
MDB_txn *rtxn;
|
||||
crspair cursors[MAXCRSS];
|
||||
int ncursors;
|
||||
} txnpair;
|
||||
|
||||
typedef struct envpair {
|
||||
void *tenv;
|
||||
MDB_env *renv;
|
||||
txnpair txns[MAXTXNS];
|
||||
int ntxns;
|
||||
} envpair;
|
||||
|
||||
envpair envs[MAXENVS];
|
||||
int nenvs;
|
||||
|
||||
envpair *lastenv;
|
||||
txnpair *lasttxn;
|
||||
crspair *lastcrs;
|
||||
|
||||
typedef struct pidpair {
|
||||
int tpid;
|
||||
pid_t rpid;
|
||||
int fdout, fdin;
|
||||
} pidpair;
|
||||
|
||||
pidpair *lastpid;
|
||||
|
||||
pidpair pids[MAXPIDS];
|
||||
int npids;
|
||||
|
||||
unsigned long lcount;
|
||||
|
||||
static int unhex(unsigned char *c2)
|
||||
{
|
||||
int x, c;
|
||||
x = *c2++ & 0x4f;
|
||||
if (x & 0x40)
|
||||
x -= 55;
|
||||
c = x << 4;
|
||||
x = *c2 & 0x4f;
|
||||
if (x & 0x40)
|
||||
x -= 55;
|
||||
c |= x;
|
||||
return c;
|
||||
}
|
||||
|
||||
int inhex(char *in, char *out)
|
||||
{
|
||||
char *c2 = out;
|
||||
while (isxdigit(*in)) {
|
||||
*c2++ = unhex((unsigned char *)in);
|
||||
in += 2;
|
||||
}
|
||||
return c2 - out;
|
||||
}
|
||||
|
||||
static void addenv(void *tenv, MDB_env *renv)
|
||||
{
|
||||
assert(nenvs < MAXENVS);
|
||||
envs[nenvs].tenv = tenv;
|
||||
envs[nenvs].renv = renv;
|
||||
envs[nenvs].ntxns = 0;
|
||||
lastenv = envs+nenvs;
|
||||
nenvs++;
|
||||
}
|
||||
|
||||
static envpair *findenv(void *tenv)
|
||||
{
|
||||
int i;
|
||||
if (!lastenv || lastenv->tenv != tenv) {
|
||||
for (i=0; i<nenvs; i++)
|
||||
if (envs[i].tenv == tenv)
|
||||
break;
|
||||
assert(i < nenvs);
|
||||
lastenv = &envs[i];
|
||||
}
|
||||
return lastenv;
|
||||
}
|
||||
|
||||
static void delenv(envpair *ep)
|
||||
{
|
||||
int i = ep - envs;
|
||||
for (; i<nenvs-1; i++)
|
||||
envs[i] = envs[i+1];
|
||||
nenvs--;
|
||||
lastenv = NULL;
|
||||
}
|
||||
|
||||
static void addtxn(void *tenv, void *ttxn, MDB_txn *rtxn)
|
||||
{
|
||||
envpair *ep;
|
||||
txnpair *tp;
|
||||
|
||||
ep = findenv(tenv);
|
||||
assert(ep->ntxns < MAXTXNS);
|
||||
tp = ep->txns+ep->ntxns;
|
||||
tp->ttxn = ttxn;
|
||||
tp->rtxn = rtxn;
|
||||
tp->ncursors = 0;
|
||||
ep->ntxns++;
|
||||
lasttxn = tp;
|
||||
}
|
||||
|
||||
static txnpair *findtxn(void *ttxn)
|
||||
{
|
||||
int i, j;
|
||||
if (lasttxn && lasttxn->ttxn == ttxn)
|
||||
return lasttxn;
|
||||
if (lastenv) {
|
||||
for (i=0; i<lastenv->ntxns; i++) {
|
||||
if (lastenv->txns[i].ttxn == ttxn) {
|
||||
lasttxn = lastenv->txns+i;
|
||||
return lasttxn;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i=0; i<nenvs; i++) {
|
||||
if (envs+i == lastenv) continue;
|
||||
for (j=0; j<envs[i].ntxns; j++) {
|
||||
if (envs[i].txns[j].ttxn == ttxn) {
|
||||
lastenv = envs+i;
|
||||
lasttxn = envs[i].txns+j;
|
||||
return lasttxn;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(0); /* should have found it */
|
||||
}
|
||||
|
||||
static void deltxn(txnpair *tp)
|
||||
{
|
||||
int i = tp - lastenv->txns;
|
||||
for (; i<lastenv->ntxns-1; i++)
|
||||
lastenv->txns[i] = lastenv->txns[i+1];
|
||||
lastenv->ntxns--;
|
||||
lasttxn = NULL;
|
||||
}
|
||||
|
||||
static void addcrs(txnpair *tp, void *tcrs, MDB_cursor *rcrs)
|
||||
{
|
||||
int j = tp->ncursors;
|
||||
assert(tp->ncursors < MAXCRSS);
|
||||
|
||||
tp->cursors[j].tcrs = tcrs;
|
||||
tp->cursors[j].rcrs = rcrs;
|
||||
tp->ncursors++;
|
||||
lastcrs = tp->cursors+j;
|
||||
}
|
||||
|
||||
static crspair *findcrs(void *tcrs)
|
||||
{
|
||||
int i, j, k;
|
||||
envpair *ep;
|
||||
txnpair *tp;
|
||||
crspair *cp;
|
||||
if (lastcrs && lastcrs->tcrs == tcrs)
|
||||
return lastcrs;
|
||||
if (lasttxn) {
|
||||
for (k=0, cp=lasttxn->cursors; k<lasttxn->ncursors; k++, cp++) {
|
||||
if (cp->tcrs == tcrs) {
|
||||
lastcrs = cp;
|
||||
return lastcrs;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastenv) {
|
||||
for (j=0, tp=lastenv->txns; j<lastenv->ntxns; j++, tp++){
|
||||
if (tp == lasttxn)
|
||||
continue;
|
||||
for (k=0, cp = tp->cursors; k<tp->ncursors; k++, cp++) {
|
||||
if (cp->tcrs == tcrs) {
|
||||
lastcrs = cp;
|
||||
lasttxn = tp;
|
||||
return lastcrs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i=0, ep=envs; i<nenvs; i++, ep++) {
|
||||
for (j=0, tp=ep->txns; j<ep->ntxns; j++, tp++) {
|
||||
if (tp == lasttxn)
|
||||
continue;
|
||||
for (k=0, cp = tp->cursors; k<tp->ncursors; k++, cp++) {
|
||||
if (cp->tcrs == tcrs) {
|
||||
lastcrs = cp;
|
||||
lasttxn = tp;
|
||||
lastenv = ep;
|
||||
return lastcrs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(0); /* should have found it already */
|
||||
}
|
||||
|
||||
static void delcrs(void *tcrs)
|
||||
{
|
||||
int i;
|
||||
crspair *cp = findcrs(tcrs);
|
||||
mdb_cursor_close(cp->rcrs);
|
||||
for (i = cp - lasttxn->cursors; i<lasttxn->ncursors-1; i++)
|
||||
lasttxn->cursors[i] = lasttxn->cursors[i+1];
|
||||
lasttxn->ncursors--;
|
||||
lastcrs = NULL;
|
||||
}
|
||||
|
||||
void child()
|
||||
{
|
||||
int rc;
|
||||
MDB_val key, data;
|
||||
char *ptr;
|
||||
|
||||
while (fgets(inbuf, sizeof(inbuf), stdin)) {
|
||||
ptr = inbuf;
|
||||
if (!strncmp(ptr, SCMP("exit")))
|
||||
break;
|
||||
|
||||
if (!strncmp(ptr, SCMP("mdb_env_create"))) {
|
||||
void *tenv;
|
||||
MDB_env *renv;
|
||||
sscanf(ptr+SOFF("mdb_env_create"), "%p", &tenv);
|
||||
E(mdb_env_create(&renv));
|
||||
addenv(tenv, renv);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_env_set_maxdbs"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
unsigned int maxdbs;
|
||||
sscanf(ptr+SOFF("mdb_env_set_maxdbs"), "%p, %u", &tenv, &maxdbs);
|
||||
ep = findenv(tenv);
|
||||
E(mdb_env_set_maxdbs(ep->renv, maxdbs));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_env_set_mapsize"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
size_t mapsize;
|
||||
sscanf(ptr+SOFF("mdb_env_set_mapsize"), "%p, %"MDB_SCNy(u), &tenv, &mapsize);
|
||||
ep = findenv(tenv);
|
||||
E(mdb_env_set_mapsize(ep->renv, mapsize));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_env_open"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
char *path;
|
||||
int len;
|
||||
unsigned int flags, mode;
|
||||
sscanf(ptr+SOFF("mdb_env_open"), "%p, %n", &tenv, &len);
|
||||
path = ptr+SOFF("mdb_env_open")+len;
|
||||
ptr = strchr(path, ',');
|
||||
*ptr++ = '\0';
|
||||
sscanf(ptr, "%u, %o", &flags, &mode);
|
||||
ep = findenv(tenv);
|
||||
E(mdb_env_open(ep->renv, path, flags, mode));
|
||||
if (!maxkey) {
|
||||
maxkey = mdb_env_get_maxkeysize(ep->renv);
|
||||
kbuf = malloc(maxkey+2);
|
||||
dbuf = malloc(maxkey+2);
|
||||
dbufsize = maxkey;
|
||||
}
|
||||
} else if (!strncmp(ptr, SCMP("mdb_env_close"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
sscanf(ptr+SOFF("mdb_env_close"), "%p", &tenv);
|
||||
ep = findenv(tenv);
|
||||
mdb_env_close(ep->renv);
|
||||
delenv(ep);
|
||||
if (!nenvs) /* if no other envs left, this process is done */
|
||||
break;
|
||||
} else if (!strncmp(ptr, SCMP("mdb_txn_begin"))) {
|
||||
unsigned int flags;
|
||||
void *tenv, *ttxn;
|
||||
envpair *ep;
|
||||
MDB_txn *rtxn;
|
||||
sscanf(ptr+SOFF("mdb_txn_begin"), "%p, %*p, %u = %p", &tenv, &flags, &ttxn);
|
||||
ep = findenv(tenv);
|
||||
E(mdb_txn_begin(ep->renv, NULL, flags, &rtxn));
|
||||
addtxn(tenv, ttxn, rtxn);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_txn_commit"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
sscanf(ptr+SOFF("mdb_txn_commit"), "%p", &ttxn);
|
||||
tp = findtxn(ttxn);
|
||||
E(mdb_txn_commit(tp->rtxn));
|
||||
deltxn(tp);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_txn_abort"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
sscanf(ptr+SOFF("mdb_txn_abort"), "%p", &ttxn);
|
||||
tp = findtxn(ttxn);
|
||||
mdb_txn_abort(tp->rtxn);
|
||||
deltxn(tp);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_dbi_open"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
char *dbname;
|
||||
unsigned int flags;
|
||||
unsigned int tdbi;
|
||||
MDB_dbi dbi;
|
||||
sscanf(ptr+SOFF("mdb_dbi_open"), "%p, ", &ttxn);
|
||||
dbname = strchr(ptr+SOFF("mdb_dbi_open"), ',');
|
||||
dbname += 2;
|
||||
ptr = strchr(dbname, ',');
|
||||
*ptr++ = '\0';
|
||||
if (!strcmp(dbname, "(null)"))
|
||||
dbname = NULL;
|
||||
sscanf(ptr, "%u = %u", &flags, &tdbi);
|
||||
tp = findtxn(ttxn);
|
||||
E(mdb_dbi_open(tp->rtxn, dbname, flags, &dbi));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_dbi_close"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
unsigned int tdbi;
|
||||
sscanf(ptr+SOFF("mdb_dbi_close"), "%p, %u", &tenv, &tdbi);
|
||||
ep = findenv(tenv);
|
||||
mdb_dbi_close(ep->renv, tdbi);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_cursor_open"))) {
|
||||
void *ttxn, *tcrs;
|
||||
txnpair *tp;
|
||||
MDB_cursor *rcrs;
|
||||
unsigned int tdbi;
|
||||
sscanf(ptr+SOFF("mdb_cursor_open"), "%p, %u = %p", &ttxn, &tdbi, &tcrs);
|
||||
tp = findtxn(ttxn);
|
||||
E(mdb_cursor_open(tp->rtxn, tdbi, &rcrs));
|
||||
addcrs(tp, tcrs, rcrs);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_cursor_close"))) {
|
||||
void *tcrs;
|
||||
sscanf(ptr+SOFF("mdb_cursor_close"), "%p", &tcrs);
|
||||
delcrs(tcrs);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_cursor_put"))) {
|
||||
void *tcrs;
|
||||
crspair *cp;
|
||||
unsigned int flags;
|
||||
int len;
|
||||
sscanf(ptr+SOFF("mdb_cursor_put"), "%p, ", &tcrs);
|
||||
cp = findcrs(tcrs);
|
||||
ptr = strchr(ptr+SOFF("mdb_cursor_put"), ',');
|
||||
sscanf(ptr+1, "%"MDB_SCNy(u)",", &key.mv_size);
|
||||
if (key.mv_size) {
|
||||
ptr = strchr(ptr, '[');
|
||||
inhex(ptr+1, kbuf);
|
||||
key.mv_data = kbuf;
|
||||
ptr += key.mv_size * 2 + 2;
|
||||
}
|
||||
ptr = strchr(ptr+1, ',');
|
||||
sscanf(ptr+1, "%"MDB_SCNy(u)"%n", &data.mv_size, &len);
|
||||
if (data.mv_size > dbufsize) {
|
||||
dbuf = realloc(dbuf, data.mv_size+2);
|
||||
assert(dbuf != NULL);
|
||||
dbufsize = data.mv_size;
|
||||
}
|
||||
ptr += len+1;
|
||||
if (*ptr == '[') {
|
||||
inhex(ptr+1, dbuf);
|
||||
data.mv_data = dbuf;
|
||||
ptr += data.mv_size * 2 + 2;
|
||||
} else {
|
||||
sprintf(dbuf, "%09ld", (long)mdb_txn_id(lasttxn->rtxn));
|
||||
}
|
||||
sscanf(ptr+1, "%u", &flags);
|
||||
E(mdb_cursor_put(cp->rcrs, &key, &data, flags));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_cursor_del"))) {
|
||||
void *tcrs;
|
||||
crspair *cp;
|
||||
unsigned int flags;
|
||||
sscanf(ptr+SOFF("mdb_cursor_del"), "%p, %u", &tcrs, &flags);
|
||||
cp = findcrs(tcrs);
|
||||
E(mdb_cursor_del(cp->rcrs, flags));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_put"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
unsigned int tdbi, flags;
|
||||
int len;
|
||||
sscanf(ptr+SOFF("mdb_put"),"%p, %u, %"MDB_SCNy(u), &ttxn, &tdbi, &key.mv_size);
|
||||
tp = findtxn(ttxn);
|
||||
ptr = strchr(ptr+SOFF("mdb_put"), '[');
|
||||
inhex(ptr+1, kbuf);
|
||||
key.mv_data = kbuf;
|
||||
ptr += key.mv_size * 2 + 2;
|
||||
sscanf(ptr+1, "%"MDB_SCNy(u)"%n", &data.mv_size, &len);
|
||||
if (data.mv_size > dbufsize) {
|
||||
dbuf = realloc(dbuf, data.mv_size+2);
|
||||
assert(dbuf != NULL);
|
||||
dbufsize = data.mv_size;
|
||||
}
|
||||
ptr += len+1;
|
||||
if (*ptr == '[') {
|
||||
inhex(ptr+1, dbuf);
|
||||
ptr += data.mv_size * 2 + 2;
|
||||
} else {
|
||||
sprintf(dbuf, "%09ld", (long)mdb_txn_id(tp->rtxn));
|
||||
}
|
||||
data.mv_data = dbuf;
|
||||
sscanf(ptr+1, "%u", &flags);
|
||||
RES(MDB_KEYEXIST,mdb_put(tp->rtxn, tdbi, &key, &data, flags));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_del"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
unsigned int tdbi;
|
||||
int len;
|
||||
sscanf(ptr+SOFF("mdb_del"),"%p, %u, %"MDB_SCNy(u), &ttxn, &tdbi, &key.mv_size);
|
||||
tp = findtxn(ttxn);
|
||||
ptr = strchr(ptr+SOFF("mdb_del"), '[');
|
||||
inhex(ptr+1, kbuf);
|
||||
key.mv_data = kbuf;
|
||||
ptr += key.mv_size * 2 + 2;
|
||||
sscanf(ptr+1, "%"MDB_SCNy(u)"%n", &data.mv_size, &len);
|
||||
if (data.mv_size > dbufsize) {
|
||||
dbuf = realloc(dbuf, data.mv_size+2);
|
||||
assert(dbuf != NULL);
|
||||
dbufsize = data.mv_size;
|
||||
}
|
||||
ptr += len+1;
|
||||
if (*ptr == '[') {
|
||||
inhex(ptr+1, dbuf);
|
||||
} else {
|
||||
sprintf(dbuf, "%09ld", (long)mdb_txn_id(tp->rtxn));
|
||||
}
|
||||
data.mv_data = dbuf;
|
||||
RES(MDB_NOTFOUND,mdb_del(tp->rtxn, tdbi, &key, &data));
|
||||
}
|
||||
write(1, "\n", 1);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static pidpair *addpid(int tpid)
|
||||
{
|
||||
int fdout[2], fdin[2];
|
||||
pid_t pid;
|
||||
assert(npids < MAXPIDS);
|
||||
pids[npids].tpid = tpid;
|
||||
pipe(fdout);
|
||||
pipe(fdin);
|
||||
if ((pid = fork()) == 0) {
|
||||
/* child */
|
||||
fflush(stdin);
|
||||
fflush(stdout);
|
||||
dup2(fdout[0], 0);
|
||||
dup2(fdin[1], 1);
|
||||
child();
|
||||
return 0; /* NOTREACHED */
|
||||
} else {
|
||||
pids[npids].rpid = pid;
|
||||
pids[npids].fdout = fdout[1];
|
||||
pids[npids].fdin = fdin[0];
|
||||
lastpid = pids+npids;
|
||||
npids++;
|
||||
return lastpid;
|
||||
}
|
||||
}
|
||||
|
||||
static pidpair *findpid(int tpid)
|
||||
{
|
||||
int i;
|
||||
if (!lastpid || lastpid->tpid != tpid) {
|
||||
for (i=0; i<npids; i++)
|
||||
if (pids[i].tpid == tpid)
|
||||
break;
|
||||
if (i == npids)
|
||||
return NULL;
|
||||
lastpid = &pids[i];
|
||||
}
|
||||
return lastpid;
|
||||
}
|
||||
|
||||
volatile pid_t killpid;
|
||||
|
||||
static void delpid(int tpid)
|
||||
{
|
||||
pidpair *pp = findpid(tpid);
|
||||
if (pp) {
|
||||
pid_t kpid = pp->rpid;
|
||||
killpid = kpid;
|
||||
write(pp->fdout, "exit\n", sizeof("exit"));
|
||||
while (killpid == kpid)
|
||||
usleep(10000);
|
||||
}
|
||||
}
|
||||
|
||||
static void reaper(int sig)
|
||||
{
|
||||
int status, i;
|
||||
pid_t pid = waitpid(-1, &status, 0);
|
||||
if (pid > 0) {
|
||||
fprintf(stderr, "# %s %d\n", WIFEXITED(status) ? "exited" : "killed", pid);
|
||||
for (i=0; i<npids; i++)
|
||||
if (pids[i].rpid == pid)
|
||||
break;
|
||||
assert(i < npids);
|
||||
close(pids[i].fdout);
|
||||
close(pids[i].fdin);
|
||||
for (;i<npids-1; i++)
|
||||
pids[i] = pids[i+1];
|
||||
npids--;
|
||||
killpid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
signal(SIGCHLD, reaper);
|
||||
|
||||
while (fgets(inbuf, sizeof(inbuf), stdin)) {
|
||||
pidpair *pp;
|
||||
int tpid, len;
|
||||
char c, *ptr;
|
||||
lcount++;
|
||||
|
||||
if (inbuf[0] == '#' && !strncmp(inbuf+1, SCMP(" killed"))) {
|
||||
sscanf(inbuf+SOFF("killed"),"%d", &tpid);
|
||||
delpid(tpid);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inbuf[0] != '>')
|
||||
continue;
|
||||
ptr = inbuf+1;
|
||||
sscanf(ptr, "%d:%n", &tpid, &len);
|
||||
pp = findpid(tpid);
|
||||
if (!pp)
|
||||
pp = addpid(tpid); /* new process */
|
||||
|
||||
ptr = inbuf+len+1;
|
||||
len = strlen(ptr);
|
||||
write(pp->fdout, ptr, len); /* send command and wait for ack */
|
||||
read(pp->fdin, &c, 1);
|
||||
}
|
||||
while (npids)
|
||||
delpid(pids[0].tpid);
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/* mtest.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor, *cur2;
|
||||
MDB_cursor_op op;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[32] = "";
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
count = (rand()%384) + 64;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = rand()%1024;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_maxreaders(env, 1));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP /*|MDB_NOSYNC*/, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, NULL, 0, &dbi));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
for (i=0;i<count;i++) {
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
/* Set <data> in each iteration, since MDB_NOOVERWRITE may modify it */
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NOOVERWRITE))) {
|
||||
j++;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
}
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
j=0;
|
||||
key.mv_data = sval;
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(sval, "%03x ", values[i]);
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, NULL))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor last\n");
|
||||
E(mdb_cursor_get(cursor, &key, &data, MDB_LAST));
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor last/prev\n");
|
||||
E(mdb_cursor_get(cursor, &key, &data, MDB_LAST));
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
E(mdb_cursor_get(cursor, &key, &data, MDB_PREV));
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
printf("Deleting with cursor\n");
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cur2));
|
||||
for (i=0; i<50; i++) {
|
||||
if (RES(MDB_NOTFOUND, mdb_cursor_get(cur2, &key, &data, MDB_NEXT)))
|
||||
break;
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
E(mdb_del(txn, dbi, &key, NULL));
|
||||
}
|
||||
|
||||
printf("Restarting cursor in txn\n");
|
||||
for (op=MDB_FIRST, i=0; i<=32; op=MDB_NEXT, i++) {
|
||||
if (RES(MDB_NOTFOUND, mdb_cursor_get(cur2, &key, &data, op)))
|
||||
break;
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
mdb_cursor_close(cur2);
|
||||
E(mdb_txn_commit(txn));
|
||||
|
||||
printf("Restarting cursor outside txn\n");
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
for (op=MDB_FIRST, i=0; i<=32; op=MDB_NEXT, i++) {
|
||||
if (RES(MDB_NOTFOUND, mdb_cursor_get(cursor, &key, &data, op)))
|
||||
break;
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/* mtest2.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Just like mtest.c, but using a subDB instead of the main DB */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[32] = "";
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
count = (rand()%384) + 64;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = rand()%1024;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_maxreaders(env, 1));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id1", MDB_CREATE, &dbi));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
for (i=0;i<count;i++) {
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NOOVERWRITE)))
|
||||
j++;
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
j=0;
|
||||
key.mv_data = sval;
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(sval, "%03x ", values[i]);
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, NULL))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/* mtest3.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Tests for sorted duplicate DBs */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[32];
|
||||
char kval[sizeof(int)];
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
memset(sval, 0, sizeof(sval));
|
||||
|
||||
count = (rand()%384) + 64;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = rand()%1024;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id2", MDB_CREATE|MDB_DUPSORT, &dbi));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
for (i=0;i<count;i++) {
|
||||
if (!(i & 0x0f))
|
||||
sprintf(kval, "%03x", values[i]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA)))
|
||||
j++;
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
j=0;
|
||||
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(kval, "%03x", values[i & ~0x0f]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/* mtest4.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Tests for sorted duplicate DBs with fixed-size keys */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[8];
|
||||
char kval[sizeof(int)];
|
||||
|
||||
memset(sval, 0, sizeof(sval));
|
||||
|
||||
count = 510;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = i*5;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id4", MDB_CREATE|MDB_DUPSORT|MDB_DUPFIXED, &dbi));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
strcpy(kval, "001");
|
||||
for (i=0;i<count;i++) {
|
||||
sprintf(sval, "%07x", values[i]);
|
||||
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA)))
|
||||
j++;
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
/* there should be one full page of dups now.
|
||||
*/
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
/* test all 3 branches of split code:
|
||||
* 1: new key in lower half
|
||||
* 2: new key at split point
|
||||
* 3: new key in upper half
|
||||
*/
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
sprintf(sval, "%07x", values[3]+1);
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
(void)RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA));
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
sprintf(sval, "%07x", values[255]+1);
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
(void)RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA));
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
sprintf(sval, "%07x", values[500]+1);
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
(void)RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA));
|
||||
E(mdb_txn_commit(txn));
|
||||
|
||||
/* Try MDB_NEXT_MULTIPLE */
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT_MULTIPLE)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
j=0;
|
||||
|
||||
for (i= count - 1; i > -1; i-= (rand()%3)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(sval, "%07x", values[i]);
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/* mtest5.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Tests for sorted duplicate DBs using cursor_put */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[32];
|
||||
char kval[sizeof(int)];
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
memset(sval, 0, sizeof(sval));
|
||||
|
||||
count = (rand()%384) + 64;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = rand()%1024;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id2", MDB_CREATE|MDB_DUPSORT, &dbi));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
for (i=0;i<count;i++) {
|
||||
if (!(i & 0x0f))
|
||||
sprintf(kval, "%03x", values[i]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
if (RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NODUPDATA)))
|
||||
j++;
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
mdb_cursor_close(cursor);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
j=0;
|
||||
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(kval, "%03x", values[i & ~0x0f]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/* mtest6.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Tests for DB splits and merges */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
char dkbuf[1024];
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data, sdata;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
long kval;
|
||||
char *sval;
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id6", MDB_CREATE|MDB_INTEGERKEY, &dbi));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
E(mdb_stat(txn, dbi, &mst));
|
||||
|
||||
sval = calloc(1, mst.ms_psize / 4);
|
||||
key.mv_size = sizeof(long);
|
||||
key.mv_data = &kval;
|
||||
sdata.mv_size = mst.ms_psize / 4 - 30;
|
||||
sdata.mv_data = sval;
|
||||
|
||||
printf("Adding 12 values, should yield 3 splits\n");
|
||||
for (i=0;i<12;i++) {
|
||||
kval = i*5;
|
||||
sprintf(sval, "%08x", kval);
|
||||
data = sdata;
|
||||
(void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE));
|
||||
}
|
||||
printf("Adding 12 more values, should yield 3 splits\n");
|
||||
for (i=0;i<12;i++) {
|
||||
kval = i*5+4;
|
||||
sprintf(sval, "%08x", kval);
|
||||
data = sdata;
|
||||
(void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE));
|
||||
}
|
||||
printf("Adding 12 more values, should yield 3 splits\n");
|
||||
for (i=0;i<12;i++) {
|
||||
kval = i*5+1;
|
||||
sprintf(sval, "%08x", kval);
|
||||
data = sdata;
|
||||
(void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE));
|
||||
}
|
||||
E(mdb_cursor_get(cursor, &key, &data, MDB_FIRST));
|
||||
|
||||
do {
|
||||
printf("key: %p %s, data: %p %.*s\n",
|
||||
key.mv_data, mdb_dkey(&key, dkbuf),
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
} while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0);
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_commit(txn);
|
||||
|
||||
#if 0
|
||||
j=0;
|
||||
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(kval, "%03x", values[i & ~0x0f]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
#endif
|
||||
mdb_env_close(env);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/* sample-bdb.txt - BerkeleyDB toy/sample
|
||||
*
|
||||
* Do a line-by-line comparison of this and sample-mdb.txt
|
||||
*/
|
||||
/*
|
||||
* Copyright 2012-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <db.h>
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int rc;
|
||||
DB_ENV *env;
|
||||
DB *dbi;
|
||||
DBT key, data;
|
||||
DB_TXN *txn;
|
||||
DBC *cursor;
|
||||
char sval[32], kval[32];
|
||||
|
||||
/* Note: Most error checking omitted for simplicity */
|
||||
|
||||
#define FLAGS (DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_TXN|DB_INIT_MPOOL|DB_CREATE|DB_THREAD)
|
||||
rc = db_env_create(&env, 0);
|
||||
rc = env->open(env, "./testdb", FLAGS, 0664);
|
||||
rc = db_create(&dbi, env, 0);
|
||||
rc = env->txn_begin(env, NULL, &txn, 0);
|
||||
rc = dbi->open(dbi, txn, "test.bdb", NULL, DB_BTREE, DB_CREATE, 0664);
|
||||
|
||||
memset(&key, 0, sizeof(DBT));
|
||||
memset(&data, 0, sizeof(DBT));
|
||||
key.size = sizeof(int);
|
||||
key.data = sval;
|
||||
data.size = sizeof(sval);
|
||||
data.data = sval;
|
||||
|
||||
sprintf(sval, "%03x %d foo bar", 32, 3141592);
|
||||
rc = dbi->put(dbi, txn, &key, &data, 0);
|
||||
rc = txn->commit(txn, 0);
|
||||
if (rc) {
|
||||
fprintf(stderr, "txn->commit: (%d) %s\n", rc, db_strerror(rc));
|
||||
goto leave;
|
||||
}
|
||||
rc = env->txn_begin(env, NULL, &txn, 0);
|
||||
rc = dbi->cursor(dbi, txn, &cursor, 0);
|
||||
key.flags = DB_DBT_USERMEM;
|
||||
key.data = kval;
|
||||
key.ulen = sizeof(kval);
|
||||
data.flags = DB_DBT_USERMEM;
|
||||
data.data = sval;
|
||||
data.ulen = sizeof(sval);
|
||||
while ((rc = cursor->c_get(cursor, &key, &data, DB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.data, (int) key.size, (char *) key.data,
|
||||
data.data, (int) data.size, (char *) data.data);
|
||||
}
|
||||
rc = cursor->c_close(cursor);
|
||||
rc = txn->abort(txn);
|
||||
leave:
|
||||
rc = dbi->close(dbi, 0);
|
||||
rc = env->close(env, 0);
|
||||
return rc;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/* sample-mdb.txt - MDB toy/sample
|
||||
*
|
||||
* Do a line-by-line comparison of this and sample-bdb.txt
|
||||
*/
|
||||
/*
|
||||
* Copyright 2012-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_cursor *cursor;
|
||||
char sval[32];
|
||||
|
||||
/* Note: Most error checking omitted for simplicity */
|
||||
|
||||
rc = mdb_env_create(&env);
|
||||
rc = mdb_env_open(env, "./testdb", 0, 0664);
|
||||
rc = mdb_txn_begin(env, NULL, 0, &txn);
|
||||
rc = mdb_dbi_open(txn, NULL, 0, &dbi);
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = sval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
sprintf(sval, "%03x %d foo bar", 32, 3141592);
|
||||
rc = mdb_put(txn, dbi, &key, &data, 0);
|
||||
rc = mdb_txn_commit(txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_commit: (%d) %s\n", rc, mdb_strerror(rc));
|
||||
goto leave;
|
||||
}
|
||||
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
|
||||
rc = mdb_cursor_open(txn, dbi, &cursor);
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
leave:
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<tagfile>
|
||||
<compound kind="page">
|
||||
<name>mdb_copy_1</name>
|
||||
<title>mdb_copy - environment copy tool</title>
|
||||
<filename>mdb_copy.1</filename>
|
||||
</compound>
|
||||
<compound kind="page">
|
||||
<name>mdb_dump_1</name>
|
||||
<title>mdb_dump - environment export tool</title>
|
||||
<filename>mdb_dump.1</filename>
|
||||
</compound>
|
||||
<compound kind="page">
|
||||
<name>mdb_load_1</name>
|
||||
<title>mdb_load - environment import tool</title>
|
||||
<filename>mdb_load.1</filename>
|
||||
</compound>
|
||||
<compound kind="page">
|
||||
<name>mdb_stat_1</name>
|
||||
<title>mdb_stat - environment status tool</title>
|
||||
<filename>mdb_stat.1</filename>
|
||||
</compound>
|
||||
</tagfile>
|
||||
+9
-5
@@ -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);
|
||||
}
|
||||
|
||||
inline void queue_waiter_call(struct ll_queue* q, queue_threshold_callback_fn cb, void* arg,
|
||||
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; ///< хендл отложенного вызова (для отмены)
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+27
-20
@@ -5,31 +5,43 @@
|
||||
#include "memory_pool.h"
|
||||
#include "mem.h"
|
||||
|
||||
#define POOL_CANARY_OFF (pool->object_size)
|
||||
#define POOL_TAG_OFF (pool->object_size + 1)
|
||||
#define POOL_ALLOC_SIZE (pool->object_size + 2)
|
||||
#define POOL_CANARY_VAL 0xAA
|
||||
// Layout: [user_data: object_size] [canary: 4 bytes] [alloc_loc: pointer] [counter: 1 byte]
|
||||
#define POOL_CANARY_OFF (pool->object_size)
|
||||
#define POOL_LOC_OFF (pool->object_size + 4)
|
||||
#define POOL_COUNTER_OFF (pool->object_size + 4 + sizeof(const char*))
|
||||
#define POOL_ALLOC_SIZE (pool->object_size + 4 + sizeof(const char*) + 1)
|
||||
#define POOL_CANARY_VAL 0xDEADBEAF
|
||||
|
||||
static void pool_init_tags(struct memory_pool* pool, void* obj)
|
||||
static void pool_init_tags(struct memory_pool* pool, void* obj, const char* location)
|
||||
{
|
||||
uint8_t* canary = (uint8_t*)obj + POOL_CANARY_OFF;
|
||||
uint8_t* counter = (uint8_t*)obj + POOL_TAG_OFF;
|
||||
uint32_t* canary = (uint32_t*)((uint8_t*)obj + POOL_CANARY_OFF);
|
||||
const char** loc = (const char**)((uint8_t*)obj + POOL_LOC_OFF);
|
||||
uint8_t* counter = (uint8_t*)obj + POOL_COUNTER_OFF;
|
||||
*canary = POOL_CANARY_VAL;
|
||||
*loc = location;
|
||||
*counter = 1;
|
||||
}
|
||||
|
||||
static void pool_check_and_clear_tags(struct memory_pool* pool, void* obj, const char* location)
|
||||
{
|
||||
uint8_t* canary = (uint8_t*)obj + POOL_CANARY_OFF;
|
||||
uint8_t* counter = (uint8_t*)obj + POOL_TAG_OFF;
|
||||
uint32_t* canary = (uint32_t*)((uint8_t*)obj + POOL_CANARY_OFF);
|
||||
const char** loc = (const char**)((uint8_t*)obj + POOL_LOC_OFF);
|
||||
uint8_t* counter = (uint8_t*)obj + POOL_COUNTER_OFF;
|
||||
|
||||
if (*canary != POOL_CANARY_VAL) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "pool_free BUFFER OVERFLOW %p canary=0x%02x from %s! halting", obj, *canary, location);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "pool_free BUFFER OVERFLOW pool=%p name=%s sz=%zu allocs=%zu reuse=%zu free=%d",
|
||||
pool, pool->name ? pool->name : "?", pool->object_size, pool->allocations, pool->reuse_count, pool->free_count);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, " obj=%p alloc=%s free=%s canary=0x%08x expected=0x%08x",
|
||||
obj, *loc ? *loc : "(null)", location, *canary, POOL_CANARY_VAL);
|
||||
if (pool->object_size)
|
||||
log_dump(DEBUG_LEVEL_ERROR, DEBUG_CATEGORY_MEMORY, " user_data", obj, pool->object_size);
|
||||
volatile int _halt = 1;
|
||||
while (_halt) {}
|
||||
}
|
||||
if (*counter == 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "pool_free DOUBLE FREE %p from %s! halting", obj, location);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "pool_free DOUBLE FREE pool=%p name=%s sz=%zu allocs=%zu reuse=%zu alloc=%s free=%s",
|
||||
pool, pool->name ? pool->name : "?", pool->object_size, pool->allocations, pool->reuse_count,
|
||||
*loc ? *loc : "(null)", location);
|
||||
volatile int _halt = 1;
|
||||
while (_halt) {}
|
||||
}
|
||||
@@ -43,7 +55,7 @@ size_t memory_pool_get_total_free_blocks(void) {
|
||||
}
|
||||
|
||||
// Инициализировать пул памяти
|
||||
struct memory_pool* memory_pool_init(size_t object_size) {
|
||||
struct memory_pool* memory_pool_init(size_t object_size, const char* name) {
|
||||
struct memory_pool* pool = u_calloc(1, sizeof(struct memory_pool));
|
||||
if (!pool) {
|
||||
return NULL;
|
||||
@@ -54,6 +66,7 @@ struct memory_pool* memory_pool_init(size_t object_size) {
|
||||
pool->allocations = 0;
|
||||
pool->reuse_count = 0;
|
||||
pool->alloc_tag_counter = 1;
|
||||
pool->name = name;
|
||||
return pool;
|
||||
}
|
||||
|
||||
@@ -70,7 +83,7 @@ void* memory_pool_alloc_impl(struct memory_pool* pool, const char* location) {
|
||||
g_total_free--;
|
||||
pool->reuse_count++;
|
||||
memset(obj, 0, pool->object_size);
|
||||
pool_init_tags(pool, obj);
|
||||
pool_init_tags(pool, obj, location);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_alloc reused: %s, remaining=%zu", location, pool->free_count);
|
||||
return obj;
|
||||
}
|
||||
@@ -78,7 +91,7 @@ void* memory_pool_alloc_impl(struct memory_pool* pool, const char* location) {
|
||||
pool->allocations++;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_alloc: %s, total_allocs=%zu", location, pool->allocations);
|
||||
void* obj = u_calloc_impl(1, POOL_ALLOC_SIZE, location);
|
||||
if (obj) pool_init_tags(pool, obj);
|
||||
if (obj) pool_init_tags(pool, obj, location);
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -122,20 +135,14 @@ void memory_pool_destroy(struct memory_pool* pool) {
|
||||
return;
|
||||
}
|
||||
|
||||
int cnt=0;
|
||||
// Освободить все объекты в пуле
|
||||
void* obj = pool->free_head;
|
||||
while (obj) {
|
||||
void* next = *(void**)obj;
|
||||
g_total_free--;
|
||||
u_free(obj);
|
||||
cnt++;
|
||||
obj = next;
|
||||
}
|
||||
|
||||
// if (cnt>MEMORY_POOL_MAX_FREE)
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "pool_destroy: allocations=%zu, reuse=%zu, current pool size=%d", pool->allocations, pool->reuse_count, cnt);
|
||||
|
||||
pool->free_head = NULL;
|
||||
pool->free_count = 0;
|
||||
u_free(pool);
|
||||
|
||||
+2
-1
@@ -18,11 +18,12 @@ struct memory_pool {
|
||||
size_t allocations; // Общее количество аллокаций (включая новые malloc)
|
||||
size_t reuse_count; // Количество повторных использований из пула
|
||||
uint8_t alloc_tag_counter; // монотонный счётчик для детекции double-free (1 байт)
|
||||
const char* name; // Имя пула для диагностики (напр. "data_pool", "pkt_pool")
|
||||
};
|
||||
|
||||
// сам пул:
|
||||
// если используем с элементами ll_queue то не забываем что к object_size надо прибавить sizeof(struct ll_entry)
|
||||
struct memory_pool* memory_pool_init(size_t object_size);
|
||||
struct memory_pool* memory_pool_init(size_t object_size, const char* name);
|
||||
void memory_pool_destroy(struct memory_pool* pool);
|
||||
|
||||
// элементы пула:
|
||||
|
||||
@@ -30,6 +30,18 @@
|
||||
// POSIX functions missing on Windows
|
||||
#define strcasecmp _stricmp
|
||||
#define strncasecmp _strnicmp
|
||||
|
||||
// memmem — not available on Windows
|
||||
#define memmem compat_memmem
|
||||
static inline void* compat_memmem(const void* h, size_t hl, const void* n, size_t nl) {
|
||||
if (nl == 0) return (void*)h;
|
||||
if (hl < nl) return NULL;
|
||||
const unsigned char* hay = (const unsigned char*)h;
|
||||
const unsigned char* ndl = (const unsigned char*)n;
|
||||
for (size_t i = 0; i <= hl - nl; i++)
|
||||
if (hay[i] == ndl[0] && memcmp(hay + i, ndl, nl) == 0) return (void*)(hay + i);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// poll() and pollfd are already defined in winsock2.h on MSYS2 UCRT64
|
||||
// Only define if not already present
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+107
-46
@@ -13,6 +13,9 @@
|
||||
#ifndef MSG_NOSIGNAL
|
||||
#define MSG_NOSIGNAL 0
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
#define SHUT_WR SD_SEND
|
||||
#endif
|
||||
|
||||
static void read_cb(socket_t sock, void* arg);
|
||||
static void write_cb(socket_t sock, void* arg);
|
||||
@@ -20,18 +23,19 @@ static void error_cb(socket_t sock, void* arg);
|
||||
static void resume_read_cb(struct ll_queue* q, void* arg);
|
||||
static void fin_deferred_cb(struct ll_queue* q, void* arg);
|
||||
static void write_queue_fetch_cb(struct ll_queue* q, void* arg);
|
||||
static void flush_write_buf(struct tcp_conn* tc);
|
||||
static int flush_write_buf(struct tcp_conn* tc);
|
||||
static void tcp_conn_handle_error(struct tcp_conn* tc, int err);
|
||||
static const uint8_t tcp_fin_sentinel;
|
||||
|
||||
struct tcp_conn* tcp_conn_create(
|
||||
struct UASYNC* ua, socket_t sock,
|
||||
size_t entry_data_size, size_t write_chunk_size,
|
||||
int read_high_water, int read_low_water,
|
||||
int read_high_water, int read_low_water, int rcvbuf_size,
|
||||
void (*on_fin)(struct tcp_conn* tc, void* arg),
|
||||
void (*on_error)(struct tcp_conn* tc, int err, void* arg),
|
||||
void* arg)
|
||||
{
|
||||
if (!ua || sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: invalid args ua=%p", 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; }
|
||||
@@ -42,14 +46,15 @@ struct tcp_conn* tcp_conn_create(
|
||||
tc->write_chunk_size = write_chunk_size;
|
||||
tc->read_high_water = read_high_water;
|
||||
tc->read_low_water = read_low_water;
|
||||
tc->rcvbuf_size = rcvbuf_size;
|
||||
tc->on_fin = on_fin;
|
||||
tc->on_error = on_error;
|
||||
tc->arg = arg;
|
||||
|
||||
tc->entry_pool = memory_pool_init(sizeof(struct ll_entry));
|
||||
tc->entry_pool = memory_pool_init(sizeof(struct ll_entry), "entry_pool");
|
||||
{
|
||||
size_t ds = entry_data_size > write_chunk_size ? entry_data_size : write_chunk_size;
|
||||
tc->data_pool = memory_pool_init(ds);
|
||||
tc->data_pool = memory_pool_init(ds, "data_pool");
|
||||
}
|
||||
if (!tc->entry_pool || !tc->data_pool) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: memory_pool_init failed");
|
||||
@@ -84,20 +89,36 @@ struct tcp_conn* tcp_conn_create(
|
||||
}
|
||||
tc->write_monitor = 1;
|
||||
|
||||
if (rcvbuf_size > 0) {
|
||||
if (socket_set_buffers(sock, 0, rcvbuf_size) < 0)
|
||||
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: socket_set_buffers rcvbuf=%d failed fd=%d", rcvbuf_size, (int)sock);
|
||||
else
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: socket_set_buffers rcvbuf=%d ok fd=%d", rcvbuf_size, (int)sock);
|
||||
}
|
||||
|
||||
{
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t alen = sizeof(addr);
|
||||
if (getpeername(sock, (struct sockaddr*)&addr, &alen) == 0) tc->connected = 1;
|
||||
}
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: fd=%d entry=%zu chunk=%zu hw=%d lw=%d connected=%d",
|
||||
(int)sock, entry_data_size, write_chunk_size, read_high_water, read_low_water, tc->connected);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: fd=%d entry=%zu chunk=%zu hw=%d lw=%d rcvbuf=%d connected=%d",
|
||||
(int)sock, entry_data_size, write_chunk_size, read_high_water, read_low_water, rcvbuf_size, tc->connected);
|
||||
return tc;
|
||||
}
|
||||
|
||||
static void tcp_conn_deferred_free(void* arg) {
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
if (tc->entry_pool) memory_pool_destroy(tc->entry_pool);
|
||||
if (tc->data_pool) memory_pool_destroy(tc->data_pool);
|
||||
u_free(tc);
|
||||
}
|
||||
|
||||
void tcp_conn_destroy(struct tcp_conn* tc) {
|
||||
if (!tc) return;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_conn_destroy: fd=%d connected=%d error=%d fin_remote=%d fin_local=%d closed=%d",
|
||||
if (tc->destroyed) return;
|
||||
tc->destroyed = 1;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_destroy: fd=%d connected=%d error=%d fin_remote=%d fin_local=%d closed=%d",
|
||||
(int)tc->sock, tc->connected, tc->error, tc->fin_remote, tc->fin_local, tc->closed);
|
||||
|
||||
if (tc->socket_id) {
|
||||
@@ -118,14 +139,53 @@ 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)
|
||||
{
|
||||
tc->error = 1;
|
||||
|
||||
queue_waiter_cancel(tc->read_queue, &tc->read_waiter);
|
||||
queue_set_empty_callback(tc->read_queue, NULL, NULL);
|
||||
queue_set_callback(tc->read_queue, NULL, NULL);
|
||||
|
||||
if (tc->sock != SOCKET_INVALID) {
|
||||
while (1) {
|
||||
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
|
||||
if (!e) break;
|
||||
uint8_t* buf = memory_pool_alloc(tc->data_pool);
|
||||
if (!buf) { queue_entry_free(e); break; }
|
||||
ssize_t n = recv(tc->sock, buf, tc->entry_data_size, 0);
|
||||
if (n > 0) { e->dgram = buf; e->len = (uint16_t)n; queue_data_put(tc->read_queue, e); }
|
||||
else { memory_pool_free(tc->data_pool, buf); queue_entry_free(e); break; }
|
||||
}
|
||||
}
|
||||
|
||||
if (tc->sock != SOCKET_INVALID) {
|
||||
uasync_remove_socket_t(tc->ua, tc->sock);
|
||||
tc->socket_id = NULL;
|
||||
#if HAS_EPOLL
|
||||
if (tc->ua && tc->ua->use_epoll && tc->ua->epoll_fd >= 0)
|
||||
epoll_ctl(tc->ua->epoll_fd, EPOLL_CTL_DEL, (int)tc->sock, NULL);
|
||||
#endif
|
||||
socket_close_wrapper(tc->sock);
|
||||
tc->sock = SOCKET_INVALID; // ДО on_error: read/write в том же epoll event увидят INVALID
|
||||
}
|
||||
|
||||
queue_set_callback(tc->write_queue, NULL, NULL);
|
||||
|
||||
if (tc->on_error) {
|
||||
void (*on_err)(struct tcp_conn*, int, void*) = tc->on_error;
|
||||
void* arg = tc->arg;
|
||||
tc->on_error = NULL;
|
||||
on_err(tc, err, arg);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
@@ -135,7 +195,7 @@ void tcp_conn_destroy(struct tcp_conn* tc) {
|
||||
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);
|
||||
@@ -152,8 +212,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);
|
||||
@@ -162,7 +224,7 @@ static void read_cb(socket_t sock, void* arg) {
|
||||
memory_pool_free(tc->data_pool, buf);
|
||||
queue_entry_free(e);
|
||||
tc->fin_remote = 1;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_io: FIN fd=%d", (int)tc->sock);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: FIN fd=%d", (int)tc->sock);
|
||||
uasync_set_socket_read(tc->ua, tc->socket_id, 0);
|
||||
queue_waiter_cancel(tc->read_queue, &tc->read_waiter);
|
||||
if (tc->read_queue->count == 0) {
|
||||
@@ -174,9 +236,8 @@ static void read_cb(socket_t sock, void* arg) {
|
||||
memory_pool_free(tc->data_pool, buf);
|
||||
queue_entry_free(e);
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return;
|
||||
tc->error = 1;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: recv error fd=%d errno=%d", (int)tc->sock, errno);
|
||||
if (tc->on_error) tc->on_error(tc, errno, tc->arg);
|
||||
tcp_conn_handle_error(tc, errno);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +259,7 @@ static void fin_deferred_cb(struct ll_queue* q, void* arg) {
|
||||
// Запись в сокет
|
||||
// ====================================================================
|
||||
|
||||
static void flush_write_buf(struct tcp_conn* tc) {
|
||||
static int flush_write_buf(struct tcp_conn* tc) {
|
||||
while (tc->write_buf && tc->write_offset < tc->write_len) {
|
||||
ssize_t n = send(tc->sock, tc->write_buf + tc->write_offset, tc->write_len - tc->write_offset, MSG_NOSIGNAL);
|
||||
if (n > 0) {
|
||||
@@ -209,16 +270,16 @@ static void flush_write_buf(struct tcp_conn* tc) {
|
||||
tc->write_len = 0;
|
||||
tc->write_offset = 0;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: flush_wbuf done fd=%d", (int)tc->sock);
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) return;
|
||||
tc->error = 1;
|
||||
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) return 0;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: send error fd=%d errno=%d", (int)tc->sock, errno);
|
||||
if (tc->on_error) tc->on_error(tc, errno, tc->arg);
|
||||
return;
|
||||
tcp_conn_handle_error(tc, errno);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
|
||||
@@ -227,7 +288,7 @@ static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
|
||||
if (tc->error || tc->closed) { DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch skip fd=%d error=%d closed=%d", (int)tc->sock, tc->error, tc->closed); return; }
|
||||
|
||||
if (tc->write_buf) {
|
||||
flush_write_buf(tc);
|
||||
if (flush_write_buf(tc)) return;
|
||||
if (tc->write_buf) return; // остался остаток, ждём write_cb
|
||||
}
|
||||
|
||||
@@ -249,14 +310,14 @@ static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
|
||||
|
||||
if (e->len == 0) {
|
||||
if (!e->dgram) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_io: CLOSE fd=%d", (int)tc->sock);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: CLOSE fd=%d", (int)tc->sock);
|
||||
queue_entry_free(e); queue_resume_callback(q);
|
||||
uasync_remove_socket_t(tc->ua, tc->sock);
|
||||
socket_close_wrapper(tc->sock);
|
||||
tc->socket_id = NULL; tc->sock = SOCKET_INVALID; tc->closed = 1;
|
||||
if (tc->on_closed) tc->on_closed(tc, tc->arg);
|
||||
} else if (e->dgram == &tcp_fin_sentinel) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_io: FIN fd=%d", (int)tc->sock);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: FIN fd=%d", (int)tc->sock);
|
||||
queue_entry_free(e); queue_resume_callback(q);
|
||||
shutdown(tc->sock, SHUT_WR);
|
||||
tc->fin_local = 1;
|
||||
@@ -278,9 +339,9 @@ static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
|
||||
tc->write_buf = memory_pool_alloc(tc->data_pool);
|
||||
if (!tc->write_buf) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: write_buf alloc failed fd=%d", (int)tc->sock);
|
||||
tc->error = 1;
|
||||
memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
tcp_conn_handle_error(tc, ENOMEM);
|
||||
return;
|
||||
}
|
||||
memcpy(tc->write_buf, e->dgram + n, rem);
|
||||
@@ -295,9 +356,9 @@ static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
|
||||
tc->write_buf = memory_pool_alloc(tc->data_pool);
|
||||
if (!tc->write_buf) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: write_buf alloc failed fd=%d", (int)tc->sock);
|
||||
tc->error = 1;
|
||||
memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
tcp_conn_handle_error(tc, ENOMEM);
|
||||
return;
|
||||
}
|
||||
memcpy(tc->write_buf, e->dgram, e->len);
|
||||
@@ -309,37 +370,38 @@ static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
|
||||
tc->write_monitor = 1;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch POLLOUT ON fd=%d (EAGAIN, wbuf=%zu bytes)", (int)tc->sock, tc->write_len);
|
||||
} else {
|
||||
tc->error = 1;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: send error fd=%d errno=%d", (int)tc->sock, errno);
|
||||
if (tc->on_error) tc->on_error(tc, errno, tc->arg);
|
||||
memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
tcp_conn_handle_error(tc, errno);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
socklen_t len = sizeof(err);
|
||||
if (getsockopt(tc->sock, SOL_SOCKET, SO_ERROR, &err, &len) == 0 && err == 0) {
|
||||
if (getsockopt(tc->sock, SOL_SOCKET, SO_ERROR, (char*)&err, &len) == 0 && err == 0) {
|
||||
tc->connected = 1;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_io: connect ok fd=%d", (int)tc->sock);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: connect ok fd=%d", (int)tc->sock);
|
||||
} else {
|
||||
tc->error = 1;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: write_cb connect fail fd=%d err=%d wmon=%d (POLLOUT still active)", (int)tc->sock, err, tc->write_monitor);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: connect fail fd=%d err=%d", (int)tc->sock, err);
|
||||
if (tc->on_error) tc->on_error(tc, err ? err : -1, tc->arg);
|
||||
tcp_conn_handle_error(tc, err ? err : -1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
flush_write_buf(tc);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -352,10 +414,9 @@ static void error_cb(socket_t sock, void* arg) {
|
||||
(void)sock;
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
if (!tc || tc->sock == SOCKET_INVALID) return;
|
||||
tc->error = 1;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: error_cb fd=%d connected=%d fin_remote=%d wmon=%d", (int)tc->sock, tc->connected, tc->fin_remote, tc->write_monitor);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: async error fd=%d", (int)tc->sock);
|
||||
if (tc->on_error) tc->on_error(tc, -1, tc->arg);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: async error fd=%d connected=%d err=%d fin_r=%d fin_l=%d closed=%d wmon=%d",
|
||||
(int)tc->sock, tc->connected, tc->error, tc->fin_remote, tc->fin_local, tc->closed, tc->write_monitor);
|
||||
tcp_conn_handle_error(tc, -1);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
@@ -369,7 +430,7 @@ int tcp_conn_push_fin(struct tcp_conn* tc) {
|
||||
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
|
||||
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin entry_pool exhausted fd=%d", (int)tc->sock); return -1; }
|
||||
e->dgram = (uint8_t*)&tcp_fin_sentinel; e->len = 0;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin fd=%d wq=%d", (int)tc->sock, tc->write_queue->count + 1);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin fd=%d wq=%d", (int)tc->sock, tc->write_queue->count + 1);
|
||||
queue_data_put(tc->write_queue, e);
|
||||
return 0;
|
||||
}
|
||||
@@ -380,7 +441,7 @@ int tcp_conn_push_close(struct tcp_conn* tc) {
|
||||
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
|
||||
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close entry_pool exhausted fd=%d", (int)tc->sock); return -1; }
|
||||
e->dgram = NULL; e->len = 0;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close fd=%d wq=%d", (int)tc->sock, tc->write_queue->count + 1);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close fd=%d wq=%d", (int)tc->sock, tc->write_queue->count + 1);
|
||||
queue_data_put(tc->write_queue, e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
+24
-38
@@ -1,40 +1,23 @@
|
||||
// tcp_io.h — управление TCP-соединением через uasync + ll_queue
|
||||
//
|
||||
// Один tcp_conn = одно TCP-соединение. Две очереди: read_queue (сокет → данные) и
|
||||
// write_queue (данные → сокет). Обе работают через автозабор ll_queue (deferred).
|
||||
// Один tcp_conn = одно TCP-соединение. Два пула:
|
||||
// entry_pool — struct ll_entry (без inline data)
|
||||
// data_pool — буферы (max(entry_data_size, write_chunk_size))
|
||||
//
|
||||
// Пул-аллокация:
|
||||
// entry_pool — только struct ll_entry (без inline data)
|
||||
// data_pool — буферы данных чтения/записи (max(entry_data_size, write_chunk_size))
|
||||
// Данные и структуры аллоцируются раздельно — не копируются при recv/send.
|
||||
// Чтение: read_cb → recv() в data_pool → entry в read_queue → callback внешнего кода.
|
||||
// high_water/low_water управляют backpressure (EPOLLIN on/off).
|
||||
// FIN (recv==0) → on_fin (сразу или через empty_callback после дренажа очереди).
|
||||
//
|
||||
// Чтение:
|
||||
// read_cb → memory_pool_alloc(read_pool) → recv() прямо в буфер → entry в read_queue
|
||||
// read_queue автозабор(deferred) → внешний коллбэк (например, отправка в ETCP)
|
||||
// high_water → пауза чтения (убираем EPOLLIN), low_water/waiter → возобновление
|
||||
// FIN: если read_queue не пуст → empty_callback откладывает on_fin; иначе сразу
|
||||
// Запись: внешний код кладёт entry в write_queue через queue_data_put().
|
||||
// write_queue_fetch_cb (deferred) → send(), при EAGAIN → write_buf + EPOLLOUT.
|
||||
// Всё отправлено → on_flushed.
|
||||
//
|
||||
// Запись:
|
||||
// Внешний код: tcp_conn_push_write(data, len) → аллокация через write_pool → entry в write_queue
|
||||
// write_queue автозабор(deferred): fetch_cb → send() в сокет
|
||||
// EAGAIN → write_buf (из data_pool) + EPOLLOUT ON → write_cb досылает → resume автозабора
|
||||
// Всё отправлено → EPOLLOUT OFF + on_flushed (если установлен)
|
||||
// FIN/Close: tcp_conn_push_fin/close ставят сентинел в write_queue.
|
||||
// FIN → shutdown(SHUT_WR) → on_fin_sent
|
||||
// CLOSE → close сокета → on_closed
|
||||
//
|
||||
// FIN / Close через очередь отправки:
|
||||
// tcp_conn_push_fin(tc) — ставит FIN в write_queue (dgram=&sentinel, len=0)
|
||||
// tcp_conn_push_close(tc) — ставит CLOSE в write_queue (dgram=NULL, len=0)
|
||||
// Оба сигнала обрабатываются после всех предшествующих данных в очереди:
|
||||
// FIN → shutdown(SHUT_WR), стоп чтения, on_fin_sent
|
||||
// CLOSE → close сокета, on_closed
|
||||
// После FIN входящие данные отбрасываются (read_cb → discard).
|
||||
// После CLOSE сокет закрыт, tc->closed=1, дальнейшая отправка невозможна.
|
||||
//
|
||||
// Connect:
|
||||
// tcp_conn_create регистрирует сокет с read_cb + write_cb в uasync
|
||||
// После create вызывается connect() (неблокирующий, EINPROGRESS)
|
||||
// write_cb детектит завершение connect через getsockopt(SO_ERROR)
|
||||
// getpeername() сразу после create проверяет pre-connected сокеты (socketpair)
|
||||
// До connect данные копятся в write_queue (автозабор не пытается send на unconnected сокет)
|
||||
// Ошибка: любая ошибка сокета → tcp_conn_handle_error → close + null коллбэков → on_error.
|
||||
// Реализация on_error обязана вызвать tcp_conn_destroy. После возврата tc недействителен.
|
||||
|
||||
#ifndef TCP_IO_H
|
||||
#define TCP_IO_H
|
||||
@@ -54,6 +37,7 @@ struct tcp_conn {
|
||||
|
||||
int read_high_water;
|
||||
int read_low_water;
|
||||
int rcvbuf_size;
|
||||
uint8_t read_paused;
|
||||
|
||||
uint8_t write_monitor; // 1 = EPOLLOUT активен
|
||||
@@ -62,8 +46,9 @@ 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
|
||||
|
||||
// Частичная отправка (из write_pool, не в очереди — досылается первой)
|
||||
// Частичная отправка (из data_pool, не в очереди — досылается первой)
|
||||
uint8_t* write_buf;
|
||||
size_t write_len;
|
||||
size_t write_offset;
|
||||
@@ -79,7 +64,7 @@ struct tcp_conn {
|
||||
// Коллбэки
|
||||
void (*on_fin)(struct tcp_conn* tc, void* arg); // FIN получен от удалённой стороны (fin_remote=1)
|
||||
void (*on_fin_sent)(struct tcp_conn* tc, void* arg); // FIN отправлен удалённой стороне (fin_local=1)
|
||||
void (*on_error)(struct tcp_conn* tc, int err, void* arg); // ошибка сокета
|
||||
void (*on_error)(struct tcp_conn* tc, int err, void* arg); // фатальная ошибка (вызывается после close сокета; обязан вызвать tcp_conn_destroy)
|
||||
void (*on_flushed)(struct tcp_conn* tc, void* arg); // все данные записи отправлены (write_queue + write_buf пусты)
|
||||
void (*on_closed)(struct tcp_conn* tc, void* arg); // сокет закрыт через очередь (после close)
|
||||
void* arg;
|
||||
@@ -92,21 +77,22 @@ struct tcp_conn* tcp_conn_create(
|
||||
size_t write_chunk_size,
|
||||
int read_high_water,
|
||||
int read_low_water,
|
||||
int rcvbuf_size,
|
||||
void (*on_fin)(struct tcp_conn* tc, void* arg),
|
||||
void (*on_error)(struct tcp_conn* tc, int err, void* arg),
|
||||
void* arg);
|
||||
|
||||
// Дренирует очереди, закрывает сокет, уничтожает пулы, освобождает tc.
|
||||
void tcp_conn_destroy(struct tcp_conn* tc);
|
||||
|
||||
// Внешний код пишет данные в tc->write_queue напрямую (queue_data_put).
|
||||
// Автозабор write_queue (deferred) сам отправляет когда сокет готов.
|
||||
// Перед push проверять порог: queue_set_threshold в tcp_conn_create (32 entries).
|
||||
// При заполнении — queue_waiter_wait на освобождение.
|
||||
// Запись: queue_data_put(tc->write_queue, entry) напрямую.
|
||||
// Автозабор (deferred) сам шлёт когда сокет готов.
|
||||
// Порог: 32 записи, при заполнении queue_waiter_wait.
|
||||
|
||||
// Поставить сигналы в очередь отправки. Все данные в очереди перед сигналом
|
||||
// будут отправлены до его обработки. Кодирование: dgram=NULL → close, len=0+dgram!=NULL → FIN.
|
||||
// tcp_conn_push_fin(tc) — FIN: после отправки предшествующих данных вызывает
|
||||
// shutdown(SHUT_WR), останавливает чтение, вызывает on_fin_sent.
|
||||
// shutdown(SHUT_WR) и on_fin_sent.
|
||||
// Повторный вызов игнорируется (fin_sent уже установлен).
|
||||
// tcp_conn_push_close(tc) — CLOSE: после отправки предшествующих данных закрывает
|
||||
// сокет (close), вызывает on_closed.
|
||||
|
||||
+611
-592
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,29 @@
|
||||
UTUN
|
||||
|
||||
Идея: объединить узлы в единую локальную сеть.
|
||||
Чтобы узлы сами находили оптимальные линки между собой, пробивали NAT где это можно, где нельзя - выбирали посредника с хорошей связью; чтобы подсто было добавлять новые узлы и адреса узлов сразу виделись во всей сети.
|
||||
|
||||
Похож на libp2p но трафик превращает в случайную последовательность кторую сложно классифицировать
|
||||
|
||||
Потенциальные варианты использования:
|
||||
- локальная сеть. хочу объединить офис, сотрудников (включая их работчие подсети), телефоны, офисы, сервер доступа итд в одно адресное пространство.
|
||||
- чат с файлообменником. узлы сети - это мемберы группы и одновременно сети.
|
||||
- CDN. узлы - потребители контента - это мемберы сети, которые распространяют трафик.
|
||||
|
||||
То есть задача utun - обеспечить связность группы узлов и адаптацию работы всей сети к подключению-отключении отдельных узлов.
|
||||
|
||||
Механизмы:
|
||||
1. Пробитие nat.
|
||||
2. автообновление списка узлов и их адресов для подключения
|
||||
3. шифрование трафика и маскировка handshake.
|
||||
handshake просиходит без сигнатур по которым можно опреедлить тип сервиса, весь трафик выглядлит как случайный набор байт случайного размера
|
||||
4. многоканальные подключения, агрегирование каналов, исключение мёртвых каналов
|
||||
подключиться к узлу можно через несколько линков. UTUN будет объединять пропускную способность каналов, наблюдая за состоянием каналов, не допуская перегрузки и обеспечивая быстрые переповторы через другие каналы при потерях или большой задержке.
|
||||
5. динамическая маршрутизация. Узлы могут назначать себе IP адреса и при подключении к сети анонсировать свои адреса/подсети.
|
||||
это максимально упрощает добавление нового узла в сеть.
|
||||
6. автоматический роутинг между узлами: узлы маршрутизируют трафик между собой.
|
||||
используя статические подключения, прописанные в конфиге.
|
||||
7. прямые подключения и поиск лучших подключений
|
||||
узлы могут подключаться друг к другу напрямую, либо если оба узла за строгим NAT - выбирать оптимального посредника.
|
||||
Узлы определяют свой тип подключения и NAT. Например у ростелеком - EIM NAT который можно использовать и для входящих подклюений.
|
||||
Можно использовать IPv6.
|
||||
+14
-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,10 +28,16 @@ utun_CORE_SOURCES = \
|
||||
etcp_dump.c \
|
||||
secure_channel.c \
|
||||
crc32.c \
|
||||
stcp_link.c \
|
||||
stcp.c \
|
||||
stcp_server.c \
|
||||
stcp_client.c \
|
||||
pkt_normalizer.c \
|
||||
packet_dump.c \
|
||||
etcp_api.c \
|
||||
etcp_connect.c \
|
||||
control_server.c \
|
||||
msg_transport.c \
|
||||
firewall.c \
|
||||
eim_nat.c \
|
||||
nat_transport.c \
|
||||
@@ -38,6 +46,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 \
|
||||
@@ -47,109 +56,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
|
||||
|
||||
+74
-11
@@ -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;
|
||||
}
|
||||
|
||||
@@ -611,6 +650,8 @@ static int parse_server(const char *key, const char *value, struct CFG_SERVER *s
|
||||
srv->type = CFG_SERVER_TYPE_NAT;
|
||||
} else if (strcmp(value, "private") == 0) {
|
||||
srv->type = CFG_SERVER_TYPE_PRIVATE;
|
||||
} else if (strcmp(value, "local") == 0) {
|
||||
srv->type = CFG_SERVER_TYPE_LOCAL;
|
||||
} else {
|
||||
srv->type = CFG_SERVER_TYPE_UNKNOWN;
|
||||
}
|
||||
@@ -624,7 +665,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 +738,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,7 +767,9 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
cfg->global.name[0] = '\0';
|
||||
cfg->global.keepalive_timeout = 2000; // Default 2 seconds
|
||||
cfg->global.keepalive_interval = 200; // Default 0.2 s
|
||||
cfg->global.bbr_max_cwnd = 100000; // Default 100KB
|
||||
cfg->global.keepalive_adaptive = 1; // Default: adaptive enabled
|
||||
cfg->global.bbr_max_cwnd = 1048576; // Default 1MB (was INFLIGHT_LIM_MAX)
|
||||
cfg->global.tcp_recv_buf = 0; // 0 = OS default
|
||||
cfg->global.firewall_rules = NULL;
|
||||
cfg->global.firewall_rule_count = 0;
|
||||
cfg->global.firewall_bypass_all = 0;
|
||||
@@ -869,8 +918,14 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
break;
|
||||
case SECTION_TCP_PROXY_SERVER:
|
||||
cfg->global.tcp_proxy_server_enabled = 1;
|
||||
// tcp_proxy_server section has no options — its presence alone enables it
|
||||
(void)key; (void)value;
|
||||
if (strcmp(key, "tcp_recv_buf") == 0) {
|
||||
cfg->global.tcp_recv_buf = atoi(value);
|
||||
}
|
||||
break;
|
||||
case SECTION_MSG_TRANSPORT:
|
||||
if (parse_msg_transport(key, value, &cfg->global, filename, line_num) < 0) {
|
||||
// parse_msg_transport already printed the error
|
||||
}
|
||||
break;
|
||||
default:
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Key outside section: %s", filename, line_num, key);
|
||||
@@ -888,10 +943,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;
|
||||
|
||||
+14
-3
@@ -24,9 +24,10 @@ struct IP {
|
||||
};
|
||||
|
||||
#define CFG_SERVER_TYPE_UNKNOWN 0
|
||||
#define CFG_SERVER_TYPE_PUBLIC 1
|
||||
#define CFG_SERVER_TYPE_NAT 2
|
||||
#define CFG_SERVER_TYPE_PUBLIC 1
|
||||
#define CFG_SERVER_TYPE_NAT 2
|
||||
#define CFG_SERVER_TYPE_PRIVATE 3
|
||||
#define CFG_SERVER_TYPE_LOCAL 4
|
||||
|
||||
#define CFG_IPV6_MODE_NONE 0 // normal explicit address
|
||||
#define CFG_IPV6_MODE_TEMPORARY 1 // temporary IPv6 from interface
|
||||
@@ -40,6 +41,7 @@ struct CFG_SERVER {
|
||||
int so_mark;
|
||||
int fib; // FreeBSD FIB (routing table), -1 = don't set
|
||||
uint8_t type; // public/nat/private
|
||||
uint8_t transport; // 0=udp (default), 1=tcp
|
||||
uint8_t ipv6_mode; // CFG_IPV6_MODE_*
|
||||
int mtu;
|
||||
uint8_t only_local; // 1 = only local connections, no forwarding
|
||||
@@ -78,7 +80,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 +96,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 +123,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,9 +164,14 @@ 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;
|
||||
int tcp_recv_buf; // SO_RCVBUF для TCP сокетов (0=default ОС)
|
||||
};
|
||||
|
||||
struct utun_config {
|
||||
|
||||
+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);
|
||||
|
||||
|
||||
+1041
File diff suppressed because it is too large.
Load diff
+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
|
||||
+247
-46
@@ -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;
|
||||
}
|
||||
|
||||
@@ -868,6 +985,7 @@ static void send_socket_list(struct control_server* server, struct control_clien
|
||||
info[i].ip[sizeof(info[i].ip) - 1] = '\0';
|
||||
info[i].port = 0;
|
||||
}
|
||||
info[i].config_type = sock->type;
|
||||
info[i].nat_type = sock->nat_type;
|
||||
|
||||
/* NAT address from socket (set when INIT_RESPONSE received) */
|
||||
@@ -882,24 +1000,21 @@ static void send_socket_list(struct control_server* server, struct control_clien
|
||||
inet_ntop(AF_INET6, &sin6->sin6_addr, info[i].nat_ip, sizeof(info[i].nat_ip));
|
||||
info[i].nat_port = ntohs(sin6->sin6_port);
|
||||
}
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NAT, "socket_list to gui: sock=%s config=%d nat_type=%d nat_ip=%s nat_port=%u",
|
||||
sock->name, sock->type, sock->nat_type,
|
||||
info[i].nat_ip[0] ? info[i].nat_ip : "none", info[i].nat_port);
|
||||
|
||||
sock = sock->next;
|
||||
}
|
||||
|
||||
if (server->log_file) {
|
||||
log_hex_data(server->log_file, "TX", buffer, rsp_size);
|
||||
fprintf(server->log_file, "%llu: [LOG] Sent RSP_SOCKET_LIST count=%d\n",
|
||||
(unsigned long long)get_timestamp_ms(), count);
|
||||
(unsigned long long)get_timestamp_ms(), count);
|
||||
fflush(server->log_file);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
send(client->fd, (const char*)buffer, rsp_size, 0);
|
||||
#else
|
||||
send(client->fd, buffer, rsp_size, 0);
|
||||
#endif
|
||||
|
||||
u_free(buffer);
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
}
|
||||
|
||||
static void send_metrics(struct control_server* server, struct control_client* client, uint8_t seq_id) {
|
||||
@@ -1078,6 +1193,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 +1274,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 +1302,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 +1341,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 +1360,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
|
||||
|
||||
+36
-1
@@ -355,7 +355,7 @@ struct dummynet* dummynet_create(struct UASYNC* ua, const char* bind_ip, uint16_
|
||||
dn->listen_port = listen_port;
|
||||
|
||||
/* Создаём пул для пакетов */
|
||||
dn->pkt_pool = memory_pool_init(sizeof(struct ll_entry) + sizeof(struct dummynet_pkt));
|
||||
dn->pkt_pool = memory_pool_init(sizeof(struct ll_entry) + sizeof(struct dummynet_pkt), "pkt_pool");
|
||||
if (!dn->pkt_pool) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Failed to create packet pool");
|
||||
u_free(dn);
|
||||
@@ -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
|
||||
+89
-42
@@ -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++) {
|
||||
@@ -205,8 +206,9 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
|
||||
etcp->input_wait_ack = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, 0, 4, "input_wait_ack"); // Hash for wait_ack
|
||||
etcp->recv_q = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, 0, 4, "recv_q"); // Hash for recv_q
|
||||
etcp->ack_q = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, 0, 4, "ack_q");
|
||||
etcp->inflight_pool = memory_pool_init(sizeof(struct INFLIGHT_PACKET));
|
||||
etcp->io_pool = memory_pool_init(sizeof(struct ETCP_FRAGMENT));
|
||||
etcp->inflight_pool = memory_pool_init(sizeof(struct INFLIGHT_PACKET), "inflight_pool");
|
||||
etcp->io_pool = memory_pool_init(sizeof(struct ETCP_FRAGMENT), "io_pool");
|
||||
etcp->max_inflight = (uint32_t)instance->config->global.bbr_max_cwnd;
|
||||
etcp->optimal_inflight=100000;
|
||||
etcp->initialized=0;
|
||||
etcp->links_up=0;
|
||||
@@ -261,12 +263,12 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
|
||||
|
||||
static void etcp_on_up(struct ETCP_CONN* etcp) {
|
||||
// if (!etcp->initialized) return;
|
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "[%s] initialized=%d", etcp->log_name, etcp->initialized);
|
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "[%s] UP links_up=%d initialized=%d", etcp->log_name, etcp->links_up, etcp->initialized);
|
||||
if (etcp->up_cbk) etcp->up_cbk(etcp, etcp->up_arg);
|
||||
}
|
||||
|
||||
static void etcp_on_down(struct ETCP_CONN* etcp) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "[%s]", etcp->log_name);
|
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "[%s] DOWN links_up=%d", etcp->log_name, etcp->links_up);
|
||||
if (etcp->down_cbk) etcp->down_cbk(etcp, etcp->down_arg);
|
||||
}
|
||||
|
||||
@@ -302,23 +304,26 @@ void etcp_connection_close(struct ETCP_CONN* etcp) {
|
||||
etcp->normalizer = NULL;
|
||||
}
|
||||
|
||||
// Drain and free all queues using helper functions
|
||||
// Clear links BEFORE draining queues: etcp_link_close calls
|
||||
// etcp_conn_on_inflight_lim_changed -> input_queue_try_resume
|
||||
// which accesses input_send_q, input_queue, input_wait_ack.
|
||||
if (etcp->links) {
|
||||
struct ETCP_LINK* link = etcp->links;
|
||||
while (link) {
|
||||
struct ETCP_LINK* next = link->next;
|
||||
etcp_link_close(link);
|
||||
link = next;
|
||||
}
|
||||
etcp->links = NULL;
|
||||
}
|
||||
|
||||
// Drain and free all queues
|
||||
drain_and_free_queue(&etcp->input_queue);
|
||||
drain_and_free_queue(&etcp->output_queue);
|
||||
drain_and_free_queue(&etcp->input_send_q);
|
||||
drain_and_free_queue(&etcp->input_wait_ack);
|
||||
drain_and_free_queue(&etcp->recv_q);
|
||||
drain_and_free_queue(&etcp->ack_q);
|
||||
|
||||
// Drain and free ack_q (contains ACK_PACKET from ack_pool - special handling)
|
||||
if (etcp->ack_q) {
|
||||
struct ACK_PACKET* pkt;
|
||||
while ((pkt = (struct ACK_PACKET*)queue_data_get(etcp->ack_q)) != NULL) {
|
||||
queue_entry_free((struct ll_entry*)pkt);
|
||||
}
|
||||
queue_free(etcp->ack_q);
|
||||
etcp->ack_q = NULL;
|
||||
}
|
||||
|
||||
// Free memory pools after all elements are returned
|
||||
if (etcp->inflight_pool) {
|
||||
@@ -331,19 +336,16 @@ void etcp_connection_close(struct ETCP_CONN* etcp) {
|
||||
etcp->io_pool = NULL;
|
||||
}
|
||||
|
||||
// Clear links list safely
|
||||
if (etcp->links) {
|
||||
struct ETCP_LINK* link = etcp->links;
|
||||
while (link) {
|
||||
struct ETCP_LINK* next = link->next;
|
||||
etcp_link_close(link);
|
||||
link = next;
|
||||
}
|
||||
etcp->links = NULL;
|
||||
}
|
||||
|
||||
u_free(etcp->name);
|
||||
|
||||
{ struct UTUN_INSTANCE* inst = etcp->instance;
|
||||
if (inst && inst->connections) {
|
||||
struct ETCP_CONN** pp = &inst->connections;
|
||||
while (*pp && *pp != etcp) pp = &(*pp)->next;
|
||||
if (*pp) { *pp = etcp->next; if (inst->connections_count) inst->connections_count--; }
|
||||
}
|
||||
}
|
||||
|
||||
// Clear next pointer to prevent dangling references
|
||||
etcp->next = NULL;
|
||||
|
||||
@@ -381,7 +383,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);
|
||||
@@ -391,12 +396,19 @@ void etcp_conn_reset(struct ETCP_CONN* etcp) {
|
||||
clear_queue(etcp->recv_q);
|
||||
clear_queue(etcp->ack_q);
|
||||
|
||||
// clear_queue leaves callback_suspended=1, resume to prevent deadlock
|
||||
queue_resume_callback(etcp->input_queue);
|
||||
queue_resume_callback(etcp->input_send_q);
|
||||
queue_resume_callback(etcp->input_wait_ack);
|
||||
|
||||
// В etcp_conn_reset(), после очистки очередей добавьте:
|
||||
struct ETCP_LINK* l = etcp->links;
|
||||
while (l) {
|
||||
l->inflight_bytes = 0;
|
||||
l->inflight_packets = 0;
|
||||
l->delivered_bytes = 0;
|
||||
l->acked_bytes = 0;
|
||||
l->acked_packets = 0;
|
||||
l->last_ack_time_tb = get_time_tb();
|
||||
l = l->next;
|
||||
}
|
||||
@@ -453,6 +465,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");
|
||||
}
|
||||
|
||||
@@ -612,6 +629,16 @@ static void input_queue_try_resume(struct ETCP_CONN* etcp) {// при ACK
|
||||
}
|
||||
}
|
||||
|
||||
// Called from link-level etcp_link_update_inflight_lim() after inflight_lim_bytes changes.
|
||||
// Recalculates connection-level optimal_inflight and resumes input_queue if room opened up.
|
||||
void etcp_conn_on_inflight_lim_changed(struct ETCP_CONN* etcp) {
|
||||
if (!etcp) return;
|
||||
uint32_t sum = 0;
|
||||
for (struct ETCP_LINK* tl = etcp->links; tl; tl = tl->next) sum += tl->inflight_lim_bytes;
|
||||
etcp->optimal_inflight = sum;
|
||||
// input_queue_try_resume(etcp);
|
||||
}
|
||||
|
||||
void etcp_stats(struct ETCP_CONN* etcp) {
|
||||
if (!etcp) return;
|
||||
|
||||
@@ -854,7 +881,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);
|
||||
@@ -924,7 +951,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 (link->send_blocked_inflight && link->inflight_bytes < link->inflight_lim_bytes) loadbalancer_link_ready(link);
|
||||
}
|
||||
|
||||
// Always add to the CURRENT link (first send or retransmission)
|
||||
@@ -1164,8 +1190,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,
|
||||
@@ -1235,18 +1262,28 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d
|
||||
link->delivered_bytes += rs.delivered;
|
||||
link->last_ack_time_tb = now_tb;
|
||||
|
||||
uint32_t new_cwnd = link->inflight_lim_bytes;
|
||||
uint32_t new_pacing = link->bbr_pacing_rate;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] BBR in: seq=%u del=%u iv=%uus rtt=%uus pri=%u tx_infl=%u cur=%u/%u lost=%d app=%d",
|
||||
link->local_link_id, seq, rs.delivered, rs.interval_us, rs.rtt_us,
|
||||
rs.prior_delivered, rs.tx_in_flight, link->inflight_bytes, link->inflight_lim_bytes,
|
||||
rs.lost, rs.is_app_limited);
|
||||
|
||||
uint32_t old_cwnd = link->inflight_lim_bytes;
|
||||
uint32_t old_pacing = link->bbr_pacing_rate;
|
||||
uint32_t new_cwnd = old_cwnd;
|
||||
uint32_t new_pacing = old_pacing;
|
||||
bbr_main(link->bbr, &rs, &new_cwnd, &new_pacing, link->mtu, link->inflight_bytes,
|
||||
(link->inflight_bytes >= link->inflight_lim_bytes));
|
||||
etcp_link_update_inflight_lim(link, new_cwnd);
|
||||
link->bbr_pacing_rate = new_pacing;
|
||||
link->bandwidth = (uint32_t)((uint64_t)new_pacing * 8 / 1000);
|
||||
|
||||
if (link->send_blocked_inflight && link->inflight_bytes < new_cwnd) {
|
||||
link->send_blocked_inflight = 0;
|
||||
loadbalancer_link_ready(link);
|
||||
}
|
||||
link->bbr_loss_since_ack = 0;
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] BBR out: cwnd %u→%u pace %u→%u bw=%uK mode=%d cyc=%d infl=%u/%u",
|
||||
link->local_link_id, old_cwnd, new_cwnd, old_pacing, new_pacing,
|
||||
link->bandwidth, link->bbr->mode, link->bbr->cycle_idx, link->inflight_bytes, new_cwnd);
|
||||
|
||||
link->acked_bytes += acked_pkt->ll.len;
|
||||
link->acked_packets++;
|
||||
}
|
||||
|
||||
|
||||
@@ -1277,6 +1314,10 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d
|
||||
void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "");
|
||||
if (!pkt) return;
|
||||
if (memory_pool_is_freed(pkt->link->etcp->instance->pkt_pool, pkt)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_conn_input: pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt);
|
||||
volatile int _halt = 1; while (_halt) {}
|
||||
}
|
||||
if (!pkt->data_len) {
|
||||
memory_pool_free(pkt->link->etcp->instance->pkt_pool, pkt);
|
||||
return;
|
||||
@@ -1404,7 +1445,7 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] set ack_timer for delayed ACK send", etcp->log_name);
|
||||
etcp->ack_resp_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_TB, etcp, ack_response_timer_cb, "etcp_ack_resp");
|
||||
}
|
||||
} else DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX ack dedup: seq=%d already in ack_q", etcp->log_name, seq);
|
||||
}
|
||||
if (((int32_t)(etcp->last_delivered_id-seq)<0) && (queue_find_data_by_index(etcp->recv_q, &seq)==NULL)) {// проверяем есть ли пакет с этим seq
|
||||
uint32_t pkt_len=len-5;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] adding packet seq=%u to recv_q (last_delivered_id=%u)", etcp->log_name, seq, etcp->last_delivered_id);
|
||||
@@ -1546,6 +1587,12 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
|
||||
}
|
||||
|
||||
if (memory_pool_is_freed(pkt->link->etcp->instance->pkt_pool, pkt)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_conn_input: pkt=%p ALREADY FREED in pkt_pool — HALTING", (void*)pkt);
|
||||
volatile int _halt = 1; while (_halt) {}
|
||||
}
|
||||
|
||||
|
||||
memory_pool_free(etcp->instance->pkt_pool, pkt); // Free the incoming dgram
|
||||
}
|
||||
|
||||
|
||||
+9
-3
@@ -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
|
||||
|
||||
@@ -154,7 +156,7 @@ struct ETCP_CONN {
|
||||
|
||||
// Window and inflight management
|
||||
uint32_t unacked_bytes; // Current inflight bytes
|
||||
// uint32_t window_size; // Receive window - Not used
|
||||
uint32_t max_inflight; // Max inflight cap from config (bbr_max_cwnd)
|
||||
uint32_t optimal_inflight; // Sum over links
|
||||
|
||||
// Timers
|
||||
@@ -173,7 +175,7 @@ struct ETCP_CONN {
|
||||
// uint32_t total_packets_sent; // Total packets sent counter - Not used
|
||||
|
||||
// Flags
|
||||
uint8_t routing_exchange_active; // 0 - не активен, 1 - надо инициировать обмен маршрутами (клиент), 2 - обмен маршрутами активен
|
||||
uint8_t routing_exchange_active; // 0-не активен, 1-надо инициировать (клиент), 2-обмен идёт, 3-завершён, 4-пропущен (нет BGP)
|
||||
uint8_t got_initial_pkt; //
|
||||
uint8_t initialized; // 0 - только созданный ETCP, 1 - хотя бы один линк проинициалзирован (обмен ключами произведен)
|
||||
uint32_t session_id; // случайный ID сессии (генерируется клиентом) для защиты от ложного reinit
|
||||
@@ -187,6 +189,7 @@ struct ETCP_CONN {
|
||||
void* up_arg; // аргумент для ready_cbk
|
||||
etcp_on_conn_ready down_cbk; // callback при готовности соединения
|
||||
void* down_arg; // аргумент для ready_cbk
|
||||
void (*bgp_ready_cbk)(struct ETCP_CONN* conn); // вызывается когда BGP готов (завершён или пропущен)
|
||||
|
||||
uint32_t cnt_ack_hit_inf; // счетчик удлений из inflight
|
||||
uint32_t cnt_ack_hit_sndq; // счетчик удалений inflight пакетов из sndq
|
||||
@@ -230,6 +233,9 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp);
|
||||
// Process ACK receipt - remove acknowledged packet from inflight queues
|
||||
void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t dts);
|
||||
|
||||
// Recalculate optimal_inflight and resume input queue after link inflight_lim change
|
||||
void etcp_conn_on_inflight_lim_changed(struct ETCP_CONN* etcp);
|
||||
|
||||
// Update log_name when peer_node_id becomes known
|
||||
void etcp_update_log_name(struct ETCP_CONN* etcp);
|
||||
|
||||
|
||||
+39
-164
@@ -1,197 +1,72 @@
|
||||
/**
|
||||
* @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* 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* e, etcp_cbk_fn fn, void* arg) {
|
||||
if (e) { e->down_cbk = fn; e->down_arg = 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; }
|
||||
}
|
||||
|
||||
void etcp_conn_set_up_cbk(struct ETCP_CONN* etcp, etcp_cbk_fn callback_fn, void* arg) {
|
||||
if (!etcp) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "NULL conn"); return; }
|
||||
etcp->up_cbk = callback_fn;
|
||||
etcp->up_arg = arg;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "conn=%s cbk=%p arg=%p", etcp->log_name, (void*)callback_fn, arg);
|
||||
}
|
||||
|
||||
void etcp_conn_set_down_cbk(struct ETCP_CONN* etcp, etcp_cbk_fn callback_fn, void* arg) {
|
||||
if (!etcp) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "NULL conn"); return; }
|
||||
etcp->down_cbk = callback_fn;
|
||||
etcp->down_arg = arg;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "conn=%s cbk=%p arg=%p", etcp->log_name, (void*)callback_fn, arg);
|
||||
}
|
||||
|
||||
// Установить callback при создании нового ETCP соединения
|
||||
void etcp_set_new_conn_cbk(struct UTUN_INSTANCE* instance, etcp_cbk_fn callback_fn, void* arg) {
|
||||
if (!instance) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_set_new_conn_cbk: NULL instance");
|
||||
return;
|
||||
}
|
||||
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_routing_exchange_state(struct ETCP_CONN* conn, uint8_t new_state) {
|
||||
if (!conn) return;
|
||||
conn->routing_exchange_active = new_state;
|
||||
if (new_state >= 3 && conn->bgp_ready_cbk)
|
||||
conn->bgp_ready_cbk(conn);
|
||||
}
|
||||
|
||||
int etcp_bind(struct UTUN_INSTANCE* inst, uint8_t id, etcp_recv_fn callback) {
|
||||
if (!inst) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_bind: NULL instance");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((unsigned)id >= ETCP_MAX_BINDINGS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Invalid ID %u (max=%d)", id, ETCP_MAX_BINDINGS);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!callback) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_bind: NULL callback for id=%u", id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (inst->api_bindings.callbacks[id] != NULL) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Overwriting existing callback for id=%u", id);
|
||||
}
|
||||
if (!inst || !callback || (unsigned)id >= ETCP_MAX_BINDINGS) return -1;
|
||||
inst->api_bindings.callbacks[id] = callback;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Bound id=%u → callback=%p", id, (void*)callback);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int etcp_unbind(struct UTUN_INSTANCE* inst, uint8_t id) {
|
||||
if (!inst) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: NULL instance");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((unsigned)id >= ETCP_MAX_BINDINGS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: Invalid ID %u", id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (inst->api_bindings.callbacks[id] == NULL) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: ID %u not bound", id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!inst || (unsigned)id >= ETCP_MAX_BINDINGS) return -1;
|
||||
inst->api_bindings.callbacks[id] = NULL;
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: Unbound ID %u for instance %p", id, (void*)inst);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int etcp_send(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "etcp_send enter conn=%p entry=%p", conn, entry);
|
||||
if (!conn) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: NULL connection");
|
||||
return -1;
|
||||
}
|
||||
if (!entry) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: NULL entry");
|
||||
return -1;
|
||||
}
|
||||
if (!conn->normalizer) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: Connection has no normalizer");
|
||||
return -1;
|
||||
}
|
||||
if (conn->initialized==0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: Connection is not initialized");
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct PKTNORM* pn = conn->normalizer;
|
||||
if (!pn || !pn->input) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: pn=%p or input null", pn);
|
||||
return -1;
|
||||
}
|
||||
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);
|
||||
}
|
||||
+28
-8
@@ -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 - пакет для передачи адресату
|
||||
@@ -20,15 +22,20 @@
|
||||
|
||||
#define ETCP_MAX_BINDINGS 256
|
||||
|
||||
// ETCP packet IDs
|
||||
// === Raw ETCP packet IDs (etcp_bind/etcp_send) ===
|
||||
#define ETCP_ID_DATA 0x00 // Пакет для передачи адресату
|
||||
#define ETCP_ID_ROUTE_ENTRY 0x01 // Элемент роутинг-таблицы
|
||||
#define ETCP_ID_NAT 0x02 // NAT трафик между узлами
|
||||
#define ETCP_ID_SVC_ROUTE 0x03 // Маршрутизируемые сервисные пакеты (etcp_router)
|
||||
#define ETCP_ID_TCP_PROXY 0x04 // TCP proxy 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_ROUTE_ENTRY 0x01 // Элемент роутинг-таблицы (BGP)
|
||||
#define ETCP_ID_SVC_ROUTE 0x03 // Транспорт роутера (etcp_router)
|
||||
|
||||
// === Router-сервисы (etcp_router_bind/etcp_route_send) ===
|
||||
#define ETCP_RT_ID_DATA 0x00 // routing.c — маршрутизация данных
|
||||
#define ETCP_RT_ID_NAT 0x02 // NAT трафик между узлами
|
||||
#define ETCP_RT_ID_SVC_ROUTE 0x03 // etcp_router — транспорт роутера
|
||||
#define ETCP_RT_ID_TCP_PROXY 0x04 // TCP proxy (клиент ↔ exit)
|
||||
#define ETCP_RT_ID_UDP_PROXY 0x05 // UDP datagram прокси (client ↔ exit)
|
||||
#define ETCP_RT_ID_ICMP_PROXY 0x06 // ICMP echo прокси (ping через exit)
|
||||
#define ETCP_RT_ID_MSG_TRANSPORT 0x10 // msg_transport — локальный IPC транспорт сообщений
|
||||
#define ETCP_RT_ID_CONN_MGR 0x11 // Connection Manager — management connections
|
||||
|
||||
// Forward declarations
|
||||
struct ETCP_CONN;
|
||||
@@ -47,8 +54,19 @@ typedef void (*etcp_recv_fn)(struct ETCP_CONN* conn, struct ll_entry* entry);
|
||||
// Forward declaration
|
||||
struct ETCP_CONN;
|
||||
struct UTUN_INSTANCE;
|
||||
struct NODEINFO_Q;
|
||||
typedef void (*etcp_cbk_fn)(struct ETCP_CONN* conn, void* arg);
|
||||
|
||||
// ---- Background connection initialization ----
|
||||
#define ETCP_CONNECT_EARLY 1
|
||||
#define ETCP_CONNECT_LATE 2
|
||||
#define ETCP_CONNECT_BGP_READY 4 // BGP-синхронизация завершена
|
||||
|
||||
typedef void (*etcp_connect_callback_t)(void* arg, struct ETCP_CONN* conn, int type);
|
||||
|
||||
int etcp_connect(struct UTUN_INSTANCE* instance, struct NODEINFO_Q* node,
|
||||
etcp_connect_callback_t cb, void* arg, uint8_t flags);
|
||||
|
||||
/**
|
||||
* @brief Установить callback при создании нового ETCP соединения
|
||||
*
|
||||
@@ -114,6 +132,8 @@ void etcp_conn_set_ready_cbk(struct ETCP_CONN* conn, etcp_cbk_fn callback_fn, vo
|
||||
void etcp_conn_set_up_cbk (struct ETCP_CONN* conn, etcp_cbk_fn callback_fn, void* arg);
|
||||
void etcp_conn_set_down_cbk (struct ETCP_CONN* conn, etcp_cbk_fn callback_fn, void* arg);
|
||||
|
||||
void etcp_set_routing_exchange_state(struct ETCP_CONN* conn, uint8_t new_state);
|
||||
|
||||
/**
|
||||
* @brief Внутренняя функция etcp: Коллбэк для очередей output ll_queue normalizer
|
||||
*
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "etcp_bbr.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
static uint64_t bbr_now(const struct bbr* bbr) { return bbr->now_tb ? bbr->now_tb : get_time_tb(); }
|
||||
@@ -327,10 +328,14 @@ static void bbr_exit_probe_rtt(struct bbr* bbr)
|
||||
{
|
||||
bbr_reset_lower_bounds(bbr);
|
||||
if (bbr->full_bw_reached) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d exit probe_rtt → PROBE_BW",
|
||||
bbr->link_id, bbr->mode, BBR_PROBE_BW);
|
||||
bbr->mode = BBR_PROBE_BW;
|
||||
bbr_start_bw_probe_down(bbr);
|
||||
bbr_start_bw_probe_cruise(bbr);
|
||||
} else {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d exit probe_rtt → STARTUP",
|
||||
bbr->link_id, bbr->mode, BBR_STARTUP);
|
||||
bbr->mode = BBR_STARTUP;
|
||||
}
|
||||
}
|
||||
@@ -536,11 +541,13 @@ static void bbr_check_drain(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
{
|
||||
(void)rs; (void)sample_bw;
|
||||
if (bbr->mode == BBR_STARTUP && bbr->full_bw_reached) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d STARTUP→DRAIN", bbr->link_id, bbr->mode, BBR_DRAIN);
|
||||
bbr->mode = BBR_DRAIN;
|
||||
bbr_reset_congestion_signals(bbr);
|
||||
}
|
||||
if (bbr->mode == BBR_DRAIN &&
|
||||
inflight_packets <= bbr_inflight(bbr, bbr_max_bw(bbr), BBR_UNIT, mss)) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d DRAIN→PROBE_BW infl_pkt=%u", bbr->link_id, bbr->mode, BBR_PROBE_BW, inflight_packets);
|
||||
bbr->mode = BBR_PROBE_BW;
|
||||
bbr_start_bw_probe_down(bbr);
|
||||
}
|
||||
@@ -584,6 +591,8 @@ static void bbr_update_min_rtt(struct bbr* bbr, const struct bbr_rate_sample* rs
|
||||
|
||||
if (BBR_PROBE_RTT_MS > 0 && probe_rtt_expired &&
|
||||
!bbr->idle_restart && bbr->mode != BBR_PROBE_RTT) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d →PROBE_RTT cwnd=%u infl_pkt=%u",
|
||||
bbr->link_id, bbr->mode, BBR_PROBE_RTT, *cwnd, inflight_packets);
|
||||
bbr->mode = BBR_PROBE_RTT;
|
||||
bbr_save_cwnd(bbr, *cwnd);
|
||||
bbr->probe_rtt_done_stamp_tb = 0;
|
||||
@@ -610,6 +619,7 @@ static void bbr_update_min_rtt(struct bbr* bbr, const struct bbr_rate_sample* rs
|
||||
|
||||
static void bbr_reset_startup_mode(struct bbr* bbr)
|
||||
{
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d →STARTUP (reset)", bbr->link_id, bbr->mode, BBR_STARTUP);
|
||||
bbr->mode = BBR_STARTUP;
|
||||
}
|
||||
|
||||
|
||||
@@ -98,6 +98,7 @@ struct bbr {
|
||||
uint8_t loss_round_start : 1;
|
||||
uint8_t full_bw_now : 1;
|
||||
uint8_t pad_unused : 6;
|
||||
uint8_t link_id; // local_link_id for debug logging
|
||||
uint64_t now_tb; // 0 = real get_time_tb(); >0 = test-controlled time
|
||||
bbr_cwnd_update_fn on_cwnd_update;
|
||||
void* cwnd_update_ctx;
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
#include "etcp_api.h"
|
||||
#include "etcp.h"
|
||||
#include "utun_instance.h"
|
||||
#include "route_node.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/socket_compat.h"
|
||||
#include <string.h>
|
||||
|
||||
#define DEBUG_CATEGORY_ETCP_CONNECT DEBUG_CATEGORY_ETCP
|
||||
|
||||
struct etcp_connect_cb_node {
|
||||
etcp_connect_callback_t cb;
|
||||
void* arg;
|
||||
uint8_t flags;
|
||||
struct etcp_connect_cb_node* next;
|
||||
};
|
||||
|
||||
struct ETCP_CONNECT {
|
||||
struct ETCP_CONNECT* next;
|
||||
struct UTUN_INSTANCE* instance;
|
||||
uint64_t node_id;
|
||||
struct ETCP_CONN* conn;
|
||||
struct etcp_connect_cb_node* cb_list;
|
||||
void* initial_timer;
|
||||
void* settle_timer;
|
||||
uint16_t min_rtt;
|
||||
uint8_t early_delivered : 1;
|
||||
uint8_t done : 1;
|
||||
};
|
||||
|
||||
static struct ETCP_CONNECT* connect_find(struct UTUN_INSTANCE* inst, uint64_t node_id) {
|
||||
struct ETCP_CONNECT* ctx = inst->pending_connects;
|
||||
while (ctx) { if (ctx->node_id == node_id) return ctx; ctx = ctx->next; }
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void connect_deliver(struct ETCP_CONNECT* ctx, int type) {
|
||||
struct etcp_connect_cb_node** pp = &ctx->cb_list;
|
||||
while (*pp) {
|
||||
struct etcp_connect_cb_node* node = *pp;
|
||||
if (type == 0 || (node->flags & (uint8_t)type)) {
|
||||
*pp = node->next;
|
||||
node->cb(node->arg, (type == 0) ? NULL : ctx->conn, type);
|
||||
u_free(node);
|
||||
} else {
|
||||
pp = &node->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void connect_cancel(struct ETCP_CONNECT* ctx) {
|
||||
if (!ctx) return;
|
||||
struct UTUN_INSTANCE* inst = ctx->instance;
|
||||
if (inst) {
|
||||
struct ETCP_CONNECT** pp = &inst->pending_connects;
|
||||
while (*pp && *pp != ctx) pp = &(*pp)->next;
|
||||
if (*pp) *pp = ctx->next;
|
||||
if (ctx->initial_timer) { uasync_cancel_timeout(inst->ua, ctx->initial_timer); ctx->initial_timer = NULL; }
|
||||
if (ctx->settle_timer) { uasync_cancel_timeout(inst->ua, ctx->settle_timer); ctx->settle_timer = NULL; }
|
||||
}
|
||||
u_free(ctx);
|
||||
}
|
||||
|
||||
static void connect_create_links_v4(struct ETCP_CONNECT* ctx, struct NODEINFO_Q* node) {
|
||||
const struct NODEINFO_IPV4_ADDR* addrs; int ac = get_node_v4_addrs(node, &addrs);
|
||||
for (int i = 0; i < ac; i++) {
|
||||
if (addrs[i].port == 0) continue;
|
||||
{ int zero = 1; for (int j = 0; j < 4; j++) if (addrs[i].addr[j] != 0) { zero = 0; break; } if (zero) continue; }
|
||||
struct sockaddr_in sin; memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
memcpy(&sin.sin_addr.s_addr, addrs[i].addr, 4);
|
||||
sin.sin_port = htons(addrs[i].port);
|
||||
struct sockaddr_storage sa; memcpy(&sa, &sin, sizeof(sin));
|
||||
struct ETCP_SOCKET* s = ctx->instance->etcp_sockets;
|
||||
while (s) {
|
||||
if (s->local_addr.ss_family == AF_INET)
|
||||
etcp_link_new(ctx->conn, s, &sa, 0);
|
||||
s = s->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void connect_create_links_v6(struct ETCP_CONNECT* ctx, struct NODEINFO_Q* node) {
|
||||
const struct NODEINFO_IPV6_ADDR* addrs; int ac = get_node_v6_addrs(node, &addrs);
|
||||
for (int i = 0; i < ac; i++) {
|
||||
if (addrs[i].port == 0) continue;
|
||||
int zero = 1;
|
||||
for (int j = 0; j < 16; j++) if (addrs[i].addr[j] != 0) { zero = 0; break; }
|
||||
if (zero) continue;
|
||||
struct sockaddr_in6 sin6; memset(&sin6, 0, sizeof(sin6));
|
||||
sin6.sin6_family = AF_INET6;
|
||||
memcpy(&sin6.sin6_addr, addrs[i].addr, 16);
|
||||
sin6.sin6_port = htons(addrs[i].port);
|
||||
struct sockaddr_storage sa; memcpy(&sa, &sin6, sizeof(sin6));
|
||||
struct ETCP_SOCKET* s = ctx->instance->etcp_sockets;
|
||||
while (s) {
|
||||
if (s->local_addr.ss_family == AF_INET6)
|
||||
etcp_link_new(ctx->conn, s, &sa, 0);
|
||||
s = s->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void connect_bgp_ready_cb(struct ETCP_CONN* conn) {
|
||||
struct ETCP_CONNECT* ctx = connect_find(conn->instance, conn->peer_node_id);
|
||||
if (!ctx || ctx->done) return;
|
||||
connect_deliver(ctx, ETCP_CONNECT_BGP_READY);
|
||||
}
|
||||
|
||||
static void connect_initial_timeout_cb(void* arg) {
|
||||
struct ETCP_CONNECT* ctx = (struct ETCP_CONNECT*)arg;
|
||||
if (!ctx || ctx->done) return;
|
||||
ctx->done = 1;
|
||||
ctx->initial_timer = NULL;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] timeout for node 0x%016llx", (unsigned long long)ctx->node_id);
|
||||
struct ETCP_CONN* conn = ctx->conn; ctx->conn = NULL;
|
||||
connect_deliver(ctx, 0);
|
||||
if (conn) etcp_connection_close(conn);
|
||||
connect_cancel(ctx);
|
||||
}
|
||||
|
||||
static void connect_settle_timeout_cb(void* arg) {
|
||||
struct ETCP_CONNECT* ctx = (struct ETCP_CONNECT*)arg;
|
||||
if (!ctx || ctx->done) return;
|
||||
ctx->done = 1;
|
||||
ctx->settle_timer = NULL;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] settle for node 0x%016llx, min_rtt=%u",
|
||||
(unsigned long long)ctx->node_id, ctx->min_rtt);
|
||||
struct ETCP_LINK* link = ctx->conn->links;
|
||||
while (link) {
|
||||
struct ETCP_LINK* next = link->next;
|
||||
if (!link->initialized) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] removing failed link=%p for node 0x%016llx",
|
||||
(void*)link, (unsigned long long)ctx->node_id);
|
||||
etcp_link_close(link);
|
||||
}
|
||||
link = next;
|
||||
}
|
||||
ctx->conn->ready_cbk = NULL;
|
||||
ctx->conn->ready_arg = NULL;
|
||||
connect_deliver(ctx, ETCP_CONNECT_LATE);
|
||||
connect_cancel(ctx);
|
||||
}
|
||||
|
||||
static void connect_ready_cb(struct ETCP_CONN* conn, void* arg) {
|
||||
struct ETCP_CONNECT* ctx = (struct ETCP_CONNECT*)arg;
|
||||
if (!ctx || ctx->done) return;
|
||||
struct ETCP_LINK* link = conn->links;
|
||||
while (link) {
|
||||
if (link->initialized && link->rtt_last && link->rtt_last < ctx->min_rtt)
|
||||
ctx->min_rtt = link->rtt_last;
|
||||
link = link->next;
|
||||
}
|
||||
if (ctx->early_delivered) return;
|
||||
ctx->early_delivered = 1;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] early ready for node 0x%016llx, min_rtt=%u",
|
||||
(unsigned long long)ctx->node_id, ctx->min_rtt);
|
||||
connect_deliver(ctx, ETCP_CONNECT_EARLY);
|
||||
if (ctx->initial_timer) { uasync_cancel_timeout(ctx->instance->ua, ctx->initial_timer); ctx->initial_timer = NULL; }
|
||||
uint32_t settle_tb;
|
||||
if (ctx->min_rtt == 0xFFFF) settle_tb = 20000;
|
||||
else {
|
||||
settle_tb = (uint32_t)ctx->min_rtt * 8;
|
||||
if (settle_tb < 5000) settle_tb = 5000;
|
||||
if (settle_tb > 20000) settle_tb = 20000;
|
||||
}
|
||||
ctx->settle_timer = uasync_set_timeout(ctx->instance->ua, (int)settle_tb, ctx,
|
||||
connect_settle_timeout_cb, "etcp_connect_settle");
|
||||
}
|
||||
|
||||
int etcp_connect(struct UTUN_INSTANCE* inst, struct NODEINFO_Q* node,
|
||||
etcp_connect_callback_t cb, void* arg, uint8_t flags) {
|
||||
if (!inst || !node || !cb) return -1;
|
||||
uint64_t node_id = node->node.node_id;
|
||||
|
||||
struct ETCP_CONN* existing = inst->connections;
|
||||
while (existing) {
|
||||
if (existing->peer_node_id == node_id) {
|
||||
struct ETCP_LINK* l = existing->links;
|
||||
while (l) { if (l->link_state == 3) break; l = l->next; }
|
||||
if (l) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] node 0x%016llx already connected, delivering immediately",
|
||||
(unsigned long long)node_id);
|
||||
if (flags & ETCP_CONNECT_EARLY) cb(arg, existing, ETCP_CONNECT_EARLY);
|
||||
if (flags & ETCP_CONNECT_LATE) cb(arg, existing, ETCP_CONNECT_LATE);
|
||||
if ((flags & ETCP_CONNECT_BGP_READY) && existing->routing_exchange_active >= 3)
|
||||
cb(arg, existing, ETCP_CONNECT_BGP_READY);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
existing = existing->next;
|
||||
}
|
||||
|
||||
struct ETCP_CONNECT* ctx = connect_find(inst, node_id);
|
||||
if (ctx) {
|
||||
struct etcp_connect_cb_node* cn = u_calloc(1, sizeof(struct etcp_connect_cb_node));
|
||||
if (!cn) return -1;
|
||||
cn->cb = cb; cn->arg = arg; cn->flags = flags; cn->next = ctx->cb_list; ctx->cb_list = cn;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] node 0x%016llx already connecting, added cb",
|
||||
(unsigned long long)node_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct ETCP_CONN* conn = etcp_connection_create(inst, NULL);
|
||||
if (!conn) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] failed to create ETCP_CONN for node 0x%016llx",
|
||||
(unsigned long long)node_id); cb(arg, NULL, 0); return -1; }
|
||||
conn->next = inst->connections; inst->connections = conn; inst->connections_count++;
|
||||
if (sc_init_ctx(&conn->crypto_ctx, &inst->my_keys) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] sc_init_ctx failed for node 0x%016llx", (unsigned long long)node_id);
|
||||
etcp_connection_close(conn); cb(arg, NULL, 0); return -1;
|
||||
}
|
||||
if (sc_set_peer_public_key(&conn->crypto_ctx, node->node.public_key, 0) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] sc_set_peer_public_key failed for node 0x%016llx", (unsigned long long)node_id);
|
||||
etcp_connection_close(conn); cb(arg, NULL, 0); return -1;
|
||||
}
|
||||
|
||||
ctx = u_calloc(1, sizeof(struct ETCP_CONNECT));
|
||||
if (!ctx) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] alloc failed");
|
||||
etcp_connection_close(conn); cb(arg, NULL, 0); return -1; }
|
||||
ctx->instance = inst; ctx->node_id = node_id; ctx->conn = conn;
|
||||
ctx->min_rtt = 0xFFFF;
|
||||
{
|
||||
struct etcp_connect_cb_node* cn = u_calloc(1, sizeof(struct etcp_connect_cb_node));
|
||||
if (!cn) { u_free(ctx); etcp_connection_close(conn); cb(arg, NULL, 0); return -1; }
|
||||
cn->cb = cb; cn->arg = arg; cn->flags = flags; ctx->cb_list = cn;
|
||||
}
|
||||
|
||||
conn->ready_cbk = connect_ready_cb;
|
||||
conn->ready_arg = ctx;
|
||||
conn->bgp_ready_cbk = connect_bgp_ready_cb;
|
||||
|
||||
connect_create_links_v4(ctx, node);
|
||||
connect_create_links_v6(ctx, node);
|
||||
|
||||
if (!conn->links) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] no links created for node 0x%016llx",
|
||||
(unsigned long long)node_id);
|
||||
connect_deliver(ctx, 0);
|
||||
etcp_connection_close(conn); u_free(ctx); return -1;
|
||||
}
|
||||
|
||||
ctx->initial_timer = uasync_set_timeout(inst->ua, (int)inst->etcp_connect_timeout_tb, ctx, connect_initial_timeout_cb, "etcp_connect_init");
|
||||
ctx->next = inst->pending_connects; inst->pending_connects = ctx;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] started for node 0x%016llx", (unsigned long long)node_id);
|
||||
return 0;
|
||||
}
|
||||
+489
-377
File diff suppressed because it is too large.
Load diff
+45
-3
@@ -14,6 +14,9 @@
|
||||
#define UDP_SC_HDR_SIZE (13+8+4 + 5)// 13+8+4 - sc_nonce+tag size+crc, 5 - payload hdr
|
||||
#define ACK_REZERV 100// сколько байт резервировать под ack и прочие заголовки
|
||||
|
||||
#define INFLIGHT_LIM_MIN 8192 // 8K
|
||||
#define INFLIGHT_LIM_MAX 1048576 // 1M
|
||||
|
||||
#define PACKET_DATA_SIZE 1600//1536
|
||||
#define PACKET_DATA_MAX_MTU 1600
|
||||
#define ETCP_MAX_PAYLOAD_SIZE (PACKET_DATA_SIZE - ETCP_ACK_BASE_SIZE - 5) /* 1587 */
|
||||
@@ -51,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 {
|
||||
@@ -103,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
|
||||
@@ -168,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;
|
||||
@@ -179,6 +219,8 @@ struct ETCP_LINK {
|
||||
size_t total_encrypted;
|
||||
size_t total_decrypted;
|
||||
uint32_t total_retransmissions;
|
||||
uint64_t acked_bytes; // подтверждённых байт на этом линке
|
||||
uint64_t acked_packets; // подтверждённых пакетов на этом линке
|
||||
|
||||
uint16_t rtt_last; // round trip (время отправки + приёма)
|
||||
uint32_t jitter; // Current jitter [>>16] x0.1 ms
|
||||
|
||||
+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);
|
||||
|
||||
+618
-200
File diff suppressed because it is too large.
Load diff
+55
-5
@@ -14,7 +14,7 @@
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct SVC_ROUTE_HDR {
|
||||
uint8_t cmd; // ETCP_ID_SVC_ROUTE (0x03)
|
||||
uint8_t cmd; // ETCP_ID_SVC_ROUTE (0x03) / ETCP_RT_ID_SVC_ROUTE
|
||||
uint64_t dst_node_id;
|
||||
uint64_t src_node_id;
|
||||
uint32_t seq; // data: tx_seq; ACK: rx_seq (ожидаемый seq)
|
||||
@@ -28,6 +28,8 @@ 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_FLAG_CLOSE 0x02 // нормальное закрытие conn
|
||||
#define ROUTER_SESS_ID_SHIFT 4
|
||||
#define ROUTER_SESS_ID_MASK 0x30
|
||||
|
||||
@@ -52,9 +54,36 @@ struct ETCP_ROUTER_CONN {
|
||||
struct ll_queue* send_q; // очередь ожидающих отправки (inflight полон)
|
||||
void* send_resume_timer; // таймер возобновления отправки
|
||||
uint8_t send_blocked; // 1 = inflight полон, ждём ack/таймер
|
||||
uint8_t no_route; // 1 = нет BGP-маршрута, передача приостановлена
|
||||
void* no_route_timer; // таймер 20ms проверки появления маршрута
|
||||
uint8_t no_ack_count; // счётчик последовательных ретрансмиссий без ACK
|
||||
uint8_t closed; // 1 = в процессе закрытия, таймеры игнорируют
|
||||
void* close_callback; // пользовательский коллбэк завершения закрытия
|
||||
void* close_callback_arg; // аргумент для close_callback
|
||||
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 (авто-создание/рестарт)
|
||||
|
||||
// Ретрансмиты: inflight очередь
|
||||
struct ll_queue* inflight_q; // хеш по seq (4 байта), непрерывный блок без SACK
|
||||
void* retrans_timer; // таймер проверки застоя ACK
|
||||
uint64_t last_ack_changed_tb; // когда tx_acked последний раз менялся (0.1ms)
|
||||
|
||||
// Очередь приёма входящего трафика (между сетью и recv_q)
|
||||
struct ll_queue* incoming_q; // FIFO очередь входящих пакетов
|
||||
uint8_t incoming_data_ready; // 1 = данные в incoming_q и callback был вызван
|
||||
|
||||
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_retrans_done; // число выполненых ретрансмитов
|
||||
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
|
||||
@@ -63,11 +92,23 @@ struct ETCP_ROUTER_CONN {
|
||||
#define ROUTER_ACK_INTERVAL_TB 100 // интервал ACK: 10ms в timebase (0.1ms)
|
||||
#define ROUTER_ACK_IDLE_TB 5000 // idle таймаут: 500ms
|
||||
#define ROUTER_SEND_RESUME_TB 500 // retry интервал send_q: 50ms
|
||||
#define ROUTER_MAX_SEND_Q_PACKETS 4 // порог backpressure на send_q
|
||||
#define ROUTER_MAX_SEND_Q_PACKETS 64 // порог backpressure на send_q
|
||||
|
||||
// Флаги в старших битах seq (взаимоисключающие)
|
||||
#define ROUTER_SEQ_CLOSE_FLAG 0x80000000 // нормальное закрытие conn
|
||||
#define ROUTER_SEQ_RST_FLAG 0x40000000 // conn не найден (reset)
|
||||
// Ретрансмиты
|
||||
#define ROUTER_RETRANS_TIMEOUT_TB 3000 // 300ms в timebase (0.1ms)
|
||||
#define ROUTER_INFLIGHT_HASH_SIZE 1024
|
||||
#define ROUTER_NO_ROUTE_RETRY_TB 200 // 20ms проверка появления маршрута
|
||||
#define ROUTER_NO_ACK_MAX_RETRANS 17 // 17 × 300ms ≈ 5s таймаут без ACK
|
||||
|
||||
// Inflight запись — копия отправленного пакета для возможного ретрансмита
|
||||
struct ROUTER_INFLIGHT {
|
||||
struct ll_entry ll; // индекс по seq (4 байта, offset 0)
|
||||
uint32_t seq;
|
||||
uint64_t last_sent_tb; // время последней отправки (0.1ms)
|
||||
uint8_t send_count; // число переотправок
|
||||
uint8_t* payload; // копия данных
|
||||
size_t payload_len;
|
||||
};
|
||||
|
||||
// Bindings для сервисов внутри etcp_router (аналогично ETCP_BINDINGS)
|
||||
struct ETCP_ROUTER_BINDINGS {
|
||||
@@ -96,9 +137,18 @@ 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);
|
||||
|
||||
// Асинхронное закрытие: коллбэк вызывается после освобождения памяти rconn
|
||||
void etcp_router_conn_close_async(struct ETCP_ROUTER_CONN* rconn,
|
||||
void (*on_close_done)(void* arg),
|
||||
void* close_arg);
|
||||
|
||||
// Закрыть все router_conn для указанного remote_node_id (peer умер)
|
||||
void etcp_router_conn_close_all_for_node(struct UTUN_INSTANCE* inst, uint64_t remote_node_id);
|
||||
|
||||
|
||||
+10
-13
@@ -95,15 +95,15 @@ struct lwip_tcp_ctx *lwip_tcp_init(struct UASYNC *ua, tcp_output_fn output, void
|
||||
ctx->ua = ua;
|
||||
ctx->output = output;
|
||||
ctx->output_arg = output_arg;
|
||||
ctx->pcb_pool = memory_pool_init(sizeof(struct tcp_pcb));
|
||||
ctx->pcb_listen_pool = memory_pool_init(sizeof(struct tcp_pcb_listen));
|
||||
ctx->seg_pool = memory_pool_init(sizeof(struct tcp_seg));
|
||||
ctx->pcb_pool = memory_pool_init(sizeof(struct tcp_pcb), "pcb_pool");
|
||||
ctx->pcb_listen_pool = memory_pool_init(sizeof(struct tcp_pcb_listen), "pcb_listen_pool");
|
||||
ctx->seg_pool = memory_pool_init(sizeof(struct tcp_seg), "seg_pool");
|
||||
if (!ctx->pcb_pool || !ctx->pcb_listen_pool || !ctx->seg_pool) {
|
||||
lwip_tcp_destroy(ctx);
|
||||
return NULL;
|
||||
}
|
||||
ctx->iss_seed = (uint16_t)(get_time_tb() & 0xFFFF);
|
||||
ctx->tmr_interval_ms = TCP_TMR_INTERVAL;
|
||||
ctx->tmr_interval_ms = TCP_TMR_INTERVAL / 4;
|
||||
ctx->rto_min_ms = 3000;
|
||||
ctx->rto_max_ms = 0;
|
||||
ctx->timer = uasync_set_timeout(ua, ctx->tmr_interval_ms * 10, ctx, tcp_tmr_cb, "lwip_tcp_tmr");
|
||||
@@ -749,7 +749,6 @@ void tcp_slowtmr(struct lwip_tcp_ctx *ctx)
|
||||
tcp_pcb_purge(pcb);
|
||||
if (prev != NULL) {
|
||||
prev->next = pcb->next;
|
||||
prev->next_owner = PCB_NEXT_SLOWTMR;
|
||||
} else {
|
||||
ctx->active_pcbs = pcb->next;
|
||||
}
|
||||
@@ -808,18 +807,16 @@ void tcp_slowtmr(struct lwip_tcp_ctx *ctx)
|
||||
tcp_pcb_purge(pcb);
|
||||
if (prev != NULL) {
|
||||
prev->next = pcb->next;
|
||||
prev->next_owner = PCB_NEXT_SLOWTMR;
|
||||
} else {
|
||||
ctx->tw_pcbs = pcb->next;
|
||||
}
|
||||
pcb2 = pcb;
|
||||
pcb = pcb->next;
|
||||
int self_loop = (pcb == pcb2);
|
||||
uint8_t sl_owner = pcb2->next_owner;
|
||||
tcp_free(pcb2);
|
||||
if (self_loop) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS SELF-LOOP in tcp_slowtmr: freed pcb=%p state=TIME_WAIT port=%u next_owner=%d, clearing tw_pcbs",
|
||||
(void*)pcb2, pcb2->local_port, sl_owner);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS SELF-LOOP in tcp_slowtmr: freed pcb=%p state=TIME_WAIT port=%u, clearing tw_pcbs",
|
||||
(void*)pcb2, pcb2->local_port);
|
||||
ctx->tw_pcbs = NULL;
|
||||
break;
|
||||
}
|
||||
@@ -1050,8 +1047,8 @@ static void tcp_kill_timewait(struct lwip_tcp_ctx *ctx)
|
||||
}
|
||||
if (inactive != NULL) {
|
||||
if (inactive->next == inactive) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS SELF-LOOP in tcp_kill_timewait: aborting pcb=%p next_owner=%d, clearing tw_pcbs",
|
||||
(void*)inactive, inactive->next_owner);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS SELF-LOOP in tcp_kill_timewait: aborting pcb=%p, clearing tw_pcbs",
|
||||
(void*)inactive);
|
||||
ctx->tw_pcbs = NULL;
|
||||
tcp_free(inactive);
|
||||
} else {
|
||||
@@ -1115,8 +1112,8 @@ struct tcp_pcb *tcp_alloc(struct lwip_tcp_ctx *ctx, uint8_t prio)
|
||||
pcb->rcv_wnd = pcb->rcv_ann_wnd = TCPWND16(TCP_WND);
|
||||
pcb->ttl = 64;
|
||||
pcb->mss = INITIAL_MSS;
|
||||
pcb->rto = (int16_t)(ctx->rto_min_ms / TCP_SLOW_INTERVAL);
|
||||
pcb->sv = (int16_t)(ctx->rto_min_ms / TCP_SLOW_INTERVAL);
|
||||
pcb->rto = (int16_t)(TCP_RTO_MIN_MS / TCP_SLOW_INTERVAL);
|
||||
pcb->sv = (int16_t)(TCP_RTO_MIN_MS / TCP_SLOW_INTERVAL);
|
||||
pcb->rtime = -1;
|
||||
pcb->cwnd = 1;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "tcp_alloc: cwnd=%u snd_buf=%u mss=%u snd_wnd=%u", pcb->cwnd, pcb->snd_buf, pcb->mss, pcb->snd_wnd);
|
||||
|
||||
@@ -67,15 +67,6 @@ enum tcp_err_enum {
|
||||
LERR_CLSD = -15,
|
||||
LERR_ARG = -16
|
||||
};
|
||||
|
||||
// кто последним записал pcb->next (диагностика зацикливания tw_pcbs)
|
||||
enum pcb_next_owner {
|
||||
PCB_NEXT_NONE = 0,
|
||||
PCB_NEXT_REG = 1,
|
||||
PCB_NEXT_RMV = 2,
|
||||
PCB_NEXT_SLOWTMR = 3,
|
||||
PCB_NEXT_INPUT = 4,
|
||||
};
|
||||
typedef int err_t;
|
||||
|
||||
// Forward declaration for callbacks
|
||||
@@ -175,9 +166,6 @@ struct tcp_pcb {
|
||||
tcp_connected_fn connected;
|
||||
tcp_poll_fn poll;
|
||||
tcp_err_fn errf;
|
||||
|
||||
uint8_t next_owner; // who last wrote pcb->next (enum pcb_next_owner)
|
||||
|
||||
uint32_t keep_idle;
|
||||
uint8_t persist_cnt;
|
||||
uint8_t persist_backoff;
|
||||
@@ -193,7 +181,6 @@ struct tcp_pcb_listen {
|
||||
uint8_t prio;
|
||||
uint16_t local_port;
|
||||
uint32_t local_ip;
|
||||
uint8_t next_owner;
|
||||
tcp_accept_fn accept;
|
||||
};
|
||||
|
||||
|
||||
@@ -198,9 +198,7 @@ void lwip_tcp_input(struct lwip_tcp_ctx *ctx, struct pbuf *p,
|
||||
pcb->local_ip == dst_ip) {
|
||||
if (prev != NULL) {
|
||||
prev->next = pcb->next;
|
||||
prev->next_owner = PCB_NEXT_INPUT;
|
||||
pcb->next = ctx->active_pcbs;
|
||||
pcb->next_owner = PCB_NEXT_INPUT;
|
||||
ctx->active_pcbs = pcb;
|
||||
}
|
||||
break;
|
||||
@@ -225,8 +223,8 @@ void lwip_tcp_input(struct lwip_tcp_ctx *ctx, struct pbuf *p,
|
||||
pcb->remote_ip == src_ip &&
|
||||
pcb->local_ip == dst_ip) {
|
||||
if (pcb->next == pcb) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS SELF-LOOP in lwip_tcp_input: pcb=%p sport=%u dport=%u next_owner=%d, aborting",
|
||||
(void*)pcb, sport, dport, pcb->next_owner);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS SELF-LOOP in lwip_tcp_input: pcb=%p sport=%u dport=%u, aborting",
|
||||
(void*)pcb, sport, dport);
|
||||
tcp_abort(pcb);
|
||||
} else {
|
||||
tcp_timewait_input(pcb);
|
||||
@@ -249,9 +247,7 @@ void lwip_tcp_input(struct lwip_tcp_ctx *ctx, struct pbuf *p,
|
||||
if (lpcb != NULL) {
|
||||
if (prev != NULL) {
|
||||
((struct tcp_pcb_listen *)prev)->next = lpcb->next;
|
||||
((struct tcp_pcb_listen *)prev)->next_owner = PCB_NEXT_INPUT;
|
||||
lpcb->next = ctx->listen_pcbs;
|
||||
lpcb->next_owner = PCB_NEXT_INPUT;
|
||||
ctx->listen_pcbs = (struct tcp_pcb *)lpcb;
|
||||
}
|
||||
tcp_listen_input(lpcb);
|
||||
|
||||
@@ -14,9 +14,10 @@
|
||||
#define TCP_FAST_INTERVAL TCP_TMR_INTERVAL
|
||||
#define TCP_SLOW_INTERVAL (2 * TCP_TMR_INTERVAL)
|
||||
|
||||
#define TCP_FIN_WAIT_TIMEOUT 6000 // ms
|
||||
#define TCP_SYN_RCVD_TIMEOUT 6000 // ms
|
||||
#define TCP_MSL 25000 // ms (2*MSL = 50s)
|
||||
#define TCP_RTO_MIN_MS 3000 // ms, initial RTO
|
||||
#define TCP_FIN_WAIT_TIMEOUT 20000 // ms
|
||||
#define TCP_SYN_RCVD_TIMEOUT 20000 // ms
|
||||
#define TCP_MSL 60000 // ms (2*MSL = 120s)
|
||||
#define TCP_OOSEQ_TIMEOUT 6 // x RTO
|
||||
|
||||
#define TCP_KEEPIDLE_DEFAULT 7200000 // ms (unused, no keepalive)
|
||||
|
||||
@@ -236,15 +236,13 @@ void lwip_tcp_stats_clear(struct lwip_tcp_ctx *ctx);
|
||||
|
||||
// PCB list management
|
||||
#define TCP_REG(pcbs, npcb) do { \
|
||||
(npcb)->next_owner = PCB_NEXT_REG; \
|
||||
(npcb)->next = *(pcbs); *(pcbs) = (npcb); \
|
||||
} while(0)
|
||||
|
||||
#define TCP_RMV(pcbs, npcb) do { \
|
||||
if(*(pcbs) == (npcb)) { *(pcbs) = (*pcbs)->next; } \
|
||||
else { struct tcp_pcb *_tmp; for(_tmp = *(pcbs); _tmp != NULL; _tmp = _tmp->next) { if(_tmp->next == (npcb)) { _tmp->next_owner = PCB_NEXT_SLOWTMR; _tmp->next = (npcb)->next; break; } } } \
|
||||
else { struct tcp_pcb *_tmp; for(_tmp = *(pcbs); _tmp != NULL; _tmp = _tmp->next) { if(_tmp->next == (npcb)) { _tmp->next = (npcb)->next; break; } } } \
|
||||
(npcb)->next = NULL; \
|
||||
(npcb)->next_owner = PCB_NEXT_RMV; \
|
||||
} while(0)
|
||||
|
||||
#define TCP_REG_ACTIVE(ctx, npcb) TCP_REG(&(ctx)->active_pcbs, npcb)
|
||||
|
||||
@@ -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 */
|
||||
+12
-12
@@ -20,7 +20,7 @@ static ip_str_t ip_host_to_str(uint32_t ip_host) {
|
||||
|
||||
// ==================== Callbacks ====================
|
||||
|
||||
// CLIENT: NAT TUN output → encapsulate in ETCP_ID_NAT → send to provider via etcp_router
|
||||
// CLIENT: NAT TUN output → encapsulate in ETCP_RT_ID_NAT → send to provider via etcp_router
|
||||
static void nat_transport_client_tun_out_cb(struct ll_queue* q, void* arg) {
|
||||
struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)arg;
|
||||
if (!inst) { queue_resume_callback(q); return; }
|
||||
@@ -35,7 +35,7 @@ static void nat_transport_client_tun_out_cb(struct ll_queue* q, void* arg) {
|
||||
uint8_t* new_dgram = u_malloc(total_len);
|
||||
if (!new_dgram) { queue_dgram_free(pkt); queue_entry_free(pkt); queue_resume_callback(q); return; }
|
||||
|
||||
new_dgram[0] = ETCP_ID_NAT;
|
||||
new_dgram[0] = ETCP_RT_ID_NAT;
|
||||
memcpy(new_dgram + 1, &tr->self_node_id, 8);
|
||||
memcpy(new_dgram + 9, pkt->dgram + 1, ip_len);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ static void nat_transport_provider_tun_out_cb(struct ll_queue* q, void* arg) {
|
||||
uint8_t* new_dgram = u_malloc(total_len);
|
||||
if (!new_dgram) { queue_dgram_free(pkt); queue_entry_free(pkt); queue_resume_callback(q); return; }
|
||||
|
||||
new_dgram[0] = ETCP_ID_NAT;
|
||||
new_dgram[0] = ETCP_RT_ID_NAT;
|
||||
memcpy(new_dgram + 1, &inst->nat_tr.self_node_id, 8);
|
||||
memcpy(new_dgram + 9, pkt->dgram + 1, ip_len);
|
||||
|
||||
@@ -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
|
||||
// ETCP_RT_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);
|
||||
@@ -173,8 +173,8 @@ int nat_transport_init(struct UTUN_INSTANCE* inst) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (etcp_router_bind(inst, ETCP_ID_NAT, nat_transport_etcp_recv_cb) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Failed to bind ETCP_ID_NAT via etcp_router");
|
||||
if (etcp_router_bind(inst, ETCP_RT_ID_NAT, nat_transport_etcp_recv_cb) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Failed to bind ETCP_RT_ID_NAT via etcp_router");
|
||||
tun_close(tr->nat_tun); tr->nat_tun = NULL;
|
||||
eim_nat_destroy_ctx(&inst->nat);
|
||||
return -1;
|
||||
@@ -200,7 +200,7 @@ void nat_transport_destroy(struct UTUN_INSTANCE* inst) {
|
||||
if (!inst || !inst->nat_tr.initialized) return;
|
||||
struct nat_transport_ctx* tr = &inst->nat_tr;
|
||||
|
||||
etcp_router_unbind(inst, ETCP_ID_NAT);
|
||||
etcp_router_unbind(inst, ETCP_RT_ID_NAT);
|
||||
|
||||
if (tr->nat_tun) { tun_close(tr->nat_tun); tr->nat_tun = NULL; }
|
||||
|
||||
|
||||
@@ -241,7 +241,7 @@ static void pn_send_to_etcp(struct PKTNORM* pn) {
|
||||
frag->ll.memlen = pn->etcp->instance->data_pool->object_size;
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NORMALIZER, "pn->etcp: size=%d memlen=%d frag_size=%d", frag->ll.len, frag->ll.memlen, pn->frag_size);
|
||||
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump("NORM->ETCP", pn->data, frag->ll.len);
|
||||
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP, "NORM->ETCP", pn->data, frag->ll.len);
|
||||
|
||||
queue_data_put(pn->etcp->input_queue, (struct ll_entry*)frag);
|
||||
// Сбросить структуру (dgram передан во фрагмент, не освобождаем)
|
||||
@@ -286,7 +286,7 @@ static void etcp_input_ready_cb(struct ll_queue* q, void* arg) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump("->NORM", in_dgram->dgram, in_dgram->len);
|
||||
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP, "->NORM", in_dgram->dgram, in_dgram->len);
|
||||
|
||||
pn_buf_renew(pn);
|
||||
// DEBUG_INFO(DEBUG_CATEGORY_NORMALIZER, "pn_packer: new pkt hdrpos=%d",pn->data_ptr);
|
||||
@@ -350,7 +350,7 @@ static void pn_unpacker_cb(struct ll_queue* q, void* arg) {
|
||||
uint8_t* payload = frag->ll.dgram;
|
||||
uint16_t len = frag->ll.len;
|
||||
uint16_t ptr = 0;
|
||||
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump("ETCP->NORM", payload, len);
|
||||
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP, "ETCP->NORM", payload, len);
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_NORMALIZER, "unpacking fragment len=%d", len);
|
||||
|
||||
@@ -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("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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ static void raw_read_cb(socket_t sock, void* arg) {
|
||||
if (!e) return;
|
||||
e->dgram = u_malloc(ICMP_PROXY_HDR_SIZE + payload_len);
|
||||
if (!e->dgram) { queue_entry_free(e); return; }
|
||||
e->dgram[0] = ETCP_ID_ICMP_PROXY;
|
||||
e->dgram[0] = ETCP_RT_ID_ICMP_PROXY;
|
||||
e->dgram[1] = ICMP_PROXY_SUBCMD_REPLY;
|
||||
memcpy(e->dgram + 2, &r->client_node_id, 8);
|
||||
memcpy(e->dgram + 10, &r->dst_ip, 4);
|
||||
@@ -149,7 +149,7 @@ static void exit_handle_request(struct ETCP_CONN* conn, struct ll_entry* entry)
|
||||
if (e) {
|
||||
e->dgram = u_malloc(ICMP_PROXY_HDR_SIZE + payload_len);
|
||||
if (e->dgram) {
|
||||
e->dgram[0] = ETCP_ID_ICMP_PROXY;
|
||||
e->dgram[0] = ETCP_RT_ID_ICMP_PROXY;
|
||||
e->dgram[1] = ICMP_PROXY_SUBCMD_REPLY;
|
||||
memcpy(e->dgram + 2, &client_node_id, 8);
|
||||
memcpy(e->dgram + 10, &dst_ip, 4);
|
||||
@@ -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];
|
||||
@@ -214,7 +214,7 @@ int icmp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
|
||||
if (!e) return -1;
|
||||
e->dgram = u_malloc(ICMP_PROXY_HDR_SIZE + payload_len);
|
||||
if (!e->dgram) { queue_entry_free(e); return -1; }
|
||||
e->dgram[0] = ETCP_ID_ICMP_PROXY;
|
||||
e->dgram[0] = ETCP_RT_ID_ICMP_PROXY;
|
||||
e->dgram[1] = ICMP_PROXY_SUBCMD_REQUEST;
|
||||
memcpy(e->dgram + 2, &inst->node_id, 8);
|
||||
memcpy(e->dgram + 10, &dst_ip, 4);
|
||||
@@ -322,7 +322,7 @@ int icmp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua) {
|
||||
}
|
||||
}
|
||||
|
||||
etcp_router_bind(inst, ETCP_ID_ICMP_PROXY, icmp_proxy_recv_cb);
|
||||
etcp_router_bind(inst, ETCP_RT_ID_ICMP_PROXY, icmp_proxy_recv_cb);
|
||||
ctx->initialized = 1;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy initialized (exit=%d raw_sock=%d)", ctx->is_exit, (int)ctx->raw_sock);
|
||||
return 0;
|
||||
@@ -330,7 +330,7 @@ int icmp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua) {
|
||||
|
||||
void icmp_proxy_destroy(struct UTUN_INSTANCE* inst) {
|
||||
if (!inst || !g_icmp_ctx) return;
|
||||
etcp_router_unbind(inst, ETCP_ID_ICMP_PROXY);
|
||||
etcp_router_unbind(inst, ETCP_RT_ID_ICMP_PROXY);
|
||||
if (g_icmp_ctx->expire_timer) { uasync_cancel_timeout(g_icmp_ctx->ua, g_icmp_ctx->expire_timer); g_icmp_ctx->expire_timer = NULL; }
|
||||
if (g_icmp_ctx->raw_sock != SOCKET_INVALID) {
|
||||
if (g_icmp_ctx->raw_read_id) { uasync_remove_socket_t(g_icmp_ctx->ua, g_icmp_ctx->raw_sock); g_icmp_ctx->raw_read_id = NULL; }
|
||||
|
||||
@@ -0,0 +1,620 @@
|
||||
// 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_RT_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);
|
||||
int ret = etcp_route_send(inst, dst, e, force);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "SOCKS send_msg subcmd=%02x sid=%08x len=%zu force=%d → ret=%d",
|
||||
subcmd, sid, len, force, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
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) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "SOCKS send CLOSE sid=%08x", c->stream_id);
|
||||
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) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "SOCKS send ERROR sid=%08x", c->stream_id);
|
||||
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) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "SOCKS send FIN sid=%08x", c->stream_id);
|
||||
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);
|
||||
c->bytes_to_client += len;
|
||||
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 / HTTP proxy handshake
|
||||
// ====================================================================
|
||||
static void process_http_request(struct socks_proxy_conn* c) {
|
||||
char* line_end = memmem(c->buf, c->buf_len, "\r\n", 2);
|
||||
if (!line_end) return;
|
||||
|
||||
uint16_t line_len = (uint16_t)((uint8_t*)line_end - c->buf);
|
||||
if (line_len < 8) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: http request too short"); goto error; }
|
||||
|
||||
char line[512]; if (line_len > sizeof(line) - 1) line_len = sizeof(line) - 1;
|
||||
memcpy(line, c->buf, line_len); line[line_len] = '\0';
|
||||
|
||||
// ===== CONNECT =====
|
||||
if (strncmp(line, "CONNECT ", 8) == 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;
|
||||
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;
|
||||
}
|
||||
|
||||
// ===== Не-CONNECT: HTTP-прокси (GET, POST, PUT, HEAD, OPTIONS, ...) =====
|
||||
char* hdr_end = memmem(c->buf, c->buf_len, "\r\n\r\n", 4);
|
||||
if (!hdr_end) return;
|
||||
|
||||
uint16_t headers_len = (uint16_t)((uint8_t*)hdr_end - c->buf) + 4;
|
||||
|
||||
char method[16] = {0}, url[512] = {0};
|
||||
if (sscanf(line, "%15s %511s", method, url) < 2) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad http request line: %s", line);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (strncmp(url, "http://", 7) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: not an absolute http URL: %s", url);
|
||||
goto error;
|
||||
}
|
||||
|
||||
char host[256];
|
||||
int port = 80;
|
||||
const char* host_start = url + 7;
|
||||
const char* path_start = strchr(host_start, '/');
|
||||
const char* col = NULL;
|
||||
for (const char* p = host_start; (path_start ? p < path_start : *p); p++) {
|
||||
if (*p == ':') { col = p; break; }
|
||||
}
|
||||
|
||||
if (col && (!path_start || col < path_start)) {
|
||||
size_t hlen = (size_t)(col - host_start);
|
||||
if (hlen >= sizeof(host)) goto error;
|
||||
memcpy(host, host_start, hlen); host[hlen] = '\0';
|
||||
port = atoi(col + 1);
|
||||
if (port <= 0 || port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad port %d in %s", port, url); goto error; }
|
||||
} else if (path_start) {
|
||||
size_t hlen = (size_t)(path_start - host_start);
|
||||
if (hlen >= sizeof(host)) goto error;
|
||||
memcpy(host, host_start, hlen); host[hlen] = '\0';
|
||||
} else {
|
||||
size_t hlen = strlen(host_start);
|
||||
if (hlen >= sizeof(host)) goto error;
|
||||
memcpy(host, host_start, hlen); host[hlen] = '\0';
|
||||
}
|
||||
|
||||
if (host[0] == '\0') { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: empty host in %s", url); goto error; }
|
||||
if (!path_start || *path_start == '\0') path_start = "/";
|
||||
|
||||
struct hostent* he = gethostbyname(host);
|
||||
if (!he || he->h_addrtype != AF_INET) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: DNS failed for %s", host);
|
||||
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 %s %s:%d%s → %d.%d.%d.%d:%d",
|
||||
method, host, port, path_start,
|
||||
c->dest_ip[0], c->dest_ip[1], c->dest_ip[2], c->dest_ip[3], port);
|
||||
|
||||
if (send_connect(c) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: send_connect failed for %s", host);
|
||||
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;
|
||||
}
|
||||
|
||||
const char* version = strstr(line, " HTTP/");
|
||||
char version_str[16] = " HTTP/1.1";
|
||||
if (version) {
|
||||
size_t vlen = strlen(version);
|
||||
if (vlen >= sizeof(version_str)) vlen = sizeof(version_str) - 1;
|
||||
memcpy(version_str, version, vlen);
|
||||
version_str[vlen] = '\0';
|
||||
}
|
||||
|
||||
uint16_t rest_off = line_len + 2;
|
||||
uint16_t rest_len = headers_len - rest_off;
|
||||
uint8_t hdr_buf[2048];
|
||||
int hdr_n = snprintf((char*)hdr_buf, sizeof(hdr_buf), "%s %s%s\r\n", method, path_start, version_str);
|
||||
if (hdr_n < 0 || (size_t)hdr_n + rest_len > sizeof(hdr_buf)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: header reconstruction overflow hdr_n=%d rest=%u", hdr_n, rest_len);
|
||||
goto error;
|
||||
}
|
||||
memcpy(hdr_buf + hdr_n, c->buf + rest_off, rest_len);
|
||||
uint16_t hdr_chunk = (uint16_t)hdr_n + rest_len;
|
||||
uint16_t extra = (headers_len < c->buf_len) ? (uint16_t)(c->buf_len - headers_len) : 0;
|
||||
uint16_t total = hdr_chunk + extra;
|
||||
|
||||
uint8_t* pkt = u_malloc(total);
|
||||
if (!pkt) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: tx pkt malloc=%u failed sid=%08x", total, c->stream_id); goto error; }
|
||||
memcpy(pkt, hdr_buf, hdr_chunk);
|
||||
if (extra) memcpy(pkt + hdr_chunk, c->buf + headers_len, extra);
|
||||
|
||||
int ret = send_data(c, pkt, total);
|
||||
if (ret == 0) {
|
||||
u_free(pkt);
|
||||
} else {
|
||||
c->tx_buf = pkt; c->tx_len = total;
|
||||
etcp_router_on_send_ready(c->inst, c->via_node_id, ETCP_RT_ID_TCP_PROXY, &c->tx_waiter, tx_waiter_cb, c);
|
||||
}
|
||||
|
||||
c->buf_len = 0;
|
||||
c->state = HTTP_STATE_RELAY;
|
||||
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
|
||||
if (c->tx_buf) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: RELAY with pending tx_buf sid=%08x — drop new len=%u", c->stream_id, e->len);
|
||||
memory_pool_free(c->tc->data_pool, e->dgram); queue_entry_free(e);
|
||||
return;
|
||||
}
|
||||
int ret = send_data(c, e->dgram, e->len);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "SOCKS PROXY SEND sid=%08x len=%u ret=%d is_http=%d", c->stream_id, e->len, ret, c->is_http);
|
||||
if (ret == 0) {
|
||||
memory_pool_free(c->tc->data_pool, e->dgram); queue_entry_free(e);
|
||||
queue_resume_callback(q);
|
||||
} else {
|
||||
c->tx_buf = u_malloc(e->len);
|
||||
if (c->tx_buf) { memcpy(c->tx_buf, e->dgram, e->len); c->tx_len = e->len; }
|
||||
else { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: tx_buf malloc=%u failed sid=%08x — drop", e->len, c->stream_id); }
|
||||
memory_pool_free(c->tc->data_pool, e->dgram); queue_entry_free(e);
|
||||
etcp_router_on_send_ready(c->inst, c->via_node_id, ETCP_RT_ID_TCP_PROXY, &c->tx_waiter, tx_waiter_cb, c);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCKS PROXY BP: sid=%08x tx_buf=%u waiter_reg", c->stream_id, c->tx_len);
|
||||
}
|
||||
}
|
||||
|
||||
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->tx_buf || c->rem_closed || c->close_sent) return;
|
||||
int ret = send_data(c, c->tx_buf, c->tx_len);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCKS PROXY WAKE: sid=%08x tx_buf=%u ret=%d is_http=%d", c->stream_id, c->tx_len, ret, c->is_http);
|
||||
if (ret == 0) {
|
||||
u_free(c->tx_buf); c->tx_buf = NULL; c->tx_len = 0;
|
||||
queue_resume_callback(c->tc->read_queue);
|
||||
} else {
|
||||
etcp_router_on_send_ready(c->inst, c->via_node_id, ETCP_RT_ID_TCP_PROXY, &c->tx_waiter, tx_waiter_cb, c);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// 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 state=%d fm_r=%d fm_l=%d rem_cl=%d cs=%d cp=%d "
|
||||
"bytes_client=%u bytes_exit=%u wq=%d",
|
||||
c->stream_id, err, c->state, tc->fin_remote, tc->fin_local, c->rem_closed, c->close_sent, c->close_pending,
|
||||
c->bytes_to_client, c->bytes_from_exit, c->tc->write_queue->count);
|
||||
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_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: tcp closed sid=%08x state=%d fm_r=%d fm_l=%d rem_cl=%d bytes_client=%u bytes_exit=%u",
|
||||
c->stream_id, c->state, tc->fin_remote, tc->fin_local, c->rem_closed, c->bytes_to_client, c->bytes_from_exit);
|
||||
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) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "SOCKS DATA <- sid=%08x len=%zu", stream_id, data_len);
|
||||
c->bytes_from_exit += (uint32_t)data_len;
|
||||
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);
|
||||
c->bytes_to_client += (uint32_t)data_len;
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: handle_data alloc failed sid=%08x wq=%d bytes_in=%u bytes_out=%u",
|
||||
stream_id, c->tc->write_queue->count, c->bytes_from_exit, c->bytes_to_client);
|
||||
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_cancel_send_ready(c->inst, c->via_node_id, ETCP_RT_ID_TCP_PROXY, &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_cancel_send_ready(c->inst, c->via_node_id, ETCP_RT_ID_TCP_PROXY, &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->tx_buf) { u_free(c->tx_buf); c->tx_buf = NULL; c->tx_len = 0; }
|
||||
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,83 @@
|
||||
// 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;
|
||||
uint8_t* tx_buf; // буфер при backpressure (retry в tx_waiter_cb)
|
||||
uint16_t tx_len;
|
||||
uint32_t bytes_to_client; // байт записано клиенту (curl) через write_queue
|
||||
uint32_t bytes_from_exit; // байт получено от exit node через ETCP DATA
|
||||
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"
|
||||
@@ -69,7 +70,7 @@ static int tcp_proxy_client_send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, u
|
||||
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_send_msg: 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, "tcp_proxy_client_send_msg: malloc(%zu) failed subcmd=%02x sid=%08x", TCP_PROXY_HDR_SIZE + len, subcmd, sid); queue_entry_free(e); return -1; }
|
||||
e->dgram[0] = ETCP_ID_TCP_PROXY;
|
||||
e->dgram[0] = ETCP_RT_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);
|
||||
@@ -516,7 +517,7 @@ void tcp_proxy_client_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* en
|
||||
struct tcp_proxy_client* proxy = inst ? inst->tcp_proxy_client : NULL;
|
||||
|
||||
if (!entry || !entry->dgram || entry->len < TCP_PROXY_HDR_SIZE) {
|
||||
if (entry && entry->len == 9 && !conn && entry->dgram[0] == ETCP_ID_TCP_PROXY_CLIENT) {
|
||||
if (entry && entry->len == 9 && !conn && entry->dgram[0] == ETCP_RT_ID_TCP_PROXY) {
|
||||
uint64_t peer_id;
|
||||
memcpy(&peer_id, entry->dgram + 1, 8);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY CLOSE_ALL from %016llx — clearing client conns for peer",
|
||||
@@ -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); }
|
||||
@@ -533,14 +536,27 @@ void tcp_proxy_client_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* en
|
||||
uint8_t subcmd = entry->dgram[1];
|
||||
uint32_t stream_id; memcpy(&stream_id, entry->dgram + 2, 4);
|
||||
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "PROXY CLIENT RECV subcmd=%02x sid=%08x pkt_len=%u", subcmd, stream_id, entry->len);
|
||||
|
||||
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; }
|
||||
if (subcmd == TCP_PROXY_SUBCMD_FIN) { tcp_proxy_client_handle_fin(proxy, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
}
|
||||
if (subcmd == TCP_PROXY_SUBCMD_ERROR) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY ERROR sid=%08x — client conn not found, silent drop", stream_id);
|
||||
if (subcmd == TCP_PROXY_SUBCMD_DATA || subcmd == TCP_PROXY_SUBCMD_CLOSE || subcmd == TCP_PROXY_SUBCMD_FIN || subcmd == TCP_PROXY_SUBCMD_ERROR) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "PROXY subcmd=%02x sid=%08x — client conn not found, silent drop", subcmd, stream_id);
|
||||
queue_dgram_free(entry); queue_entry_free(entry); return;
|
||||
}
|
||||
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy client: unhandled subcmd=%02x sid=%08x", subcmd, stream_id);
|
||||
@@ -553,66 +569,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));
|
||||
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) {
|
||||
if (etcp_router_bind(inst, ETCP_RT_ID_TCP_PROXY, 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);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client: etcp_router bind registered for ID=0x%02x", ETCP_RT_ID_TCP_PROXY);
|
||||
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);
|
||||
if (p->inst) etcp_router_unbind(p->inst, ETCP_ID_TCP_PROXY_CLIENT);
|
||||
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_RT_ID_TCP_PROXY);
|
||||
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);
|
||||
|
||||
|
||||
+126
-35
@@ -33,6 +33,7 @@ static void read_queue_drain_cb(struct ll_queue* q, void* arg);
|
||||
static void pause_resume_cb(struct ll_queue* q, void* arg);
|
||||
static void close_retry_cb(void* arg);
|
||||
static void diag_timer_cb(void* arg);
|
||||
static void retry_timer_cb(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);
|
||||
static void send_close(struct tcp_proxy_server_conn* rc);
|
||||
@@ -53,10 +54,14 @@ static int send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd,
|
||||
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_server: 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, "tcp_proxy_server: malloc(%zu) failed", TCP_PROXY_HDR_SIZE + len); queue_entry_free(e); return -1; }
|
||||
e->dgram[0] = ETCP_ID_TCP_PROXY_CLIENT;
|
||||
e->dgram[0] = ETCP_RT_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, "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);
|
||||
}
|
||||
@@ -112,19 +117,26 @@ static void send_fin(struct tcp_proxy_server_conn* rc) {
|
||||
static void on_fin_cb(struct tcp_conn* tc, void* arg) {
|
||||
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
|
||||
if (rc->cli_closed) return;
|
||||
int pend = write_pending(tc);
|
||||
int pend_w = write_pending(tc);
|
||||
int pend_r = (rc->tx_buf != NULL) || (tc->read_queue && tc->read_queue->head);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET,
|
||||
"SOCK:DST_FIN fd=%d sid=%08x fin_l=%d pend_w=%d pend_r=%d "
|
||||
"sent=%u recv=%u relayed=%u drain#=%u data#=%u "
|
||||
"rq=%d(%zub) wq=%d(%zub) wbuf=%s err=%d tx_buf=%s",
|
||||
(int)tc->sock, rc->stream_id, tc->fin_local, pend_w, pend_r,
|
||||
rc->bytes_sent, rc->bytes_recv, rc->bytes_relayed,
|
||||
rc->drain_count, rc->data_count,
|
||||
tc->read_queue->count, queue_total_bytes(tc->read_queue),
|
||||
tc->write_queue->count, queue_total_bytes(tc->write_queue),
|
||||
tc->write_buf ? "y" : "n", tc->error, rc->tx_buf ? "y" : "n");
|
||||
if (tc->fin_local) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x fin_local=%d — both FINs, closing",
|
||||
(int)tc->sock, rc->stream_id, tc->fin_local);
|
||||
send_close(rc); tcp_conn_push_close(tc);
|
||||
} else if (pend) {
|
||||
} else if (pend_w) {
|
||||
tcp_conn_set_flushed(tc, on_flushed_cb);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x write_pend=%d → deferred",
|
||||
(int)tc->sock, rc->stream_id, pend);
|
||||
} else if (pend_r) {
|
||||
rc->dst_fin_deferred = 1;
|
||||
} else {
|
||||
send_fin(rc);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x → relay now",
|
||||
(int)tc->sock, rc->stream_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +144,7 @@ static void on_flushed_cb(struct tcp_conn* tc, void* arg) {
|
||||
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
|
||||
if (rc->cli_closed) return;
|
||||
if (tc->fin_local) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FLUSHED fd=%d sid=%08x fin_local=%d — both FINs, closing",
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FLUSHED fd=%d sid=%08x fin_local=%d — both FINs, closing",
|
||||
(int)tc->sock, rc->stream_id, tc->fin_local);
|
||||
send_close(rc); tcp_conn_push_close(tc); return;
|
||||
}
|
||||
@@ -141,7 +153,7 @@ static void on_flushed_cb(struct tcp_conn* tc, void* arg) {
|
||||
|
||||
static void on_closed_cb(struct tcp_conn* tc, void* arg) {
|
||||
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:CLOSED fd=%d sid=%08x — freeing",
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:CLOSED fd=%d sid=%08x — freeing",
|
||||
tc ? (int)tc->sock : -1, rc->stream_id);
|
||||
tcp_proxy_server_conn_free(rc);
|
||||
}
|
||||
@@ -150,12 +162,23 @@ static void on_error_cb(struct tcp_conn* tc, int err, void* arg) {
|
||||
(void)err;
|
||||
if (tc->closed) return;
|
||||
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET,
|
||||
"SOCK:ERR fd=%d sid=%08x err=%d fin_r=%d fin_l=%d "
|
||||
"sent=%u recv=%u relayed=%u drain#=%u data#=%u "
|
||||
"rq=%d wq=%d wbuf=%s conn=%d",
|
||||
tc ? (int)tc->sock : -1, rc->stream_id, err,
|
||||
tc ? tc->fin_remote : 0, tc ? tc->fin_local : 0,
|
||||
rc->bytes_sent, rc->bytes_recv, rc->bytes_relayed,
|
||||
rc->drain_count, rc->data_count,
|
||||
tc ? tc->read_queue->count : 0, tc ? tc->write_queue->count : 0,
|
||||
tc && tc->write_buf ? "y" : "n", tc ? tc->connected : 0);
|
||||
send_error(rc);
|
||||
tcp_proxy_server_conn_free(rc);
|
||||
}
|
||||
|
||||
static void diag_timer_cb(void* arg) {
|
||||
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
|
||||
rc->diag_timer = NULL;
|
||||
struct tcp_conn* tc = rc->tc;
|
||||
if (!tc || tc->sock == SOCKET_INVALID) return;
|
||||
|
||||
@@ -167,7 +190,7 @@ static void diag_timer_cb(void* arg) {
|
||||
getsockopt(tc->sock, SOL_SOCKET, SO_RCVBUF, &rcv_buf, &optlen);
|
||||
getsockopt(tc->sock, SOL_SOCKET, SO_SNDBUF, &snd_buf, &optlen);
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET,
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET,
|
||||
"SOCK:DIAG fd=%d sid=%08x conn=%d fin=%d "
|
||||
"rq=%d(%zub) wq=%d(%zub) wbuf=%s "
|
||||
"ent_f=%d dat_f=%d tcp_rb=%zu tcp_sb=%zu",
|
||||
@@ -188,8 +211,36 @@ static void diag_timer_cb(void* arg) {
|
||||
static void read_queue_drain_cb(struct ll_queue* q, void* arg) {
|
||||
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
|
||||
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
|
||||
struct ll_entry* e = queue_data_get(q);
|
||||
if (!e) { queue_resume_callback(q); return; }
|
||||
struct ll_entry* e;
|
||||
|
||||
if (rc->tx_buf) {
|
||||
int ret = send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_DATA, rc->stream_id, rc->tx_buf, rc->tx_len, 0);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "TPS TXBUF RETRY: sid=%08x len=%u ret=%d", rc->stream_id, rc->tx_len, ret);
|
||||
if (ret == 0) {
|
||||
u_free(rc->tx_buf); rc->tx_buf = NULL; rc->tx_len = 0;
|
||||
if (rc->retry_timer) { uasync_cancel_timeout(rc->ua, rc->retry_timer); rc->retry_timer = NULL; }
|
||||
if (rc->dst_fin_deferred && !q->head) {
|
||||
rc->dst_fin_deferred = 0;
|
||||
send_fin(rc);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
etcp_router_on_send_ready(inst, rc->peer_node_id, ETCP_RT_ID_TCP_PROXY,
|
||||
&rc->pause_waiter, pause_resume_cb, rc);
|
||||
if (!rc->retry_timer) rc->retry_timer = uasync_set_timeout(rc->ua, 5000, rc, retry_timer_cb, "tps_retry");
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "TPS TXBUF BP AGAIN: sid=%08x waiter_reg", rc->stream_id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
e = queue_data_get(q);
|
||||
if (!e) {
|
||||
if (rc->dst_fin_deferred) {
|
||||
rc->dst_fin_deferred = 0;
|
||||
send_fin(rc);
|
||||
}
|
||||
queue_resume_callback(q); return;
|
||||
}
|
||||
if (rc->cli_closed) {
|
||||
do {
|
||||
memory_pool_free(rc->tc->data_pool, e->dgram);
|
||||
@@ -200,27 +251,55 @@ static void read_queue_drain_cb(struct ll_queue* q, void* arg) {
|
||||
return;
|
||||
}
|
||||
int ret = send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_DATA, rc->stream_id, e->dgram, e->len, 0);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "SOCK:RECV fd=%d sid=%08x len=%u total=%d",
|
||||
(int)rc->tc->sock, rc->stream_id, e->len, rc->ctx ? rc->ctx->conn_count : 0);
|
||||
memory_pool_free(rc->tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
rc->bytes_relayed += e->len; rc->drain_count++;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "TPS DRAIN #%u sid=%08x len=%u → ret=%d rq=%d",
|
||||
rc->drain_count, rc->stream_id, e->len, ret, q->count);
|
||||
if (ret == 0) {
|
||||
memory_pool_free(rc->tc->data_pool, e->dgram); queue_entry_free(e);
|
||||
queue_resume_callback(q);
|
||||
} else {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:BACKPRESSURE fd=%d sid=%08x — pausing drain",
|
||||
(int)rc->tc->sock, rc->stream_id);
|
||||
etcp_router_on_send_ready(inst, rc->peer_node_id, ETCP_ID_TCP_PROXY_CLIENT,
|
||||
rc->tx_buf = u_malloc(e->len);
|
||||
if (rc->tx_buf) { memcpy(rc->tx_buf, e->dgram, e->len); rc->tx_len = e->len; }
|
||||
else { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TPS tx_buf malloc=%u failed sid=%08x — drop", e->len, rc->stream_id); }
|
||||
memory_pool_free(rc->tc->data_pool, e->dgram); queue_entry_free(e);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:BACKPRESSURE fd=%d sid=%08x — buffered %u for retry",
|
||||
(int)rc->tc->sock, rc->stream_id, rc->tx_len);
|
||||
etcp_router_on_send_ready(inst, rc->peer_node_id, ETCP_RT_ID_TCP_PROXY,
|
||||
&rc->pause_waiter, pause_resume_cb, rc);
|
||||
if (!rc->retry_timer) rc->retry_timer = uasync_set_timeout(rc->ua, 5000, rc, retry_timer_cb, "tps_retry");
|
||||
}
|
||||
}
|
||||
|
||||
static void pause_resume_cb(struct ll_queue* q, void* arg) {
|
||||
(void)q;
|
||||
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
|
||||
if (!rc->tc || rc->tc->sock == SOCKET_INVALID) return;
|
||||
if (!rc->tc || rc->tc->sock == SOCKET_INVALID || rc->cli_closed) return;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "TPS RESUME sid=%08x tx_buf=%s rq=%d",
|
||||
rc->stream_id, rc->tx_buf ? "y" : "n",
|
||||
rc->tc->read_queue ? rc->tc->read_queue->count : 0);
|
||||
queue_resume_callback(rc->tc->read_queue);
|
||||
}
|
||||
|
||||
static void retry_timer_cb(void* arg) {
|
||||
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
|
||||
rc->retry_timer = NULL;
|
||||
if (!rc->tx_buf || !rc->tc || rc->tc->sock == SOCKET_INVALID || rc->cli_closed) return;
|
||||
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
|
||||
int ret = send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_DATA, rc->stream_id, rc->tx_buf, rc->tx_len, 1);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "TPS RETRY TIMER: sid=%08x len=%u force=1 ret=%d", rc->stream_id, rc->tx_len, ret);
|
||||
if (ret == 0) {
|
||||
u_free(rc->tx_buf); rc->tx_buf = NULL; rc->tx_len = 0;
|
||||
if (rc->dst_fin_deferred && rc->tc->read_queue && !rc->tc->read_queue->head) {
|
||||
rc->dst_fin_deferred = 0;
|
||||
send_fin(rc);
|
||||
return;
|
||||
}
|
||||
queue_resume_callback(rc->tc->read_queue);
|
||||
} else {
|
||||
rc->retry_timer = uasync_set_timeout(rc->ua, 5000, rc, retry_timer_cb, "tps_retry");
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Управление жизненным циклом коннекта
|
||||
// ====================================================================
|
||||
@@ -234,14 +313,14 @@ static int conn_total(struct tcp_proxy_server_conn* rc) {
|
||||
|
||||
void tcp_proxy_server_conn_free(struct tcp_proxy_server_conn* rc) {
|
||||
if (!rc) return;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FREE enter rc=%p freed=%d sid=%08x", (void*)rc, rc->freed, rc->stream_id);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FREE enter rc=%p freed=%d sid=%08x", (void*)rc, rc->freed, rc->stream_id);
|
||||
if (rc->freed) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:FREE double rc=%p — IGNORED", (void*)rc);
|
||||
return;
|
||||
}
|
||||
rc->freed = 1;
|
||||
int total = conn_total(rc);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FREE fd=%d sid=%08x total=%d cli_closed=%d fin=%d error=%d",
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FREE fd=%d sid=%08x total=%d cli_closed=%d fin=%d error=%d",
|
||||
rc->tc ? (int)rc->tc->sock : -1, rc->stream_id, total, rc->cli_closed, rc->tc ? rc->tc->fin_remote : 0, rc->tc ? rc->tc->error : 0);
|
||||
if (rc->close_pending && rc->ctx && rc->ctx->inst) {
|
||||
rc->close_pending = 0;
|
||||
@@ -252,11 +331,13 @@ 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_RT_ID_TCP_PROXY, &rc->pause_waiter);
|
||||
if (rc->tx_buf) { u_free(rc->tx_buf); rc->tx_buf = NULL; rc->tx_len = 0; }
|
||||
if (rc->retry_timer) { uasync_cancel_timeout(rc->ua, rc->retry_timer); rc->retry_timer = NULL; }
|
||||
if (rc->close_timer) { uasync_cancel_timeout(rc->ua, rc->close_timer); rc->close_timer = NULL; }
|
||||
if (rc->diag_timer) { uasync_cancel_timeout(rc->ua, rc->diag_timer); rc->diag_timer = NULL; }
|
||||
if (rc->ctx && rc->ctx->inst) etcp_router_cancel_send_ready(rc->ctx->inst, rc->peer_node_id, ETCP_ID_TCP_PROXY_CLIENT, &rc->pause_waiter);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FREE u_free rc=%p", (void*)rc);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -290,7 +371,7 @@ int tcp_proxy_server_handle_connect(struct UTUN_INSTANCE* inst, struct ll_entry*
|
||||
(int)sock, stream_id, dest_ip[0],dest_ip[1],dest_ip[2],dest_ip[3],ntohs(dest_port), ctx->conn_count);
|
||||
socket_set_nonblocking(sock);
|
||||
|
||||
rc->tc = tcp_conn_create(inst->ua, sock, 1500, 8192, 4, 0, on_fin_cb, on_error_cb, rc);
|
||||
rc->tc = tcp_conn_create(inst->ua, sock, 1500, 8192, 4, 0, inst->config->global.tcp_recv_buf, on_fin_cb, on_error_cb, rc);
|
||||
if (!rc->tc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: tcp_conn_create failed"); socket_close_wrapper(sock); ctx->conn_count--; u_free(rc); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
|
||||
rc->tc->on_closed = on_closed_cb;
|
||||
queue_set_callback(rc->tc->read_queue, read_queue_drain_cb, rc);
|
||||
@@ -324,7 +405,15 @@ int tcp_proxy_server_handle_data(struct UTUN_INSTANCE* inst, struct ETCP_CONN* c
|
||||
queue_dgram_free(entry); queue_entry_free(entry); return -1;
|
||||
}
|
||||
size_t data_len = entry->len - TCP_PROXY_HDR_SIZE;
|
||||
rc->bytes_sent += (uint32_t)data_len; rc->data_count++;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "TPS DATA #%u sid=%08x len=%zu",
|
||||
rc->data_count, stream_id, data_len);
|
||||
if (data_len > 0) {
|
||||
if (data_len > rc->tc->data_pool->object_size) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_server: data_len=%zu > pool_sz=%zu, dropping sid=%08x",
|
||||
data_len, rc->tc->data_pool->object_size, stream_id);
|
||||
queue_dgram_free(entry); queue_entry_free(entry); return -1;
|
||||
}
|
||||
struct ll_entry* e = queue_entry_new_from_pool(rc->tc->entry_pool);
|
||||
uint8_t* buf = memory_pool_alloc(rc->tc->data_pool);
|
||||
if (e && buf) {
|
||||
@@ -346,7 +435,7 @@ void tcp_proxy_server_handle_close(struct UTUN_INSTANCE* inst, uint32_t stream_i
|
||||
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
|
||||
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(ctx, stream_id);
|
||||
if (!rc) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy server: CLOSE sid=%08x — no conn", stream_id); return; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:CLOSE_RECV fd=%d sid=%08x total=%d fin=%d write_pend=%d",
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:CLOSE_RECV fd=%d sid=%08x total=%d fin=%d write_pend=%d",
|
||||
rc->tc ? (int)rc->tc->sock : -1, stream_id, conn_total(rc),
|
||||
rc->tc ? rc->tc->fin_remote : 0, rc->tc ? write_pending(rc->tc) : 0);
|
||||
rc->cli_closed = 1;
|
||||
@@ -368,7 +457,7 @@ void tcp_proxy_server_handle_error(struct UTUN_INSTANCE* inst, uint32_t stream_i
|
||||
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
|
||||
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(ctx, stream_id);
|
||||
if (!rc) { DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy server: ERROR sid=%08x — no conn", stream_id); return; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:ERROR_RECV fd=%d sid=%08x total=%d fin=%d write_pend=%d",
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:ERROR_RECV fd=%d sid=%08x total=%d fin=%d write_pend=%d",
|
||||
rc->tc ? (int)rc->tc->sock : -1, stream_id, conn_total(rc),
|
||||
rc->tc ? rc->tc->fin_remote : 0, rc->tc ? write_pending(rc->tc) : 0);
|
||||
tcp_proxy_server_handle_close(inst, stream_id);
|
||||
@@ -379,7 +468,7 @@ void tcp_proxy_server_handle_fin(struct UTUN_INSTANCE* inst, uint32_t stream_id)
|
||||
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
|
||||
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(ctx, stream_id);
|
||||
if (!rc || !rc->tc || !rc->tc->connected) return;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FIN_RECV fd=%d sid=%08x — pushing FIN to wq",
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FIN_RECV fd=%d sid=%08x — pushing FIN to wq",
|
||||
(int)rc->tc->sock, stream_id);
|
||||
tcp_conn_push_fin(rc->tc);
|
||||
}
|
||||
@@ -391,7 +480,7 @@ void tcp_proxy_server_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
struct UTUN_INSTANCE* inst = conn ? conn->instance : NULL;
|
||||
|
||||
if (!entry || !entry->dgram || entry->len < TCP_PROXY_HDR_SIZE) {
|
||||
if (entry && entry->len == 9 && !conn && entry->dgram[0] == ETCP_ID_TCP_PROXY) {
|
||||
if (entry && entry->len == 9 && !conn && entry->dgram[0] == ETCP_RT_ID_TCP_PROXY) {
|
||||
uint64_t peer_id;
|
||||
memcpy(&peer_id, entry->dgram + 1, 8);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY CLOSE_ALL from %016llx — clearing server conns for peer",
|
||||
@@ -410,6 +499,8 @@ void tcp_proxy_server_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
uint8_t subcmd = entry->dgram[1];
|
||||
uint32_t stream_id; memcpy(&stream_id, entry->dgram + 2, 4);
|
||||
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "PROXY SERVER RECV subcmd=%02x sid=%08x pkt_len=%u", subcmd, stream_id, entry->len);
|
||||
|
||||
if (subcmd == TCP_PROXY_SUBCMD_CONNECT) {
|
||||
uint64_t src_node_id = conn ? conn->peer_node_id : (inst ? inst->node_id : 0);
|
||||
tcp_proxy_server_handle_connect(inst, entry, stream_id, src_node_id);
|
||||
@@ -424,8 +515,8 @@ void tcp_proxy_server_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
if (subcmd == TCP_PROXY_SUBCMD_FIN) { tcp_proxy_server_handle_fin(inst, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
}
|
||||
}
|
||||
if (subcmd == TCP_PROXY_SUBCMD_ERROR) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY ERROR sid=%08x — server conn not found, silent drop", stream_id);
|
||||
if (subcmd == TCP_PROXY_SUBCMD_DATA || subcmd == TCP_PROXY_SUBCMD_CLOSE || subcmd == TCP_PROXY_SUBCMD_FIN || subcmd == TCP_PROXY_SUBCMD_ERROR) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "PROXY subcmd=%02x sid=%08x — server conn not found, silent drop", subcmd, stream_id);
|
||||
queue_dgram_free(entry); queue_entry_free(entry); return;
|
||||
}
|
||||
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy server: unhandled subcmd=%02x sid=%08x", subcmd, stream_id);
|
||||
@@ -445,7 +536,7 @@ int tcp_proxy_server_init(struct UTUN_INSTANCE* inst) {
|
||||
ctx->inst = inst;
|
||||
if (!ctx->enabled) return 0;
|
||||
g_tcp_proxy_server_ctx = ctx;
|
||||
etcp_router_bind(inst, ETCP_ID_TCP_PROXY, tcp_proxy_server_recv_cb);
|
||||
etcp_router_bind(inst, ETCP_RT_ID_TCP_PROXY, tcp_proxy_server_recv_cb);
|
||||
if (!inst->config->global.tcp_proxy_client_enabled) {
|
||||
udp_proxy_init(inst, inst->ua);
|
||||
icmp_proxy_init(inst, inst->ua);
|
||||
|
||||
@@ -36,9 +36,18 @@ struct tcp_proxy_server_conn {
|
||||
void* close_timer; // таймер повтора CLOSE/ERROR
|
||||
int close_backoff; // backoff: 50..5000 tb (5ms..500ms)
|
||||
void* diag_timer; // 1-секундный таймер диагностики
|
||||
uint8_t dst_fin_deferred; // FIN от destination отложен (ждём drain read_queue + tx_buf)
|
||||
uint8_t freed; // 1 = уже освобождён, защита от double-free
|
||||
|
||||
struct queue_waiter_handle pause_waiter;
|
||||
void* retry_timer; // fallback timer (500ms) for force=1 retry on stall
|
||||
uint8_t* tx_buf; // буфер при backpressure (retry в pause_resume_cb)
|
||||
uint16_t tx_len;
|
||||
uint32_t bytes_sent; // байт записано в destination (реальный TCP)
|
||||
uint32_t bytes_recv; // байт прочитано от destination
|
||||
uint32_t bytes_relayed; // байт отправлено клиенту через ETCP
|
||||
uint32_t drain_count; // счётчик вызовов read_queue_drain_cb
|
||||
uint32_t data_count; // счётчик входящих DATA от клиента
|
||||
struct UASYNC* ua;
|
||||
};
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ static struct udp_flow* flow_find(struct udp_flow* head, uint64_t client_node_id
|
||||
static void flow_read_cb(socket_t sock, void* arg) {
|
||||
(void)sock; struct udp_flow* f = (struct udp_flow*)arg;
|
||||
if (!f || f->sock == SOCKET_INVALID || !g_udp_ctx) return;
|
||||
uint8_t buf[65536];
|
||||
uint8_t buf[1600];
|
||||
struct sockaddr_in from; socklen_t flen = sizeof(from);
|
||||
ssize_t n = recvfrom(f->sock, buf, sizeof(buf), 0, (struct sockaddr*)&from, &flen);
|
||||
if (n <= 0) return;
|
||||
@@ -52,7 +52,7 @@ static void flow_read_cb(socket_t sock, void* arg) {
|
||||
if (!e) return;
|
||||
e->dgram = u_malloc(UDP_PROXY_HDR_SIZE + n);
|
||||
if (!e->dgram) { queue_entry_free(e); return; }
|
||||
e->dgram[0] = ETCP_ID_UDP_PROXY;
|
||||
e->dgram[0] = ETCP_RT_ID_UDP_PROXY;
|
||||
e->dgram[1] = UDP_PROXY_SUBCMD_DATA;
|
||||
memcpy(e->dgram + 2, &f->client_node_id, 8);
|
||||
// Ответ src = оригинальный dst_ip:dest_port
|
||||
@@ -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];
|
||||
@@ -160,7 +160,7 @@ int udp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
|
||||
if (!e) return -1;
|
||||
e->dgram = u_malloc(UDP_PROXY_HDR_SIZE + payload_len);
|
||||
if (!e->dgram) { queue_entry_free(e); return -1; }
|
||||
e->dgram[0] = ETCP_ID_UDP_PROXY;
|
||||
e->dgram[0] = ETCP_RT_ID_UDP_PROXY;
|
||||
e->dgram[1] = UDP_PROXY_SUBCMD_DATA;
|
||||
memcpy(e->dgram + 2, &inst->node_id, 8);
|
||||
memcpy(e->dgram + 10, &src_ip, 4);
|
||||
@@ -245,7 +245,7 @@ int udp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua) {
|
||||
ctx->is_exit = inst->tcp_proxy_server.enabled;
|
||||
g_udp_ctx = ctx;
|
||||
|
||||
etcp_router_bind(inst, ETCP_ID_UDP_PROXY, udp_proxy_recv_cb);
|
||||
etcp_router_bind(inst, ETCP_RT_ID_UDP_PROXY, udp_proxy_recv_cb);
|
||||
ctx->initialized = 1;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "udp_proxy initialized (exit=%d)", ctx->is_exit);
|
||||
return 0;
|
||||
@@ -253,7 +253,7 @@ int udp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua) {
|
||||
|
||||
void udp_proxy_destroy(struct UTUN_INSTANCE* inst) {
|
||||
if (!inst || !g_udp_ctx) return;
|
||||
etcp_router_unbind(inst, ETCP_ID_UDP_PROXY);
|
||||
etcp_router_unbind(inst, ETCP_RT_ID_UDP_PROXY);
|
||||
if (g_udp_ctx->expire_timer) { uasync_cancel_timeout(g_udp_ctx->ua, g_udp_ctx->expire_timer); g_udp_ctx->expire_timer = NULL; }
|
||||
struct udp_flow* f = g_udp_ctx->flows;
|
||||
while (f) { struct udp_flow* n = f->next;
|
||||
|
||||
+104
-55
@@ -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"
|
||||
|
||||
|
||||
// ============================================================================
|
||||
@@ -45,10 +47,24 @@ static void route_bgp_send_table_request(struct ROUTE_BGP* bgp, struct ETCP_CONN
|
||||
}
|
||||
}
|
||||
|
||||
static void route_bgp_send_table_complete(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn) {
|
||||
if (!bgp || !conn) return;
|
||||
struct BGP_ROUTE_REQUEST* req = u_calloc(1, sizeof(struct BGP_ROUTE_REQUEST));
|
||||
if (!req) return;
|
||||
req->cmd = ETCP_ID_ROUTE_ENTRY;
|
||||
req->subcmd = ROUTE_SUBCMD_TABLE_COMPLETE;
|
||||
struct ll_entry* e = queue_entry_new(0);
|
||||
if (!e) { u_free(req); return; }
|
||||
e->dgram = (uint8_t*)req;
|
||||
e->len = sizeof(struct BGP_ROUTE_REQUEST);
|
||||
if (etcp_send(conn, e) != 0) { u_free(req); queue_entry_free(e); }
|
||||
}
|
||||
|
||||
static void route_bgp_add_to_senders(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn);
|
||||
static bool route_bgp_should_send_to(const struct NODEINFO_Q* nq, uint64_t target_id);
|
||||
static void route_bgp_send_full_table(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn);
|
||||
static void route_bgp_handle_request_table(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn);
|
||||
static void route_bgp_send_table_complete(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn);
|
||||
|
||||
static void nodeinfo_dump_log(const uint8_t* data, size_t len) {
|
||||
if (!data || len < sizeof(struct BGP_NODEINFO_PACKET)) return;
|
||||
@@ -101,6 +117,7 @@ static const char* bgp_subcmd_name(uint8_t subcmd) {
|
||||
case ROUTE_SUBCMD_PING_RESP: return "PING_RESP";
|
||||
case ROUTE_SUBCMD_NAT_INFO: return "NAT_INFO";
|
||||
case ROUTE_SUBCMD_NAT_CHECK_REQ: return "NAT_CHECK_REQ";
|
||||
case ROUTE_SUBCMD_TABLE_COMPLETE: return "TABLE_COMPLETE";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
@@ -188,6 +205,9 @@ static void route_bgp_receive_cbk(struct ETCP_CONN* from_conn, struct ll_entry*
|
||||
route_bgp_handle_nat_info(bgp, from_conn, data, entry->len);
|
||||
} else if (subcmd == ROUTE_SUBCMD_NAT_CHECK_REQ) {
|
||||
route_bgp_handle_nat_check_req(bgp, from_conn, data, entry->len);
|
||||
} else if (subcmd == ROUTE_SUBCMD_TABLE_COMPLETE) {
|
||||
etcp_set_routing_exchange_state(from_conn, 3);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP initial sync complete with %s", from_conn->log_name);
|
||||
}
|
||||
|
||||
queue_dgram_free(entry);
|
||||
@@ -244,6 +264,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 +354,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 +388,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 +459,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 +476,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 +495,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 +504,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) {
|
||||
@@ -627,7 +663,8 @@ static struct ETCP_CONN* route_bgp_find_third_node(struct ROUTE_BGP* bgp, struct
|
||||
struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)e->data;
|
||||
if (item && item->conn && item->conn != exclude && item->conn->links && item->conn->links->remote_only_local==0) {
|
||||
// Skip nodes with private socket type - they can't help with NAT detection
|
||||
if (item->conn->links->remote_type != CFG_SERVER_TYPE_PRIVATE) return item->conn;
|
||||
if (item->conn->links->remote_type != CFG_SERVER_TYPE_PRIVATE
|
||||
&& item->conn->links->remote_type != CFG_SERVER_TYPE_LOCAL) return item->conn;
|
||||
}
|
||||
e = e->next;
|
||||
}
|
||||
@@ -650,10 +687,11 @@ 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) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "skip nat check for private socket link");
|
||||
if (link->conn->type == CFG_SERVER_TYPE_PRIVATE || link->conn->type == CFG_SERVER_TYPE_LOCAL) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "skip nat check for private/local socket link");
|
||||
return;
|
||||
}
|
||||
// Find third node to send ping through
|
||||
@@ -728,11 +766,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 +809,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 +821,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 +832,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 +849,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 +863,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 +894,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 +976,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;
|
||||
@@ -972,6 +1018,7 @@ static void route_bgp_handle_request_table(struct ROUTE_BGP* bgp, struct ETCP_CO
|
||||
route_bgp_send_nodeinfo(bgp->local_node, conn);
|
||||
route_bgp_send_full_table(bgp, conn);
|
||||
route_bgp_add_to_senders(bgp, conn);
|
||||
route_bgp_send_table_complete(bgp, conn);
|
||||
}
|
||||
|
||||
static void route_bgp_handle_nat_info(struct ROUTE_BGP* bgp, struct ETCP_CONN* from_conn, const uint8_t* data, size_t len) {
|
||||
@@ -990,13 +1037,14 @@ 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;
|
||||
for (int i = 0; i < bgp->local_node->node.local_v4_sockets; i++) {
|
||||
if (meta[i].id == socket_id) {
|
||||
if (meta[i].type != verified_type) {
|
||||
meta[i].type = verified_type;
|
||||
if (meta[i].nat_type != verified_type) {
|
||||
meta[i].nat_type = verified_type;
|
||||
data_changed = 1;
|
||||
}
|
||||
break;
|
||||
@@ -1051,10 +1099,11 @@ static void route_bgp_handle_nat_info(struct ROUTE_BGP* bgp, struct ETCP_CONN* f
|
||||
struct ETCP_LINK* l = from_conn->links;
|
||||
while (l) { l->nat_type = info->nat_type; l = l->next; }
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT_INFO from %s: socket_id=%u type=%s ip=%s port=%u",
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT_INFO from %s: sock_id=%u type=%s ip=%s port=%u matched_sock=%s old_nat_type=%d",
|
||||
from_conn->log_name, socket_id,
|
||||
info->nat_type == NAT_TYPE_EIM ? "EIM" : "STRICT",
|
||||
ip_to_str(info->nat_ip, AF_INET).str, (unsigned)nat_port);
|
||||
info->nat_type == NAT_TYPE_EIM ? "EIM" : info->nat_type == NAT_TYPE_DIRECT ? "DIRECT" : "STRICT",
|
||||
ip_to_str(info->nat_ip, AF_INET).str, (unsigned)nat_port,
|
||||
es ? es->name : "none", es ? (int)es->nat_type : -1);
|
||||
}
|
||||
|
||||
static void route_bgp_handle_nat_check_req(struct ROUTE_BGP* bgp, struct ETCP_CONN* from_conn, const uint8_t* data, size_t len) {// поиск нужного линка и запуск nat_check
|
||||
|
||||
+6
-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"
|
||||
@@ -18,6 +19,7 @@
|
||||
#define ROUTE_SUBCMD_WITHDRAW 0x06 // узел стал недоступен
|
||||
#define ROUTE_SUBCMD_NAT_INFO 0x09 // информация о типе NAT клиента
|
||||
#define ROUTE_SUBCMD_NAT_CHECK_REQ 0x0A // запрос от клиента на проверку NAT для сокета
|
||||
#define ROUTE_SUBCMD_TABLE_COMPLETE 0x0B // завершение начальной синхронизации таблицы
|
||||
|
||||
#define MAX_HOPS 16
|
||||
#define BGP_NODES_HASH_SIZE 256
|
||||
@@ -88,6 +90,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 +156,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.
|
||||
|
||||
+47
-32
@@ -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_*
|
||||
@@ -123,14 +124,15 @@ static int conn_match_candidate_sockets(struct UTUN_INSTANCE* instance,
|
||||
uint32_t if_ip = if_sin->sin_addr.s_addr;
|
||||
|
||||
if (addr_type == ADDR_TYPE_INTERFACE) {
|
||||
// предпочитаем PRIVATE сокеты в той же /24 подсети
|
||||
if (e_sock->type == CFG_SERVER_TYPE_PRIVATE && (if_ip & 0x00FFFFFF) == (target_ip & 0x00FFFFFF))
|
||||
// предпочитаем PRIVATE/LOCAL сокеты в той же /24 подсети
|
||||
if ((e_sock->type == CFG_SERVER_TYPE_PRIVATE || e_sock->type == CFG_SERVER_TYPE_LOCAL)
|
||||
&& (if_ip & 0x00FFFFFF) == (target_ip & 0x00FFFFFF))
|
||||
match = 1;
|
||||
} else {
|
||||
// NAT или REAL: предпочитаем DIRECT > EIM > PUBLIC
|
||||
// NAT или REAL: предпочитаем DIRECT > EIM > PUBLIC/UNKNOWN
|
||||
if (e_sock->nat_type == NAT_VERIFIED_DIRECT) match = 1;
|
||||
else if (e_sock->nat_type == NAT_VERIFIED_EIM) match = 1;
|
||||
else if (e_sock->type == CFG_SERVER_TYPE_PUBLIC) match = 1;
|
||||
else if (e_sock->type == CFG_SERVER_TYPE_PUBLIC || e_sock->type == CFG_SERVER_TYPE_UNKNOWN) match = 1;
|
||||
}
|
||||
|
||||
if (match) {
|
||||
@@ -149,10 +151,12 @@ static int conn_match_candidate_sockets(struct UTUN_INSTANCE* instance,
|
||||
for (int i = 0; i < found; i++) if (out_sockets[i] == e_sock) { dup = 1; break; }
|
||||
if (!dup) {
|
||||
if (addr_type == ADDR_TYPE_INTERFACE) {
|
||||
if (e_sock->type == CFG_SERVER_TYPE_PRIVATE) out_sockets[found++] = e_sock;
|
||||
if (e_sock->type == CFG_SERVER_TYPE_PRIVATE || e_sock->type == CFG_SERVER_TYPE_LOCAL)
|
||||
out_sockets[found++] = e_sock;
|
||||
} else {
|
||||
// любой не-private
|
||||
if (e_sock->type != CFG_SERVER_TYPE_PRIVATE) out_sockets[found++] = e_sock;
|
||||
// любой не-private и не-local
|
||||
if (e_sock->type != CFG_SERVER_TYPE_PRIVATE && e_sock->type != CFG_SERVER_TYPE_LOCAL)
|
||||
out_sockets[found++] = e_sock;
|
||||
}
|
||||
}
|
||||
e_sock = e_sock->next;
|
||||
@@ -245,33 +249,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 +357,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 +417,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 +440,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;
|
||||
}
|
||||
|
||||
|
||||
+331
-6
@@ -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 config=%u nat=%u", i, v4sm[i].id, v4sm[i].config_type, v4sm[i].nat_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 config=%u nat=%u", i, v6sm[i].id, v6sm[i].config_type, v6sm[i].nat_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 config=%u nat=%u\n", i, v4sm[i].id, v4sm[i].config_type, v4sm[i].nat_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 config=%u nat=%u\n", i, v6sm[i].id, v6sm[i].config_type, v6sm[i].nat_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");
|
||||
@@ -139,6 +444,7 @@ int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BG
|
||||
int sock6_count = 0, addr6_count = 0;
|
||||
struct ETCP_SOCKET* e_sock = instance->etcp_sockets;
|
||||
while (e_sock) {
|
||||
if (e_sock->type == CFG_SERVER_TYPE_PRIVATE || e_sock->type == CFG_SERVER_TYPE_LOCAL) { e_sock = e_sock->next; continue; }
|
||||
if (e_sock->local_addr.ss_family == AF_INET) {
|
||||
sock_count++;
|
||||
addr_count++; // INTERFACE
|
||||
@@ -149,7 +455,8 @@ int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BG
|
||||
}
|
||||
}
|
||||
if (e_sock->nat_type == NAT_VERIFIED_DIRECT ||
|
||||
(e_sock->type == CFG_SERVER_TYPE_PUBLIC && e_sock->nat_type < NAT_VERIFIED_UNKNOWN)) addr_count++;
|
||||
((e_sock->type == CFG_SERVER_TYPE_PUBLIC || e_sock->type == CFG_SERVER_TYPE_UNKNOWN)
|
||||
&& e_sock->nat_type < NAT_VERIFIED_UNKNOWN)) addr_count++;
|
||||
} else if (e_sock->local_addr.ss_family == AF_INET6) {
|
||||
sock6_count++;
|
||||
addr6_count++; // INTERFACE only (no NAT for IPv6)
|
||||
@@ -176,10 +483,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 +506,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;
|
||||
|
||||
@@ -204,9 +518,11 @@ int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BG
|
||||
e_sock = instance->etcp_sockets;
|
||||
int midx = 0;
|
||||
while (e_sock) {
|
||||
if (e_sock->type == CFG_SERVER_TYPE_PRIVATE || e_sock->type == CFG_SERVER_TYPE_LOCAL) { e_sock = e_sock->next; continue; }
|
||||
if (e_sock->local_addr.ss_family == AF_INET) {
|
||||
meta[midx].id = e_sock->sock_id;
|
||||
meta[midx].type = e_sock->nat_type;
|
||||
meta[midx].config_type = e_sock->type;
|
||||
meta[midx].nat_type = e_sock->nat_type;
|
||||
midx++;
|
||||
}
|
||||
e_sock = e_sock->next;
|
||||
@@ -218,6 +534,7 @@ int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BG
|
||||
int aidx = 0;
|
||||
e_sock = instance->etcp_sockets;
|
||||
while (e_sock) {
|
||||
if (e_sock->type == CFG_SERVER_TYPE_PRIVATE || e_sock->type == CFG_SERVER_TYPE_LOCAL) { e_sock = e_sock->next; continue; }
|
||||
if (e_sock->local_addr.ss_family == AF_INET) {
|
||||
struct sockaddr_in* if_sin = (struct sockaddr_in*)&e_sock->interface_addr;
|
||||
struct sockaddr_in* nat_sin = (struct sockaddr_in*)&e_sock->nat_addr;
|
||||
@@ -238,7 +555,8 @@ int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BG
|
||||
}
|
||||
}
|
||||
if (e_sock->nat_type == NAT_VERIFIED_DIRECT ||
|
||||
(e_sock->type == CFG_SERVER_TYPE_PUBLIC && e_sock->nat_type < NAT_VERIFIED_UNKNOWN)) {
|
||||
((e_sock->type == CFG_SERVER_TYPE_PUBLIC || e_sock->type == CFG_SERVER_TYPE_UNKNOWN)
|
||||
&& e_sock->nat_type < NAT_VERIFIED_UNKNOWN)) {
|
||||
memcpy(addrs[aidx].addr, &if_sin->sin_addr.s_addr, 4);
|
||||
addrs[aidx].port = ntohs(if_sin->sin_port);
|
||||
addrs[aidx].type = ADDR_TYPE_REAL;
|
||||
@@ -255,9 +573,11 @@ int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BG
|
||||
e_sock = instance->etcp_sockets;
|
||||
int m6idx = 0;
|
||||
while (e_sock) {
|
||||
if (e_sock->type == CFG_SERVER_TYPE_PRIVATE || e_sock->type == CFG_SERVER_TYPE_LOCAL) { e_sock = e_sock->next; continue; }
|
||||
if (e_sock->local_addr.ss_family == AF_INET6) {
|
||||
meta6[m6idx].id = e_sock->sock_id;
|
||||
meta6[m6idx].type = e_sock->nat_type;
|
||||
meta6[m6idx].config_type = e_sock->type;
|
||||
meta6[m6idx].nat_type = e_sock->nat_type;
|
||||
m6idx++;
|
||||
}
|
||||
e_sock = e_sock->next;
|
||||
@@ -269,6 +589,7 @@ int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BG
|
||||
int a6idx = 0;
|
||||
e_sock = instance->etcp_sockets;
|
||||
while (e_sock) {
|
||||
if (e_sock->type == CFG_SERVER_TYPE_PRIVATE || e_sock->type == CFG_SERVER_TYPE_LOCAL) { e_sock = e_sock->next; continue; }
|
||||
if (e_sock->local_addr.ss_family == AF_INET6) {
|
||||
struct sockaddr_in6* if_sin6 = (struct sockaddr_in6*)&e_sock->interface_addr;
|
||||
memcpy(addrs6[a6idx].addr, &if_sin6->sin6_addr, 16);
|
||||
@@ -300,6 +621,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;
|
||||
}
|
||||
|
||||
+23
-3
@@ -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 адресов узла (плоский список)
|
||||
@@ -82,7 +91,8 @@ struct NODEINFO {
|
||||
// метаданные одного сокета
|
||||
struct NODEINFO_IPV4_SOCKET_META {
|
||||
uint8_t id; // unique socket id (0-255)
|
||||
uint8_t type; // CFG_SERVER_TYPE_*/NAT_VERIFIED_*
|
||||
uint8_t config_type; // CFG_SERVER_TYPE_PUBLIC/NAT/PRIVATE
|
||||
uint8_t nat_type; // NAT_TYPE_* / NAT_VERIFIED_*
|
||||
} __attribute__((packed));
|
||||
|
||||
// одна типизированная адресная запись (плоский список)
|
||||
@@ -95,7 +105,8 @@ struct NODEINFO_IPV4_ADDR {
|
||||
|
||||
struct NODEINFO_IPV6_SOCKET_META {
|
||||
uint8_t id; // unique socket id (0-255)
|
||||
uint8_t type; // CFG_SERVER_TYPE_*/NAT_VERIFIED_*
|
||||
uint8_t config_type; // CFG_SERVER_TYPE_PUBLIC/NAT/PRIVATE
|
||||
uint8_t nat_type; // NAT_TYPE_* / NAT_VERIFIED_*
|
||||
} __attribute__((packed));
|
||||
|
||||
struct NODEINFO_IPV6_ADDR {
|
||||
@@ -132,6 +143,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 +188,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);
|
||||
|
||||
+3
-6
@@ -22,9 +22,6 @@
|
||||
#define IP_HDR_DST_ADDR_OFFSET 16
|
||||
#define IP_HDR_MIN_SIZE 20
|
||||
|
||||
// ETCP packet ID for routing data
|
||||
#define ETCP_ID_DATA 0x00
|
||||
|
||||
// Extract destination IP from IPv4 packet
|
||||
// Returns 0 if not IPv4 or packet too small
|
||||
static uint32_t extract_dst_ip(uint8_t* data, size_t len) {
|
||||
@@ -254,8 +251,8 @@ int routing_bind(struct UTUN_INSTANCE* instance) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "routing_bind: instance is NULL");
|
||||
return -1;
|
||||
}
|
||||
if (etcp_router_bind(instance, ETCP_ID_DATA, routing_pkt_from_etcp_cb) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "routing_bind: failed to bind ETCP_ID_DATA via etcp_router for node %016llx",
|
||||
if (etcp_router_bind(instance, ETCP_RT_ID_DATA, routing_pkt_from_etcp_cb) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "routing_bind: failed to bind ETCP_RT_ID_DATA via etcp_router for node %016llx",
|
||||
(unsigned long long)instance->node_id);
|
||||
return -1;
|
||||
}
|
||||
@@ -272,7 +269,7 @@ void routing_destroy(struct UTUN_INSTANCE* instance) {
|
||||
(unsigned long long)instance->node_id);
|
||||
|
||||
// Unbind DATA handler from etcp_router
|
||||
etcp_router_unbind(instance, ETCP_ID_DATA);
|
||||
etcp_router_unbind(instance, ETCP_RT_ID_DATA);
|
||||
|
||||
// Clean up route table if exists
|
||||
if (instance->rt) {
|
||||
|
||||
+272
-600
File diff suppressed because it is too large.
Load diff
Loaded 100 of 563 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user