Compare commits
180
Commits
| 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 | ||
|
|
dd1b9f2680 | ||
|
|
52f32da3f1 | ||
|
|
defb70f63a | ||
|
|
40c7a5b487 | ||
|
|
e81299a727 | ||
|
|
3217f744f9 | ||
|
|
24a5de9398 | ||
|
|
a09c647dfd | ||
|
|
11002bcb57 | ||
|
|
7a35f4c386 | ||
|
|
483bf440fb | ||
|
|
6f1916b6b2 | ||
|
|
01dbd76834 | ||
|
|
6ee030ca7c | ||
|
|
3ae4988353 | ||
|
|
57281a975e | ||
|
|
b953a6daf0 | ||
|
|
612b6bf1bf | ||
|
|
58e45e9232 | ||
|
|
f8111f7f03 | ||
|
|
859290c202 | ||
|
|
cfd885de7c | ||
|
|
8d0c40195b | ||
|
|
e765d62e43 | ||
|
|
c56ec0b0ed | ||
|
|
4dfc0ce947 | ||
|
|
b8da0db1ab | ||
|
|
30acde7b18 | ||
|
|
c0b6e5769e | ||
|
|
3c036ecc4c | ||
|
|
cb764a1143 | ||
|
|
0c8de3db84 | ||
|
|
4ba790b921 | ||
|
|
e0378ebf1d | ||
|
|
d784b2bfbe | ||
|
|
0449a5fd9d | ||
|
|
d0029178e7 | ||
|
|
ca7f6c0d01 | ||
|
|
609be50b8f | ||
|
|
e4e5ed71f8 | ||
|
|
4716641dff | ||
|
|
bbf39c091d | ||
|
|
a0da29f1eb | ||
|
|
dc2b394e7a | ||
|
|
04f6b3e040 | ||
|
|
63f019530b | ||
|
|
3533ec3122 | ||
|
|
ac3fe79db8 | ||
|
|
e7eb24537d | ||
|
|
51d5790938 | ||
|
|
2a259191b9 | ||
|
|
331d43c6f7 | ||
|
|
7f5325ac24 | ||
|
|
3a2b427287 | ||
|
|
43baeb2306 | ||
|
|
fceec1aea6 | ||
|
|
c5c4967b69 | ||
|
|
4556f327c2 | ||
|
|
226a5744b5 | ||
|
|
1f4fe37fe5 | ||
|
|
e7a9163011 | ||
|
|
b43de4c1f0 | ||
|
|
60800a7f35 | ||
|
|
f43631a532 | ||
|
|
6d661360b1 | ||
|
|
b2e72db31b | ||
|
|
c5b4ec7a05 | ||
|
|
0adff0253d | ||
|
|
4f0baa37db | ||
|
|
6c79939db3 |
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,10 +297,15 @@ 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
|
||||
|
||||
## Работа с очередями. Важно!
|
||||
С очередью следуею работать придерживаясь следующих правил:
|
||||
- при сетевом обмене не забиваем очередь. всегда используем Пороговое ожидание (backpressure).
|
||||
- для работы с очередью полностью загрузи ll_queue.h и пойми как работает.
|
||||
|
||||
### Запись в очередь
|
||||
- Очереди забивать нельзя. Добавляй следующий элемент только когда очередь стала пустой.
|
||||
- Используй `queue_wait_threshold` для ожидания освобождения очереди до заданного порога.
|
||||
@@ -314,6 +331,35 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
|
||||
|
||||
## Bugs Debugging Protocol
|
||||
Всегда когда начинаешь диагностику ознакомься со скиллом (используй 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 для редактирования исходников - запрещено
|
||||
@@ -331,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 архитектура
|
||||
@@ -347,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 и собственным публичным ключом). Передаётся в конце пакета
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
# ETCP Router Architecture
|
||||
|
||||
## Структуры данных
|
||||
|
||||
```
|
||||
ETCP_ROUTER_CONN (один на пару remote_node_id + svc_id)
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ ll_entry (хеш-индекс: remote_node_id[8] + svc_id[1]) │
|
||||
│ remote_node_id: u64 │
|
||||
│ svc_id: u8 │
|
||||
│ last_dgram_ts: u16 (timestamp последней датаграммы) │
|
||||
│ │
|
||||
│ tx_seq: u32 следующий seq для отправки │
|
||||
│ rx_seq: u32 ожидаемый seq для сборки │
|
||||
│ tx_acked: u32 сколько наших пакетов подтвердил remote │
|
||||
│ last_sent_ack_seq: u32 последний отправленный ACK │
|
||||
│ │
|
||||
│ recv_q: ll_queue* очередь reorder (хеш по seq[4]) │
|
||||
│ send_q: ll_queue* очередь при переполнении inflight │
|
||||
│ │
|
||||
│ ack_timer: void* периодический 100ms │
|
||||
│ idle_ack_timer: void* idle таймаут 500ms │
|
||||
│ send_resume_timer: void* retry send_q 50ms │
|
||||
│ send_blocked: u8 inflight полон │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
|
||||
SVC_ROUTE_HDR (22 байта)
|
||||
┌──────┬────────────────┬────────────────┬───────┬────────┬──────────┐
|
||||
│ cmd │ dst_node_id │ src_node_id │ seq │ svc_id │ payload │
|
||||
│ u8 │ u64 │ u64 │ u32 │ u8 │ ... │
|
||||
└──────┴────────────────┴────────────────┴───────┴────────┴──────────┘
|
||||
|
||||
seq в data-пакетах = tx_seq (счетчик отправленных)
|
||||
seq в ACK-пакетах = rx_seq (ожидаемый следующий)
|
||||
CLOSE-флаг = 0x80000000 в seq, RST-флаг = 0x40000000
|
||||
```
|
||||
|
||||
## Send Path (отправка данных)
|
||||
|
||||
```
|
||||
etcp_route_send(inst, dst, entry)
|
||||
etcp_router_conn_send(rconn, data, len)
|
||||
│
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ rconn = find/get │ поиск по (dst, svc_id)
|
||||
│ авто-создание если │ или etcp_router_conn_get()
|
||||
│ ещё нет │
|
||||
└────────┬────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────┐
|
||||
│ inflight < ROUTER_MAX(256)? │
|
||||
│ tx_seq - tx_acked < 256 ? │
|
||||
└──────┬───────────┬───────────┘
|
||||
│ YES │ NO (inflight полон)
|
||||
▼ ▼
|
||||
┌──────────────┐ ┌─────────────────────┐
|
||||
│router_send_one│ │ router_enqueue_send │ → send_q (FIFO)
|
||||
│ (прямая) │ │ (в очередь) │ send_blocked = 1
|
||||
└──────┬────────┘ └─────────┬───────────┘ send_resume_timer (50ms)
|
||||
│ │
|
||||
▼ │
|
||||
┌────────────────────┐ │
|
||||
│ tx_seq++ │ │
|
||||
│ hdr.dst = dst │ │
|
||||
│ hdr.src = node_id │ │
|
||||
│ hdr.seq = tx_seq │ │
|
||||
│ hdr.svc_id = svc │ │
|
||||
│ │ │
|
||||
│ etcp_send(conn, e) │ │
|
||||
└────────────────────┘ │
|
||||
│
|
||||
┌────────────────────────────┘
|
||||
│ (при получении ACK от remote)
|
||||
▼
|
||||
┌────────────────────┐
|
||||
│ router_drain_send_q│ вызывается из ACK handler
|
||||
│ │ или по send_resume_timer
|
||||
│ while inflight < 256│
|
||||
│ e = send_q.pop() │
|
||||
│ router_send_one(e)│
|
||||
│ │
|
||||
│ если send_q пуст: │
|
||||
│ send_blocked = 0 │
|
||||
└────────────────────┘
|
||||
```
|
||||
|
||||
## Receive Path (приём данных)
|
||||
|
||||
```
|
||||
etcp_router_recv_cb(conn, entry)
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ dst == node_id? │
|
||||
└──┬───────────────┬───┘
|
||||
│ YES │ NO (транзит)
|
||||
▼ ▼
|
||||
┌────────────────────┐ ┌──────────────────────────┐
|
||||
│ Мы — целевая нода │ │ Транзит: найти next hop │
|
||||
└────────┬───────────┘ │ route_bgp_find_conn_for_ │
|
||||
│ │ node(bgp, hdr.dst) │
|
||||
▼ │ etcp_send(next, entry) │
|
||||
┌────────────────────┐ └──────────────────────────┘
|
||||
│ pl_len == 0? │
|
||||
└──┬───────────┬─────┘
|
||||
│ YES │ NO (данные)
|
||||
▼ ▼
|
||||
┌─────────┐ ┌─────────────────────────┐
|
||||
│ CLOSE/ │ │ rconn = find/get │
|
||||
│ RST? │ │ (авто-создание если нет) │
|
||||
└──┬──┬───┘ └───────────┬─────────────┘
|
||||
│ │ │
|
||||
│ │ NO (ACK) ▼
|
||||
│ ▼ ┌───────────────────────────────────────┐
|
||||
│ ┌──────────┐│ Проверка границ seq: │
|
||||
│ │ tx_acked ││ d = seq - rx_seq │
|
||||
│ │ = hdr.seq││ |d| < ROUTER_MAX_INFLIGHT? │
|
||||
│ │ (только ││ │
|
||||
│ │ вперёд) ││ Дубликат? │
|
||||
│ │ drain_q ││ rx_seq - seq > 0 ИЛИ │
|
||||
│ └──────────┘│ seq уже в recv_q? │
|
||||
│ └──────┬─────────────┬──────────────────┘
|
||||
│ │ OK │ out-of-bounds / dup
|
||||
│ ▼ ▼
|
||||
│ ┌──────────────┐ ┌─────────┐
|
||||
│ │ recv_q.put() │ │ DROP │
|
||||
│ │ хеш по seq │ └─────────┘
|
||||
│ └──────┬───────┘
|
||||
│ │
|
||||
│ ▼
|
||||
│ ┌──────────────────────┐
|
||||
│ │ seq == rx_seq? │
|
||||
│ └──┬───────────────┬───┘
|
||||
│ │ YES │ NO
|
||||
│ ▼ │
|
||||
│ ┌──────────────┐ │
|
||||
│ │ try_assembly │ │
|
||||
│ │ while next в │ │
|
||||
│ │ recv_q: │ │
|
||||
│ │ deliver() │ │
|
||||
│ │ rx_seq++ │ │
|
||||
│ └──────┬───────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────┴──┐
|
||||
│ │ router_deliver() │
|
||||
│ │ вызывает callback │
|
||||
│ │ сервиса (svc_id) │
|
||||
│ │ entry: [svc_id|payload] │
|
||||
│ └──────────────┬──────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌────────────────────────────────────┐
|
||||
│ router_schedule_ack(rconn) │
|
||||
│ запускает ack_timer (100ms) если │
|
||||
│ ещё не запущен │
|
||||
└────────────────────────────────────┘
|
||||
```
|
||||
|
||||
```
|
||||
CLOSE / RST ветка:
|
||||
┌────────────────────────────────────────┐
|
||||
│ pl_len == 0 && seq & CLOSE/RST │
|
||||
│ → router_close_and_notify(rconn) │
|
||||
│ ├─ router_send_close_to_service() │
|
||||
│ │ (callback с NULL conn) │
|
||||
│ ├─ отправить CLOSE удалённой стороне│
|
||||
│ ├─ отмена всех таймеров │
|
||||
│ ├─ очистка recv_q + send_q │
|
||||
│ └─ удаление из inst->router_conns │
|
||||
└────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## ACK Mechanism
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ router_schedule_ack(rconn) │
|
||||
│ (вызывается при получении данных) │
|
||||
└────────────────┬────────────────────────┘
|
||||
│
|
||||
┌────────────────▼────────────────────────┐
|
||||
│ если idle_ack_timer активен — отменить │
|
||||
│ если ack_timer не активен — запустить │
|
||||
│ ROUTER_ACK_INTERVAL_TB = 1000 (100ms) │
|
||||
└────────────────┬────────────────────────┘
|
||||
│
|
||||
▼ (через 100ms)
|
||||
┌─────────────────────────────────────────┐
|
||||
│ router_ack_timer_cb() │
|
||||
│ │
|
||||
│ if rx_seq != last_sent_ack_seq: │
|
||||
│ router_send_ack(rconn) │
|
||||
│ last_sent_ack_seq = rx_seq │
|
||||
│ перезапустить ack_timer │
|
||||
│ else: │
|
||||
│ запустить idle_ack_timer (500ms) │
|
||||
└────────────────┬────────────────────────┘
|
||||
│
|
||||
▼ (через 500ms)
|
||||
┌─────────────────────────────────────────┐
|
||||
│ router_idle_ack_timer_cb() │
|
||||
│ │
|
||||
│ if rx_seq != last_sent_ack_seq: │
|
||||
│ router_send_ack(rconn) (досылка) │
|
||||
│ last_sent_ack_seq = rx_seq │
|
||||
└─────────────────────────────────────────┘
|
||||
|
||||
router_send_ack(rconn):
|
||||
hdr.seq = rx_seq
|
||||
hdr.svc_id = svc_id
|
||||
pl_len = 0 → это ACK-пакет
|
||||
etcp_send(conn, entry)
|
||||
```
|
||||
|
||||
```
|
||||
Обработка входящего ACK:
|
||||
┌────────────────────────────────────────┐
|
||||
│ pl_len == 0 && нет CLOSE/RST флагов │
|
||||
│ │
|
||||
│ if seq >= tx_acked: // только вперёд │
|
||||
│ tx_acked = seq // обновить │
|
||||
│ else: │
|
||||
│ DEBUG_WARN "stale ACK" │
|
||||
│ │
|
||||
│ if send_blocked: │
|
||||
│ router_drain_send_q(rconn) │
|
||||
└────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Inflight Control
|
||||
|
||||
```
|
||||
ROUTER_MAX_INFLIGHT = 256
|
||||
|
||||
Отправка возможна когда: tx_seq - tx_acked < 256
|
||||
Отправка заблокирована: tx_seq - tx_acked >= 256
|
||||
|
||||
При блокировке:
|
||||
данные → send_q (FIFO, без дропов)
|
||||
send_blocked = 1
|
||||
send_resume_timer = 50ms retry
|
||||
|
||||
Разблокировка:
|
||||
приход ACK от remote → tx_acked обновляется
|
||||
→ router_drain_send_q() разгребает send_q
|
||||
или send_resume_timer (50ms) → router_drain_send_q()
|
||||
|
||||
send_q полностью разобран → send_blocked = 0
|
||||
```
|
||||
|
||||
## Полная схема потоков
|
||||
|
||||
```
|
||||
┌──────────┐
|
||||
│ Сервис │ (routing, proxy, NAT, ...)
|
||||
│ (svc_id)│
|
||||
└────┬─────┘
|
||||
│
|
||||
etcp_route_send() │ callback(svc_id, entry)
|
||||
▼ ▲
|
||||
┌─────────┐ send ┌─────────────────────────────────┐ recv ┌─────────┐
|
||||
│ TUN │─────────▶│ ETCP ROUTER │─────────▶│ TUN │
|
||||
│ (выход) │ │ │ │ (вход) │
|
||||
└─────────┘ │ ┌──────────┐ ┌──────────┐ │ └─────────┘
|
||||
│ │ send_q │ │ recv_q │ │
|
||||
│ │ (FIFO) │ │ (хеш seq)│ │
|
||||
│ └────┬─────┘ └────┬─────┘ │
|
||||
│ │ │ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌─────────────────────────┐ │
|
||||
│ │ Inflight / Assembly │ │
|
||||
│ │ tx_seq - tx_acked │ │
|
||||
│ │ rx_seq → deliver │ │
|
||||
│ └───────────┬─────────────┘ │
|
||||
│ │ │
|
||||
└──────────────┼──────────────────┘
|
||||
│
|
||||
┌──────▼──────┐
|
||||
│ BGP │
|
||||
│ (нахождение│
|
||||
│ next hop) │
|
||||
└──────┬──────┘
|
||||
│
|
||||
┌──────▼──────┐
|
||||
│ ETCP conn │──▶ сеть
|
||||
└─────────────┘
|
||||
|
||||
──▶ send (исходящие данные)
|
||||
──▶ recv (входящие данные, callback сервису)
|
||||
──▶ транзит (через BGP к следующему hop)
|
||||
```
|
||||
|
||||
## Таймеры (один rconn)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ack_timer ──────── 100ms ─────▶ router_ack_timer_cb│
|
||||
│ (периодический) │
|
||||
│ │
|
||||
│ idle_ack_timer ─── 500ms ─────▶ router_idle_ack_cb │
|
||||
│ (досылка последнего ACK) │
|
||||
│ │
|
||||
│ send_resume_timer ─ 50ms ─────▶ router_send_resume │
|
||||
│ (retry когда send_q не пуст) │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Флаги seq
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────┐
|
||||
│ CLOSE: 0x80000000 нормальное закрытие conn │
|
||||
│ RST: 0x40000000 conn не найден (reset) │
|
||||
│ │
|
||||
│ CLOSE/RST-пакеты: seq=0 | флаг, pl_len=0 │
|
||||
│ Data-пакеты: seq=tx_seq, pl_len>0 │
|
||||
│ ACK-пакеты: seq=rx_seq, pl_len=0 │
|
||||
└──────────────────────────────────────────────────┘
|
||||
```
|
||||
+9
-2
@@ -26,14 +26,21 @@ libuasync_a_SOURCES = \
|
||||
radix.c \
|
||||
radix.h \
|
||||
serialize.c \
|
||||
serialize.h
|
||||
serialize.h \
|
||||
tcp_io.c \
|
||||
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:
|
||||
|
||||
+20
-40
@@ -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]);
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
@@ -127,6 +126,9 @@ static const struct {
|
||||
{"general", DEBUG_CATEGORY_GENERAL},
|
||||
{"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}
|
||||
};
|
||||
@@ -198,31 +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_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 */
|
||||
|
||||
+6
-2
@@ -59,7 +59,10 @@ typedef int debug_category_t;
|
||||
#define DEBUG_CATEGORY_GENERAL 19 // Messages for user (common log)
|
||||
#define DEBUG_CATEGORY_NAT 20 // EIM NAT module
|
||||
#define DEBUG_CATEGORY_KEEPALIVE 21 // Link keepalive logic
|
||||
#define DEBUG_CATEGORY_COUNT 22 // Total number of categories
|
||||
#define DEBUG_CATEGORY_ETCPROUTE 22 // ETCP routing
|
||||
#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 */
|
||||
@@ -85,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) */
|
||||
@@ -122,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>
|
||||
+198
-43
@@ -46,8 +46,23 @@ static inline void queue_check_thread(struct ll_queue* q) {
|
||||
// Предварительные объявления внутренних функций
|
||||
static void queue_resume_timeout_cb(void* arg);
|
||||
static void check_waiters(struct ll_queue* q);
|
||||
static void add_to_hash(struct ll_queue* q, struct ll_entry* entry);
|
||||
static void remove_from_hash(struct ll_queue* q, struct ll_entry* entry);
|
||||
static void empty_trampoline(void* arg);
|
||||
static void on_get_trampoline(void* arg);
|
||||
|
||||
static void empty_trampoline(void* arg) {
|
||||
struct ll_queue* q = (struct ll_queue*)arg;
|
||||
q->empty_call_soon_id = NULL;
|
||||
queue_callback_fn cb = q->empty_callback;
|
||||
void* cb_arg = q->empty_callback_arg;
|
||||
q->empty_callback = NULL;
|
||||
q->empty_callback_arg = NULL;
|
||||
if (q->count == 0 && cb) cb(q, cb_arg);
|
||||
}
|
||||
|
||||
static void on_get_trampoline(void* arg) {
|
||||
struct ll_queue* q = (struct ll_queue*)arg;
|
||||
if (q->on_get) q->on_get(q, q->on_get_arg);
|
||||
}
|
||||
|
||||
// ==================== Управление очередью ====================
|
||||
|
||||
@@ -60,6 +75,9 @@ struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size, uint16_t index_o
|
||||
q->name = name;
|
||||
q->ua = ua;
|
||||
q->size_limit = -1; // Без ограничения по умолчанию
|
||||
q->threshold_max_packets = 0; // По умолчанию: ждать пустой очереди
|
||||
q->threshold_max_bytes = 0; // По умолчанию: не проверять байты
|
||||
q->waiter_defer = 0;
|
||||
q->hash_size = hash_size;
|
||||
q->index_offset = index_offset;
|
||||
q->index_size = index_size;
|
||||
@@ -112,6 +130,17 @@ void queue_free(struct ll_queue* q) {
|
||||
// ВАЖНО: Не освобождаем элементы в очереди - они должны быть извлечены отдельно
|
||||
// Это упрощает архитектуру и предотвращает double-u_free
|
||||
|
||||
// Освободить список 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->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;
|
||||
}
|
||||
q->waiter_head = NULL;
|
||||
q->waiter_tail = NULL;
|
||||
|
||||
// Освободить хеш-таблицу
|
||||
if (q->hash_table) {
|
||||
u_free(q->hash_table);
|
||||
@@ -122,6 +151,14 @@ void queue_free(struct ll_queue* q) {
|
||||
uasync_call_soon_cancel(q->ua, q->resume_timeout_id);
|
||||
q->resume_timeout_id = NULL;
|
||||
}
|
||||
|
||||
void* old_eid = q->empty_call_soon_id;
|
||||
q->empty_call_soon_id = NULL;
|
||||
if (old_eid) uasync_call_soon_cancel(q->ua, old_eid);
|
||||
q->empty_callback = NULL;
|
||||
q->empty_callback_arg = NULL;
|
||||
q->on_get = NULL;
|
||||
q->on_get_arg = NULL;
|
||||
|
||||
u_free(q);
|
||||
}
|
||||
@@ -266,24 +303,47 @@ static uint32_t make_hash(const void* data, uint16_t len) {// алгоритм F
|
||||
return hash;
|
||||
}
|
||||
|
||||
// Проверить и запустить ожидающие коллбэки
|
||||
static void waiter_defer_cb(void* arg) {
|
||||
struct queue_waiter_handle* h = (struct queue_waiter_handle*)arg;
|
||||
h->call_soon_id = NULL;
|
||||
if (h->defer_cb) h->defer_cb(h->defer_q, h->defer_arg);
|
||||
else DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "waiter_defer_cb: NULL callback (stale?) q=%p", (void*)h->defer_q);
|
||||
}
|
||||
|
||||
static inline void queue_waiter_call(struct ll_queue* q, queue_threshold_callback_fn cb, void* arg,
|
||||
struct queue_waiter_handle* h) {
|
||||
if (q->waiter_defer) {
|
||||
if (h->call_soon_id) { DEBUG_WARN(DEBUG_CATEGORY_LL_QUEUE, "queue_waiter_call: overwriting existing call_soon_id=%p", h->call_soon_id); uasync_call_soon_cancel(q->ua, h->call_soon_id); }
|
||||
h->defer_q = q; h->defer_cb = cb; h->defer_arg = arg;
|
||||
h->call_soon_id = uasync_call_soon(q->ua, h, waiter_defer_cb);
|
||||
} else {
|
||||
cb(q, arg);
|
||||
}
|
||||
}
|
||||
|
||||
// Проверить и запустить первый ожидающий коллбэк (FIFO / round-robin)
|
||||
static void check_waiters(struct ll_queue* q) {
|
||||
if (!q) return;
|
||||
if (!q || !q->waiter_head) return;
|
||||
|
||||
// DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: checking waiters, count=%d, bytes=%zu", q->count, q->total_bytes);
|
||||
const int cond = q->count <= q->threshold_max_packets
|
||||
&& (q->threshold_max_bytes == 0 || q->total_bytes <= q->threshold_max_bytes);
|
||||
if (!cond) return;
|
||||
|
||||
struct queue_waiter* waiter = &q->waiter;
|
||||
struct queue_waiter* waiter = q->waiter_head;
|
||||
q->waiter_head = waiter->next;
|
||||
if (!q->waiter_head) q->waiter_tail = NULL;
|
||||
|
||||
if (waiter->callback) {
|
||||
// Проверить условие: не больше max_packets и не больше max_bytes
|
||||
// max_bytes = 0 означает "не проверять байты"
|
||||
if (q->count <= waiter->max_packets && (waiter->max_bytes == 0 || q->total_bytes <= waiter->max_bytes)) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition met, calling callback, count=%d<=%d, bytes=%zu<=%zu (max_bytes_check=%s)",
|
||||
q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes,
|
||||
waiter->max_bytes == 0 ? "disabled" : "enabled");
|
||||
waiter->callback(q, waiter->callback_arg);
|
||||
memset(waiter, 0, sizeof(*waiter));
|
||||
}
|
||||
struct queue_waiter_handle* h = waiter->handle;
|
||||
if (h) h->internal = NULL;
|
||||
|
||||
queue_threshold_callback_fn cb = waiter->callback;
|
||||
void* arg = waiter->callback_arg;
|
||||
u_free(waiter);
|
||||
|
||||
if (cb) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: waking head waiter, count=%d<=%d, bytes=%zu<=%zu",
|
||||
q->count, q->threshold_max_packets, q->total_bytes, q->threshold_max_bytes);
|
||||
queue_waiter_call(q, cb, arg, h);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,6 +353,12 @@ int queue_data_put(struct ll_queue* q, struct ll_entry* entry) {
|
||||
queue_check_thread(q);
|
||||
#endif
|
||||
|
||||
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] double put detected! entry=%p prev=%p next=%p len=%u",
|
||||
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next, entry->len);
|
||||
abort();
|
||||
}
|
||||
|
||||
if (q->hash_size > 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] Put no-hash data to hash queue",q->name);
|
||||
queue_dgram_free(entry);
|
||||
@@ -334,6 +400,12 @@ int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry) {
|
||||
queue_check_thread(q);
|
||||
#endif
|
||||
|
||||
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] double put_with_index detected! entry=%p prev=%p next=%p",
|
||||
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next);
|
||||
abort();
|
||||
}
|
||||
|
||||
if (q->index_size == 0 || (size_t)q->index_offset + q->index_size > (size_t)entry->size) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] invalid index: offset=%u size=%u (data_size=%u)",q->name, q->index_offset, q->index_size, entry->size);
|
||||
queue_dgram_free(entry);
|
||||
@@ -377,6 +449,12 @@ int queue_data_put_first(struct ll_queue* q, struct ll_entry* entry) {
|
||||
queue_check_thread(q);
|
||||
#endif
|
||||
|
||||
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] double put_first detected! entry=%p prev=%p next=%p",
|
||||
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next);
|
||||
abort();
|
||||
}
|
||||
|
||||
if (q->hash_size > 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] Put no-hash data to hash queue",q->name);
|
||||
queue_dgram_free(entry);
|
||||
@@ -410,6 +488,12 @@ int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry)
|
||||
queue_check_thread(q);
|
||||
#endif
|
||||
|
||||
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] double put_first_with_index detected! entry=%p prev=%p next=%p",
|
||||
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next);
|
||||
abort();
|
||||
}
|
||||
|
||||
if (q->index_size == 0 || (size_t)q->index_offset + q->index_size > (size_t)entry->size) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] invalid index: offset=%u size=%u (data_size=%u)",q->name, q->index_offset, q->index_size, entry->size);
|
||||
queue_dgram_free(entry);
|
||||
@@ -476,6 +560,12 @@ struct ll_entry* queue_data_get(struct ll_queue* q) {
|
||||
// Проверить ожидающие коллбэки
|
||||
check_waiters(q);
|
||||
|
||||
if (q->count == 0 && q->empty_callback && !q->empty_call_soon_id)
|
||||
q->empty_call_soon_id = uasync_call_soon(q->ua, q, (timeout_callback_t)empty_trampoline);
|
||||
|
||||
if (q->on_get)
|
||||
uasync_call_soon(q->ua, q, (timeout_callback_t)on_get_trampoline);
|
||||
|
||||
#ifdef QUEUE_DEBUG
|
||||
queue_check_consistency(q);// !!!! for debug
|
||||
#endif
|
||||
@@ -489,14 +579,27 @@ int queue_entry_count(struct ll_queue* q) {
|
||||
// Функция проверки консистентности count и total_bytes
|
||||
// Возвращает 0 если ok, -1 если есть несоответствия
|
||||
int queue_check_consistency(struct ll_queue* q) {
|
||||
if (!q) return -1; // Недопустимая очередь
|
||||
if (!q) return -1;
|
||||
|
||||
// Проверка: если count > 0, то head не должен быть NULL
|
||||
if (q->count > 0 && !q->head) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "Queue '%s': count=%d but head is NULL!",
|
||||
q->name ? q->name : "unknown", q->count);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Floyd cycle detection — за O(N) без риска зависания
|
||||
{
|
||||
struct ll_entry *slow = q->head, *fast = q->head;
|
||||
while (fast && fast->next) {
|
||||
slow = slow->next;
|
||||
fast = fast->next->next;
|
||||
if (slow == fast) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "Queue '%s': CYCLE detected! entry=%p count=%d",
|
||||
q->name ? q->name : "unknown", (void*)slow, q->count);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int actual_count = 0;
|
||||
size_t actual_bytes = 0;
|
||||
@@ -507,7 +610,6 @@ int queue_check_consistency(struct ll_queue* q) {
|
||||
actual_bytes += current->int_len;
|
||||
if (current->next) {
|
||||
if (current->next->prev != current) {
|
||||
// Несоответствие в связях prev/next
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "Queue '%s': prev/next error at entry %p != %p entries: %d!=%d bytes: %zu!=%zu",
|
||||
q->name ? q->name : "unknown", (void*)current, (void*)current->next->prev, actual_count, q->count, actual_bytes, q->total_bytes);
|
||||
return -1;
|
||||
@@ -532,34 +634,87 @@ int queue_check_consistency(struct ll_queue* q) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ==================== Асинхронное ожидание ====================
|
||||
// ==================== Пороговое ожидание (backpressure) ====================
|
||||
|
||||
struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes,
|
||||
queue_threshold_callback_fn callback, void* arg) {
|
||||
if (!q || !callback) return NULL;
|
||||
|
||||
struct queue_waiter* waiter = &q->waiter;
|
||||
|
||||
// Проверить условие немедленно
|
||||
if (q->count <= max_packets && (max_bytes == 0 || q->total_bytes <= max_bytes)) {
|
||||
// Условие уже выполнено - вызвать коллбэк немедленно
|
||||
callback(q, arg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Установить waiter для отложенного вызова
|
||||
waiter->max_packets = max_packets;
|
||||
waiter->max_bytes = max_bytes;
|
||||
waiter->callback = callback;
|
||||
waiter->callback_arg = arg;
|
||||
|
||||
return waiter;
|
||||
void queue_set_threshold(struct ll_queue* q, int max_packets, size_t max_bytes) {
|
||||
if (!q) return;
|
||||
q->threshold_max_packets = max_packets;
|
||||
q->threshold_max_bytes = max_bytes;
|
||||
}
|
||||
|
||||
void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter) {
|
||||
if (!q || !waiter || waiter != &q->waiter) return;
|
||||
void queue_set_waiter_defer(struct ll_queue* q, int enable) {
|
||||
if (!q) return;
|
||||
q->waiter_defer = enable;
|
||||
}
|
||||
|
||||
void queue_set_empty_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg) {
|
||||
if (!q) return;
|
||||
void* old = q->empty_call_soon_id;
|
||||
q->empty_call_soon_id = NULL;
|
||||
if (old) uasync_call_soon_cancel(q->ua, old);
|
||||
q->empty_callback = cbk_fn;
|
||||
q->empty_callback_arg = arg;
|
||||
if (cbk_fn && q->count == 0)
|
||||
q->empty_call_soon_id = uasync_call_soon(q->ua, q, (timeout_callback_t)empty_trampoline);
|
||||
}
|
||||
|
||||
void queue_set_on_get(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg) {
|
||||
if (!q) return;
|
||||
q->on_get = cbk_fn;
|
||||
q->on_get_arg = arg;
|
||||
}
|
||||
|
||||
int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h,
|
||||
queue_threshold_callback_fn callback, void* arg) {
|
||||
if (!q || !h) return -1;
|
||||
|
||||
if (h->internal) queue_waiter_cancel(q, h);
|
||||
|
||||
if (q->count <= q->threshold_max_packets
|
||||
&& (q->threshold_max_bytes == 0 || q->total_bytes <= q->threshold_max_bytes)) {
|
||||
queue_waiter_call(q, callback, arg, h);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct queue_waiter* waiter = u_malloc(sizeof(struct queue_waiter));
|
||||
if (!waiter) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] queue_waiter_wait: u_malloc failed", q->name);
|
||||
return -1;
|
||||
}
|
||||
waiter->next = NULL;
|
||||
waiter->handle = h;
|
||||
waiter->callback = callback;
|
||||
waiter->callback_arg = arg;
|
||||
h->internal = waiter;
|
||||
|
||||
if (q->waiter_tail) q->waiter_tail->next = waiter; else q->waiter_head = waiter;
|
||||
q->waiter_tail = waiter;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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; }
|
||||
|
||||
memset(waiter, 0, sizeof(*waiter));
|
||||
struct queue_waiter* waiter = h->internal;
|
||||
struct queue_waiter* prev = NULL;
|
||||
struct queue_waiter* curr = q->waiter_head;
|
||||
while (curr && curr != waiter) {
|
||||
prev = curr;
|
||||
curr = curr->next;
|
||||
}
|
||||
if (!curr) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] queue_waiter_cancel: waiter not in list", q->name);
|
||||
h->internal = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (prev) prev->next = waiter->next; else q->waiter_head = waiter->next;
|
||||
if (waiter == q->waiter_tail) q->waiter_tail = prev;
|
||||
|
||||
h->internal = NULL;
|
||||
u_free(waiter);
|
||||
}
|
||||
|
||||
// ==================== Поиск и удаление по ID ====================
|
||||
|
||||
+86
-20
@@ -12,7 +12,7 @@
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#define QUEUE_DEBUG 1
|
||||
//#define QUEUE_DEBUG 1 // раскомментировать для отладки очереди
|
||||
//#define QUEUE_THREAD_CHECK 1 // 0 to disable
|
||||
|
||||
/**
|
||||
@@ -26,7 +26,7 @@
|
||||
* - Обязательный вызов queue_resume_callback после обработки элемента
|
||||
* - Поддержка пулов памяти для entry и отдельно для dgram
|
||||
* - Хеш-таблица для быстрого поиска по ID (опционально)
|
||||
* - Встроенный одиночный waiter для ожидания освобождения очереди до заданного порога
|
||||
* - Связный список waiters для backpressure c общим порогом (round-robin)
|
||||
* - Использует uasync для отложенного возобновления callback'ов (без рекурсии в стеке)
|
||||
*
|
||||
* @note Важные правила использования автозабора (queue_set_callback):
|
||||
@@ -37,13 +37,15 @@
|
||||
* 5. queue_data_get() автоматически suspend'ит коллбэки для предотвращения рекурсии
|
||||
*
|
||||
* @note Память:
|
||||
* - queue_free() освобождает ТОЛЬКО структуру очереди и хеш-таблицу
|
||||
* - queue_free() освобождает структуру очереди, хеш-таблицу и список waiters
|
||||
* (handle->internal каждого ожидающего сбрасывается в NULL)
|
||||
* - Все элементы должны быть извлечены через queue_data_get() и освобождены через queue_entry_free()
|
||||
* - dgram освобождается отдельно через queue_dgram_free() или кастомную dgram_free_fn
|
||||
*/
|
||||
|
||||
// Forward declaration
|
||||
// Forward declarations
|
||||
struct ll_queue;
|
||||
struct queue_waiter_handle;
|
||||
|
||||
/**
|
||||
* @struct ll_entry
|
||||
@@ -84,7 +86,7 @@ typedef void (*queue_callback_fn)(struct ll_queue* q, void* arg);
|
||||
|
||||
/**
|
||||
* @typedef queue_threshold_callback_fn
|
||||
* @brief Одноразовый коллбэк при достижении порога очереди (queue_wait_threshold).
|
||||
* @brief Одноразовый коллбэк при достижении порога очереди.
|
||||
* @param q указатель на очередь
|
||||
* @param arg пользовательский аргумент
|
||||
*/
|
||||
@@ -92,13 +94,30 @@ typedef void (*queue_threshold_callback_fn)(struct ll_queue* q, void* arg);
|
||||
|
||||
/**
|
||||
* @struct queue_waiter
|
||||
* @brief Описание ожидания освобождения места в очереди (встроен в ll_queue — только один).
|
||||
* @brief Внутренний узел в связном списке ожидающих (очередь владеет, аллоцирует/освобождает сама).
|
||||
*/
|
||||
struct queue_waiter {
|
||||
int max_packets; ///< Максимально допустимое количество элементов
|
||||
size_t max_bytes; ///< Максимально допустимый объём данных (0 = не проверять)
|
||||
queue_threshold_callback_fn callback; ///< Коллбэк (вызывается один раз)
|
||||
void* callback_arg; ///< Аргумент коллбэка
|
||||
struct queue_waiter* next; ///< Следующий в списке ожидающих
|
||||
struct queue_waiter_handle* handle; ///< Обратная ссылка на публичную структуру
|
||||
queue_threshold_callback_fn callback;
|
||||
void* callback_arg;
|
||||
};
|
||||
|
||||
/**
|
||||
* @struct queue_waiter_handle
|
||||
* @brief Публичная управляющая структура (встраивается в структуру вызывающей стороны).
|
||||
*
|
||||
* Вызывающая сторона:
|
||||
* 1. Объявляет поле struct queue_waiter_handle в своей структуре (= {0} или u_calloc)
|
||||
* 3. Вызывает queue_waiter_wait() когда хочет дождаться освобождения очереди
|
||||
* 4. Вызывает queue_waiter_cancel() при деинициализации для отмены ожидания
|
||||
*/
|
||||
struct queue_waiter_handle {
|
||||
struct queue_waiter* internal; ///< NULL = не ждём; иначе — внутренний узел
|
||||
struct ll_queue* defer_q;
|
||||
queue_threshold_callback_fn defer_cb;
|
||||
void* defer_arg;
|
||||
void* call_soon_id; ///< хендл отложенного вызова (для отмены)
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -120,7 +139,18 @@ struct ll_queue {
|
||||
void* resume_timeout_id; // ID таймера uasync для отложенного resume
|
||||
struct UASYNC* ua; // Экземпляр uasync (обязателен для таймеров)
|
||||
|
||||
struct queue_waiter waiter; // Встроенный waiter (только один)
|
||||
struct queue_waiter* waiter_head; // Голова списка ожидающих
|
||||
struct queue_waiter* waiter_tail; // Хвост списка ожидающих
|
||||
int threshold_max_packets; // Общий порог: макс. кол-во элементов (по умолчанию 0)
|
||||
size_t threshold_max_bytes; // Общий порог: макс. объём данных (0 = не проверять)
|
||||
int waiter_defer; // 0=immediate callback, 1=defer via uasync_call_soon
|
||||
|
||||
queue_callback_fn empty_callback; // одноразовый коллбэк при опустошении очереди
|
||||
void* empty_callback_arg;
|
||||
void* empty_call_soon_id; // handle от uasync_call_soon
|
||||
|
||||
queue_callback_fn on_get; // коллбэк после каждого queue_data_get (deferred)
|
||||
void* on_get_arg;
|
||||
|
||||
struct ll_entry** hash_table; // Хеш-таблица для поиска по id (если hash_size > 0)
|
||||
size_t hash_size; // Размер хеш-таблицы
|
||||
@@ -190,26 +220,62 @@ void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg)
|
||||
*/
|
||||
void queue_resume_callback(struct ll_queue* q);
|
||||
|
||||
/* ==================== Пороговое ожидание ==================== */
|
||||
/* ==================== Пороговое ожидание (backpressure) ==================== */
|
||||
|
||||
/**
|
||||
* @brief Регистрирует одноразовый коллбэк, который сработает, когда очередь освободится до заданного порога.
|
||||
* @brief Устанавливает общий порог освобождения очереди.
|
||||
* @param q очередь
|
||||
* @param max_packets максимальное количество элементов
|
||||
* @param max_packets максимальное количество элементов (условие: count <= max_packets)
|
||||
* @param max_bytes максимальный объём данных (0 = не проверять)
|
||||
*
|
||||
* Порог — общий на всю очередь. Все waiters в связном списке используют этот порог.
|
||||
* При достижении порога вызывается следующий waiter из списка (FIFO / round-robin).
|
||||
* По умолчанию: max_packets=0, max_bytes=0 (пустая очередь).
|
||||
*/
|
||||
void queue_set_threshold(struct ll_queue* q, int max_packets, size_t max_bytes);
|
||||
|
||||
void queue_set_waiter_defer(struct ll_queue* q, int enable);
|
||||
|
||||
/**
|
||||
* @brief Устанавливает одноразовый коллбэк при опустошении очереди (count==0).
|
||||
* @param q очередь
|
||||
* @param cbk_fn коллбэк (NULL для отмены)
|
||||
* @param arg пользовательский аргумент
|
||||
*
|
||||
* Коллбэк вызывается асинхронно (через uasync_call_soon) когда count становится 0.
|
||||
* После вызова сбрасывается — для повторного использования нужно вызвать снова.
|
||||
*/
|
||||
void queue_set_empty_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
|
||||
|
||||
/**
|
||||
* @brief Устанавливает коллбэк вызываемый после каждого queue_data_get (deferred через call_soon).
|
||||
* @param q очередь
|
||||
* @param cbk_fn коллбэк (NULL для отмены)
|
||||
* @param arg пользовательский аргумент
|
||||
*/
|
||||
void queue_set_on_get(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
|
||||
|
||||
/**
|
||||
* @brief Регистрирует ожидание освобождения очереди до общего порога.
|
||||
* @param q очередь
|
||||
* @param h указатель на handle (создаётся как `struct queue_waiter_handle h = {0}` или `h.internal = NULL`)
|
||||
* @param callback функция, вызываемая при достижении порога
|
||||
* @param arg аргумент коллбэка
|
||||
* @return указатель на waiter (для отмены) или NULL, если условие уже выполнено (коллбэк вызван сразу)
|
||||
* @return 1 — условие уже выполнено (callback вызван немедленно), 0 — зарегистрирован в списке, -1 — ошибка
|
||||
*
|
||||
* Если handle уже ожидает (h->internal != NULL) — старый waiter отменяется и создаётся новый.
|
||||
*/
|
||||
struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes,
|
||||
queue_threshold_callback_fn callback, void* arg);
|
||||
int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h,
|
||||
queue_threshold_callback_fn callback, void* arg);
|
||||
|
||||
/**
|
||||
* @brief Отменяет ранее установленное ожидание порога.
|
||||
* @brief Отменяет ожидание (удаляет из списка, если зарегистрирован).
|
||||
* @param q очередь
|
||||
* @param waiter указатель, возвращённый queue_wait_threshold()
|
||||
* @param h указатель на handle
|
||||
*
|
||||
* Безопасно вызывать в любом состоянии: если waiter не зарегистрирован — ничего не делает.
|
||||
*/
|
||||
void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter);
|
||||
void queue_waiter_cancel(struct ll_queue* q, struct queue_waiter_handle* h);
|
||||
|
||||
/* ==================== Работа с данными ==================== */
|
||||
|
||||
|
||||
@@ -14,8 +14,9 @@
|
||||
#define PADDING_FILL 0xAA
|
||||
#define METADATA_SIZE 128
|
||||
#define POINTER_SIZE sizeof(void*)
|
||||
#define LOCATION_MAX (METADATA_SIZE - POINTER_SIZE)
|
||||
#define NEXT_OFFSET LOCATION_MAX
|
||||
#define LOCATION_MAX (METADATA_SIZE - POINTER_SIZE * 2)
|
||||
#define PREV_OFFSET LOCATION_MAX
|
||||
#define NEXT_OFFSET (LOCATION_MAX + POINTER_SIZE)
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
|
||||
#include <execinfo.h>
|
||||
@@ -136,8 +137,12 @@ void* u_malloc_impl(uint32_t size, const char* location) {
|
||||
*(uint32_t*)(prefix_start + BOUNDARY_CHECK_SIZE + 8 + size) = CANARY;
|
||||
// Add to list (thread-safe)
|
||||
LOCK();
|
||||
void** prev_ptr = (void**)(base + PREV_OFFSET);
|
||||
void** next_ptr = (void**)(base + NEXT_OFFSET);
|
||||
*prev_ptr = NULL;
|
||||
*next_ptr = allocated_head;
|
||||
if (allocated_head)
|
||||
*(void**)((uint8_t*)allocated_head + PREV_OFFSET) = base;
|
||||
allocated_head = base;
|
||||
allocated_count++;
|
||||
allocations_count++;
|
||||
@@ -175,29 +180,14 @@ void u_free_impl(void* ptr, const char* location) {
|
||||
uint8_t* base = (uint8_t*)ptr - 8 - BOUNDARY_CHECK_SIZE - METADATA_SIZE;
|
||||
char* alloc_loc = (char*)base;
|
||||
uint32_t size = *(uint32_t*)(base + METADATA_SIZE + BOUNDARY_CHECK_SIZE + 4);
|
||||
// Remove from linked list (thread-safe)
|
||||
// Remove from doubly-linked list (O(1))
|
||||
LOCK();
|
||||
void* prev = NULL;
|
||||
void* curr = allocated_head;
|
||||
bool found = false;
|
||||
while (curr) {
|
||||
if (curr == base) {
|
||||
if (prev) {
|
||||
*(void**)((uint8_t*)prev + NEXT_OFFSET) = *(void**)((uint8_t*)curr + NEXT_OFFSET);
|
||||
} else {
|
||||
allocated_head = *(void**)((uint8_t*)curr + NEXT_OFFSET);
|
||||
}
|
||||
allocated_count--;
|
||||
frees_count++;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
prev = curr;
|
||||
curr = *(void**)((uint8_t*)curr + NEXT_OFFSET);
|
||||
}
|
||||
void* prev = *(void**)(base + PREV_OFFSET);
|
||||
void* next = *(void**)(base + NEXT_OFFSET);
|
||||
size_t active_count = allocated_count;
|
||||
UNLOCK();
|
||||
if (!found) {
|
||||
|
||||
if (prev == NULL && allocated_head != base) {
|
||||
UNLOCK();
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "========================================");
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "DOUBLE FREE DETECTED");
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "========================================");
|
||||
@@ -215,24 +205,19 @@ void u_free_impl(void* ptr, const char* location) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "--- User data dump at %p (%u of %u bytes) ---", ptr, dump_size, size);
|
||||
hex_dump(ptr, dump_size);
|
||||
}
|
||||
#if BACKTRACE_ENABLED
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "--- Backtrace ---");
|
||||
# if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
|
||||
void* bt_buf[32];
|
||||
int bt_size = backtrace(bt_buf, 32);
|
||||
char** bt_symbols = backtrace_symbols(bt_buf, bt_size);
|
||||
if (bt_symbols) {
|
||||
for (int i = 0; i < bt_size; i++) DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, " #%d %s", i, bt_symbols[i]);
|
||||
free(bt_symbols);
|
||||
}
|
||||
# elif defined(_WIN32)
|
||||
void* bt_buf[32];
|
||||
USHORT frames = CaptureStackBackTrace(0, 32, bt_buf, NULL);
|
||||
for (USHORT i = 0; i < frames; i++) DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, " #%d %p", i, bt_buf[i]);
|
||||
# endif
|
||||
#endif
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (prev)
|
||||
*(void**)((uint8_t*)prev + NEXT_OFFSET) = next;
|
||||
else
|
||||
allocated_head = next;
|
||||
if (next)
|
||||
*(void**)((uint8_t*)next + PREV_OFFSET) = prev;
|
||||
|
||||
allocated_count--;
|
||||
frees_count++;
|
||||
UNLOCK();
|
||||
uint32_t total = size + 12 + 2 * BOUNDARY_CHECK_SIZE + METADATA_SIZE;
|
||||
memset(base, 0, total);
|
||||
free(base);
|
||||
|
||||
+66
-13
@@ -5,6 +5,49 @@
|
||||
#include "memory_pool.h"
|
||||
#include "mem.h"
|
||||
|
||||
// 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, const char* location)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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 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 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) {}
|
||||
}
|
||||
*counter = 0;
|
||||
}
|
||||
|
||||
static size_t g_total_free = 0;
|
||||
|
||||
size_t memory_pool_get_total_free_blocks(void) {
|
||||
@@ -12,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;
|
||||
@@ -22,6 +65,8 @@ struct memory_pool* memory_pool_init(size_t object_size) {
|
||||
pool->free_count = 0;
|
||||
pool->allocations = 0;
|
||||
pool->reuse_count = 0;
|
||||
pool->alloc_tag_counter = 1;
|
||||
pool->name = name;
|
||||
return pool;
|
||||
}
|
||||
|
||||
@@ -33,18 +78,21 @@ void* memory_pool_alloc_impl(struct memory_pool* pool, const char* location) {
|
||||
|
||||
if (pool->free_head) {
|
||||
void* obj = pool->free_head;
|
||||
pool->free_head = *(void**)obj; // Извлекаем next из начала блока
|
||||
pool->free_head = *(void**)obj;
|
||||
pool->free_count--;
|
||||
g_total_free--;
|
||||
pool->reuse_count++;
|
||||
memset(obj, 0, pool->object_size); // Опционально: очищаем память для безопасности
|
||||
memset(obj, 0, pool->object_size);
|
||||
pool_init_tags(pool, obj, location);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_alloc reused: %s, remaining=%zu", location, pool->free_count);
|
||||
return obj;
|
||||
}
|
||||
|
||||
pool->allocations++;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_alloc: %s, total_allocs=%zu", location, pool->allocations);
|
||||
return u_calloc_impl(1, pool->object_size, location); // Используем calloc для инициализации нулями
|
||||
void* obj = u_calloc_impl(1, POOL_ALLOC_SIZE, location);
|
||||
if (obj) pool_init_tags(pool, obj, location);
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Освободить объект в пул или в free
|
||||
@@ -53,9 +101,10 @@ void memory_pool_free_impl(struct memory_pool* pool, void* obj, const char* loca
|
||||
return;
|
||||
}
|
||||
|
||||
// Если пул не заполнен, сохранить объект для повторного использования
|
||||
pool_check_and_clear_tags(pool, obj, location);
|
||||
|
||||
if (pool->free_count < MEMORY_POOL_MAX_FREE) {
|
||||
*(void**)obj = pool->free_head; // Сохраняем next в начало блока
|
||||
*(void**)obj = pool->free_head;
|
||||
pool->free_head = obj;
|
||||
pool->free_count++;
|
||||
g_total_free++;
|
||||
@@ -63,7 +112,6 @@ void memory_pool_free_impl(struct memory_pool* pool, void* obj, const char* loca
|
||||
return;
|
||||
}
|
||||
|
||||
// Иначе освободить через free
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_free direct: %s", location);
|
||||
u_free_impl(obj, location);
|
||||
}
|
||||
@@ -87,21 +135,26 @@ 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);
|
||||
}
|
||||
|
||||
int memory_pool_is_freed(struct memory_pool* pool, void* obj)
|
||||
{
|
||||
if (!pool || !obj) return 0;
|
||||
void* cur = pool->free_head;
|
||||
while (cur) {
|
||||
if (cur == obj) return 1;
|
||||
cur = *(void**)cur;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
+10
-6
@@ -12,16 +12,18 @@
|
||||
#define PLOCATION __FILE__ ":" XSTR(__LINE__) "[POOL]"
|
||||
|
||||
struct memory_pool {
|
||||
void* free_head; // Голова linked list свободных блоков (next хранится в начале каждого блока)
|
||||
int free_count; // Количество свободных блоков
|
||||
size_t object_size; // Размер объектов в пуле
|
||||
size_t allocations; // Общее количество аллокаций (включая новые malloc)
|
||||
size_t reuse_count; // Количество повторных использований из пула
|
||||
void* free_head; // Голова linked list свободных блоков (next хранится в начале каждого блока)
|
||||
int free_count; // Количество свободных блоков
|
||||
size_t object_size; // Размер объектов в пуле (пользовательские данные)
|
||||
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);
|
||||
|
||||
// элементы пула:
|
||||
@@ -33,4 +35,6 @@ size_t memory_pool_get_total_free_blocks(void);
|
||||
#define memory_pool_alloc(pool) memory_pool_alloc_impl(pool, PLOCATION)
|
||||
#define memory_pool_free(pool, obj) memory_pool_free_impl(pool, obj, PLOCATION)
|
||||
|
||||
int memory_pool_is_freed(struct memory_pool* pool, void* obj);
|
||||
|
||||
#endif // MEMORY_POOL_H
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+459
@@ -0,0 +1,459 @@
|
||||
// tcp_io.c — управление TCP-соединением через uasync + ll_queue
|
||||
#include "tcp_io.h"
|
||||
#include "debug_config.h"
|
||||
#include "mem.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
#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);
|
||||
static void error_cb(socket_t sock, void* arg);
|
||||
static void resume_read_cb(struct ll_queue* q, void* arg);
|
||||
static void fin_deferred_cb(struct ll_queue* q, void* arg);
|
||||
static void write_queue_fetch_cb(struct ll_queue* q, void* arg);
|
||||
static int flush_write_buf(struct tcp_conn* tc);
|
||||
static void tcp_conn_handle_error(struct tcp_conn* tc, int err);
|
||||
static const uint8_t tcp_fin_sentinel;
|
||||
|
||||
struct tcp_conn* tcp_conn_create(
|
||||
struct UASYNC* ua, socket_t sock,
|
||||
size_t entry_data_size, size_t write_chunk_size,
|
||||
int read_high_water, int read_low_water, int rcvbuf_size,
|
||||
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", (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; }
|
||||
|
||||
tc->ua = ua;
|
||||
tc->sock = sock;
|
||||
tc->entry_data_size = entry_data_size;
|
||||
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), "entry_pool");
|
||||
{
|
||||
size_t ds = entry_data_size > write_chunk_size ? entry_data_size : write_chunk_size;
|
||||
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");
|
||||
if (tc->entry_pool) memory_pool_destroy(tc->entry_pool);
|
||||
if (tc->data_pool) memory_pool_destroy(tc->data_pool);
|
||||
u_free(tc); return NULL;
|
||||
}
|
||||
|
||||
tc->read_queue = queue_new(ua, 0, 0, 0, "tcp_io_rq");
|
||||
tc->write_queue = queue_new(ua, 0, 0, 0, "tcp_io_wq");
|
||||
if (!tc->read_queue || !tc->write_queue) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: queue_new failed");
|
||||
if (tc->read_queue) queue_free(tc->read_queue);
|
||||
if (tc->write_queue) queue_free(tc->write_queue);
|
||||
memory_pool_destroy(tc->entry_pool);
|
||||
memory_pool_destroy(tc->data_pool);
|
||||
u_free(tc); return NULL;
|
||||
}
|
||||
queue_set_threshold(tc->read_queue, read_low_water, 0);
|
||||
queue_set_threshold(tc->write_queue, 32, 0);
|
||||
queue_set_callback(tc->write_queue, write_queue_fetch_cb, tc);
|
||||
queue_set_waiter_defer(tc->write_queue, 1);
|
||||
|
||||
tc->socket_id = uasync_add_socket_t(ua, sock, read_cb, write_cb, error_cb, tc);
|
||||
if (!tc->socket_id) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: uasync_add_socket_t failed");
|
||||
queue_free(tc->read_queue);
|
||||
queue_free(tc->write_queue);
|
||||
memory_pool_destroy(tc->entry_pool);
|
||||
memory_pool_destroy(tc->data_pool);
|
||||
u_free(tc); return NULL;
|
||||
}
|
||||
tc->write_monitor = 1;
|
||||
|
||||
if (rcvbuf_size > 0) {
|
||||
if (socket_set_buffers(sock, 0, rcvbuf_size) < 0)
|
||||
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: socket_set_buffers rcvbuf=%d failed fd=%d", rcvbuf_size, (int)sock);
|
||||
else
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: socket_set_buffers rcvbuf=%d ok fd=%d", rcvbuf_size, (int)sock);
|
||||
}
|
||||
|
||||
{
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t alen = sizeof(addr);
|
||||
if (getpeername(sock, (struct sockaddr*)&addr, &alen) == 0) tc->connected = 1;
|
||||
}
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: fd=%d entry=%zu chunk=%zu hw=%d lw=%d rcvbuf=%d connected=%d",
|
||||
(int)sock, entry_data_size, write_chunk_size, read_high_water, read_low_water, rcvbuf_size, tc->connected);
|
||||
return tc;
|
||||
}
|
||||
|
||||
static void tcp_conn_deferred_free(void* arg) {
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
if (tc->entry_pool) memory_pool_destroy(tc->entry_pool);
|
||||
if (tc->data_pool) memory_pool_destroy(tc->data_pool);
|
||||
u_free(tc);
|
||||
}
|
||||
|
||||
void tcp_conn_destroy(struct tcp_conn* tc) {
|
||||
if (!tc) return;
|
||||
if (tc->destroyed) return;
|
||||
tc->destroyed = 1;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_destroy: fd=%d connected=%d error=%d fin_remote=%d fin_local=%d closed=%d",
|
||||
(int)tc->sock, tc->connected, tc->error, tc->fin_remote, tc->fin_local, tc->closed);
|
||||
|
||||
if (tc->socket_id) {
|
||||
uasync_remove_socket_t(tc->ua, tc->sock);
|
||||
tc->socket_id = NULL;
|
||||
}
|
||||
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);
|
||||
queue_set_callback(tc->write_queue, NULL, NULL);
|
||||
|
||||
struct ll_entry* e;
|
||||
while ((e = queue_data_get(tc->read_queue)) != NULL) {
|
||||
if (e->dgram) memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e); queue_resume_callback(tc->read_queue);
|
||||
}
|
||||
while ((e = queue_data_get(tc->write_queue)) != NULL) {
|
||||
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); 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); 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);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Чтение из сокета
|
||||
// ====================================================================
|
||||
|
||||
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 || !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);
|
||||
if (!e || !buf) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: read_cb alloc failed fd=%d", (int)tc->sock);
|
||||
if (e) queue_entry_free(e);
|
||||
if (buf) memory_pool_free(tc->data_pool, buf);
|
||||
uasync_set_socket_read(tc->ua, tc->socket_id, 0);
|
||||
tc->read_paused = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
ssize_t n = recv(tc->sock, buf, tc->entry_data_size, 0);
|
||||
if (n > 0) {
|
||||
e->dgram = buf;
|
||||
e->len = (uint16_t)n;
|
||||
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);
|
||||
}
|
||||
} else if (n == 0) {
|
||||
memory_pool_free(tc->data_pool, buf);
|
||||
queue_entry_free(e);
|
||||
tc->fin_remote = 1;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: FIN fd=%d", (int)tc->sock);
|
||||
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) {
|
||||
if (tc->on_fin) tc->on_fin(tc, tc->arg);
|
||||
} else {
|
||||
queue_set_empty_callback(tc->read_queue, fin_deferred_cb, tc);
|
||||
}
|
||||
} else {
|
||||
memory_pool_free(tc->data_pool, buf);
|
||||
queue_entry_free(e);
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: recv error fd=%d errno=%d", (int)tc->sock, errno);
|
||||
tcp_conn_handle_error(tc, errno);
|
||||
}
|
||||
}
|
||||
|
||||
static void resume_read_cb(struct ll_queue* q, void* arg) {
|
||||
(void)q;
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
if (!tc || tc->sock == SOCKET_INVALID) return;
|
||||
uasync_set_socket_read(tc->ua, tc->socket_id, 1);
|
||||
tc->read_paused = 0;
|
||||
}
|
||||
|
||||
static void fin_deferred_cb(struct ll_queue* q, void* arg) {
|
||||
(void)q;
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
if (tc->on_fin) tc->on_fin(tc, tc->arg);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Запись в сокет
|
||||
// ====================================================================
|
||||
|
||||
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) {
|
||||
tc->write_offset += (size_t)n;
|
||||
if (tc->write_offset >= tc->write_len) {
|
||||
memory_pool_free(tc->data_pool, tc->write_buf);
|
||||
tc->write_buf = NULL;
|
||||
tc->write_len = 0;
|
||||
tc->write_offset = 0;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: flush_wbuf done fd=%d", (int)tc->sock);
|
||||
return 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
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);
|
||||
tcp_conn_handle_error(tc, errno);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
|
||||
if (tc->error || tc->closed) { DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch skip fd=%d error=%d closed=%d", (int)tc->sock, tc->error, tc->closed); return; }
|
||||
|
||||
if (tc->write_buf) {
|
||||
if (flush_write_buf(tc)) return;
|
||||
if (tc->write_buf) return; // остался остаток, ждём write_cb
|
||||
}
|
||||
|
||||
if (!tc->connected) { DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch skip fd=%d not connected", (int)tc->sock); return; }
|
||||
|
||||
struct ll_entry* e = queue_data_get(q);
|
||||
if (!e) {
|
||||
uasync_set_socket_write(tc->ua, tc->socket_id, 0);
|
||||
tc->write_monitor = 0;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch POLLOUT OFF fd=%d (queue empty)", (int)tc->sock);
|
||||
queue_resume_callback(q);
|
||||
if (tc->on_flushed) {
|
||||
void (*cb)(struct tcp_conn*, void*) = tc->on_flushed;
|
||||
tc->on_flushed = NULL;
|
||||
cb(tc, tc->arg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (e->len == 0) {
|
||||
if (!e->dgram) {
|
||||
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_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;
|
||||
if (tc->on_fin_sent) tc->on_fin_sent(tc, tc->arg);
|
||||
} else {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch zero-len entry with unknown dgram=%p fd=%d", (void*)e->dgram, (int)tc->sock);
|
||||
queue_entry_free(e); queue_resume_callback(q);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ssize_t n = send(tc->sock, e->dgram, e->len, MSG_NOSIGNAL);
|
||||
if (n == (ssize_t)e->len) {
|
||||
memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
queue_resume_callback(q);
|
||||
} else if (n > 0) {
|
||||
size_t rem = e->len - (size_t)n;
|
||||
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);
|
||||
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);
|
||||
tc->write_len = rem;
|
||||
tc->write_offset = 0;
|
||||
memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
uasync_set_socket_write(tc->ua, tc->socket_id, 1);
|
||||
tc->write_monitor = 1;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch POLLOUT ON fd=%d (partial send, wbuf=%zu bytes)", (int)tc->sock, tc->write_len);
|
||||
} else if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) {
|
||||
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);
|
||||
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);
|
||||
tc->write_len = e->len;
|
||||
tc->write_offset = 0;
|
||||
memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
uasync_set_socket_write(tc->ua, tc->socket_id, 1);
|
||||
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 {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: send error fd=%d errno=%d", (int)tc->sock, errno);
|
||||
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 || !tc->write_queue) return;
|
||||
|
||||
if (!tc->connected) {
|
||||
int err = 0;
|
||||
socklen_t len = sizeof(err);
|
||||
if (getsockopt(tc->sock, SOL_SOCKET, SO_ERROR, (char*)&err, &len) == 0 && err == 0) {
|
||||
tc->connected = 1;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: connect ok fd=%d", (int)tc->sock);
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: connect fail fd=%d err=%d", (int)tc->sock, err);
|
||||
tcp_conn_handle_error(tc, err ? err : -1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Асинхронная ошибка сокета
|
||||
// ====================================================================
|
||||
|
||||
static void error_cb(socket_t sock, void* arg) {
|
||||
(void)sock;
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
if (!tc || tc->sock == SOCKET_INVALID) return;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: async error fd=%d connected=%d err=%d fin_r=%d fin_l=%d closed=%d wmon=%d",
|
||||
(int)tc->sock, tc->connected, tc->error, tc->fin_remote, tc->fin_local, tc->closed, tc->write_monitor);
|
||||
tcp_conn_handle_error(tc, -1);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// FIN / Close через очередь отправки
|
||||
// ====================================================================
|
||||
|
||||
int tcp_conn_push_fin(struct tcp_conn* tc) {
|
||||
if (!tc || tc->sock == SOCKET_INVALID) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin invalid tc"); return -1; }
|
||||
if (tc->fin_local) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin already fin_local fd=%d", (int)tc->sock); return -1; }
|
||||
if (tc->closed) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin already closed fd=%d", (int)tc->sock); return -1; }
|
||||
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
|
||||
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin entry_pool exhausted fd=%d", (int)tc->sock); return -1; }
|
||||
e->dgram = (uint8_t*)&tcp_fin_sentinel; e->len = 0;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin fd=%d wq=%d", (int)tc->sock, tc->write_queue->count + 1);
|
||||
queue_data_put(tc->write_queue, e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tcp_conn_push_close(struct tcp_conn* tc) {
|
||||
if (!tc || tc->sock == SOCKET_INVALID) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close invalid tc"); return -1; }
|
||||
if (tc->closed) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close already closed fd=%d", (int)tc->sock); return -1; }
|
||||
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
|
||||
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close entry_pool exhausted fd=%d", (int)tc->sock); return -1; }
|
||||
e->dgram = NULL; e->len = 0;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close fd=%d wq=%d", (int)tc->sock, tc->write_queue->count + 1);
|
||||
queue_data_put(tc->write_queue, e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tcp_conn_set_flushed(struct tcp_conn* tc, void (*on_flushed)(struct tcp_conn* tc, void* arg)) {
|
||||
if (!tc) return;
|
||||
tc->on_flushed = on_flushed;
|
||||
}
|
||||
|
||||
void tcp_conn_pause_read(struct tcp_conn* tc) {
|
||||
if (!tc || tc->sock == SOCKET_INVALID) return;
|
||||
uasync_set_socket_read(tc->ua, tc->socket_id, 0);
|
||||
queue_waiter_cancel(tc->read_queue, &tc->read_waiter);
|
||||
tc->read_paused = 1;
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
// tcp_io.h — управление TCP-соединением через uasync + ll_queue
|
||||
//
|
||||
// Один tcp_conn = одно TCP-соединение. Два пула:
|
||||
// entry_pool — struct ll_entry (без inline data)
|
||||
// data_pool — буферы (max(entry_data_size, write_chunk_size))
|
||||
//
|
||||
// Чтение: read_cb → recv() в data_pool → entry в read_queue → callback внешнего кода.
|
||||
// high_water/low_water управляют backpressure (EPOLLIN on/off).
|
||||
// FIN (recv==0) → on_fin (сразу или через empty_callback после дренажа очереди).
|
||||
//
|
||||
// Запись: внешний код кладёт entry в write_queue через queue_data_put().
|
||||
// write_queue_fetch_cb (deferred) → send(), при EAGAIN → write_buf + EPOLLOUT.
|
||||
// Всё отправлено → on_flushed.
|
||||
//
|
||||
// FIN/Close: tcp_conn_push_fin/close ставят сентинел в write_queue.
|
||||
// FIN → shutdown(SHUT_WR) → on_fin_sent
|
||||
// CLOSE → close сокета → on_closed
|
||||
//
|
||||
// Ошибка: любая ошибка сокета → tcp_conn_handle_error → close + null коллбэков → on_error.
|
||||
// Реализация on_error обязана вызвать tcp_conn_destroy. После возврата tc недействителен.
|
||||
|
||||
#ifndef TCP_IO_H
|
||||
#define TCP_IO_H
|
||||
|
||||
#include "u_async.h"
|
||||
#include "socket_compat.h"
|
||||
#include "ll_queue.h"
|
||||
#include "memory_pool.h"
|
||||
|
||||
struct tcp_conn {
|
||||
socket_t sock;
|
||||
struct UASYNC* ua;
|
||||
void* socket_id;
|
||||
|
||||
struct ll_queue* read_queue; // сокет → данные (блоки до entry_data_size)
|
||||
struct ll_queue* write_queue; // данные → сокет (блоки до write_chunk_size)
|
||||
|
||||
int read_high_water;
|
||||
int read_low_water;
|
||||
int rcvbuf_size;
|
||||
uint8_t read_paused;
|
||||
|
||||
uint8_t write_monitor; // 1 = EPOLLOUT активен
|
||||
uint8_t connected;
|
||||
uint8_t error;
|
||||
uint8_t fin_remote; // FIN получен от удалённой стороны (recv == 0)
|
||||
uint8_t fin_local; // FIN отправлен удалённой стороне (shutdown SHUT_WR)
|
||||
uint8_t closed; // сокет полностью закрыт (close)
|
||||
uint8_t destroyed; // 1 = tcp_conn_destroy вызван, tc ожидает отложенного free
|
||||
|
||||
// Частичная отправка (из data_pool, не в очереди — досылается первой)
|
||||
uint8_t* write_buf;
|
||||
size_t write_len;
|
||||
size_t write_offset;
|
||||
size_t write_chunk_size;
|
||||
|
||||
// Пулы памяти
|
||||
struct memory_pool* entry_pool; // sizeof(struct ll_entry)
|
||||
struct memory_pool* data_pool; // max(entry_data_size, write_chunk_size)
|
||||
size_t entry_data_size;
|
||||
|
||||
struct queue_waiter_handle read_waiter;
|
||||
|
||||
// Коллбэки
|
||||
void (*on_fin)(struct tcp_conn* tc, void* arg); // FIN получен от удалённой стороны (fin_remote=1)
|
||||
void (*on_fin_sent)(struct tcp_conn* tc, void* arg); // FIN отправлен удалённой стороне (fin_local=1)
|
||||
void (*on_error)(struct tcp_conn* tc, int err, void* arg); // фатальная ошибка (вызывается после close сокета; обязан вызвать tcp_conn_destroy)
|
||||
void (*on_flushed)(struct tcp_conn* tc, void* arg); // все данные записи отправлены (write_queue + write_buf пусты)
|
||||
void (*on_closed)(struct tcp_conn* tc, void* arg); // сокет закрыт через очередь (после close)
|
||||
void* arg;
|
||||
};
|
||||
|
||||
struct tcp_conn* tcp_conn_create(
|
||||
struct UASYNC* ua,
|
||||
socket_t sock,
|
||||
size_t entry_data_size,
|
||||
size_t write_chunk_size,
|
||||
int read_high_water,
|
||||
int read_low_water,
|
||||
int rcvbuf_size,
|
||||
void (*on_fin)(struct tcp_conn* tc, void* arg),
|
||||
void (*on_error)(struct tcp_conn* tc, int err, void* arg),
|
||||
void* arg);
|
||||
|
||||
// Дренирует очереди, закрывает сокет, уничтожает пулы, освобождает tc.
|
||||
void tcp_conn_destroy(struct tcp_conn* tc);
|
||||
|
||||
// Запись: queue_data_put(tc->write_queue, entry) напрямую.
|
||||
// Автозабор (deferred) сам шлёт когда сокет готов.
|
||||
// Порог: 32 записи, при заполнении queue_waiter_wait.
|
||||
|
||||
// Поставить сигналы в очередь отправки. Все данные в очереди перед сигналом
|
||||
// будут отправлены до его обработки. Кодирование: dgram=NULL → close, len=0+dgram!=NULL → FIN.
|
||||
// tcp_conn_push_fin(tc) — FIN: после отправки предшествующих данных вызывает
|
||||
// shutdown(SHUT_WR) и on_fin_sent.
|
||||
// Повторный вызов игнорируется (fin_sent уже установлен).
|
||||
// tcp_conn_push_close(tc) — CLOSE: после отправки предшествующих данных закрывает
|
||||
// сокет (close), вызывает on_closed.
|
||||
// После close сокет удалён из uasync, tc->closed=1.
|
||||
// Повторный вызов игнорируется.
|
||||
int tcp_conn_push_fin(struct tcp_conn* tc);
|
||||
int tcp_conn_push_close(struct tcp_conn* tc);
|
||||
|
||||
// Одноразовый коллбэк: вызывается когда write_queue + write_buf полностью опустели.
|
||||
// После вызова сбрасывается. Установить повторно можно в любой момент.
|
||||
void tcp_conn_set_flushed(struct tcp_conn* tc, void (*on_flushed)(struct tcp_conn* tc, void* arg));
|
||||
|
||||
// Принудительная остановка чтения: убирает EPOLLIN и отменяет read_waiter.
|
||||
void tcp_conn_pause_read(struct tcp_conn* tc);
|
||||
|
||||
#endif
|
||||
+589
-483
File diff suppressed because it is too large.
Load diff
+11
-2
@@ -86,6 +86,11 @@ uint64_t get_time_tb(void);
|
||||
uint64_t get_time_us(void);
|
||||
|
||||
// Timeouts, timebase = 0.1 mS
|
||||
// Как работать с таймаутами если таймаут может быть использован несколько раз:
|
||||
// 1. заводим дескриптор таймаута и обнуляем
|
||||
// 2. перед активацией таймаута проверяем дескриптор (t_id) на null
|
||||
// 3. при cancel или срабатывании дескриптор обнуляем
|
||||
// это обеспечит отсутствие утечек и задвоений таймаутов
|
||||
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* user_arg, timeout_callback_t callback, const char* name);
|
||||
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id);
|
||||
|
||||
@@ -97,8 +102,12 @@ err_t uasync_call_soon_cancel(struct UASYNC* ua, void* t_id);
|
||||
void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_arg);
|
||||
// Sockets - for socket_t (cross-platform sockets)
|
||||
void* uasync_add_socket_t(struct UASYNC* ua, socket_t sock, socket_t_callback_t read_cbk, socket_t_callback_t write_cbk, socket_t_callback_t except_cbk, void* user_arg);
|
||||
err_t uasync_remove_socket(struct UASYNC* ua, void* s_id);
|
||||
err_t uasync_remove_socket_t(struct UASYNC* ua, socket_t sock);
|
||||
err_t uasync_remove_socket(struct UASYNC* ua, void* s_id);
|
||||
err_t uasync_remove_socket_t(struct UASYNC* ua, socket_t sock);
|
||||
|
||||
// Динамическое управление мониторингом чтения/записи для сокета
|
||||
err_t uasync_set_socket_read(struct UASYNC* ua, void* s_id, int enable);
|
||||
err_t uasync_set_socket_write(struct UASYNC* ua, void* s_id, int enable);
|
||||
|
||||
// Single iteration of event loop with timeout (timebase units)
|
||||
void uasync_poll(struct UASYNC* ua, int timeout_tb);
|
||||
|
||||
@@ -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.
|
||||
@@ -0,0 +1,9 @@
|
||||
tui enable
|
||||
stepi # шаг по инструкции
|
||||
nexti
|
||||
step # шаг по строке исходника
|
||||
next
|
||||
|
||||
bt
|
||||
frame n
|
||||
p *var
|
||||
+2408
File diff suppressed because it is too large.
Load diff
+19
-96
@@ -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 \
|
||||
@@ -20,24 +22,32 @@ utun_CORE_SOURCES = \
|
||||
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 \
|
||||
etcp_connect.c \
|
||||
control_server.c \
|
||||
msg_transport.c \
|
||||
firewall.c \
|
||||
eim_nat.c \
|
||||
nat_transport.c \
|
||||
dummynet.c \
|
||||
tcp_proxy.c \
|
||||
proxy/tcp_proxy_client.c \
|
||||
etcp_router.c \
|
||||
remote_proxy.c \
|
||||
udp_proxy.c \
|
||||
icmp_proxy.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 \
|
||||
lwip_tcp/lwip_tcp_in.c \
|
||||
@@ -46,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
|
||||
|
||||
+106
-48
@@ -45,8 +45,9 @@ typedef enum {
|
||||
SECTION_CONTROL,
|
||||
SECTION_ALLOWED_KEYS,
|
||||
SECTION_NAT,
|
||||
SECTION_TCP_PROXY,
|
||||
SECTION_REMOTE_PROXY
|
||||
SECTION_TCP_PROXY_CLIENT,
|
||||
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,12 +381,12 @@ static int parse_global(const char *key, const char *value, struct global_config
|
||||
global->keepalive_interval = atoi(value);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "inflight_min_bytes") == 0) {
|
||||
global->inflight_min_bytes = atoi(value);
|
||||
if (strcmp(key, "keepalive_adaptive") == 0) {
|
||||
global->keepalive_adaptive = atoi(value);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "inflight_max_bytes") == 0) {
|
||||
global->inflight_max_bytes = atoi(value);
|
||||
if (strcmp(key, "bbr_max_cwnd") == 0) {
|
||||
global->bbr_max_cwnd = atoi(value);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "debug_level") == 0) {
|
||||
@@ -390,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);
|
||||
@@ -411,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, inflight_min_bytes, inflight_max_bytes, 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;
|
||||
}
|
||||
|
||||
@@ -440,30 +448,57 @@ static int parse_control(const char *key, const char *value, struct global_confi
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int parse_tcp_proxy(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
|
||||
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_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
|
||||
global->tcp_proxy_client_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "tun_name") == 0) {
|
||||
strncpy(global->tcp_proxy_tun_name, value, sizeof(global->tcp_proxy_tun_name) - 1);
|
||||
strncpy(global->tcp_proxy_client_tun_name, value, sizeof(global->tcp_proxy_client_tun_name) - 1);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "tun_ip") == 0) {
|
||||
strncpy(global->tcp_proxy_tun_ip, value, sizeof(global->tcp_proxy_tun_ip) - 1);
|
||||
strncpy(global->tcp_proxy_client_tun_ip, value, sizeof(global->tcp_proxy_client_tun_ip) - 1);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "mtu") == 0) {
|
||||
global->tcp_proxy_mtu = atoi(value);
|
||||
global->tcp_proxy_client_mtu = atoi(value);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "via_node") == 0) {
|
||||
global->tcp_proxy_via_node_id = strtoull(value, NULL, 16);
|
||||
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_mapping_count >= MAX_TCP_PROXY_MAPPINGS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Too many tcp_proxy forward rules (max %d)", MAX_TCP_PROXY_MAPPINGS);
|
||||
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);
|
||||
return -1;
|
||||
}
|
||||
char buf[256];
|
||||
@@ -475,29 +510,29 @@ static int parse_tcp_proxy(const char *key, const char *value, struct global_con
|
||||
char* arrow = strtok(NULL, " ");
|
||||
char* remote_str = strtok(NULL, "");
|
||||
if (!local_port_str || !arrow || !remote_str || strcmp(arrow, "->") != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward format: %s (expected: port -> ip:port)", value);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_client forward format: %s (expected: port -> ip:port)", value);
|
||||
return -1;
|
||||
}
|
||||
int local_port = atoi(local_port_str);
|
||||
if (local_port <= 0 || local_port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward local port: %s", local_port_str); return -1; }
|
||||
if (local_port <= 0 || local_port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_client forward local port: %s", local_port_str); return -1; }
|
||||
|
||||
char* remote_ip_str = strtok(remote_str, ":");
|
||||
char* remote_port_str = strtok(NULL, "");
|
||||
if (!remote_ip_str || !remote_port_str) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward remote: %s (expected ip:port)", remote_str);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_client forward remote: %s (expected ip:port)", remote_str);
|
||||
return -1;
|
||||
}
|
||||
int remote_port = atoi(remote_port_str);
|
||||
if (remote_port <= 0 || remote_port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward remote port: %s", remote_port_str); return -1; }
|
||||
if (remote_port <= 0 || remote_port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_client forward remote port: %s", remote_port_str); return -1; }
|
||||
|
||||
int idx = global->tcp_proxy_mapping_count;
|
||||
global->tcp_proxy_mappings[idx].local_port = (uint16_t)local_port;
|
||||
strncpy(global->tcp_proxy_mappings[idx].remote_ip, remote_ip_str, sizeof(global->tcp_proxy_mappings[idx].remote_ip) - 1);
|
||||
global->tcp_proxy_mappings[idx].remote_port = (uint16_t)remote_port;
|
||||
global->tcp_proxy_mapping_count++;
|
||||
int idx = global->tcp_proxy_client_mapping_count;
|
||||
global->tcp_proxy_client_mappings[idx].local_port = (uint16_t)local_port;
|
||||
strncpy(global->tcp_proxy_client_mappings[idx].remote_ip, remote_ip_str, sizeof(global->tcp_proxy_client_mappings[idx].remote_ip) - 1);
|
||||
global->tcp_proxy_client_mappings[idx].remote_port = (uint16_t)remote_port;
|
||||
global->tcp_proxy_client_mapping_count++;
|
||||
return 0;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown tcp_proxy 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;
|
||||
}
|
||||
|
||||
@@ -615,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;
|
||||
}
|
||||
@@ -628,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;
|
||||
}
|
||||
|
||||
@@ -694,8 +736,9 @@ static section_type_t parse_section_header(const char *line, char *name, size_t
|
||||
if (strcasecmp(section, "control") == 0) return SECTION_CONTROL;
|
||||
if (strcasecmp(section, "allowed_keys") == 0) return SECTION_ALLOWED_KEYS;
|
||||
if (strcasecmp(section, "nat") == 0) return SECTION_NAT;
|
||||
if (strcasecmp(section, "tcp_proxy") == 0) return SECTION_TCP_PROXY;
|
||||
if (strcasecmp(section, "remote_proxy") == 0) return SECTION_REMOTE_PROXY;
|
||||
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;
|
||||
@@ -724,8 +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.inflight_min_bytes = 2000; // Default 2KB
|
||||
cfg->global.inflight_max_bytes = 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;
|
||||
@@ -866,16 +910,22 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
// parse_nat already printed the error
|
||||
}
|
||||
break;
|
||||
case SECTION_TCP_PROXY:
|
||||
cfg->global.tcp_proxy_enabled = 1;
|
||||
if (parse_tcp_proxy(key, value, &cfg->global, filename, line_num) < 0) {
|
||||
// parse_tcp_proxy already printed the error
|
||||
case SECTION_TCP_PROXY_CLIENT:
|
||||
cfg->global.tcp_proxy_client_enabled = 1;
|
||||
if (parse_tcp_proxy_client(key, value, &cfg->global, filename, line_num) < 0) {
|
||||
// parse_tcp_proxy_client already printed the error
|
||||
}
|
||||
break;
|
||||
case SECTION_REMOTE_PROXY:
|
||||
cfg->global.remote_proxy_enabled = 1;
|
||||
// remote_proxy section has no options — its presence alone enables it
|
||||
(void)key; (void)value;
|
||||
case SECTION_TCP_PROXY_SERVER:
|
||||
cfg->global.tcp_proxy_server_enabled = 1;
|
||||
if (strcmp(key, "tcp_recv_buf") == 0) {
|
||||
cfg->global.tcp_recv_buf = atoi(value);
|
||||
}
|
||||
break;
|
||||
case SECTION_MSG_TRANSPORT:
|
||||
if (parse_msg_transport(key, value, &cfg->global, filename, line_num) < 0) {
|
||||
// parse_msg_transport already printed the error
|
||||
}
|
||||
break;
|
||||
default:
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Key outside section: %s", filename, line_num, key);
|
||||
@@ -893,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;
|
||||
@@ -991,9 +1049,9 @@ void print_config(const struct utun_config *cfg) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Servers:");
|
||||
struct CFG_SERVER *s = cfg->servers;
|
||||
while (s) {
|
||||
struct sockaddr_in *sin = (struct sockaddr_in *)&s->ip;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s: %s:%d (mark=%d, netif=%u, type=%u, mtu=%d)",
|
||||
s->name, ip_to_str(&sin->sin_addr, AF_INET).str, ntohs(sin->sin_port),
|
||||
ip_str_t addr_str = sockaddr_storage_to_str(&s->ip);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s: %s (mark=%d, netif=%u, type=%u, mtu=%d)",
|
||||
s->name, addr_str.str,
|
||||
s->so_mark, s->netif_index, s->type, s->mtu);
|
||||
s = s->next;
|
||||
}
|
||||
@@ -1004,9 +1062,9 @@ void print_config(const struct utun_config *cfg) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s: peer_key=%s, keepalive=%d", c->name, c->peer_public_key_hex, c->keepalive);
|
||||
struct CFG_CLIENT_LINK *link = c->links;
|
||||
while (link) {
|
||||
struct sockaddr_in *sin = (struct sockaddr_in *)&link->remote_addr;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " Link: %s:%d (via %s)",
|
||||
ip_to_str(&sin->sin_addr, AF_INET).str, ntohs(sin->sin_port), link->local_srv->name);
|
||||
ip_str_t addr_str = sockaddr_storage_to_str(&link->remote_addr);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " Link: %s (via %s)",
|
||||
addr_str.str, link->local_srv->name);
|
||||
link = link->next;
|
||||
}
|
||||
c = c->next;
|
||||
|
||||
+27
-17
@@ -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,12 +80,12 @@ 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;
|
||||
};
|
||||
|
||||
#define MAX_TCP_PROXY_MAPPINGS 32
|
||||
struct tcp_proxy_mapping_config {
|
||||
#define MAX_TCP_PROXY_CLIENT_MAPPINGS 32
|
||||
struct tcp_proxy_client_mapping_config {
|
||||
uint16_t local_port;
|
||||
char remote_ip[64];
|
||||
uint16_t remote_port;
|
||||
@@ -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,8 +123,8 @@ 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 inflight_min_bytes; // inflight window starting size (default: 2000)
|
||||
int inflight_max_bytes; // inflight window max cap (default: 100000)
|
||||
int keepalive_adaptive; // 1 = adaptive period (default), 0 = fixed interval
|
||||
int bbr_max_cwnd; // BBR cwnd cap in bytes (default: 100000)
|
||||
|
||||
// Firewall configuration
|
||||
struct CFG_FIREWALL_RULE *firewall_rules;
|
||||
@@ -151,17 +156,22 @@ struct global_config {
|
||||
} nat_forwards[MAX_NAT_FORWARDS];
|
||||
int nat_forward_count;
|
||||
|
||||
// TCP proxy configuration ([tcp_proxy] section)
|
||||
int tcp_proxy_enabled;
|
||||
char tcp_proxy_tun_name[16];
|
||||
char tcp_proxy_tun_ip[64];
|
||||
int tcp_proxy_mtu;
|
||||
uint64_t tcp_proxy_via_node_id; // через этот узел проксируются все forward-правила
|
||||
struct tcp_proxy_mapping_config tcp_proxy_mappings[MAX_TCP_PROXY_MAPPINGS];
|
||||
int tcp_proxy_mapping_count;
|
||||
// TCP proxy client configuration ([tcp_proxy_client] section)
|
||||
int tcp_proxy_client_enabled;
|
||||
char tcp_proxy_client_tun_name[16];
|
||||
char tcp_proxy_client_tun_ip[64];
|
||||
int tcp_proxy_client_mtu;
|
||||
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"
|
||||
|
||||
// Remote proxy (exit node) configuration ([remote_proxy] section)
|
||||
int remote_proxy_enabled;
|
||||
// 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
|
||||
+329
-39
@@ -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,14 +91,24 @@ 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);
|
||||
static void send_metrics(struct control_server* server, struct control_client* client, uint8_t seq_id);
|
||||
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
|
||||
* ============================================================================ */
|
||||
@@ -399,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);
|
||||
|
||||
@@ -543,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) {
|
||||
@@ -559,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",
|
||||
@@ -582,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) {
|
||||
@@ -613,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_ACTION) {
|
||||
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;
|
||||
@@ -690,12 +779,68 @@ 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);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ETCPMON_CMD_GET_DEBUG_CONFIG:
|
||||
send_debug_config(server, client, req_seq);
|
||||
break;
|
||||
|
||||
case ETCPMON_CMD_SET_DEBUG_CONFIG:
|
||||
if (payload_size >= 2) {
|
||||
uint8_t global_lvl = payload[0];
|
||||
uint8_t cat_count = payload[1];
|
||||
if (cat_count > ETCPMON_MAX_DEBUG_CATEGORIES) cat_count = ETCPMON_MAX_DEBUG_CATEGORIES;
|
||||
if (global_lvl <= DEBUG_LEVEL_TRACE) debug_set_level((debug_level_t)global_lvl);
|
||||
const uint8_t* levels = payload + 2;
|
||||
for (uint8_t i = 0; i < cat_count && i + 1 < DEBUG_CATEGORY_COUNT; i++) {
|
||||
if (levels[i] <= DEBUG_LEVEL_TRACE)
|
||||
debug_set_category_level((debug_category_t)(i + 1), (debug_level_t)levels[i]);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Debug config applied: global=%d categories=%d", global_lvl, cat_count);
|
||||
if (server->log_file) {
|
||||
fprintf(server->log_file, "%llu: [LOG] Debug config set: global=%d categories=%d\n",
|
||||
(unsigned long long)get_timestamp_ms(), global_lvl, cat_count);
|
||||
fflush(server->log_file);
|
||||
}
|
||||
}
|
||||
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",
|
||||
@@ -711,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) {
|
||||
@@ -770,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) {
|
||||
@@ -794,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;
|
||||
}
|
||||
|
||||
@@ -843,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) */
|
||||
@@ -857,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) {
|
||||
@@ -1053,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;
|
||||
@@ -1068,7 +1231,7 @@ static void send_metrics(struct control_server* server, struct control_client* c
|
||||
link_info[i].bandwidth = link->bandwidth;
|
||||
link_info[i].nat_changes_count = link->nat_changes_count;
|
||||
link_info[i].rtt_last = link->rtt_last;
|
||||
link_info[i].rtt_avg10 = link->rtt_avg10;
|
||||
link_info[i].rtt_avg10 = link->bbr ? link->bbr->min_rtt_us / 100 : 0;
|
||||
link_info[i].tt_last = link->tt_last;
|
||||
link_info[i].init_timer_active = (link->init_timer != NULL) ? 1 : 0;
|
||||
link_info[i].keepalive_timer_active = (link->keepalive_timer != NULL) ? 1 : 0;
|
||||
@@ -1078,6 +1241,25 @@ static void send_metrics(struct control_server* server, struct control_client* c
|
||||
link_info[i].inflight_bytes = link->inflight_bytes;
|
||||
link_info[i].inflight_packets = link->inflight_packets;
|
||||
link_info[i].inflight_lim_bytes = link->inflight_lim_bytes;
|
||||
|
||||
/* BBR fields */
|
||||
if (link->bbr) {
|
||||
link_info[i].bbr_mode = link->bbr->mode;
|
||||
link_info[i].bbr_cycle_idx = link->bbr->cycle_idx;
|
||||
link_info[i].bbr_full_bw_reached = link->bbr->full_bw_reached;
|
||||
link_info[i].bbr_loss_in_round = link->bbr->loss_in_round;
|
||||
link_info[i].bbr_pacing_rate = link->bbr_pacing_rate;
|
||||
link_info[i].bbr_min_rtt_us = link->bbr->min_rtt_us;
|
||||
link_info[i].bbr_pacing_gain = link->bbr->pacing_gain;
|
||||
link_info[i].bbr_inflight_hi = link->bbr->inflight_hi;
|
||||
link_info[i].bbr_inflight_lo = link->bbr->inflight_lo;
|
||||
/* bw_hi/lo: convert from BW_UNIT scale (pkts/usec<<24) to bytes/sec */
|
||||
uint32_t max_bw = link->bbr->bw_hi[0] > link->bbr->bw_hi[1] ? link->bbr->bw_hi[0] : link->bbr->bw_hi[1];
|
||||
link_info[i].bbr_bw_hi = max_bw ? (uint32_t)((uint64_t)max_bw * link->mtu * 1000000ULL / 16777216ULL) : 0;
|
||||
link_info[i].bbr_bw_lo = link->bbr->bw_lo != ~0U ? (uint32_t)((uint64_t)link->bbr->bw_lo * link->mtu * 1000000ULL / 16777216ULL) : 0;
|
||||
} else {
|
||||
memset(&link_info[i].bbr_mode, 0, 11 * 4 + 2 + 2); // zero out all BBR fields
|
||||
}
|
||||
link = link->next;
|
||||
}
|
||||
|
||||
@@ -1092,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) {
|
||||
@@ -1126,13 +1302,46 @@ 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
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
}
|
||||
|
||||
u_free(buffer);
|
||||
static void send_debug_config(struct control_server* server, struct control_client* client, uint8_t seq_id) {
|
||||
uint8_t cat_count = 0;
|
||||
char names_buf[512];
|
||||
names_buf[0] = '\0';
|
||||
size_t off = 0;
|
||||
for (int i = 1; i < DEBUG_CATEGORY_COUNT; i++) {
|
||||
const char* name = debug_get_category_name(i);
|
||||
size_t name_len = strlen(name);
|
||||
if (off + name_len + 2 > sizeof(names_buf)) break;
|
||||
if (cat_count > 0) { names_buf[off++] = ','; }
|
||||
memcpy(names_buf + off, name, name_len); off += name_len;
|
||||
cat_count++;
|
||||
}
|
||||
names_buf[off] = '\0';
|
||||
uint16_t names_len = (uint16_t)(off + 1);
|
||||
|
||||
uint16_t payload_size = 2 + names_len + cat_count;
|
||||
uint16_t rsp_size = sizeof(struct etcpmon_msg_header) + payload_size;
|
||||
uint8_t* buffer = (uint8_t*)u_malloc(rsp_size);
|
||||
if (!buffer) { DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate debug config buffer"); return; }
|
||||
|
||||
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
|
||||
etcpmon_build_header(hdr, payload_size, ETCPMON_RSP_DEBUG_CONFIG, seq_id);
|
||||
|
||||
uint8_t* pay = buffer + sizeof(*hdr);
|
||||
pay[0] = (uint8_t)g_debug_config.level;
|
||||
pay[1] = cat_count;
|
||||
memcpy(pay + 2, names_buf, names_len);
|
||||
for (uint8_t i = 0; i < cat_count; i++) pay[2 + names_len + i] = (uint8_t)g_debug_config.category_levels[i + 1];
|
||||
|
||||
if (server->log_file) {
|
||||
fprintf(server->log_file, "%llu: [LOG] Sent RSP_DEBUG_CONFIG global=%d categories=%d\n",
|
||||
(unsigned long long)get_timestamp_ms(), g_debug_config.level, cat_count);
|
||||
fflush(server->log_file);
|
||||
}
|
||||
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
@@ -1151,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
-4
@@ -316,9 +316,6 @@ static void dummynet_delay_callback(void* user_arg) {
|
||||
dir->stats.queue_max = queue_count + 1;
|
||||
}
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DUMMYNET, "Dir %d: packet added to queue (size=%d)",
|
||||
dir_idx, queue_count + 1);
|
||||
|
||||
/* Запускаем шейпер если не активен */
|
||||
if (!dir->shaper_pending) {
|
||||
dir->shaper_pending = 1;
|
||||
@@ -358,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);
|
||||
@@ -580,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,
|
||||
@@ -588,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) {
|
||||
@@ -640,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
|
||||
+159
-123
@@ -16,6 +16,7 @@
|
||||
#include <math.h> // For bandwidth calcs
|
||||
#include <limits.h> // For UINT16_MAX
|
||||
#include "../lib/mem.h"
|
||||
#include "../lib/memory_pool.h"
|
||||
|
||||
// Enable comprehensive debug output for ETCP module
|
||||
#define DEBUG_CATEGORY_ETCP_DETAILED 1
|
||||
@@ -69,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) {
|
||||
@@ -77,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;
|
||||
@@ -137,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++) {
|
||||
@@ -198,13 +200,15 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
|
||||
|
||||
etcp->instance = instance;
|
||||
etcp->input_queue = queue_new(instance->ua, 0, 0, 0, "ETCP input"); // No hash for input_queue
|
||||
queue_set_threshold(etcp->input_queue, 0, 0); // Backpressure: ждать полного освобождения
|
||||
etcp->output_queue = queue_new(instance->ua, 0, 0, 0, "ETCP output"); // No hash for output_queue
|
||||
etcp->input_send_q = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, 0, 4, "input_send_q"); // Hash for send_q
|
||||
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;
|
||||
@@ -259,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);
|
||||
}
|
||||
|
||||
@@ -300,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) {
|
||||
@@ -329,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;
|
||||
|
||||
@@ -379,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);
|
||||
@@ -389,17 +396,20 @@ 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->window_pkt_transmitted = 0;
|
||||
l->window_retransmissions = 0;
|
||||
l->win_timebase = 50000;
|
||||
l->win_ptr = 0;
|
||||
memset(l->stat_win, 0, sizeof(l->stat_win));
|
||||
start_stats_timer(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;
|
||||
}
|
||||
|
||||
@@ -455,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");
|
||||
}
|
||||
|
||||
@@ -614,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;
|
||||
|
||||
@@ -754,8 +779,8 @@ static void ack_timeout_check(struct ETCP_CONN* etcp) {
|
||||
// shedule timer
|
||||
int64_t next_timeout=timeout - elapsed;
|
||||
if (next_timeout<0) next_timeout=0;
|
||||
etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, next_timeout+10, etcp, ack_timeout_cb, "etcp_retrans");
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] retransmission timer set for %llu units", etcp->log_name, next_timeout);
|
||||
etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, next_timeout+10, etcp, ack_timeout_cb, "etcp_retrans");
|
||||
return;
|
||||
}
|
||||
current = etcp->input_wait_ack->head;
|
||||
@@ -781,8 +806,8 @@ static void input_send_q_cb(struct ll_queue* q, void* arg) {// etcp->input_send_
|
||||
struct ETCP_CONN* etcp=(struct ETCP_CONN*)arg;
|
||||
etcp_conn_process_send_queue(etcp);
|
||||
if (etcp->tx_state==ETCP_TX_STATE_DATA_WAIT) {
|
||||
queue_resume_callback(etcp->input_send_q);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "resume input_send_q");
|
||||
queue_resume_callback(etcp->input_send_q);
|
||||
} else DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "no resume - link busy");
|
||||
}
|
||||
/*
|
||||
@@ -840,14 +865,12 @@ static void etcp_link_ready_callback(struct ETCP_CONN* etcp) {
|
||||
|
||||
if (etcp->tx_state!=ETCP_TX_STATE_LINK_WAIT) return;
|
||||
etcp->tx_state = ETCP_TX_STATE_DATA_WAIT;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "resume input_send_q+ack_q; link_wait->data_wait");
|
||||
queue_resume_callback(etcp->input_send_q);
|
||||
// queue_resume_callback(etcp->ack_q);
|
||||
if (etcp->ack_q->count && etcp->ack_resp_timer == NULL) {
|
||||
ack_response_timer_cb(etcp);
|
||||
}
|
||||
|
||||
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "resume input_send_q+ack_q; link_wait->data_wait");
|
||||
}
|
||||
|
||||
// Process packets in send queue and transmit them
|
||||
@@ -858,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);
|
||||
@@ -923,17 +946,21 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
|
||||
|
||||
// Always subtract from previous last_link (if any) – this handles retransmission
|
||||
if (inf_pkt->last_link) {
|
||||
inf_pkt->last_link->window_retransmissions++;
|
||||
inf_pkt->last_link->total_retransmissions++;
|
||||
inf_pkt->last_link->bbr_loss_since_ack += inf_pkt->ll.len;
|
||||
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)
|
||||
link->inflight_bytes += inf_pkt->ll.len;
|
||||
link->inflight_packets++;
|
||||
link->window_pkt_transmitted++;
|
||||
|
||||
// BBR: сохраняем снэпшот на момент отправки
|
||||
inf_pkt->delivered_at_send = link->delivered_bytes;
|
||||
inf_pkt->inflight_at_send = link->inflight_bytes;
|
||||
inf_pkt->is_app_limited = (etcp->input_queue->count == 0) ? 1 : 0;
|
||||
|
||||
// Update last_link for future ACK/retrans
|
||||
inf_pkt->last_link = link;
|
||||
@@ -1161,11 +1188,12 @@ void etcp_output_try_assembly(struct ETCP_CONN* etcp) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "move: ETCP -> PN");
|
||||
|
||||
// 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++;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] moved packet id=%u to output_queue (qlen=%d)", etcp->log_name,
|
||||
next_expected_id, etcp->output_queue->count);
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to add packet id=%u to output_queue", etcp->log_name,
|
||||
next_expected_id);
|
||||
@@ -1212,11 +1240,50 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d
|
||||
return;
|
||||
}
|
||||
|
||||
// === NEW: subtract inflight from the LAST link the packet was sent on ===
|
||||
// === subtract inflight from the LAST link the packet was sent on ===
|
||||
if (acked_pkt->last_link) {
|
||||
acked_pkt->last_link->inflight_bytes -= acked_pkt->ll.len;
|
||||
acked_pkt->last_link->inflight_packets--;
|
||||
if (acked_pkt->last_link->send_blocked_inflight && acked_pkt->last_link->inflight_bytes < acked_pkt->last_link->inflight_lim_bytes) loadbalancer_link_ready(acked_pkt->last_link);
|
||||
struct ETCP_LINK* link = acked_pkt->last_link;
|
||||
link->inflight_bytes -= acked_pkt->ll.len;
|
||||
link->inflight_packets--;
|
||||
|
||||
// BBR: собираем rate_sample и вызываем bbr_main
|
||||
uint64_t now_tb = get_time_tb();
|
||||
uint32_t interval_us = link->last_ack_time_tb ? (uint32_t)((now_tb - link->last_ack_time_tb) * 100) : 0;
|
||||
struct bbr_rate_sample rs = {
|
||||
.delivered = acked_pkt->ll.len,
|
||||
.interval_us = interval_us,
|
||||
.rtt_us = (uint32_t)link->rtt_last * 100,
|
||||
.acked_sacked = acked_pkt->ll.len,
|
||||
.prior_delivered = (uint32_t)acked_pkt->delivered_at_send,
|
||||
.tx_in_flight = acked_pkt->inflight_at_send,
|
||||
.lost = (int)link->bbr_loss_since_ack,
|
||||
.is_app_limited = acked_pkt->is_app_limited,
|
||||
};
|
||||
link->delivered_bytes += rs.delivered;
|
||||
link->last_ack_time_tb = now_tb;
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] BBR in: seq=%u del=%u iv=%uus rtt=%uus pri=%u tx_infl=%u cur=%u/%u lost=%d app=%d",
|
||||
link->local_link_id, seq, rs.delivered, rs.interval_us, rs.rtt_us,
|
||||
rs.prior_delivered, rs.tx_in_flight, link->inflight_bytes, link->inflight_lim_bytes,
|
||||
rs.lost, rs.is_app_limited);
|
||||
|
||||
uint32_t old_cwnd = link->inflight_lim_bytes;
|
||||
uint32_t old_pacing = link->bbr_pacing_rate;
|
||||
uint32_t new_cwnd = old_cwnd;
|
||||
uint32_t new_pacing = old_pacing;
|
||||
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);
|
||||
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++;
|
||||
}
|
||||
|
||||
|
||||
@@ -1247,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;
|
||||
@@ -1259,7 +1330,7 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
uint16_t len = pkt->data_len;
|
||||
uint16_t ts = pkt->timestamp; // Received timestamp
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX pkt", etcp->log_name);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX pkt dlen=%d", etcp->log_name, len);
|
||||
|
||||
while (len >= 1) {
|
||||
uint8_t type = data[0];
|
||||
@@ -1298,45 +1369,6 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
uint16_t new_rtt=cur_ts-ret_ts;
|
||||
pkt->link->rtt_last=new_rtt;
|
||||
|
||||
uint8_t old_idx = pkt->link->rtt_history_index;
|
||||
uint16_t old_val = pkt->link->rtt_history[old_idx];
|
||||
|
||||
pkt->link->rtt_history[old_idx] = new_rtt;
|
||||
pkt->link->rtt_history_index = (old_idx + 1) % 10;
|
||||
if (pkt->link->rtt_history_count < 10) pkt->link->rtt_history_count++;
|
||||
|
||||
if (new_rtt >= pkt->link->rtt_max_val) {
|
||||
pkt->link->rtt_max_val = new_rtt;
|
||||
pkt->link->rtt_max_idx = old_idx;
|
||||
} else if (old_val == pkt->link->rtt_max_val) {
|
||||
pkt->link->rtt_max_idx = 255;
|
||||
}
|
||||
|
||||
uint8_t rtt_cnt = pkt->link->rtt_history_count;
|
||||
if (rtt_cnt == 1) {
|
||||
pkt->link->rtt_avg10 = new_rtt;
|
||||
} else {
|
||||
uint32_t sum = 0;
|
||||
if (pkt->link->rtt_max_idx == 255) {
|
||||
uint16_t max_val = 0;
|
||||
uint8_t max_pos = 0;
|
||||
for (uint8_t i = 0; i < rtt_cnt; i++) {
|
||||
uint16_t v = pkt->link->rtt_history[i];
|
||||
sum += v;
|
||||
if (v > max_val) {
|
||||
max_val = v;
|
||||
max_pos = i;
|
||||
}
|
||||
}
|
||||
pkt->link->rtt_max_val = max_val;
|
||||
pkt->link->rtt_max_idx = max_pos;
|
||||
} else {
|
||||
for (uint8_t i = 0; i < rtt_cnt; i++) {
|
||||
sum += pkt->link->rtt_history[i];
|
||||
}
|
||||
}
|
||||
pkt->link->rtt_avg10 = (sum - pkt->link->rtt_max_val) / (rtt_cnt - 1);
|
||||
}
|
||||
int recv_dt_tx1=data[3] | (data[4]<<8);// localtime удаленной стороны момента принятия пакета - timestamp этого пакета (на стороне отправителя, т.е. у нас)
|
||||
|
||||
int recv_dt_rx=cur_ts - pkt->link->rtt_last/2 - 1000 - ts;
|
||||
@@ -1347,29 +1379,30 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
pkt->link->recv_dt_avg_tx +=((int32_t)(recv_dt_tx*65536 - pkt->link->recv_dt_avg_tx))/32;
|
||||
pkt->link->rt_last = cur_ts - ts - pkt->link->recv_dt_avg_rx/65536;
|
||||
pkt->link->tt_last = recv_dt_tx1 - pkt->link->recv_dt_avg_tx/65536;
|
||||
//tts_correction += ((NOW - RTT/2 - TTS) - tts_correction)/16 (инициализируем сразу по 1 пакету)
|
||||
//tts_correction += ((NOW - RTT/2 - TTS) - tts_correction)/16 (инициализируем сразу по 1 пакету)
|
||||
data+=5; len-=5;
|
||||
|
||||
int d_rtt=pkt->link->rtt_avg10 - pkt->link->rtt_last;
|
||||
int prev_rtt = pkt->link->rtt_last;
|
||||
int d_rtt = new_rtt - prev_rtt;
|
||||
if (d_rtt<0) d_rtt=-d_rtt;
|
||||
pkt->link->jitter +=((int32_t)(d_rtt*65536 - pkt->link->jitter))/32;
|
||||
|
||||
struct ETCP_LINK* c=etcp->links;
|
||||
int rtt_sum=0;
|
||||
int rtt_a10_max=0;
|
||||
int rtt_max=0;
|
||||
int tt_sum=0;
|
||||
int j_sum=0;
|
||||
int cnt=0;
|
||||
while (c) {
|
||||
rtt_sum += c->rtt_last;
|
||||
if (rtt_a10_max < c->rtt_avg10) rtt_a10_max = c->rtt_avg10;
|
||||
if (rtt_max < c->rtt_last) rtt_max = c->rtt_last;
|
||||
tt_sum += c->tt_last;
|
||||
j_sum += c->jitter/32768;
|
||||
cnt++;
|
||||
c=c->next;
|
||||
}
|
||||
etcp->rtt_last=rtt_sum/cnt;
|
||||
etcp->rtt_avg_10=rtt_a10_max;
|
||||
etcp->rtt_avg_10=rtt_max;
|
||||
etcp->tt_last=tt_sum/cnt;
|
||||
etcp->jitter=j_sum/cnt;
|
||||
break;
|
||||
@@ -1395,21 +1428,23 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
struct ACK_PACKET* p = (struct ACK_PACKET*)queue_entry_new_from_pool(etcp->instance->ack_pool);
|
||||
if (!p) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to allocate ACK_PACKET", etcp->log_name);
|
||||
len = 0;
|
||||
break;
|
||||
}
|
||||
p->seq=seq;
|
||||
p->pkt_timestamp=pkt->timestamp;
|
||||
p->recv_timestamp=get_current_timestamp();
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX add to ack_q seq=%d", etcp->log_name, seq);
|
||||
if (queue_find_data_by_index(etcp->ack_q, &seq) == NULL) {
|
||||
struct ACK_PACKET* p = (struct ACK_PACKET*)queue_entry_new_from_pool(etcp->instance->ack_pool);
|
||||
if (!p) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to allocate ACK_PACKET", etcp->log_name);
|
||||
len = 0;
|
||||
break;
|
||||
}
|
||||
p->seq=seq;
|
||||
p->pkt_timestamp=pkt->timestamp;
|
||||
p->recv_timestamp=get_current_timestamp();
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX add to ack_q seq=%d", etcp->log_name, seq);
|
||||
|
||||
queue_data_put_with_index(etcp->ack_q, (struct ll_entry*)p);
|
||||
if (etcp->ack_resp_timer == NULL) {
|
||||
etcp->ack_resp_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_TB, etcp, ack_response_timer_cb, "etcp_ack_resp");
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] set ack_timer for delayed ACK send", etcp->log_name);
|
||||
queue_data_put_with_index(etcp->ack_q, (struct ll_entry*)p);
|
||||
if (etcp->ack_resp_timer == NULL) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
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;
|
||||
@@ -1450,8 +1485,8 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
}
|
||||
// Copy the actual payload data
|
||||
memcpy(payload_data, data + 5, pkt_len);
|
||||
queue_data_put_with_index(etcp->recv_q, (struct ll_entry*)rx_pkt);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] RX seq=%u need=%u asm_len=%d", etcp->log_name, seq, etcp->last_delivered_id+1, etcp->recv_q->count);
|
||||
queue_data_put_with_index(etcp->recv_q, (struct ll_entry*)rx_pkt);
|
||||
if ((int32_t)(seq - etcp->last_delivered_id) == 1) etcp_output_try_assembly(etcp);// пробуем собрать выходную очередь из фрагментов
|
||||
} else {
|
||||
etcp->rx_dup_count++;
|
||||
@@ -1523,18 +1558,13 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
|
||||
uint64_t bw_kbps = (uint64_t)link->burst_pkt_size * 8000ULL / gap_min;
|
||||
if (bw_kbps > 10000000) bw_kbps = 10000000;
|
||||
link->bandwidth = (uint32_t)bw_kbps;
|
||||
float rtt_sec = (float)link->rtt_min / 10000.f;
|
||||
float rtt_sec = (float)link->bbr->min_rtt_us / 1000000.f;
|
||||
if (rtt_sec < 0.005f) rtt_sec = 0.005f;
|
||||
link->burst_target_bdp = (uint32_t)((float)bw_kbps * 1000.f / 8.f * rtt_sec);
|
||||
link->slow_start_threshold = link->burst_target_bdp * 9 / 10;
|
||||
if (link->inflight_phase == INFLIGHT_PHASE_SLOW_START) {
|
||||
if (link->inflight_lim_bytes > link->slow_start_threshold) {
|
||||
link->inflight_lim_bytes = link->slow_start_threshold;
|
||||
etcp_link_update_inflight_lim(link, link->inflight_lim_bytes);
|
||||
}
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] burst resp: BW=%u Kbps, BDP=%u, slow_start_thr=%u gap_min=%u us pkt_cnt=%u",
|
||||
link->etcp->log_name, (uint32_t)bw_kbps, link->burst_target_bdp, link->slow_start_threshold, gap_min, pkt_cnt);
|
||||
// BBR: инициализируем верхнюю границу из burst
|
||||
if (link->bbr->inflight_hi == ~0U) link->bbr->inflight_hi = link->burst_target_bdp;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] burst resp: BW=%u Kbps, BDP=%u gap_min=%u us pkt_cnt=%u",
|
||||
link->etcp->log_name, (uint32_t)bw_kbps, link->burst_target_bdp, gap_min, pkt_cnt);
|
||||
}
|
||||
}
|
||||
data += MEAS_RESP_SECTION_SIZE; len -= MEAS_RESP_SECTION_SIZE;
|
||||
@@ -1557,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
|
||||
}
|
||||
|
||||
|
||||
+12
-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;
|
||||
|
||||
@@ -72,6 +73,9 @@ struct INFLIGHT_PACKET {// выделяется из etcp->inflight_pool
|
||||
uint8_t retrans_req_count; // Number of retrans requests
|
||||
uint8_t state; // WAIT_ACK or WAIT_SEND
|
||||
uint8_t send_hist[8]; // через какие каналы передавался пакет (NEW). send_count - head ptr
|
||||
uint64_t delivered_at_send; // link->delivered_bytes на момент отправки (для BBR prior_delivered)
|
||||
uint32_t inflight_at_send; // link->inflight_bytes на момент отправки (для BBR tx_in_flight)
|
||||
uint8_t is_app_limited; // данные ограничены приложением на момент отправки
|
||||
};
|
||||
|
||||
// Список пакетов для сборки. собирается в ll_queue (используем быстрый поиск с хешем)
|
||||
@@ -101,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;
|
||||
@@ -128,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
|
||||
|
||||
@@ -151,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
|
||||
@@ -170,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
|
||||
@@ -184,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
|
||||
@@ -227,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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_send: queued to [%s]", conn->log_name);
|
||||
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
-7
@@ -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,14 +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 через удаленный узел (remote_proxy)
|
||||
#define ETCP_ID_UDP_PROXY 0x05 // UDP datagram прокси (client ↔ exit)
|
||||
#define ETCP_ID_ICMP_PROXY 0x06 // ICMP echo прокси (ping через exit)
|
||||
#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;
|
||||
@@ -46,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 соединения
|
||||
*
|
||||
@@ -113,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
|
||||
*
|
||||
|
||||
+938
@@ -0,0 +1,938 @@
|
||||
#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(); }
|
||||
|
||||
static void bbr_start_bw_probe_down(struct bbr* bbr);
|
||||
static void bbr_start_bw_probe_cruise(struct bbr* bbr);
|
||||
static void bbr_exit_probe_rtt(struct bbr* bbr);
|
||||
|
||||
static const int bbr_pacing_gain[] = {
|
||||
BBR_UNIT * 5 / 4, /* PROBE_UP: 1.25 */
|
||||
BBR_UNIT * 3 / 4, /* PROBE_DOWN: 0.75 */
|
||||
BBR_UNIT, /* CRUISE: 1.00 */
|
||||
BBR_UNIT, /* REFILL: 1.00 */
|
||||
};
|
||||
|
||||
static uint32_t bbr_max_bw(const struct bbr* bbr)
|
||||
{
|
||||
return (uint32_t)(bbr->bw_hi[0] > bbr->bw_hi[1] ? bbr->bw_hi[0] : bbr->bw_hi[1]);
|
||||
}
|
||||
|
||||
static uint32_t bbr_bw(const struct bbr* bbr)
|
||||
{
|
||||
uint32_t max_bw = bbr_max_bw(bbr);
|
||||
return (uint32_t)(max_bw < bbr->bw_lo ? max_bw : bbr->bw_lo);
|
||||
}
|
||||
|
||||
static int bbr_full_bw_reached(const struct bbr* bbr)
|
||||
{
|
||||
return bbr->full_bw_reached;
|
||||
}
|
||||
|
||||
static uint16_t bbr_extra_acked(const struct bbr* bbr)
|
||||
{
|
||||
return (uint16_t)(bbr->extra_acked[0] > bbr->extra_acked[1] ?
|
||||
bbr->extra_acked[0] : bbr->extra_acked[1]);
|
||||
}
|
||||
|
||||
static uint64_t bbr_rate_bytes_per_sec(uint64_t rate, int gain, int margin, uint32_t mss)
|
||||
{
|
||||
rate *= mss;
|
||||
rate *= (uint64_t)gain;
|
||||
rate >>= BBR_SCALE;
|
||||
rate *= (uint64_t)(1000000 / 100) * (uint64_t)(100 - margin);
|
||||
rate >>= BW_SCALE;
|
||||
if (rate < 1) rate = 1;
|
||||
return rate;
|
||||
}
|
||||
|
||||
static uint32_t bbr_bw_to_pacing_rate(uint32_t bw, int gain, uint32_t mss)
|
||||
{
|
||||
uint64_t rate = bbr_rate_bytes_per_sec(bw, gain, 1, mss);
|
||||
return (uint32_t)rate;
|
||||
}
|
||||
|
||||
static uint32_t bbr_bdp(const struct bbr* bbr, uint32_t bw, int gain, uint32_t mss)
|
||||
{
|
||||
uint64_t w;
|
||||
if (bbr->min_rtt_us == ~0U)
|
||||
return (uint32_t)bbr->init_cwnd * mss;
|
||||
w = (uint64_t)bw * bbr->min_rtt_us;
|
||||
return (uint32_t)((((w * (uint64_t)gain) >> BBR_SCALE) + BW_UNIT - 1) / BW_UNIT) * mss;
|
||||
}
|
||||
|
||||
static uint32_t bbr_quantization_budget(struct bbr* bbr, uint32_t cwnd, uint32_t mss)
|
||||
{
|
||||
cwnd = (uint32_t)(cwnd > BBR_CWND_MIN_TARGET * mss ? cwnd : BBR_CWND_MIN_TARGET * mss);
|
||||
if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_UP)
|
||||
cwnd += 2;
|
||||
return cwnd;
|
||||
}
|
||||
|
||||
static uint32_t bbr_inflight(const struct bbr* bbr, uint32_t bw, int gain, uint32_t mss)
|
||||
{
|
||||
uint32_t inflight = bbr_bdp(bbr, bw, gain, mss);
|
||||
return bbr_quantization_budget((struct bbr*)bbr, inflight, mss);
|
||||
}
|
||||
|
||||
static uint32_t bbr_ack_aggregation_cwnd(const struct bbr* bbr, uint32_t mss)
|
||||
{
|
||||
uint32_t max_aggr_cwnd, aggr_cwnd = 0;
|
||||
(void)mss;
|
||||
max_aggr_cwnd = (uint32_t)((uint64_t)bbr_bw(bbr) * 100000U / BW_UNIT);
|
||||
aggr_cwnd = (uint32_t)((uint32_t)BBR_EXTRA_ACKED_GAIN * bbr_extra_acked(bbr) >> BBR_SCALE);
|
||||
aggr_cwnd = (uint32_t)(aggr_cwnd < max_aggr_cwnd ? aggr_cwnd : max_aggr_cwnd);
|
||||
return aggr_cwnd;
|
||||
}
|
||||
|
||||
static uint32_t bbr_target_inflight(const struct bbr* bbr, uint32_t cwnd, uint32_t mss)
|
||||
{
|
||||
uint32_t bdp = bbr_inflight(bbr, bbr_bw(bbr), BBR_UNIT, mss);
|
||||
return (uint32_t)(bdp < cwnd ? bdp : cwnd);
|
||||
}
|
||||
|
||||
static int bbr_is_probing_bandwidth(const struct bbr* bbr)
|
||||
{
|
||||
return (bbr->mode == BBR_STARTUP) ||
|
||||
(bbr->mode == BBR_PROBE_BW &&
|
||||
(bbr->cycle_idx == BBR_BW_PROBE_REFILL ||
|
||||
bbr->cycle_idx == BBR_BW_PROBE_UP));
|
||||
}
|
||||
|
||||
static int bbr_has_elapsed_in_phase(const struct bbr* bbr, uint32_t interval_us)
|
||||
{
|
||||
uint64_t now_tb = bbr_now(bbr);
|
||||
uint64_t expire_tb = bbr->cycle_mstamp_tb + (uint64_t)(interval_us / 100U);
|
||||
return (int32_t)((uint32_t)expire_tb - (uint32_t)now_tb) <= 0;
|
||||
}
|
||||
|
||||
static uint32_t bbr_update_round_start(struct bbr* bbr, const struct bbr_rate_sample* rs)
|
||||
{
|
||||
uint32_t round_delivered = 0;
|
||||
bbr->round_start = 0;
|
||||
if (rs->interval_us > 0 && !((int32_t)(rs->prior_delivered - bbr->next_rtt_delivered) < 0)) {
|
||||
round_delivered = bbr->delivered - bbr->next_rtt_delivered;
|
||||
bbr->next_rtt_delivered = bbr->delivered;
|
||||
bbr->round_start = 1;
|
||||
}
|
||||
return round_delivered;
|
||||
}
|
||||
|
||||
static uint32_t bbr_calculate_bw_sample(const struct bbr_rate_sample* rs)
|
||||
{
|
||||
if (rs->interval_us > 0)
|
||||
return (uint32_t)(((uint64_t)rs->delivered * BW_UNIT + rs->interval_us - 1) / rs->interval_us);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bbr_update_latest_delivery_signals(struct bbr* bbr,
|
||||
const struct bbr_rate_sample* rs, uint32_t sample_bw)
|
||||
{
|
||||
bbr->loss_round_start = 0;
|
||||
if (rs->interval_us <= 0 || !rs->acked_sacked)
|
||||
return;
|
||||
bbr->bw_latest = (uint32_t)(bbr->bw_latest > sample_bw ? bbr->bw_latest : sample_bw);
|
||||
bbr->inflight_latest = (uint32_t)(bbr->inflight_latest > (uint32_t)rs->delivered ?
|
||||
bbr->inflight_latest : (uint32_t)rs->delivered);
|
||||
if (!((int32_t)(rs->prior_delivered - bbr->loss_round_delivered) < 0)) {
|
||||
bbr->loss_round_delivered = bbr->delivered;
|
||||
bbr->loss_round_start = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void bbr_advance_latest_delivery_signals(struct bbr* bbr,
|
||||
const struct bbr_rate_sample* rs, uint32_t sample_bw)
|
||||
{
|
||||
if (bbr->loss_round_start) {
|
||||
bbr->bw_latest = sample_bw;
|
||||
bbr->inflight_latest = (uint32_t)rs->delivered;
|
||||
}
|
||||
}
|
||||
|
||||
static void bbr_take_max_bw_sample(struct bbr* bbr, uint32_t bw)
|
||||
{
|
||||
bbr->bw_hi[1] = (uint32_t)(bw > bbr->bw_hi[1] ? bw : bbr->bw_hi[1]);
|
||||
}
|
||||
|
||||
static void bbr_init_lower_bounds(struct bbr* bbr, int init_bw, uint32_t cwnd)
|
||||
{
|
||||
if (init_bw && bbr->bw_lo == ~0U)
|
||||
bbr->bw_lo = bbr_max_bw(bbr);
|
||||
if (bbr->inflight_lo == ~0U)
|
||||
bbr->inflight_lo = cwnd;
|
||||
if (bbr->inflight_hi == ~0U)
|
||||
bbr->inflight_hi = (uint32_t)(cwnd > 4U * (uint32_t)bbr->init_cwnd ? cwnd : 4U * (uint32_t)bbr->init_cwnd);
|
||||
}
|
||||
|
||||
static void bbr_loss_lower_bounds(struct bbr* bbr)
|
||||
{
|
||||
uint32_t loss_cut = BBR_UNIT - BBR_BETA;
|
||||
bbr->bw_lo = (uint32_t)(bbr->bw_latest >
|
||||
(uint32_t)((uint64_t)bbr->bw_lo * loss_cut >> BBR_SCALE) ?
|
||||
bbr->bw_latest :
|
||||
(uint32_t)((uint64_t)bbr->bw_lo * loss_cut >> BBR_SCALE));
|
||||
bbr->inflight_lo = (uint32_t)(bbr->inflight_latest >
|
||||
(uint32_t)((uint64_t)bbr->inflight_lo * loss_cut >> BBR_SCALE) ?
|
||||
bbr->inflight_latest :
|
||||
(uint32_t)((uint64_t)bbr->inflight_lo * loss_cut >> BBR_SCALE));
|
||||
}
|
||||
|
||||
static void bbr_adapt_lower_bounds(struct bbr* bbr, uint32_t cwnd)
|
||||
{
|
||||
if (bbr_is_probing_bandwidth(bbr))
|
||||
return;
|
||||
if (bbr->loss_in_round) {
|
||||
bbr_init_lower_bounds(bbr, 1, cwnd);
|
||||
bbr_loss_lower_bounds(bbr);
|
||||
}
|
||||
bbr->bw_lo = (uint32_t)(bbr->bw_lo > 1U ? bbr->bw_lo : 1U);
|
||||
}
|
||||
|
||||
static void bbr_reset_lower_bounds(struct bbr* bbr)
|
||||
{
|
||||
bbr->bw_lo = ~0U;
|
||||
bbr->inflight_lo = ~0U;
|
||||
}
|
||||
|
||||
static void bbr_reset_congestion_signals(struct bbr* bbr)
|
||||
{
|
||||
bbr->loss_in_round = 0;
|
||||
bbr->loss_in_cycle = 0;
|
||||
bbr->bw_latest = 0;
|
||||
bbr->inflight_latest = 0;
|
||||
}
|
||||
|
||||
static int bbr_is_inflight_too_high(const struct bbr* bbr, const struct bbr_rate_sample* rs)
|
||||
{
|
||||
uint32_t loss_thresh;
|
||||
(void)bbr;
|
||||
if (rs->lost > 0 && rs->tx_in_flight) {
|
||||
loss_thresh = (uint32_t)((uint64_t)rs->tx_in_flight * BBR_LOSS_THRESH >> BBR_SCALE);
|
||||
if ((uint32_t)rs->lost > loss_thresh)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bbr_handle_queue_too_high_in_startup(struct bbr* bbr, uint32_t mss)
|
||||
{
|
||||
uint32_t bdp;
|
||||
(void)mss;
|
||||
bbr->full_bw_reached = 1;
|
||||
bdp = bbr_inflight(bbr, bbr_max_bw(bbr), BBR_UNIT, mss);
|
||||
bbr->inflight_hi = (uint32_t)(bdp > bbr->inflight_latest ? bdp : bbr->inflight_latest);
|
||||
}
|
||||
|
||||
static void bbr_check_loss_too_high_in_startup(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t mss)
|
||||
{
|
||||
if (bbr->full_bw_reached)
|
||||
return;
|
||||
if (rs->lost > 0 && bbr->loss_events_in_round < 0xf)
|
||||
bbr->loss_events_in_round++;
|
||||
if (bbr->loss_round_start &&
|
||||
bbr->loss_events_in_round >= BBR_FULL_LOSS_CNT &&
|
||||
bbr_is_inflight_too_high(bbr, rs)) {
|
||||
bbr_handle_queue_too_high_in_startup(bbr, mss);
|
||||
return;
|
||||
}
|
||||
if (bbr->loss_round_start)
|
||||
bbr->loss_events_in_round = 0;
|
||||
}
|
||||
|
||||
static void bbr_reset_full_bw(struct bbr* bbr)
|
||||
{
|
||||
bbr->full_bw = 0;
|
||||
bbr->full_bw_cnt = 0;
|
||||
bbr->full_bw_now = 0;
|
||||
}
|
||||
|
||||
static void bbr_advance_max_bw_filter(struct bbr* bbr)
|
||||
{
|
||||
if (!bbr->bw_hi[1])
|
||||
return;
|
||||
bbr->bw_hi[0] = bbr->bw_hi[1];
|
||||
bbr->bw_hi[1] = 0;
|
||||
}
|
||||
|
||||
static void bbr_check_full_bw_reached(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t sample_bw)
|
||||
{
|
||||
uint32_t bw_thresh;
|
||||
if (bbr->full_bw_now || rs->is_app_limited)
|
||||
return;
|
||||
bw_thresh = (uint32_t)((uint64_t)bbr->full_bw * BBR_FULL_BW_THRESH >> BBR_SCALE);
|
||||
if (sample_bw >= bw_thresh) {
|
||||
bbr_reset_full_bw(bbr);
|
||||
bbr->full_bw = sample_bw;
|
||||
return;
|
||||
}
|
||||
if (!bbr->round_start)
|
||||
return;
|
||||
bbr->full_bw_cnt = (uint8_t)(bbr->full_bw_cnt + 1);
|
||||
bbr->full_bw_now = (uint8_t)(bbr->full_bw_cnt >= BBR_FULL_BW_CNT ? 1 : 0);
|
||||
bbr->full_bw_reached |= bbr->full_bw_now;
|
||||
}
|
||||
|
||||
static void bbr_raise_inflight_hi_slope(struct bbr* bbr, uint32_t cwnd)
|
||||
{
|
||||
uint32_t growth_this_round = 1U << bbr->bw_probe_up_rounds;
|
||||
uint32_t cnt;
|
||||
bbr->bw_probe_up_rounds = (uint8_t)(bbr->bw_probe_up_rounds + 1 < 30 ?
|
||||
bbr->bw_probe_up_rounds + 1 : 30);
|
||||
cnt = cwnd / growth_this_round;
|
||||
cnt = (uint32_t)(cnt > 1U ? cnt : 1U);
|
||||
bbr->bw_probe_up_cnt = cnt;
|
||||
}
|
||||
|
||||
static void bbr_set_cycle_idx(struct bbr* bbr, int cycle_idx)
|
||||
{
|
||||
bbr->cycle_idx = (uint8_t)cycle_idx;
|
||||
bbr->try_fast_path = 0;
|
||||
}
|
||||
|
||||
static void bbr_start_bw_probe_refill(struct bbr* bbr, uint32_t bw_probe_up_rounds)
|
||||
{
|
||||
bbr_reset_lower_bounds(bbr);
|
||||
bbr->bw_probe_up_rounds = (uint8_t)bw_probe_up_rounds;
|
||||
bbr->bw_probe_up_acks = 0;
|
||||
bbr->stopped_risky_probe = 0;
|
||||
bbr->ack_phase = BBR_ACKS_REFILLING;
|
||||
bbr->next_rtt_delivered = bbr->delivered;
|
||||
bbr_set_cycle_idx(bbr, BBR_BW_PROBE_REFILL);
|
||||
}
|
||||
|
||||
static void bbr_start_bw_probe_up(struct bbr* bbr, uint32_t sample_bw, uint32_t cwnd)
|
||||
{
|
||||
bbr->ack_phase = BBR_ACKS_PROBE_STARTING;
|
||||
bbr->next_rtt_delivered = bbr->delivered;
|
||||
bbr->cycle_mstamp_tb = bbr_now(bbr);
|
||||
bbr_reset_full_bw(bbr);
|
||||
bbr->full_bw = sample_bw;
|
||||
bbr_set_cycle_idx(bbr, BBR_BW_PROBE_UP);
|
||||
bbr_raise_inflight_hi_slope(bbr, cwnd);
|
||||
}
|
||||
|
||||
static void bbr_save_cwnd(struct bbr* bbr, uint32_t cwnd)
|
||||
{
|
||||
if (bbr->mode != BBR_PROBE_RTT)
|
||||
bbr->prior_cwnd = cwnd;
|
||||
else
|
||||
bbr->prior_cwnd = (uint32_t)(bbr->prior_cwnd > cwnd ? bbr->prior_cwnd : cwnd);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t bbr_probe_rtt_cwnd(const struct bbr* bbr, uint32_t mss)
|
||||
{
|
||||
(void)bbr;
|
||||
return BBR_CWND_MIN_TARGET * mss;
|
||||
}
|
||||
|
||||
static uint32_t bbr_inflight_with_headroom(const struct bbr* bbr)
|
||||
{
|
||||
uint32_t headroom;
|
||||
if (bbr->inflight_hi == ~0U)
|
||||
return ~0U;
|
||||
headroom = (uint32_t)((uint64_t)bbr->inflight_hi * BBR_INFLIGHT_HEADROOM >> BBR_SCALE);
|
||||
headroom = (uint32_t)(headroom > 1U ? headroom : 1U);
|
||||
return bbr->inflight_hi - headroom;
|
||||
}
|
||||
|
||||
static void bbr_pick_probe_wait(struct bbr* bbr)
|
||||
{
|
||||
bbr->rounds_since_probe = (uint8_t)((uint32_t)rand() % BBR_BW_PROBE_RAND_ROUNDS);
|
||||
bbr->probe_wait_us = BBR_BW_PROBE_BASE_US + (uint32_t)rand() % BBR_BW_PROBE_RAND_US;
|
||||
}
|
||||
|
||||
static void bbr_start_bw_probe_down(struct bbr* bbr)
|
||||
{
|
||||
bbr_reset_congestion_signals(bbr);
|
||||
bbr->bw_probe_up_cnt = ~0U;
|
||||
bbr_pick_probe_wait(bbr);
|
||||
bbr->cycle_mstamp_tb = bbr_now(bbr);
|
||||
bbr->ack_phase = BBR_ACKS_PROBE_STOPPING;
|
||||
bbr->next_rtt_delivered = bbr->delivered;
|
||||
bbr_set_cycle_idx(bbr, BBR_BW_PROBE_DOWN);
|
||||
}
|
||||
|
||||
static void bbr_start_bw_probe_cruise(struct bbr* bbr)
|
||||
{
|
||||
if (bbr->inflight_lo != ~0U)
|
||||
bbr->inflight_lo = (uint32_t)(bbr->inflight_lo < bbr->inflight_hi ?
|
||||
bbr->inflight_lo : bbr->inflight_hi);
|
||||
bbr_set_cycle_idx(bbr, BBR_BW_PROBE_CRUISE);
|
||||
}
|
||||
|
||||
static void bbr_handle_inflight_too_high(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t cwnd, uint32_t mss)
|
||||
{
|
||||
const uint32_t beta = BBR_BETA;
|
||||
bbr->prev_probe_too_high = 1;
|
||||
bbr->bw_probe_samples = 0;
|
||||
if (!rs->is_app_limited) {
|
||||
uint32_t ti = bbr_target_inflight(bbr, cwnd, mss);
|
||||
bbr->inflight_hi = (uint32_t)(rs->tx_in_flight >
|
||||
(uint32_t)((uint64_t)ti * (BBR_UNIT - beta) >> BBR_SCALE) ?
|
||||
rs->tx_in_flight :
|
||||
(uint32_t)((uint64_t)ti * (BBR_UNIT - beta) >> BBR_SCALE));
|
||||
}
|
||||
if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_UP)
|
||||
bbr_start_bw_probe_down(bbr);
|
||||
}
|
||||
|
||||
static void bbr_probe_inflight_hi_upward(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
int is_cwnd_limited, uint32_t cwnd)
|
||||
{
|
||||
uint32_t delta;
|
||||
if (!is_cwnd_limited || cwnd < bbr->inflight_hi)
|
||||
return;
|
||||
bbr->bw_probe_up_acks += rs->acked_sacked;
|
||||
if (bbr->bw_probe_up_acks >= bbr->bw_probe_up_cnt &&
|
||||
bbr->bw_probe_up_cnt > 0) {
|
||||
delta = bbr->bw_probe_up_acks / bbr->bw_probe_up_cnt;
|
||||
bbr->bw_probe_up_acks -= delta * bbr->bw_probe_up_cnt;
|
||||
bbr->inflight_hi += delta;
|
||||
bbr->try_fast_path = 0;
|
||||
}
|
||||
if (bbr->round_start)
|
||||
bbr_raise_inflight_hi_slope(bbr, cwnd);
|
||||
}
|
||||
|
||||
static void bbr_adapt_upper_bounds(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t sample_bw, int is_cwnd_limited,
|
||||
uint32_t cwnd, uint32_t mss, int* decided)
|
||||
{
|
||||
(void)sample_bw;
|
||||
*decided = 0;
|
||||
if (bbr->ack_phase == BBR_ACKS_PROBE_STARTING && bbr->round_start)
|
||||
bbr->ack_phase = BBR_ACKS_PROBE_FEEDBACK;
|
||||
if (bbr->ack_phase == BBR_ACKS_PROBE_STOPPING && bbr->round_start) {
|
||||
bbr->bw_probe_samples = 0;
|
||||
bbr->ack_phase = BBR_ACKS_INIT;
|
||||
if (bbr->mode == BBR_PROBE_BW && !rs->is_app_limited)
|
||||
bbr_advance_max_bw_filter(bbr);
|
||||
if (bbr->mode == BBR_PROBE_BW && bbr->stopped_risky_probe && !bbr->prev_probe_too_high) {
|
||||
bbr_start_bw_probe_refill(bbr, 0);
|
||||
*decided = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (bbr_is_inflight_too_high(bbr, rs)) {
|
||||
if (bbr->bw_probe_samples)
|
||||
bbr_handle_inflight_too_high(bbr, rs, cwnd, mss);
|
||||
} else {
|
||||
if (bbr->inflight_hi == ~0U)
|
||||
return;
|
||||
if (rs->tx_in_flight > bbr->inflight_hi)
|
||||
bbr->inflight_hi = rs->tx_in_flight;
|
||||
if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_UP)
|
||||
bbr_probe_inflight_hi_upward(bbr, rs, is_cwnd_limited, cwnd);
|
||||
}
|
||||
}
|
||||
|
||||
static int bbr_is_reno_coexistence_probe_time(struct bbr* bbr, uint32_t cwnd, uint32_t mss)
|
||||
{
|
||||
uint32_t rounds = bbr_target_inflight(bbr, cwnd, mss);
|
||||
if (rounds > BBR_BW_PROBE_MAX_ROUNDS)
|
||||
rounds = BBR_BW_PROBE_MAX_ROUNDS;
|
||||
return bbr->rounds_since_probe >= rounds;
|
||||
}
|
||||
|
||||
static int bbr_check_time_to_probe_bw(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t cwnd, uint32_t mss)
|
||||
{
|
||||
(void)rs;
|
||||
if (bbr_has_elapsed_in_phase(bbr, bbr->probe_wait_us) ||
|
||||
bbr_is_reno_coexistence_probe_time(bbr, cwnd, mss)) {
|
||||
bbr_start_bw_probe_refill(bbr, 0);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bbr_check_time_to_cruise(const struct bbr* bbr, uint32_t inflight,
|
||||
uint32_t bw, uint32_t mss)
|
||||
{
|
||||
if (inflight > bbr_inflight_with_headroom(bbr))
|
||||
return 0;
|
||||
return inflight <= bbr_inflight(bbr, bw, BBR_UNIT, mss);
|
||||
}
|
||||
|
||||
static void bbr_update_cycle_phase(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t sample_bw, uint32_t mss,
|
||||
uint32_t inflight_packets, int is_cwnd_limited,
|
||||
uint32_t cwnd)
|
||||
{
|
||||
int decided = 0;
|
||||
int is_bw_probe_done;
|
||||
uint32_t inflight, bw;
|
||||
|
||||
if (!bbr->full_bw_reached)
|
||||
return;
|
||||
bbr_adapt_upper_bounds(bbr, rs, sample_bw, is_cwnd_limited, cwnd, mss, &decided);
|
||||
if (decided)
|
||||
return;
|
||||
if (bbr->mode != BBR_PROBE_BW)
|
||||
return;
|
||||
|
||||
inflight = inflight_packets;
|
||||
bw = bbr_max_bw(bbr);
|
||||
|
||||
switch (bbr->cycle_idx) {
|
||||
case BBR_BW_PROBE_CRUISE:
|
||||
if (bbr_check_time_to_probe_bw(bbr, rs, cwnd, mss))
|
||||
return;
|
||||
break;
|
||||
case BBR_BW_PROBE_REFILL:
|
||||
if (bbr->round_start) {
|
||||
bbr->bw_probe_samples = 1;
|
||||
bbr_start_bw_probe_up(bbr, sample_bw, cwnd);
|
||||
}
|
||||
break;
|
||||
case BBR_BW_PROBE_UP:
|
||||
is_bw_probe_done = 0;
|
||||
if (bbr->prev_probe_too_high && inflight >= bbr->inflight_hi) {
|
||||
bbr->stopped_risky_probe = 1;
|
||||
is_bw_probe_done = 1;
|
||||
} else {
|
||||
if (is_cwnd_limited && cwnd >= bbr->inflight_hi) {
|
||||
bbr_reset_full_bw(bbr);
|
||||
bbr->full_bw = sample_bw;
|
||||
} else if (bbr->full_bw_now) {
|
||||
is_bw_probe_done = 1;
|
||||
}
|
||||
}
|
||||
if (is_bw_probe_done) {
|
||||
bbr->prev_probe_too_high = 0;
|
||||
bbr_start_bw_probe_down(bbr);
|
||||
}
|
||||
break;
|
||||
case BBR_BW_PROBE_DOWN:
|
||||
if (bbr_check_time_to_probe_bw(bbr, rs, cwnd, mss))
|
||||
return;
|
||||
if (bbr_check_time_to_cruise(bbr, inflight, bw, mss))
|
||||
bbr_start_bw_probe_cruise(bbr);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void bbr_check_drain(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t sample_bw, uint32_t mss, uint32_t inflight_packets)
|
||||
{
|
||||
(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);
|
||||
}
|
||||
}
|
||||
|
||||
static void bbr_check_probe_rtt_done(struct bbr* bbr, uint32_t* cwnd)
|
||||
{
|
||||
if (!bbr->probe_rtt_done_stamp_tb)
|
||||
return;
|
||||
if (!((int32_t)((uint32_t)bbr_now(bbr) - (uint32_t)bbr->probe_rtt_done_stamp_tb) >= 0))
|
||||
return;
|
||||
bbr->probe_rtt_min_stamp_tb = bbr_now(bbr);
|
||||
*cwnd = (uint32_t)(*cwnd > bbr->prior_cwnd ? *cwnd : bbr->prior_cwnd);
|
||||
bbr_exit_probe_rtt(bbr);
|
||||
}
|
||||
|
||||
static void bbr_update_min_rtt(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t* cwnd, uint32_t mss, uint32_t inflight_packets)
|
||||
{
|
||||
uint32_t expire;
|
||||
int probe_rtt_expired, min_rtt_expired;
|
||||
(void)mss;
|
||||
|
||||
expire = (uint32_t)(bbr->probe_rtt_min_stamp_tb + BBR_PROBE_RTT_WIN_MS * 10U);
|
||||
probe_rtt_expired = ((int32_t)(expire - (uint32_t)bbr_now(bbr)) < 0);
|
||||
if (rs->rtt_us > 0 &&
|
||||
(rs->rtt_us < bbr->probe_rtt_min_us || probe_rtt_expired)) {
|
||||
bbr->probe_rtt_min_us = rs->rtt_us;
|
||||
bbr->probe_rtt_min_stamp_tb = bbr_now(bbr);
|
||||
bbr->has_seen_rtt = 1;
|
||||
}
|
||||
if (bbr->probe_rtt_min_us == 0)
|
||||
bbr->probe_rtt_min_us = ~0U;
|
||||
|
||||
expire = (uint32_t)(bbr->min_rtt_stamp_tb + BBR_MIN_RTT_WIN_SEC * 10000U);
|
||||
min_rtt_expired = ((int32_t)(expire - (uint32_t)bbr_now(bbr)) < 0);
|
||||
if (bbr->probe_rtt_min_us <= bbr->min_rtt_us || min_rtt_expired) {
|
||||
bbr->min_rtt_us = bbr->probe_rtt_min_us;
|
||||
bbr->min_rtt_stamp_tb = bbr_now(bbr);
|
||||
}
|
||||
|
||||
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;
|
||||
bbr->ack_phase = BBR_ACKS_PROBE_STOPPING;
|
||||
bbr->next_rtt_delivered = bbr->delivered;
|
||||
}
|
||||
|
||||
if (bbr->mode == BBR_PROBE_RTT) {
|
||||
if (!bbr->probe_rtt_done_stamp_tb &&
|
||||
inflight_packets <= bbr_probe_rtt_cwnd(bbr, mss)) {
|
||||
bbr->probe_rtt_done_stamp_tb = (uint32_t)bbr_now(bbr) + BBR_PROBE_RTT_MS * 10U;
|
||||
bbr->probe_rtt_round_done = 0;
|
||||
bbr->next_rtt_delivered = bbr->delivered;
|
||||
} else if (bbr->probe_rtt_done_stamp_tb) {
|
||||
if (bbr->round_start)
|
||||
bbr->probe_rtt_round_done = 1;
|
||||
if (bbr->probe_rtt_round_done)
|
||||
bbr_check_probe_rtt_done(bbr, cwnd);
|
||||
}
|
||||
}
|
||||
if (rs->delivered > 0)
|
||||
bbr->idle_restart = 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static void bbr_update_gains(struct bbr* bbr)
|
||||
{
|
||||
switch (bbr->mode) {
|
||||
case BBR_STARTUP:
|
||||
bbr->pacing_gain = BBR_STARTUP_PACING_GAIN;
|
||||
bbr->cwnd_gain = BBR_STARTUP_CWND_GAIN;
|
||||
break;
|
||||
case BBR_DRAIN:
|
||||
bbr->pacing_gain = BBR_DRAIN_GAIN;
|
||||
bbr->cwnd_gain = BBR_STARTUP_CWND_GAIN;
|
||||
break;
|
||||
case BBR_PROBE_BW:
|
||||
bbr->pacing_gain = bbr_pacing_gain[bbr->cycle_idx];
|
||||
bbr->cwnd_gain = BBR_CWND_GAIN;
|
||||
break;
|
||||
case BBR_PROBE_RTT:
|
||||
bbr->pacing_gain = BBR_UNIT;
|
||||
bbr->cwnd_gain = BBR_UNIT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void bbr_update_congestion_signals(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t sample_bw, uint32_t cwnd)
|
||||
{
|
||||
uint32_t bw;
|
||||
if (rs->interval_us <= 0 || !rs->acked_sacked)
|
||||
return;
|
||||
bw = sample_bw;
|
||||
if (!rs->is_app_limited || bw >= bbr_max_bw(bbr))
|
||||
bbr_take_max_bw_sample(bbr, bw);
|
||||
bbr->loss_in_round |= (rs->lost > 0);
|
||||
if (!bbr->loss_round_start)
|
||||
return;
|
||||
bbr_adapt_lower_bounds(bbr, cwnd);
|
||||
bbr->loss_in_round = 0;
|
||||
}
|
||||
|
||||
static void bbr_update_ack_aggregation(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t cwnd, uint32_t mss)
|
||||
{
|
||||
uint32_t epoch_us, expected_acked, extra_acked;
|
||||
uint32_t extra_acked_win_rtts_thresh = BBR_EXTRA_ACKED_WIN_RTTS;
|
||||
uint64_t now_us;
|
||||
(void)mss;
|
||||
|
||||
if (rs->acked_sacked <= 0 || rs->interval_us <= 0)
|
||||
return;
|
||||
|
||||
if (bbr->round_start) {
|
||||
bbr->extra_acked_win_rtts = (uint8_t)(bbr->extra_acked_win_rtts + 1 < 0x1F ?
|
||||
bbr->extra_acked_win_rtts + 1 : 0x1F);
|
||||
if (!bbr->full_bw_reached)
|
||||
extra_acked_win_rtts_thresh = 1;
|
||||
if (bbr->extra_acked_win_rtts >= extra_acked_win_rtts_thresh) {
|
||||
bbr->extra_acked_win_rtts = 0;
|
||||
bbr->extra_acked_win_idx = bbr->extra_acked_win_idx ? 0 : 1;
|
||||
bbr->extra_acked[bbr->extra_acked_win_idx] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
now_us = bbr_now(bbr) * 100ULL;
|
||||
if (now_us > bbr->ack_epoch_mstamp_tb * 100ULL)
|
||||
epoch_us = (uint32_t)(now_us - bbr->ack_epoch_mstamp_tb * 100ULL);
|
||||
else
|
||||
epoch_us = 0;
|
||||
expected_acked = (uint32_t)((uint64_t)bbr_bw(bbr) * epoch_us / BW_UNIT);
|
||||
|
||||
if (bbr->ack_epoch_acked <= expected_acked ||
|
||||
(bbr->ack_epoch_acked + rs->acked_sacked >= BBR_ACK_EPOCH_ACKED_RESET_THRESH)) {
|
||||
bbr->ack_epoch_acked = 0;
|
||||
bbr->ack_epoch_mstamp_tb = bbr_now(bbr);
|
||||
expected_acked = 0;
|
||||
}
|
||||
|
||||
bbr->ack_epoch_acked = (uint32_t)(bbr->ack_epoch_acked + rs->acked_sacked < 0xFFFFF ?
|
||||
bbr->ack_epoch_acked + rs->acked_sacked : 0xFFFFF);
|
||||
extra_acked = bbr->ack_epoch_acked - expected_acked;
|
||||
extra_acked = (uint32_t)(extra_acked < cwnd ? extra_acked : cwnd);
|
||||
if (extra_acked > bbr->extra_acked[bbr->extra_acked_win_idx])
|
||||
bbr->extra_acked[bbr->extra_acked_win_idx] = (uint16_t)extra_acked;
|
||||
}
|
||||
|
||||
static void bbr_init_pacing_rate_from_rtt(struct bbr* bbr, uint32_t cwnd,
|
||||
uint32_t mss, uint32_t* pacing_rate_out)
|
||||
{
|
||||
uint64_t bw;
|
||||
uint32_t rtt_us;
|
||||
|
||||
if (bbr->has_seen_rtt && bbr->min_rtt_us != ~0U) {
|
||||
rtt_us = bbr->min_rtt_us;
|
||||
if (rtt_us < 1) rtt_us = 1;
|
||||
} else {
|
||||
rtt_us = 1000;
|
||||
}
|
||||
bw = (uint64_t)cwnd * BW_UNIT / rtt_us;
|
||||
*pacing_rate_out = (uint32_t)bbr_rate_bytes_per_sec(bw, BBR_STARTUP_PACING_GAIN, 1, mss);
|
||||
}
|
||||
|
||||
static void bbr_set_pacing_rate(struct bbr* bbr, uint32_t bw, int gain,
|
||||
uint32_t mss, uint32_t* pacing_rate_out)
|
||||
{
|
||||
uint32_t rate = bbr_bw_to_pacing_rate(bw, gain, mss);
|
||||
|
||||
if (!bbr->has_seen_rtt && bbr->min_rtt_us != ~0U)
|
||||
bbr_init_pacing_rate_from_rtt(bbr, *pacing_rate_out > 0 ? *pacing_rate_out : 4, mss, pacing_rate_out);
|
||||
if (bbr->full_bw_reached || rate > *pacing_rate_out)
|
||||
*pacing_rate_out = rate;
|
||||
}
|
||||
|
||||
static void bbr_set_cwnd(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t acked, uint32_t bw, int gain,
|
||||
uint32_t mss, uint32_t* cwnd)
|
||||
{
|
||||
uint32_t target_cwnd = 0;
|
||||
uint32_t local_cwnd = *cwnd;
|
||||
|
||||
(void)rs;
|
||||
if (!acked)
|
||||
goto done;
|
||||
|
||||
target_cwnd = bbr_bdp(bbr, bw, gain, mss);
|
||||
target_cwnd += bbr_ack_aggregation_cwnd(bbr, mss);
|
||||
target_cwnd = bbr_quantization_budget(bbr, target_cwnd, mss);
|
||||
|
||||
bbr->try_fast_path = 0;
|
||||
if (bbr->full_bw_reached) {
|
||||
local_cwnd += acked;
|
||||
if (local_cwnd >= target_cwnd) {
|
||||
local_cwnd = target_cwnd;
|
||||
bbr->try_fast_path = 1;
|
||||
}
|
||||
} else if (local_cwnd < target_cwnd || local_cwnd < 2 * (uint32_t)bbr->init_cwnd * mss) {
|
||||
local_cwnd += acked;
|
||||
} else {
|
||||
bbr->try_fast_path = 1;
|
||||
}
|
||||
|
||||
local_cwnd = (uint32_t)(local_cwnd > BBR_CWND_MIN_TARGET * mss ?
|
||||
local_cwnd : BBR_CWND_MIN_TARGET * mss);
|
||||
done:
|
||||
*cwnd = local_cwnd;
|
||||
if (bbr->mode == BBR_PROBE_RTT) {
|
||||
uint32_t pr_cwnd = bbr_probe_rtt_cwnd(bbr, mss);
|
||||
*cwnd = (uint32_t)(*cwnd < pr_cwnd ? *cwnd : pr_cwnd);
|
||||
}
|
||||
}
|
||||
|
||||
static void bbr_bound_cwnd_for_inflight_model(struct bbr* bbr, uint32_t* cwnd, uint32_t mss)
|
||||
{
|
||||
uint32_t cap = ~0U;
|
||||
|
||||
if (!bbr->initialized)
|
||||
return;
|
||||
|
||||
if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx != BBR_BW_PROBE_CRUISE)
|
||||
cap = bbr->inflight_hi;
|
||||
else if (bbr->mode == BBR_PROBE_RTT ||
|
||||
(bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_CRUISE))
|
||||
cap = bbr_inflight_with_headroom(bbr);
|
||||
|
||||
cap = (uint32_t)(cap > BBR_CWND_MIN_TARGET * mss ? cap : BBR_CWND_MIN_TARGET * mss);
|
||||
cap = (uint32_t)(cap < bbr->inflight_lo ? cap : bbr->inflight_lo);
|
||||
*cwnd = (uint32_t)(cap < *cwnd ? cap : *cwnd);
|
||||
}
|
||||
|
||||
static int bbr_run_fast_path(struct bbr* bbr, int* update_model,
|
||||
const struct bbr_rate_sample* rs, uint32_t sample_bw,
|
||||
uint32_t mss, uint32_t inflight_packets,
|
||||
int is_cwnd_limited, uint32_t* cwnd)
|
||||
{
|
||||
uint32_t prev_min_rtt_us;
|
||||
uint8_t prev_mode;
|
||||
|
||||
if (bbr->try_fast_path && rs->is_app_limited &&
|
||||
sample_bw < bbr_max_bw(bbr) && !bbr->loss_in_round) {
|
||||
prev_mode = bbr->mode;
|
||||
prev_min_rtt_us = bbr->min_rtt_us;
|
||||
bbr_check_drain(bbr, rs, sample_bw, mss, inflight_packets);
|
||||
bbr_update_cycle_phase(bbr, rs, sample_bw, mss, inflight_packets,
|
||||
is_cwnd_limited, *cwnd);
|
||||
bbr_update_min_rtt(bbr, rs, cwnd, mss, inflight_packets);
|
||||
|
||||
if (bbr->mode == prev_mode &&
|
||||
bbr->min_rtt_us == prev_min_rtt_us &&
|
||||
bbr->try_fast_path)
|
||||
return 1;
|
||||
*update_model = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void bbr_init(struct bbr* bbr)
|
||||
{
|
||||
bbr->initialized = 1;
|
||||
bbr->init_cwnd = 0;
|
||||
bbr->prior_cwnd = 0;
|
||||
bbr->delivered = 0;
|
||||
bbr->next_rtt_delivered = 0;
|
||||
|
||||
bbr->probe_rtt_done_stamp_tb = 0;
|
||||
bbr->probe_rtt_round_done = 0;
|
||||
bbr->probe_rtt_min_us = ~0U;
|
||||
bbr->probe_rtt_min_stamp_tb = bbr_now(bbr);
|
||||
bbr->min_rtt_us = ~0U;
|
||||
bbr->min_rtt_stamp_tb = bbr_now(bbr);
|
||||
|
||||
bbr->has_seen_rtt = 0;
|
||||
|
||||
bbr->round_start = 0;
|
||||
bbr->idle_restart = 0;
|
||||
bbr->full_bw_reached = 0;
|
||||
bbr->full_bw = 0;
|
||||
bbr->full_bw_cnt = 0;
|
||||
bbr->full_bw_now = 0;
|
||||
bbr->cycle_mstamp_tb = 0;
|
||||
bbr->cycle_idx = 0;
|
||||
|
||||
bbr_reset_startup_mode(bbr);
|
||||
|
||||
bbr->ack_epoch_mstamp_tb = bbr_now(bbr);
|
||||
bbr->ack_epoch_acked = 0;
|
||||
bbr->extra_acked_win_rtts = 0;
|
||||
bbr->extra_acked_win_idx = 0;
|
||||
bbr->extra_acked[0] = 0;
|
||||
bbr->extra_acked[1] = 0;
|
||||
|
||||
bbr->try_fast_path = 0;
|
||||
bbr->loss_round_delivered = 1;
|
||||
bbr->loss_round_start = 0;
|
||||
bbr->loss_events_in_round = 0;
|
||||
bbr_reset_congestion_signals(bbr);
|
||||
bbr->bw_lo = ~0U;
|
||||
bbr->bw_hi[0] = 0;
|
||||
bbr->bw_hi[1] = 0;
|
||||
bbr->inflight_lo = ~0U;
|
||||
bbr->inflight_hi = ~0U;
|
||||
bbr_reset_full_bw(bbr);
|
||||
bbr->bw_probe_up_cnt = ~0U;
|
||||
bbr->bw_probe_up_acks = 0;
|
||||
bbr->bw_probe_up_rounds = 0;
|
||||
bbr->probe_wait_us = 0;
|
||||
bbr->stopped_risky_probe = 0;
|
||||
bbr->ack_phase = BBR_ACKS_INIT;
|
||||
bbr->rounds_since_probe = 0;
|
||||
bbr->bw_probe_samples = 0;
|
||||
bbr->prev_probe_too_high = 0;
|
||||
bbr->on_cwnd_update = NULL;
|
||||
bbr->cwnd_update_ctx = NULL;
|
||||
}
|
||||
|
||||
void bbr_main(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t* cwnd_out, uint32_t* pacing_rate_out,
|
||||
uint32_t mss, uint32_t inflight_bytes, int is_cwnd_limited)
|
||||
{
|
||||
uint32_t cwnd = *cwnd_out;
|
||||
uint32_t sample_bw, bw;
|
||||
int update_model = 1;
|
||||
|
||||
bbr->delivered += rs->delivered;
|
||||
bbr_update_round_start(bbr, rs);
|
||||
|
||||
if (bbr->round_start)
|
||||
bbr->rounds_since_probe = (uint8_t)(bbr->rounds_since_probe + 1 < 0xFF ?
|
||||
bbr->rounds_since_probe + 1 : 0xFF);
|
||||
|
||||
sample_bw = bbr_calculate_bw_sample(rs);
|
||||
bbr_update_latest_delivery_signals(bbr, rs, sample_bw);
|
||||
|
||||
if (bbr_run_fast_path(bbr, &update_model, rs, sample_bw, mss,
|
||||
inflight_bytes, is_cwnd_limited, &cwnd))
|
||||
goto out;
|
||||
|
||||
if (update_model) {
|
||||
bbr_update_congestion_signals(bbr, rs, sample_bw, cwnd);
|
||||
bbr_update_ack_aggregation(bbr, rs, cwnd, mss);
|
||||
bbr_check_loss_too_high_in_startup(bbr, rs, mss);
|
||||
bbr_check_full_bw_reached(bbr, rs, sample_bw);
|
||||
bbr_check_drain(bbr, rs, sample_bw, mss, inflight_bytes);
|
||||
bbr_update_cycle_phase(bbr, rs, sample_bw, mss, inflight_bytes,
|
||||
is_cwnd_limited, cwnd);
|
||||
bbr_update_min_rtt(bbr, rs, &cwnd, mss, inflight_bytes);
|
||||
}
|
||||
|
||||
bbr_update_gains(bbr);
|
||||
bw = bbr_bw(bbr);
|
||||
bbr_set_pacing_rate(bbr, bw, bbr->pacing_gain, mss, pacing_rate_out);
|
||||
bbr_set_cwnd(bbr, rs, rs->acked_sacked, bw, bbr->cwnd_gain, mss, &cwnd);
|
||||
bbr_bound_cwnd_for_inflight_model(bbr, &cwnd, mss);
|
||||
|
||||
out:
|
||||
bbr_advance_latest_delivery_signals(bbr, rs, sample_bw);
|
||||
bbr->loss_in_cycle |= (rs->lost > 0);
|
||||
if (bbr->on_cwnd_update) bbr->on_cwnd_update(bbr->cwnd_update_ctx, cwnd);
|
||||
*cwnd_out = cwnd;
|
||||
}
|
||||
|
||||
void bbr_note_loss(struct bbr* bbr)
|
||||
{
|
||||
if (!bbr->loss_in_round)
|
||||
bbr->loss_round_delivered = bbr->delivered;
|
||||
bbr->loss_in_round = 1;
|
||||
bbr->loss_in_cycle = 1;
|
||||
}
|
||||
|
||||
void bbr_tx_start(struct bbr* bbr)
|
||||
{
|
||||
bbr->idle_restart = 1;
|
||||
bbr->ack_epoch_mstamp_tb = bbr_now(bbr);
|
||||
bbr->ack_epoch_acked = 0;
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#define BBR_SCALE 8
|
||||
#define BBR_UNIT (1 << BBR_SCALE)
|
||||
#define BW_SCALE 24
|
||||
#define BW_UNIT (1 << BW_SCALE)
|
||||
|
||||
#define BBR_STARTUP_PACING_GAIN (BBR_UNIT * 277 / 100 + 1)
|
||||
#define BBR_STARTUP_CWND_GAIN (BBR_UNIT * 2)
|
||||
#define BBR_DRAIN_GAIN (BBR_UNIT * 1000 / 2885)
|
||||
#define BBR_CWND_GAIN (BBR_UNIT * 2)
|
||||
#define BBR_PROBE_RTT_CWND_GAIN (BBR_UNIT * 1 / 2)
|
||||
#define BBR_CWND_MIN_TARGET 4
|
||||
#define BBR_FULL_BW_THRESH (BBR_UNIT * 5 / 4)
|
||||
#define BBR_FULL_BW_CNT 3
|
||||
#define BBR_LOSS_THRESH (BBR_UNIT * 2 / 100)
|
||||
#define BBR_BETA (BBR_UNIT * 30 / 100)
|
||||
#define BBR_INFLIGHT_HEADROOM (BBR_UNIT * 15 / 100)
|
||||
#define BBR_EXTRA_ACKED_GAIN BBR_UNIT
|
||||
#define BBR_EXTRA_ACKED_WIN_RTTS 5
|
||||
#define BBR_ACK_EPOCH_ACKED_RESET_THRESH (1U << 20)
|
||||
#define BBR_EXTRA_ACKED_MAX_US (100 * 1000)
|
||||
#define BBR_FULL_LOSS_CNT 6
|
||||
#define BBR_BW_PROBE_MAX_ROUNDS 63
|
||||
#define BBR_BW_PROBE_RAND_ROUNDS 2
|
||||
#define BBR_BW_PROBE_BASE_US (2 * 1000000)
|
||||
#define BBR_BW_PROBE_RAND_US (1 * 1000000)
|
||||
#define BBR_PROBE_RTT_WIN_MS 5000
|
||||
#define BBR_MIN_RTT_WIN_SEC 10
|
||||
#define BBR_PROBE_RTT_MS 200
|
||||
|
||||
enum bbr_mode { BBR_STARTUP = 0, BBR_DRAIN = 1, BBR_PROBE_BW = 2, BBR_PROBE_RTT = 3 };
|
||||
enum bbr_bw_probe_cyc { BBR_BW_PROBE_UP = 0, BBR_BW_PROBE_DOWN = 1, BBR_BW_PROBE_CRUISE = 2, BBR_BW_PROBE_REFILL = 3 };
|
||||
enum bbr_ack_phase { BBR_ACKS_INIT = 0, BBR_ACKS_REFILLING, BBR_ACKS_PROBE_STARTING, BBR_ACKS_PROBE_FEEDBACK, BBR_ACKS_PROBE_STOPPING };
|
||||
|
||||
struct bbr_rate_sample {
|
||||
uint32_t delivered;
|
||||
uint32_t interval_us;
|
||||
uint32_t rtt_us;
|
||||
uint32_t acked_sacked;
|
||||
uint32_t prior_delivered;
|
||||
uint32_t tx_in_flight;
|
||||
int lost;
|
||||
int is_app_limited;
|
||||
};
|
||||
|
||||
typedef void (*bbr_cwnd_update_fn)(void* ctx, uint32_t new_cwnd);
|
||||
|
||||
struct bbr {
|
||||
uint32_t min_rtt_us;
|
||||
uint32_t bw_lo;
|
||||
uint32_t bw_hi[2];
|
||||
uint32_t inflight_lo;
|
||||
uint32_t inflight_hi;
|
||||
uint32_t bw_latest;
|
||||
uint32_t inflight_latest;
|
||||
uint8_t mode;
|
||||
uint8_t cycle_idx;
|
||||
uint16_t pacing_gain;
|
||||
uint16_t cwnd_gain;
|
||||
uint32_t next_rtt_delivered;
|
||||
uint8_t round_start : 1;
|
||||
uint8_t full_bw_reached : 1;
|
||||
uint8_t loss_in_round : 1;
|
||||
uint8_t loss_in_cycle : 1;
|
||||
uint8_t idle_restart : 1;
|
||||
uint8_t try_fast_path : 1;
|
||||
uint8_t has_seen_rtt : 1;
|
||||
uint8_t initialized : 1;
|
||||
uint32_t loss_round_delivered;
|
||||
uint32_t bw_probe_up_cnt;
|
||||
uint32_t bw_probe_up_acks;
|
||||
uint8_t bw_probe_up_rounds;
|
||||
uint8_t bw_probe_samples : 1;
|
||||
uint8_t prev_probe_too_high: 1;
|
||||
uint8_t stopped_risky_probe: 1;
|
||||
uint8_t rounds_since_probe;
|
||||
uint32_t probe_wait_us;
|
||||
uint64_t cycle_mstamp_tb;
|
||||
uint32_t probe_rtt_min_us;
|
||||
uint64_t probe_rtt_min_stamp_tb;
|
||||
uint64_t probe_rtt_done_stamp_tb;
|
||||
uint8_t probe_rtt_round_done : 1;
|
||||
uint16_t extra_acked[2];
|
||||
uint32_t ack_epoch_acked;
|
||||
uint64_t ack_epoch_mstamp_tb;
|
||||
uint8_t extra_acked_win_idx : 1;
|
||||
uint8_t extra_acked_win_rtts : 5;
|
||||
uint32_t prior_cwnd;
|
||||
uint8_t init_cwnd : 7;
|
||||
uint8_t full_bw_cnt : 2;
|
||||
uint8_t ack_phase : 3;
|
||||
uint8_t loss_events_in_round : 4;
|
||||
uint32_t delivered;
|
||||
uint32_t full_bw;
|
||||
uint64_t min_rtt_stamp_tb;
|
||||
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;
|
||||
};
|
||||
|
||||
void bbr_init(struct bbr* bbr);
|
||||
void bbr_main(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t* cwnd_out, uint32_t* pacing_rate_out,
|
||||
uint32_t mss, uint32_t inflight_bytes, int is_cwnd_limited);
|
||||
void bbr_note_loss(struct bbr* bbr);
|
||||
void bbr_tx_start(struct bbr* bbr);
|
||||
@@ -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;
|
||||
}
|
||||
+553
-592
File diff suppressed because it is too large.
Load diff
+61
-38
@@ -5,8 +5,8 @@
|
||||
|
||||
#include "secure_channel.h"
|
||||
#include "utun_instance.h"
|
||||
#include "etcp_bbr.h"
|
||||
#include "../lib/socket_compat.h"
|
||||
#include "../lib/swm_min.h"
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
@@ -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 */
|
||||
@@ -26,6 +29,12 @@
|
||||
#define ETCP_INIT_RESPONSE_NOINIT 0x05
|
||||
#define ETCP_PING 0x06
|
||||
#define ETCP_PONG 0x07
|
||||
#define ETCP_KEEPALIVE 0x08
|
||||
|
||||
/* Адаптивный keepalive */
|
||||
#define KA_PERIOD_MIN_MS 200
|
||||
#define KA_PERIOD_MAX_MS 10000
|
||||
#define KA_TIMEOUT_MULT 10 /* timeout = period * mult */
|
||||
|
||||
|
||||
#pragma pack(push, 1)
|
||||
@@ -45,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 {
|
||||
@@ -97,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
|
||||
@@ -121,7 +170,6 @@ struct ETCP_LINK {
|
||||
|
||||
struct ETCP_CONN* etcp; // подключение (parent)
|
||||
struct ETCP_SOCKET* conn; // сокет через который работаем
|
||||
SlidingWindowMin* rtt_swm;
|
||||
// Путь соединения
|
||||
struct sockaddr_storage remote_addr; // Удалённый адрес
|
||||
|
||||
@@ -163,26 +211,6 @@ struct ETCP_LINK {
|
||||
uint32_t inflight_packets;
|
||||
uint32_t inflight_lim_bytes;
|
||||
|
||||
/* Биты блокировки отправки по причине (для корректного resume) */
|
||||
// unsigned send_blocked_input_queue : 1;
|
||||
// unsigned send_blocked_sack_full : 1;
|
||||
// unsigned send_blocked_bandwidth : 1;
|
||||
unsigned send_blocked_inflight : 1;
|
||||
// unsigned send_blocked_other : 4;
|
||||
|
||||
// lim периодически обновляется. = (rtt_avg10 + jitter * K2[=1.0]) * K1[=1.5] * channel_bandwidth
|
||||
uint32_t win_timebase; // x1 us (по умолчанию 50 000 = 50 ms, потом подстраивается под rtt/2)
|
||||
uint32_t window_pkt_transmitted; // текущие накопители для следующего окна (обнуляются в таймере)
|
||||
uint32_t window_retransmissions;
|
||||
void* stats_timer; // Таймер для обновления окна
|
||||
uint32_t win_ptr; // текущий слот в кольцевом буфере (0..31)
|
||||
struct {
|
||||
uint16_t rtt; // x 0.1ms (avg10 в конеце окна)
|
||||
// uint16_t avg_rtt; // x 0.1ms (avg10 усредненный за период)
|
||||
uint16_t pkt_loss; // штук (за период)
|
||||
uint32_t pkt_transmitted; // штук (за период)
|
||||
} stat_win[32]; // последние 32 замера статистики
|
||||
|
||||
// statistics
|
||||
size_t encrypt_errors;
|
||||
size_t decrypt_errors;
|
||||
@@ -191,16 +219,12 @@ 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 (время отправки + приёма)
|
||||
uint16_t rtt_history[10]; // Circular buffer for last 10 RTT values
|
||||
uint8_t rtt_history_count; // Number of valid entries in history (0-10)
|
||||
uint8_t rtt_history_index; // Current write position in history buffer
|
||||
uint32_t jitter; // Current jitter [>>16] x0.1 ms
|
||||
uint16_t rtt_max_val; // Current max RTT in history
|
||||
uint8_t rtt_max_idx; // Index of max (255 = needs recalc)
|
||||
uint16_t rtt_avg10; // round trip average (excl. max)
|
||||
uint16_t rtt_min; // round trip min (rtt_swm floating window)
|
||||
// rtt_avg10 удалён — заменён BBR (bbr->min_rtt_us/100)
|
||||
uint32_t recv_dt_avg_tx; // дельта времени для отправленных пакетов (относительное время отправки) x256
|
||||
uint32_t recv_dt_avg_rx; // дельта времени для принятых пакетов (относительное время отправки) x256
|
||||
|
||||
@@ -218,6 +242,7 @@ struct ETCP_LINK {
|
||||
// Keepalive state
|
||||
void* keepalive_timer; // Таймер для отправки keepalive пакетов
|
||||
uint32_t keepalive_timeout; // таймаут (ms)
|
||||
uint16_t ka_period_ms; // адаптивный период отправки keepalive (200→10000->200)
|
||||
uint8_t pkt_sent_since_keepalive; // Флаг: был ли отправлен пакет с последнего keepalive тика
|
||||
uint32_t keepalive_sent_count; // Счётчик отправленных keepalive
|
||||
uint32_t keepalive_recv_count; // Счётчик полученных keepalive
|
||||
@@ -249,11 +274,12 @@ struct ETCP_LINK {
|
||||
uint8_t burst_resp_pkt_count; // число пакетов в измерении
|
||||
uint8_t burst_resp_valid; // валидность измерения
|
||||
|
||||
// === Фазы управления inflight ===
|
||||
// 0 = slow_start, 1 = congestion_avoidance
|
||||
uint8_t inflight_phase;
|
||||
uint64_t last_window_update_tb; // время последнего обновления окна (0.1ms)
|
||||
uint32_t slow_start_threshold; // порог выхода из slow start (burst_target_bdp * 0.9)
|
||||
// === BBR congestion control ===
|
||||
struct bbr* bbr; // BBR state (per-link, создаётся в etcp_link_new)
|
||||
uint64_t delivered_bytes; // кумулятивно delivered bytes на линке (для round detection)
|
||||
uint64_t last_ack_time_tb; // 0.1ms timestamp последнего ACK (для interval_us)
|
||||
uint32_t bbr_pacing_rate; // bytes/sec output BBR → shaper
|
||||
uint32_t bbr_loss_since_ack; // счётчик потерь на линке с последнего ACK (для rs->lost)
|
||||
|
||||
etcp_udp_send_fn_t send_hook; // NULL = socket_sendto напрямую
|
||||
void* send_hook_ctx;
|
||||
@@ -278,20 +304,17 @@ void etcp_socket_remove(struct ETCP_SOCKET* conn);
|
||||
// connection functions
|
||||
// создает новый канал связи для etcp подключения (ETCP_CONN)
|
||||
struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn, struct sockaddr_storage* remote_addr, uint8_t is_server);
|
||||
void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim);
|
||||
void etcp_link_close(struct ETCP_LINK* link);
|
||||
//int etcp_input_cbk(struct packet_buffer* pkt, struct ETCP_SOCKET* conn);// получает расшифрованный пакет
|
||||
int etcp_encrypt_send(struct ETCP_DGRAM* dgram);// зашифровывает и отправляет пакет
|
||||
// find link by address
|
||||
struct ETCP_LINK* etcp_link_find_by_addr(struct ETCP_SOCKET* e_sock, struct sockaddr_storage* addr);
|
||||
|
||||
// обновляет лимит inflight_bytes для линка и корректно снимает блокировку если нужно
|
||||
void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim);
|
||||
|
||||
// find free local_link_id for connection
|
||||
// scans all links in connection, marks used ids in bit array
|
||||
// returns first free id (0-255) or -1 if all occupied
|
||||
int etcp_find_free_local_link_id(struct ETCP_CONN* etcp);
|
||||
void start_stats_timer(struct ETCP_LINK* link);
|
||||
|
||||
// Burst measurement functions
|
||||
void etcp_link_burst_start(struct ETCP_LINK* link);
|
||||
|
||||
+14
-23
@@ -4,7 +4,6 @@
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/memory_pool.h"
|
||||
#include "../lib/socket_compat.h"
|
||||
#include "../lib/swm_min.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -153,32 +152,29 @@ void etcp_dump_conn_state(struct ETCP_CONN* conn) {
|
||||
link->keepalive_interval, link->keepalive_timeout);
|
||||
|
||||
/* timers */
|
||||
DUMP_LINE("TIMERS: init=%s(%u/%u) ka=%s shaper=%s stats=%s burst=%s keepalive=%s",
|
||||
DUMP_LINE("TIMERS: init=%s(%u/%u) ka=%s shaper=%s burst=%s keepalive=%s",
|
||||
link->init_timer ? "ACTIVE" : "free",
|
||||
link->init_timeout, link->init_retry_count,
|
||||
link->keepalive_timer ? "ACTIVE" : "free",
|
||||
link->shaper_timer ? "ACTIVE" : "free",
|
||||
link->stats_timer ? "ACTIVE" : "free",
|
||||
link->burst_resp_timer ? "ACTIVE" : "free",
|
||||
link->keepalive_sent_count > 0 ? "has_sent" : "idle");
|
||||
|
||||
/* inflight */
|
||||
DUMP_LINE("INFLIGHT: bytes=%u pkts=%u lim=%u blocked=%d phase=%d sst=%u last_win_tb=%llu",
|
||||
/* inflight (BBR) */
|
||||
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->inflight_phase,
|
||||
link->slow_start_threshold, (unsigned long long)link->last_window_update_tb);
|
||||
link->bbr ? link->bbr->mode : -1,
|
||||
link->bbr ? link->bbr->cycle_idx : -1,
|
||||
link->bbr_pacing_rate);
|
||||
|
||||
/* rtt */
|
||||
char hist_str[128] = "";
|
||||
int hist_pos = 0;
|
||||
for (int i = 0; i < 10 && i < (int)link->rtt_history_count; i++) {
|
||||
hist_pos += snprintf(hist_str + hist_pos, sizeof(hist_str) - hist_pos,
|
||||
"%s%u", i == 0 ? "" : ",", link->rtt_history[i]);
|
||||
}
|
||||
DUMP_LINE("RTT: last=%u avg10=%u min=%u max=%u jitter=%u hist=[%s] cnt=%d swm=%u",
|
||||
link->rtt_last, link->rtt_avg10, link->rtt_min, link->rtt_max_val,
|
||||
link->jitter, hist_str, link->rtt_history_count,
|
||||
link->rtt_swm ? swm_get_min(link->rtt_swm) : 0);
|
||||
/* rtt (BBR) */
|
||||
DUMP_LINE("RTT: last=%u jitter=%u min_rtt_us=%u bw_lo=%u bw_hi=%u inflight_lo=%u inflight_hi=%u",
|
||||
link->rtt_last, link->jitter,
|
||||
link->bbr ? link->bbr->min_rtt_us : 0,
|
||||
link->bbr ? link->bbr->bw_lo : 0,
|
||||
link->bbr ? (link->bbr->bw_hi[0] > link->bbr->bw_hi[1] ? link->bbr->bw_hi[0] : link->bbr->bw_hi[1]) : 0,
|
||||
link->bbr ? link->bbr->inflight_lo : 0,
|
||||
link->bbr ? link->bbr->inflight_hi : 0);
|
||||
|
||||
/* tt/rt/bandwidth */
|
||||
DUMP_LINE("TT/RT: tt=%u rt=%u recv_dt_tx=%u recv_dt_rx=%u bw=%u",
|
||||
@@ -201,11 +197,6 @@ void etcp_dump_conn_state(struct ETCP_CONN* conn) {
|
||||
link->nat_changes_count, link->nat_hits_count,
|
||||
link->nat_check_status, link->nat_type);
|
||||
|
||||
/* stats win */
|
||||
DUMP_LINE("WIN: ptr=%u tb=%u tx=%u retrans=%u",
|
||||
link->win_ptr, link->win_timebase,
|
||||
link->window_pkt_transmitted, link->window_retransmissions);
|
||||
|
||||
/* last recv */
|
||||
DUMP_LINE("LAST_RECV: time=%llu ts=%u updated=%d",
|
||||
(unsigned long long)link->last_recv_local_time,
|
||||
|
||||
+7
-19
@@ -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;
|
||||
}
|
||||
|
||||
@@ -164,9 +164,9 @@ void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram) {
|
||||
uint64_t now_tb=get_time_tb();
|
||||
if (link->shaper_load_time_tb >= now_tb + SHAPER_BURST_DELAY_TB) {
|
||||
uint64_t wait_tb = link->shaper_load_time_tb - now_tb;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] scheduling shaper timer (wait_tb=%llu) BW=%d pkt_size=%d", etcp->log_name, (unsigned long long)wait_tb, link->bandwidth, pkt_size);
|
||||
link->shaper_timer = uasync_set_timeout(link->etcp->instance->ua, wait_tb, link, shaper_timer_cb, "shaper");
|
||||
if (link->shaper_timer == NULL) DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Filed to allocate timer");
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] scheduled shaper timer (wait_tb=%llu) BW=%d pkt_size=%d", etcp->log_name, (unsigned long long)wait_tb, link->bandwidth, pkt_size);
|
||||
}
|
||||
|
||||
// Inactivity correction
|
||||
@@ -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);
|
||||
|
||||
+792
-224
File diff suppressed because it is too large.
Load diff
+93
-10
@@ -3,7 +3,8 @@
|
||||
// Упрощённый TCP поверх ETCP: восстановление порядка, дедупликация, без переповторов
|
||||
//
|
||||
// Формат SVC_ROUTE пакета:
|
||||
// [cmd:1] [dst_node_id:8] [src_node_id:8] [seq:4] [svc_id:1] [payload...]
|
||||
// [cmd:1] [dst_node_id:8] [src_node_id:8] [seq:4] [svc_id:1] [flags:1] [payload...]
|
||||
// flags: bit7=START, bit6=RST, bit5-4=sess_id(0-3)
|
||||
// ACK-пакет: тот же заголовок, seq=rx_seq, payload_len=0
|
||||
#ifndef ETCP_ROUTER_H
|
||||
#define ETCP_ROUTER_H
|
||||
@@ -13,16 +14,25 @@
|
||||
|
||||
#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)
|
||||
uint8_t svc_id; // идентификатор сервиса
|
||||
uint8_t flags; // bit7=START, bit6=RST, bit5-4=sess_id
|
||||
};
|
||||
#pragma pack(pop)
|
||||
#define SVC_ROUTE_HDR_SIZE sizeof(struct SVC_ROUTE_HDR) // 22
|
||||
#define SVC_ROUTE_HDR_SIZE sizeof(struct SVC_ROUTE_HDR) // 23
|
||||
#define SVC_ROUTE_MAX_BINDINGS 256
|
||||
|
||||
// Биты в 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
|
||||
|
||||
// Состояние одного логического подключения (remote_node_id + svc_id)
|
||||
struct ETCP_ROUTER_CONN {
|
||||
struct ll_entry ll; // data[0..7]=remote_node_id, data[8]=svc_id — хеш-индекс
|
||||
@@ -32,28 +42,73 @@ struct ETCP_ROUTER_CONN {
|
||||
|
||||
uint32_t tx_seq; // следующий seq для отправки
|
||||
uint32_t rx_seq; // ожидаемый seq для сборки (next expected)
|
||||
uint32_t rx_acked; // последний отправленный ACK (= rx_seq на момент отправки)
|
||||
uint32_t tx_acked; // сколько наших пакетов подтвердил remote (для inflight)
|
||||
uint32_t last_sent_ack_seq; // последний отправленный ACK (= rx_seq на момент отправки)
|
||||
uint64_t last_ack_sent_tb; // время отправки последнего ACK (timebase 0.1ms)
|
||||
|
||||
struct UTUN_INSTANCE* inst;
|
||||
struct ll_queue* recv_q; // reorder очередь: hash по seq (4 байта, offset 0)
|
||||
void* ack_timer; // периодический таймер (100ms)
|
||||
void* ack_timer; // периодический таймер (10ms при consumer_ack)
|
||||
void* idle_ack_timer; // idle таймер (дослать последний ack)
|
||||
|
||||
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
|
||||
#define ROUTER_RECVQ_HASH_SIZE 1024
|
||||
#define ROUTER_MAX_INFLIGHT 256 // макс пакетов в полёте (для контроля inflight)
|
||||
#define ROUTER_ACK_INTERVAL_TB 1000 // интервал ACK: 100ms в timebase (0.1ms)
|
||||
#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 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 {
|
||||
@@ -71,7 +126,7 @@ int etcp_router_bind(struct UTUN_INSTANCE* inst, uint8_t svc_id, etcp_recv_fn c
|
||||
int etcp_router_unbind(struct UTUN_INSTANCE* inst, uint8_t svc_id);
|
||||
|
||||
// Отправить сервисный пакет (авто-conn, seq, inflight-контроль через send_q)
|
||||
int etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t dst_node_id, struct ll_entry* entry);
|
||||
int etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t dst_node_id, struct ll_entry* entry, int force);
|
||||
|
||||
// Найти/создать состояние seq-подключения по (remote_node_id, svc_id)
|
||||
struct ETCP_ROUTER_CONN* etcp_router_conn_get(struct UTUN_INSTANCE* inst,
|
||||
@@ -82,13 +137,41 @@ 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);
|
||||
|
||||
// Возвращает количество пакетов в очереди normalizer->input для узла node_id
|
||||
int etcp_router_input_q_count(struct UTUN_INSTANCE* inst, uint64_t node_id);
|
||||
|
||||
// Backpressure: зарегистрировать/отменить waiter на normalizer->input очереди
|
||||
void etcp_router_waiter_register(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
|
||||
struct queue_waiter_handle* h,
|
||||
queue_threshold_callback_fn callback, void* arg);
|
||||
void etcp_router_waiter_cancel(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
|
||||
struct queue_waiter_handle* h);
|
||||
|
||||
// Backpressure: зарегистрировать/отменить waiter на send_q очереди
|
||||
// h — handle из структуры сервиса, callback вызывается когда send_q.count <= threshold (128)
|
||||
void etcp_router_on_send_ready(struct UTUN_INSTANCE* inst, uint64_t node_id, uint8_t svc_id,
|
||||
struct queue_waiter_handle* h,
|
||||
queue_threshold_callback_fn callback, void* arg);
|
||||
void etcp_router_cancel_send_ready(struct UTUN_INSTANCE* inst, uint64_t node_id, uint8_t svc_id,
|
||||
struct queue_waiter_handle* h);
|
||||
|
||||
// Сбросить состояние роутера для конкретного peer+svc (перезапуск удалённой стороны).
|
||||
// Очищает send_q/recv_q, уведомляет сервис через cb(NULL, entry) с remote_node_id.
|
||||
void etcp_router_conn_restart(struct UTUN_INSTANCE* inst, uint64_t remote_node_id, uint8_t svc_id);
|
||||
|
||||
#endif // ETCP_ROUTER_H
|
||||
+39
-7
@@ -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");
|
||||
@@ -788,6 +788,14 @@ void tcp_slowtmr(struct lwip_tcp_ctx *ctx)
|
||||
prev = NULL;
|
||||
pcb = ctx->tw_pcbs;
|
||||
while (pcb != NULL) {
|
||||
if (!pcb->ctx) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS DANGLING pcb=%p ctx=NULL in tcp_slowtmr — HALTING", (void*)pcb);
|
||||
volatile int _halt = 1; while (_halt) {}
|
||||
}
|
||||
if (memory_pool_is_freed(pcb->ctx->pcb_pool, pcb)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS DANGLING pcb=%p IS FREED in tcp_slowtmr — HALTING", (void*)pcb);
|
||||
volatile int _halt = 1; while (_halt) {}
|
||||
}
|
||||
pcb_remove = 0;
|
||||
|
||||
if ((uint32_t)(ctx->ticks - pcb->tmr) > 2 * TCP_MSL / TCP_SLOW_INTERVAL) {
|
||||
@@ -804,7 +812,14 @@ void tcp_slowtmr(struct lwip_tcp_ctx *ctx)
|
||||
}
|
||||
pcb2 = pcb;
|
||||
pcb = pcb->next;
|
||||
int self_loop = (pcb == pcb2);
|
||||
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, clearing tw_pcbs",
|
||||
(void*)pcb2, pcb2->local_port);
|
||||
ctx->tw_pcbs = NULL;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
prev = pcb;
|
||||
pcb = pcb->next;
|
||||
@@ -1017,12 +1032,29 @@ static void tcp_kill_timewait(struct lwip_tcp_ctx *ctx)
|
||||
inactivity = 0;
|
||||
inactive = NULL;
|
||||
for (pcb = ctx->tw_pcbs; pcb != NULL; pcb = pcb->next) {
|
||||
if (!pcb->ctx) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS DANGLING pcb=%p ctx=NULL in tcp_kill_timewait — HALTING", (void*)pcb);
|
||||
volatile int _halt = 1; while (_halt) {}
|
||||
}
|
||||
if (memory_pool_is_freed(pcb->ctx->pcb_pool, pcb)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS DANGLING pcb=%p IS FREED in tcp_kill_timewait — HALTING", (void*)pcb);
|
||||
volatile int _halt = 1; while (_halt) {}
|
||||
}
|
||||
if ((uint32_t)(ctx->ticks - pcb->tmr) >= inactivity) {
|
||||
inactivity = ctx->ticks - pcb->tmr;
|
||||
inactive = pcb;
|
||||
}
|
||||
}
|
||||
if (inactive != NULL) tcp_abort(inactive);
|
||||
if (inactive != NULL) {
|
||||
if (inactive->next == inactive) {
|
||||
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 {
|
||||
tcp_abort(inactive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void tcp_handle_closepend(struct lwip_tcp_ctx *ctx)
|
||||
@@ -1080,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 = 3000 / TCP_SLOW_INTERVAL;
|
||||
pcb->sv = 3000 / 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);
|
||||
|
||||
@@ -166,7 +166,6 @@ struct tcp_pcb {
|
||||
tcp_connected_fn connected;
|
||||
tcp_poll_fn poll;
|
||||
tcp_err_fn errf;
|
||||
|
||||
uint32_t keep_idle;
|
||||
uint8_t persist_cnt;
|
||||
uint8_t persist_backoff;
|
||||
@@ -185,7 +184,7 @@ struct tcp_pcb_listen {
|
||||
tcp_accept_fn accept;
|
||||
};
|
||||
|
||||
// Context for the TCP module (one per tcp_proxy instance)
|
||||
// Context for the TCP module (one per tcp_proxy_client instance)
|
||||
struct lwip_tcp_ctx {
|
||||
struct UASYNC *ua;
|
||||
tcp_output_fn output;
|
||||
@@ -247,6 +246,7 @@ err_t tcp_connect(struct tcp_pcb *pcb, uint32_t ip, uint16_t port, tcp_connected
|
||||
err_t tcp_write(struct tcp_pcb *pcb, const void *data, uint16_t len, uint8_t apiflags);
|
||||
err_t tcp_output(struct tcp_pcb *pcb);
|
||||
err_t tcp_close(struct tcp_pcb *pcb);
|
||||
err_t tcp_shutdown(struct tcp_pcb *pcb, int shut_rx, int shut_tx);
|
||||
void tcp_abort(struct tcp_pcb *pcb);
|
||||
void tcp_recved(struct tcp_pcb *pcb, uint16_t len);
|
||||
|
||||
|
||||
@@ -208,11 +208,27 @@ void lwip_tcp_input(struct lwip_tcp_ctx *ctx, struct pbuf *p,
|
||||
|
||||
if (pcb == NULL) {
|
||||
for (pcb = ctx->tw_pcbs; pcb != NULL; pcb = pcb->next) {
|
||||
if (!pcb->ctx) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS DANGLING pcb=%p ctx=NULL in lwip_tcp_input — HALTING", (void*)pcb);
|
||||
pbuf_free(p);
|
||||
volatile int _halt = 1; while (_halt) {}
|
||||
}
|
||||
if (memory_pool_is_freed(pcb->ctx->pcb_pool, pcb)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ALL, "TW_PCBS DANGLING pcb=%p IS FREED in lwip_tcp_input — HALTING", (void*)pcb);
|
||||
pbuf_free(p);
|
||||
volatile int _halt = 1; while (_halt) {}
|
||||
}
|
||||
if (pcb->remote_port == sport &&
|
||||
pcb->local_port == dport &&
|
||||
pcb->remote_ip == src_ip &&
|
||||
pcb->local_ip == dst_ip) {
|
||||
tcp_timewait_input(pcb);
|
||||
if (pcb->next == pcb) {
|
||||
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);
|
||||
}
|
||||
pbuf_free(p);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -14,9 +14,10 @@
|
||||
#define TCP_FAST_INTERVAL TCP_TMR_INTERVAL
|
||||
#define TCP_SLOW_INTERVAL (2 * TCP_TMR_INTERVAL)
|
||||
|
||||
#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
|
||||
#define TCP_MSL 60000 // ms (2*MSL = 120s)
|
||||
#define TCP_OOSEQ_TIMEOUT 6 // x RTO
|
||||
|
||||
#define TCP_KEEPIDLE_DEFAULT 7200000 // ms (unused, no keepalive)
|
||||
|
||||
@@ -504,7 +504,7 @@ tcp_output(struct tcp_pcb *pcb)
|
||||
lwip_tcp_trace_record(pcb->ctx, 'X', ntohl(seg->tcphdr->seqno), seg->len, wnd,
|
||||
(uint16_t)pcb->rto, pcb->rtime, (uint8_t)pcb->state);
|
||||
if (g_tcp_trace) g_tcp_trace(pcb->ctx, 'X', ntohl(seg->tcphdr->seqno), seg->len, wnd, "cwd");
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_TRAFFIC, "TCP_OUTPUT cwnd limited: seqno_diff=%u seg_len=%u wnd=%u cwnd=%u snd_wnd=%u",
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "TCP_OUTPUT cwnd limited: seqno_diff=%u seg_len=%u wnd=%u cwnd=%u snd_wnd=%u",
|
||||
ntohl(seg->tcphdr->seqno) - pcb->lastack, seg->len, wnd, pcb->cwnd, pcb->snd_wnd);
|
||||
if (wnd == pcb->snd_wnd && pcb->unacked == NULL && pcb->persist_backoff == 0) {
|
||||
pcb->persist_cnt = 0;
|
||||
@@ -534,7 +534,7 @@ tcp_output(struct tcp_pcb *pcb)
|
||||
lwip_tcp_trace_record(pcb->ctx, 'S', ntohl(seg->tcphdr->seqno), seg->len, wnd,
|
||||
(uint16_t)pcb->rto, pcb->rtime, (uint8_t)pcb->state);
|
||||
if (g_tcp_trace) g_tcp_trace(pcb->ctx, 'S', ntohl(seg->tcphdr->seqno), seg->len, wnd, "snt");
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_TRAFFIC, "TCP_OUTPUT seg seqno=%u len=%u wnd=%u cwnd=%u ret=%d",
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "TCP_OUTPUT seg seqno=%u len=%u wnd=%u cwnd=%u ret=%d",
|
||||
ntohl(seg->tcphdr->seqno), seg->len, wnd, pcb->cwnd, err);
|
||||
if (err != LERR_OK) {
|
||||
tcp_set_flags(pcb, TF_NAGLEMEMERR);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#define LWIP_TCP_PRIV_H
|
||||
|
||||
#include "lwip_tcp.h"
|
||||
#include "lwip_tcp_opts.h"
|
||||
#include <string.h>
|
||||
|
||||
// Packed struct support
|
||||
@@ -240,7 +241,7 @@ void lwip_tcp_stats_clear(struct lwip_tcp_ctx *ctx);
|
||||
|
||||
#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 = (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; \
|
||||
} while(0)
|
||||
|
||||
|
||||
@@ -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 */
|
||||
+14
-14
@@ -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);
|
||||
|
||||
@@ -47,11 +47,11 @@ static void nat_transport_client_tun_out_cb(struct ll_queue* q, void* arg) {
|
||||
queue_dgram_free(pkt); queue_entry_free(pkt);
|
||||
queue_resume_callback(q);
|
||||
|
||||
int ret = etcp_route_send(inst, tr->nat_via_node_id, new_entry);
|
||||
int ret = etcp_route_send(inst, tr->nat_via_node_id, new_entry, 0);
|
||||
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);
|
||||
|
||||
@@ -90,25 +90,25 @@ static void nat_transport_provider_tun_out_cb(struct ll_queue* q, void* arg) {
|
||||
queue_dgram_free(pkt); queue_entry_free(pkt);
|
||||
queue_resume_callback(q);
|
||||
|
||||
int send_ret = etcp_route_send(inst, entry->src_node_id, new_entry);
|
||||
int send_ret = etcp_route_send(inst, entry->src_node_id, new_entry, 0);
|
||||
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; }
|
||||
|
||||
|
||||
+11
-8
@@ -69,6 +69,8 @@ struct PKTNORM* pn_init(struct ETCP_CONN* etcp) {
|
||||
pn->recvpart = NULL;
|
||||
pn->flush_timer = NULL;
|
||||
|
||||
pn->input_waiter_handle.internal = NULL;
|
||||
|
||||
return pn;
|
||||
}
|
||||
|
||||
@@ -120,7 +122,7 @@ void pn_deinit(struct PKTNORM* pn) {
|
||||
// during etcp_connection_close() when drain_and_free_fragment_queue() is called
|
||||
if (pn->etcp) {
|
||||
if (pn->etcp->input_queue) {
|
||||
queue_cancel_wait(pn->etcp->input_queue, &pn->etcp->input_queue->waiter);
|
||||
queue_waiter_cancel(pn->etcp->input_queue, &pn->input_waiter_handle);
|
||||
}
|
||||
if (pn->etcp->output_queue) {
|
||||
queue_set_callback(pn->etcp->output_queue, NULL, NULL);
|
||||
@@ -213,7 +215,7 @@ static void packer_cb(struct ll_queue* q, void* arg) {
|
||||
struct PKTNORM* pn = (struct PKTNORM*)arg;
|
||||
if (!pn) return;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_NORMALIZER, "input_q->pn: waiting etcp input threshold");
|
||||
queue_wait_threshold(pn->etcp->input_queue, 0, 0, etcp_input_ready_cb, pn);
|
||||
queue_waiter_wait(pn->etcp->input_queue, &pn->input_waiter_handle, etcp_input_ready_cb, pn);
|
||||
}
|
||||
|
||||
// Helper to send block to ETCP as ETCP_FRAGMENT
|
||||
@@ -239,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 передан во фрагмент, не освобождаем)
|
||||
@@ -284,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);
|
||||
@@ -348,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);
|
||||
|
||||
@@ -392,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,9 @@ struct PKTNORM {
|
||||
struct ll_entry* pending; // Partial processed input entry
|
||||
uint16_t pending_in_ptr; // Pointer in pending entry
|
||||
|
||||
// backpressure:
|
||||
struct queue_waiter_handle input_waiter_handle; // waiter на etcp->input_queue
|
||||
|
||||
// unpacker:
|
||||
struct ll_entry* recvpart; // блок ожидающий заполнение
|
||||
// uint16_t recvpart_rem; // сколько байт осталось собрать (0 = ждём заголовок нового пакета)
|
||||
|
||||
@@ -0,0 +1,477 @@
|
||||
## TCP Proxy Protocol — архитектура (v2, без CONNECTED)
|
||||
|
||||
### 1. Протокольный формат (общий для client и server)
|
||||
|
||||
```
|
||||
┌──────┬──────┬───────────────────────┬──────────────────────┐
|
||||
│svc_id│subcmd│ stream_id (4) │ data ... │
|
||||
│ 1B │ 1B │ │ │
|
||||
└──────┴──────┴───────────────────────┴──────────────────────┘
|
||||
HDR = 6 байт
|
||||
|
||||
subcmd:
|
||||
0x01 CONNECT client→server : dest_ip(4)+dest_port(2)
|
||||
0x03 DATA ↔ bidirectional: payload
|
||||
0x04 CLOSE ↔ bidirectional: (нет данных)
|
||||
0x05 ERROR ↔ bidirectional: (нет данных)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Диаграмма состояний `tcp_proxy_client_conn`
|
||||
|
||||
```
|
||||
┌──────────────┐
|
||||
│ ALLOC + │ accept_cb: u_calloc, stream_id++
|
||||
│ CONNECT │ send CONNECT → exit
|
||||
│ отправлен │
|
||||
└──────┬───────┘
|
||||
│
|
||||
┌───────────┴───────────┐
|
||||
│ send успешен? │
|
||||
└───────────┬───────────┘
|
||||
Y │ N
|
||||
┌────────┘ └──────┐
|
||||
▼ ▼
|
||||
┌────────────┐ ┌──────────────┐
|
||||
│ ACTIVE │ │ FREE │
|
||||
│ traffic │ └──────────────┘
|
||||
└────┬──┬────┘
|
||||
│ │
|
||||
│ └─────────────────────────────────┐
|
||||
│ │
|
||||
▼ lwIP FIN (p=NULL) ▼ ERROR от lwIP/exit
|
||||
┌──────────────┐ ┌──────────────┐
|
||||
│ TUN_CLOSED │ │ ERROR │
|
||||
│ tun_closed=1 │ │ error=1 │
|
||||
│ ↓ send_close │ │ send ERROR→ │
|
||||
│ после drain │ └──────┬───────┘
|
||||
│ tx_buf+lwIP │ │
|
||||
└──────┬───────┘ poll_cb: tcp_abort+free
|
||||
│
|
||||
│ recv CLOSE от exit
|
||||
▼
|
||||
┌───────────────────┐
|
||||
│ TUN+REM CLOSED │
|
||||
│ tun=1 rem=1 │
|
||||
│ ждём flush буферов│
|
||||
└────────┬──────────┘
|
||||
│ poll_cb: pending=0, unsent=NULL, unacked=NULL
|
||||
│ (или sndbuf==0)
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ CLEANUP │
|
||||
│ free(pc) │
|
||||
└──────────────┘
|
||||
|
||||
|
||||
ДОП. СОСТОЯНИЯ:
|
||||
┌─────────────────┐ send_data в ETCP вернул -1 (normalizer полон)
|
||||
│ TX_BACKPRESSURE│ не tcp_recved() → окно lwIP=0 → recv_cb остановлен
|
||||
│ tx_buf != NULL │ etcp_router_waiter_register() → ждём освобождения
|
||||
│ tx_waiter pend │ waiter_cb: retry send_data → tcp_recved → окно открыто
|
||||
└─────────────────┘
|
||||
|
||||
┌─────────────────┐ send_close/send_error вернул -1
|
||||
│ CLOSE_PENDING │ → retry в poll_cb
|
||||
│ close_pending=1│
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Диаграмма состояний `tcp_proxy_server_conn`
|
||||
|
||||
```
|
||||
┌───────────────────┐
|
||||
│ CREATING │ recv CONNECT
|
||||
│ socket() создан │ → неблокирующий connect()
|
||||
│ sock = неблок. │
|
||||
└────────┬──────────┘
|
||||
│
|
||||
┌────────────┴────────────┐
|
||||
│ connect() результат? │
|
||||
└────┬──────────┬──────────┘
|
||||
EINPROGRESS│ │ошибка сразу
|
||||
▼ ▼
|
||||
┌──────────┐ ┌──────────────┐
|
||||
│CONNECTING│ │ ERROR │ send ERROR→client
|
||||
│ждём write│ │ FREE │
|
||||
└────┬─────┘ └──────────────┘
|
||||
│ write_cb: SO_ERROR==0
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ CONNECTED │ connected=1
|
||||
│ flush pend │ → flush pending_buf в сокет
|
||||
└──────┬──────┘
|
||||
│
|
||||
┌──────────────┼──────────────┐
|
||||
│ │ │
|
||||
client→DATA │ │ ERROR (sock/pipe)
|
||||
(sock_send) │ ▼
|
||||
или pending_buf │ ┌──────────────┐
|
||||
│ │ │ ERROR │ send ERROR→client
|
||||
│ │ │ FREE │
|
||||
│ sock EOF (n=0) │ │
|
||||
│ │ └──────────────┘
|
||||
│ ▼
|
||||
│ ┌──────────────────┐
|
||||
│ │ SOCK_CLOSED │ sock_closed=1
|
||||
│ │ out_buf/pause? │ если очереди пусты → send_close
|
||||
│ │ ждём drain │ иначе ждём retry_cb/waiter_cb
|
||||
│ └────────┬─────────┘
|
||||
│ │ очереди опустошены → send_close
|
||||
│ │ если cli_closed → FREE
|
||||
│ │
|
||||
client→CLOSE │
|
||||
│ │
|
||||
▼ │
|
||||
┌──────────────┐ │
|
||||
│ CLI_CLOSED │ │ cli_closed=1
|
||||
│ out_buf pend? │◄────────┘ если out_buf пуст → shutdown(SHUT_WR)
|
||||
│ ждём flush │ иначе ждём sock_retry_cb → shutdown
|
||||
└──────┬───────┘
|
||||
│
|
||||
└──────────────┐
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ cli && sock │ оба закрыты + очереди пусты
|
||||
│ очереди пусты │
|
||||
└────────┬─────────┘
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ FREE │ conn_free(rc)
|
||||
└──────────────┘
|
||||
|
||||
|
||||
ДОП. СОСТОЯНИЯ:
|
||||
┌─────────────────┐ send_msg в ETCP вернул -1
|
||||
│ PAUSED │ буферизуем в pause_buf, убираем read_id (пауза чтения)
|
||||
│ pause_buf>0 │ etcp_router_waiter_register() → ждём освобождения
|
||||
│ pause_waiter │ waiter_cb: retry send_msg → resume read_id
|
||||
└─────────────────┘
|
||||
|
||||
┌─────────────────┐ CLOSE/ERROR не доставлен через ETCP
|
||||
│ CLOSE_PENDING │ close_timer с backoff 50..5000 tb → ретрай
|
||||
│ close_pending=1│
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. DATA FLOW
|
||||
|
||||
```
|
||||
══════════════════════════════════════════════════════════════════════
|
||||
APP → lwIP → recv_cb → ETCP → handle → socket → DEST
|
||||
APP ← lwIP ← tcp_write ← ETCP ← read_cb ← socket ← DEST
|
||||
══════════════════════════════════════════════════════════════════════
|
||||
|
||||
CLIENT (узел A) SERVER (узел B)
|
||||
|
||||
APP (через TUN) DEST (реальный)
|
||||
│ │
|
||||
▼ TCP SYN/ACK (lwIP) ▼ TCP SYN/ACK (OS)
|
||||
┌──────────┐ ┌──────────┐
|
||||
│ lwIP │ │ socket │
|
||||
│ TCP │ │ (неблок) │
|
||||
└────┬─────┘ └────┬─────┘
|
||||
│ recv_cb │ read_cb
|
||||
│ │
|
||||
┌────▼────────────┐ ┌────▼───────────────┐
|
||||
│ tcp_proxy │ ETCP │ tcp_proxy │
|
||||
│ _client │◄═══════════════════════►│ _server │
|
||||
│ │ │ │
|
||||
│ ┌────────────┐ │ │ ┌──────────────┐ │
|
||||
│ │ recv_cb │ │──CONNECT+DATA──► │ │handle_connect│ │
|
||||
│ │(lwIP→ETCP) │ │ │ │handle_data │ │
|
||||
│ │ │ │ backpressure: │ └──────┬───────┘ │
|
||||
│ │ send fail: │ │ waiter на normalizer │ │ sock_send │
|
||||
│ │ tx_buf+ │ │ │ ┌──────▼───────┐ │
|
||||
│ │ waiter │ │ │ │ out_buf │ │
|
||||
│ │ окно lwIP=0│ │ │ │ pending_buf │ │
|
||||
│ └────────────┘ │ │ └─────────────┘ │
|
||||
│ │ │ │ send() │
|
||||
│ ┌────────────┐ │ │ ┌──────▼───────┐ │
|
||||
│ │handle_data │ │◄────DATA──────── │ │ read_cb │ │
|
||||
│ │handle_close│ │ │ │(sock→ETCP) │ │
|
||||
│ └─────┬──────┘ │ │ │ │ │
|
||||
│ │ │ │ │ send fail: │ │
|
||||
│ ┌─────▼──────┐ │ │ │ pause_buf+ │ │
|
||||
│ │ to_lwip │ │ │ │ waiter+ │ │
|
||||
│ │ (очередь) │ │ │ │ пауза read_id│ │
|
||||
│ └─────┬──────┘ │ │ └─────────────┘ │
|
||||
│ │tcp_write│ │ │
|
||||
│ ▼ │ └───────────────────┘
|
||||
│ lwIP→TUN→APP │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. BACKPRESSURE (lwIP→ETCP и socket→ETCP)
|
||||
|
||||
```
|
||||
══════════════════════════════════════════════════════════════════════
|
||||
|
||||
Используется встроенный механизм ll_queue threshold waiter:
|
||||
etcp_router_waiter_register(inst, peer_node_id, &waiter)
|
||||
→ внутри: queue_waiter_wait(normalizer->input_queue, &waiter)
|
||||
etcp_router_waiter_cancel(inst, peer_node_id, &waiter)
|
||||
|
||||
CLIENT (lwIP → ETCP):
|
||||
recv_cb(buf, len):
|
||||
ret = send_data(buf, len)
|
||||
if ret == 0: tcp_recved(len)
|
||||
if ret < 0:
|
||||
tx_buf = copy(buf), tx_len = len
|
||||
НЕ tcp_recved() → окно lwIP = 0, recv_cb больше не вызывается
|
||||
etcp_router_waiter_register(&tx_waiter)
|
||||
|
||||
tx_waiter callback:
|
||||
ret = send_data(tx_buf, tx_len)
|
||||
if ret == 0:
|
||||
tcp_recved(tx_len) → окно открыто, lwIP возобновляет recv_cb
|
||||
free(tx_buf)
|
||||
if tun_closed → send_close()
|
||||
// если ret < 0 → waiter остаётся, normalizer вызовет снова
|
||||
|
||||
SERVER (socket → ETCP):
|
||||
read_cb(buf, n):
|
||||
ret = send_msg(DATA, buf, n)
|
||||
if ret == 0: ok
|
||||
if ret < 0:
|
||||
pause_buf = copy(buf), pause_len = n
|
||||
uasync_remove_socket_t(sock) → пауза чтения с сокета
|
||||
etcp_router_waiter_register(&pause_waiter)
|
||||
|
||||
pause_waiter callback:
|
||||
ret = send_msg(DATA, pause_buf, pause_len)
|
||||
if ret == 0:
|
||||
free(pause_buf)
|
||||
uasync_add_socket_t(sock) → возобновляем чтение
|
||||
if sock_closed → send_close()
|
||||
// если ret < 0 → waiter остаётся
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. Последовательность: HANDSHAKE (упрощённый, без CONNECTED)
|
||||
|
||||
```
|
||||
CLIENT (lwIP) CLIENT PROXY ETCP SERVER PROXY SOCKET
|
||||
─────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
tcp_proxy_client_accept_cb() CONNECT
|
||||
├─ u_calloc(conn) ────────────────────────────→ tcp_proxy_server_handle_connect()
|
||||
├─ stream_id++ ├─ socket(AF_INET,SOCK_STREAM)
|
||||
├─ send_connect() ├─ socket_set_nonblocking()
|
||||
│ [dest_ip(4)+dest_port(2)] ├─ uasync_add_socket_t(read/write/error)
|
||||
├─ conn→conns list ├─ connect(неблок.) → EINPROGRESS
|
||||
│ └─ rc→conns list
|
||||
▼
|
||||
ACTIVE — данные идут СРАЗУ (без ожидания CONNECTED)
|
||||
│
|
||||
│ DATA ...connect завершён...
|
||||
├──────────────────────────────────────────────────→ tcp_proxy_server_sock_write_cb()
|
||||
│ ├─ connected=1
|
||||
│ DATA └─ flush pending_buf в сокет
|
||||
├──────────────────────────────────────────────────→
|
||||
│
|
||||
│ DATA ←─ tcp_proxy_server_sock_read_cb()
|
||||
│◄────────────────────────────────────────────── (socket recv → ETCP)
|
||||
│
|
||||
└──→ трафик идёт в обе стороны
|
||||
|
||||
При ошибке connect:
|
||||
...connect fail... → tcp_proxy_server_send_error() → client получает ERROR, error=1, cleanup
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. Последовательность: CLOSE (drain очередей → close в конце)
|
||||
|
||||
```
|
||||
CLIENT (lwIP) CLIENT PROXY ETCP SERVER PROXY SOCKET
|
||||
─────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
══ Вариант А: lwIP закрывает первым ══
|
||||
|
||||
tcp_proxy_client_recv_cb(p=NULL)
|
||||
├─ tun_closed=1
|
||||
│ если tx_buf пуст → send_close CLOSE
|
||||
│ если tx_buf pend → waiter досылает ──────────→ tcp_proxy_server_handle_close()
|
||||
│ потом send_close ├─ cli_closed=1
|
||||
└─ │ если out_buf пуст → shutdown(SHUT_WR)
|
||||
│ иначе ждём sock_retry_cb
|
||||
│
|
||||
...out_buf дослан...
|
||||
sock_retry_cb:
|
||||
shutdown(SHUT_WR)
|
||||
│
|
||||
...сокет дочитывает остаток...
|
||||
│
|
||||
tcp_proxy_server_sock_read_cb(n=0)
|
||||
├─ sock_closed=1
|
||||
│ если pause_buf пуст → send_close
|
||||
│ иначе ждём waiter_cb
|
||||
│
|
||||
CLOSE │
|
||||
tcp_proxy_client_handle_close() ◄────────────────────────┘
|
||||
└─ rem_closed=1
|
||||
│
|
||||
poll_cb: tun=1, rem=1, to_lwip пуст, unsent=NULL, unacked=NULL
|
||||
├─ tcp_close(pcb)
|
||||
└─ conn_free()
|
||||
|
||||
|
||||
══ Вариант Б: серверный сокет закрывается первым ══
|
||||
|
||||
tcp_proxy_server_sock_read_cb(n=0)
|
||||
├─ sock_closed=1
|
||||
│ если out_buf+pause_buf пусты → send_close
|
||||
│ иначе ждём retry/waiter
|
||||
CLOSE │
|
||||
tcp_proxy_client_handle_close() ◄────────────────────────┘
|
||||
└─ rem_closed=1
|
||||
│
|
||||
▼
|
||||
...приложение закрывает TCP...
|
||||
│
|
||||
tcp_proxy_client_recv_cb(p=NULL)
|
||||
├─ tun_closed=1
|
||||
│ если tx_buf пуст → send_close
|
||||
└─
|
||||
|
||||
poll_cb: оба закрыты, буферы пусты → cleanup
|
||||
|
||||
|
||||
══ Вариант В: Ошибка / аномалия ══
|
||||
|
||||
ERROR
|
||||
─────────────────────── (или ←) ──────────
|
||||
error=1
|
||||
│
|
||||
▼
|
||||
poll_cb: tcp_abort(pcb) → немедленная очистка
|
||||
или: rc→error=1 → conn_free
|
||||
|
||||
|
||||
══ Вариант Г: conn не найден / closed / error ══
|
||||
|
||||
Любой пришедший пакет (DATA/CLOSE/ERROR) для conn в закрытом состоянии:
|
||||
→ tcp_proxy_*_send_msg(ERROR, stream_id)
|
||||
→ удалённая сторона получает ERROR и немедленно закрывается
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8. Диспетчеризация в `tcp_proxy_client_etcp_recv_cb()`
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────────────────┐
|
||||
│ tcp_proxy_client_etcp_recv_cb() │
|
||||
│ (единая точка входа для обоих направлений) │
|
||||
├────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ subcmd == CONNECT? │
|
||||
│ └── tcp_proxy_server_handle_connect() ←── всегда сервер │
|
||||
│ │
|
||||
│ stream_id в server.conns? (если server.enabled) │
|
||||
│ ├── DATA → tcp_proxy_server_handle_data() │
|
||||
│ ├── CLOSE → tcp_proxy_server_handle_close() │
|
||||
│ └── ERROR → rc->error=1; conn_free() │
|
||||
│ │
|
||||
│ stream_id в client.conns? (если client proxy != NULL) │
|
||||
│ ├── DATA → tcp_proxy_client_handle_data() │
|
||||
│ ├── CLOSE → tcp_proxy_client_handle_close() │
|
||||
│ └── ERROR → tcp_proxy_client_handle_error() │
|
||||
│ │
|
||||
│ Приоритет: │
|
||||
│ 1. CONNECT всегда интерпретируется сервером │
|
||||
│ 2. Если server.enabled И stream_id найден в server.conns → server │
|
||||
│ 3. Иначе если client proxy != NULL → client │
|
||||
│ 4. Иначе drop │
|
||||
└────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 9. INIT
|
||||
|
||||
```
|
||||
CLIENT SERVER
|
||||
─────────────────────────────────────────────────────────
|
||||
|
||||
utun_instance.c
|
||||
│
|
||||
├─ tcp_proxy_client_create() tcp_proxy_server_init()
|
||||
│ ├─ tun_init_nat() (TUN iface) ├─ memset(ctx,0)
|
||||
│ ├─ lwip_tcp_init() (TCP стек) ├─ ctx->enabled = config ...
|
||||
│ ├─ etcp_router_bind( ├─ etcp_router_bind(
|
||||
│ │ ETCP_ID_TCP_PROXY, │ ETCP_ID_TCP_PROXY,
|
||||
│ │ tcp_proxy_client_etcp_recv_cb) │ tcp_proxy_client_etcp_recv_cb)
|
||||
│ ├─ udp_proxy_init() ├─ udp_proxy_init()
|
||||
│ └─ icmp_proxy_init() └─ icmp_proxy_init() (если client не активен)
|
||||
│
|
||||
└─ ОБА слушают ETCP_ID_TCP_PROXY через один обработчик
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 10. Поля структур
|
||||
|
||||
```
|
||||
tcp_proxy_client_conn:
|
||||
stream_id — уникальный ID потока
|
||||
tun_closed — lwIP/FIN (сторона TUN)
|
||||
rem_closed — CLOSE от exit
|
||||
error — немедленная очистка
|
||||
close_sent — CLOSE/ERROR отправлен
|
||||
close_pending — отправка не удалась, ретрай
|
||||
to_lwip — очередь DATA от exit → lwIP (feed_from_transport)
|
||||
pcb — lwIP tcp_pcb
|
||||
tx_buf — буфер при backpressure lwIP→ETCP (send_data fail)
|
||||
tx_len — длина tx_buf
|
||||
tx_waiter — ll_queue waiter на normalizer.input_queue
|
||||
|
||||
tcp_proxy_server_conn:
|
||||
stream_id — уникальный ID потока
|
||||
cli_closed — клиент прислал CLOSE
|
||||
sock_closed — сокет получил EOF
|
||||
error — ошибка
|
||||
connected — сокет подключился (connect завершён)
|
||||
close_pending — CLOSE/ERROR не доставлен, ретрай
|
||||
pending_buf — буфер данных от клиента до завершения connect
|
||||
out_buf — буфер отправки в сокет (неблокирующий send, EAGAIN retry)
|
||||
out_off/out_len — offset/размер out_buf
|
||||
out_timer — таймер повтора send в сокет
|
||||
out_backoff — backoff для retry send в сокет
|
||||
pause_buf — буфер при backpressure socket→ETCP (send_msg fail)
|
||||
pause_len — длина pause_buf
|
||||
pause_waiter — ll_queue waiter на normalizer.input_queue
|
||||
close_timer — таймер повтора CLOSE/ERROR
|
||||
close_backoff — backoff для retry CLOSE/ERROR
|
||||
sock — OS сокет к адресату
|
||||
read_id — handle uasync для событий сокета
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 11. etcp_router waiter API
|
||||
|
||||
```
|
||||
// etcp_router.h
|
||||
void etcp_router_waiter_register(struct UTUN_INSTANCE* inst,
|
||||
uint64_t peer_node_id, struct queue_waiter_handle* h);
|
||||
void etcp_router_waiter_cancel(struct UTUN_INSTANCE* inst,
|
||||
uint64_t peer_node_id, struct queue_waiter_handle* h);
|
||||
|
||||
// Внутри:
|
||||
// conn = route_bgp_find_conn_for_node(peer_node_id)
|
||||
// queue_waiter_wait(conn->normalizer->input, h)
|
||||
// queue_waiter_cancel(conn->normalizer->input, h)
|
||||
//
|
||||
// normalizer.input_queue уже имеет threshold=0 (ждёт полного освобождения)
|
||||
// через queue_set_threshold(input_queue, 0, 0) в etcp.c
|
||||
```
|
||||
@@ -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);
|
||||
@@ -120,7 +120,7 @@ static void raw_read_cb(socket_t sock, void* arg) {
|
||||
memcpy(e->dgram + 20, &icmp_hdr->icmp_seq, 2);
|
||||
if (payload_len > 0) memcpy(e->dgram + ICMP_PROXY_HDR_SIZE, payload, payload_len);
|
||||
e->len = ICMP_PROXY_HDR_SIZE + payload_len;
|
||||
int ret = etcp_route_send(g_icmp_ctx->inst, r->client_node_id, e);
|
||||
int ret = etcp_route_send(g_icmp_ctx->inst, r->client_node_id, e, 0);
|
||||
if (ret != 0) DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: etcp_route_send reply failed: %d", ret);
|
||||
else DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: reply forwarded to client %016llx", (unsigned long long)r->client_node_id);
|
||||
u_free(r);
|
||||
@@ -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);
|
||||
@@ -158,7 +158,7 @@ static void exit_handle_request(struct ETCP_CONN* conn, struct ll_entry* entry)
|
||||
memcpy(e->dgram + 20, &echo_seq, 2);
|
||||
if (payload_len > 0) memcpy(e->dgram + ICMP_PROXY_HDR_SIZE, payload, payload_len);
|
||||
e->len = ICMP_PROXY_HDR_SIZE + payload_len;
|
||||
etcp_route_send(inst, client_node_id, e);
|
||||
etcp_route_send(inst, client_node_id, e, 0);
|
||||
} else queue_entry_free(e);
|
||||
}
|
||||
} else {
|
||||
@@ -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);
|
||||
@@ -223,7 +223,7 @@ int icmp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
|
||||
memcpy(e->dgram + 20, &echo_seq, 2);
|
||||
if (payload_len > 0) memcpy(e->dgram + ICMP_PROXY_HDR_SIZE, payload, payload_len);
|
||||
e->len = ICMP_PROXY_HDR_SIZE + payload_len;
|
||||
return etcp_route_send(inst, exit_node_id, e);
|
||||
return etcp_route_send(inst, exit_node_id, e, 0);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
@@ -232,12 +232,12 @@ int icmp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
|
||||
int icmp_proxy_deliver_reply(struct UTUN_INSTANCE* inst,
|
||||
uint32_t dst_ip, uint32_t src_ip, uint16_t echo_id, uint16_t echo_seq,
|
||||
const uint8_t* payload, size_t payload_len) {
|
||||
if (!inst || !inst->tcp_proxy || !inst->tcp_proxy->tun) {
|
||||
if (!inst || !inst->tcp_proxy_client || !inst->tcp_proxy_client->tun) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: deliver_reply failed — no %s",
|
||||
!inst ? "inst" : !inst->tcp_proxy ? "tcp_proxy" : "TUN");
|
||||
!inst ? "inst" : !inst->tcp_proxy_client ? "tcp_proxy_client" : "TUN");
|
||||
return -1;
|
||||
}
|
||||
struct tun_if* tun = inst->tcp_proxy->tun;
|
||||
struct tun_if* tun = inst->tcp_proxy_client->tun;
|
||||
|
||||
size_t icmp_len = ICMP_MINLEN + payload_len;
|
||||
size_t pkt_len = 20 + icmp_len;
|
||||
@@ -270,8 +270,8 @@ int icmp_proxy_deliver_reply(struct UTUN_INSTANCE* inst,
|
||||
e->dgram = u_malloc(1 + pkt_len);
|
||||
e->dgram[0] = 4; memcpy(e->dgram + 1, pkt, pkt_len); e->len = 1 + pkt_len;
|
||||
u_free(pkt);
|
||||
queue_data_put(tun->input_queue, e);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: reply delivered to TUN id=0x%04x seq=%u", echo_id, echo_seq);
|
||||
queue_data_put(tun->input_queue, e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -306,7 +306,7 @@ int icmp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua) {
|
||||
if (!ctx) return -1;
|
||||
ctx->inst = inst; ctx->ua = ua; ctx->raw_sock = SOCKET_INVALID;
|
||||
ctx->request_timeout_tb = ICMP_TIMEOUT_TB;
|
||||
ctx->is_exit = inst->remote_proxy.enabled;
|
||||
ctx->is_exit = inst->tcp_proxy_server.enabled;
|
||||
ctx->test_loopback = 0;
|
||||
ctx->expire_timer = NULL;
|
||||
g_icmp_ctx = ctx;
|
||||
@@ -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; }
|
||||
File renamed without changes.
@@ -0,0 +1,106 @@
|
||||
Как узнать, что TCP полностью завершился?
|
||||
|
||||
В raw API прямого callback'а вида:
|
||||
|
||||
on_connection_closed()
|
||||
|
||||
нет.
|
||||
|
||||
Обычно используют собственное состояние:
|
||||
|
||||
struct conn {
|
||||
bool closing;
|
||||
};
|
||||
|
||||
и после успешного:
|
||||
|
||||
tcp_close(pcb);
|
||||
conn->closing = true;
|
||||
|
||||
считают соединение закрытым для приложения.
|
||||
|
||||
Сам TCP завершится позже внутри lwIP.
|
||||
|
||||
А вызовется ли err callback?
|
||||
|
||||
Обычно нет.
|
||||
|
||||
tcp_err()
|
||||
|
||||
вызывается при ненормальном завершении:
|
||||
|
||||
RST
|
||||
abort
|
||||
потеря PCB
|
||||
сетевые ошибки
|
||||
|
||||
но не при штатном FIN/ACK закрытии.
|
||||
|
||||
Нужно ли освобождать память после tcp_close()?
|
||||
|
||||
Сам pcb освобождать нельзя:
|
||||
|
||||
tcp_close(pcb);
|
||||
|
||||
/* НЕЛЬЗЯ */
|
||||
mem_free(pcb);
|
||||
|
||||
lwIP сделает это самостоятельно.
|
||||
|
||||
Но ваши данные (arg) нужно освободить самостоятельно.
|
||||
|
||||
Обычно так:
|
||||
|
||||
struct my_conn *c = arg;
|
||||
|
||||
tcp_arg(pcb, NULL);
|
||||
|
||||
free(c);
|
||||
|
||||
tcp_close(pcb);
|
||||
|
||||
или
|
||||
|
||||
free(c);
|
||||
tcp_close(pcb);
|
||||
pcb = NULL;
|
||||
|
||||
если после этого код больше не обращается к c.
|
||||
|
||||
Безопасная практика
|
||||
|
||||
После успешного tcp_close():
|
||||
|
||||
if (tcp_close(pcb) == ERR_OK) {
|
||||
pcb = NULL;
|
||||
}
|
||||
|
||||
и считать объект недействительным.
|
||||
|
||||
Потому что lwIP может уничтожить его в любой момент по мере завершения TCP state machine.
|
||||
|
||||
Типичный шаблон
|
||||
static void close_conn(struct my_conn *c)
|
||||
{
|
||||
err_t err;
|
||||
|
||||
err = tcp_close(c->pcb);
|
||||
|
||||
if (err == ERR_OK) {
|
||||
c->pcb = NULL;
|
||||
free(c);
|
||||
} else {
|
||||
/* ERR_MEM -> попробуем позже */
|
||||
}
|
||||
}
|
||||
|
||||
Но если free(c) делается сразу, нужно быть уверенным, что никакие callback'и lwIP больше не используют arg. Часто перед закрытием сначала снимают callback'и:
|
||||
|
||||
tcp_arg(pcb, NULL);
|
||||
tcp_recv(pcb, NULL);
|
||||
tcp_sent(pcb, NULL);
|
||||
tcp_err(pcb, NULL);
|
||||
|
||||
а затем вызывают tcp_close().
|
||||
|
||||
Это особенно важно в сложных приложениях с собственными структурами соединений.
|
||||
@@ -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
|
||||
@@ -0,0 +1,684 @@
|
||||
// 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"
|
||||
#include "config_parser.h"
|
||||
#include "tun_if.h"
|
||||
#include "utun_instance.h"
|
||||
#include "etcp.h"
|
||||
#include "etcp_api.h"
|
||||
#include "etcp_router.h"
|
||||
#include "tcp_proxy_server.h"
|
||||
#include "udp_proxy.h"
|
||||
#include "icmp_proxy.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/memory_pool.h"
|
||||
#include "../lib/mem.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
#ifndef INADDR_ANY
|
||||
#define INADDR_ANY 0
|
||||
#endif
|
||||
|
||||
// ====================================================================
|
||||
// Предварительные объявления
|
||||
// ====================================================================
|
||||
static err_t tcp_proxy_client_recv_cb(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err);
|
||||
static err_t tcp_proxy_client_sent_cb(void *arg, struct tcp_pcb *pcb, uint16_t len);
|
||||
static void tcp_proxy_client_err_cb(void *arg, err_t err);
|
||||
static err_t tcp_proxy_client_poll_cb(void *arg, struct tcp_pcb *pcb);
|
||||
static err_t tcp_proxy_client_accept_cb(void *arg, struct tcp_pcb *newpcb, err_t err);
|
||||
static err_t tcp_proxy_client_output_cb(void *arg, struct pbuf *p, uint32_t src_ip, uint32_t dst_ip);
|
||||
static void tcp_proxy_client_feed_from_transport(struct tcp_proxy_client_conn *pc);
|
||||
static struct ll_entry* tcp_proxy_client_entry_from_data(struct memory_pool* pool, const uint8_t* data, uint16_t len);
|
||||
static void tcp_proxy_client_conn_free(struct tcp_proxy_client_conn *pc);
|
||||
static int tcp_proxy_client_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 int tcp_proxy_client_send_data(struct tcp_proxy_client_conn* pc, const uint8_t* data, uint16_t len);
|
||||
static void tcp_proxy_client_tx_waiter_cb(struct ll_queue* q, void* arg);
|
||||
|
||||
// ====================================================================
|
||||
// Помощник ll_entry
|
||||
// ====================================================================
|
||||
static struct ll_entry* tcp_proxy_client_entry_from_data(struct memory_pool* pool, const uint8_t* data, uint16_t len) {
|
||||
struct ll_entry* e = queue_entry_new_from_pool(pool);
|
||||
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_entry_from_data: pool exhausted"); return NULL; }
|
||||
e->len = 0; e->dgram = NULL;
|
||||
if (len > 0) {
|
||||
uint8_t* buf = u_malloc(len);
|
||||
if (!buf) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_entry_from_data: malloc(%u) failed", len); queue_entry_free(e); return NULL; }
|
||||
memcpy(buf, data, len); e->dgram = buf; e->len = len;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Протокол: сборка и отправка сообщений прокси через ETCP
|
||||
// ====================================================================
|
||||
static int tcp_proxy_client_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, "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_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);
|
||||
e->len = TCP_PROXY_HDR_SIZE + len;
|
||||
return etcp_route_send(inst, dst, e, force);
|
||||
}
|
||||
|
||||
static int tcp_proxy_client_send_connect(struct tcp_proxy_client_conn* pc) {
|
||||
uint8_t buf[6];
|
||||
memcpy(buf, pc->dest_ip, 4); memcpy(buf + 4, &pc->dest_port, 2);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client: CONNECT sid=%08x to %d.%d.%d.%d:%d via node %016llx",
|
||||
pc->stream_id, pc->dest_ip[0], pc->dest_ip[1], pc->dest_ip[2], pc->dest_ip[3],
|
||||
ntohs(pc->dest_port), (unsigned long long)pc->proxy->via_node_id);
|
||||
return tcp_proxy_client_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
|
||||
TCP_PROXY_SUBCMD_CONNECT, pc->stream_id, buf, 6, 1);
|
||||
}
|
||||
|
||||
static int tcp_proxy_client_send_data(struct tcp_proxy_client_conn* pc, const uint8_t* data, uint16_t len) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "PROXY SEND sid=%08x len=%u", pc->stream_id, len);
|
||||
return tcp_proxy_client_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
|
||||
TCP_PROXY_SUBCMD_DATA, pc->stream_id, data, len, 0);
|
||||
}
|
||||
|
||||
static int tcp_proxy_client_send_close(struct tcp_proxy_client_conn* pc) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "PROXY SEND sid=%08x", pc->stream_id);
|
||||
int ret = tcp_proxy_client_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
|
||||
TCP_PROXY_SUBCMD_CLOSE, pc->stream_id, NULL, 0, 1);
|
||||
if (ret < 0) { pc->close_pending = 1; return -1; }
|
||||
pc->close_pending = 0;
|
||||
pc->close_sent = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tcp_proxy_client_send_error(struct tcp_proxy_client_conn* pc) {
|
||||
int ret = tcp_proxy_client_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
|
||||
TCP_PROXY_SUBCMD_ERROR, pc->stream_id, NULL, 0, 1);
|
||||
if (ret < 0) { pc->close_pending = 1; return -1; }
|
||||
pc->close_pending = 0;
|
||||
pc->close_sent = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tcp_proxy_client_send_fin(struct tcp_proxy_client_conn* pc) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_TRAFFIC, "PROXY FIN RELAY sid=%08x", pc->stream_id);
|
||||
return tcp_proxy_client_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
|
||||
TCP_PROXY_SUBCMD_FIN, pc->stream_id, NULL, 0, 1);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Вывод: lwIP TCP отправляет IP пакеты через этот callback
|
||||
// ====================================================================
|
||||
static err_t tcp_proxy_client_output_cb(void *arg, struct pbuf *p, uint32_t src_ip, uint32_t dst_ip) {
|
||||
struct tcp_proxy_client *proxy = (struct tcp_proxy_client *)arg;
|
||||
(void)src_ip; (void)dst_ip;
|
||||
uint16_t len = p->tot_len;
|
||||
if (len > 2000) return LERR_BUF;
|
||||
uint8_t buf[2002];
|
||||
if (proxy->tun) {
|
||||
pbuf_copy_partial(p, buf, len, 0);
|
||||
ssize_t wr = tun_platform_write(proxy->tun, buf, len);
|
||||
if (wr != (ssize_t)len) {
|
||||
uint16_t ip_total = ((uint16_t)buf[2] << 8) | buf[3];
|
||||
uint32_t sip, dip; memcpy(&sip, buf + 12, 4); memcpy(&dip, buf + 16, 4);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_TRAFFIC, "TUN_WRITE_ERR ret=%zd len=%u ip_total=%u", wr, len, ip_total);
|
||||
}
|
||||
}
|
||||
return LERR_OK;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Обработчик не-TCP (UDP/ICMP прокси)
|
||||
// ====================================================================
|
||||
static int tcp_proxy_client_handle_non_tcp(struct tcp_proxy_client* p, uint8_t* buf, size_t len) {
|
||||
if (len < 20) return 0;
|
||||
uint8_t ip_ver = (buf[0] >> 4) & 0xF;
|
||||
if (ip_ver != 4) return 0;
|
||||
uint8_t proto = buf[9];
|
||||
if (proto == IPPROTO_UDP) {
|
||||
if (len < 28) return 0;
|
||||
uint32_t src_ip, dst_ip; uint16_t src_port, dst_port;
|
||||
memcpy(&src_ip, buf + 12, 4); memcpy(&dst_ip, buf + 16, 4);
|
||||
memcpy(&src_port, buf + 20, 2); memcpy(&dst_port, buf + 22, 2);
|
||||
udp_proxy_send_to_exit(p->inst, p->via_node_id, src_ip, src_port, dst_ip, dst_port, buf + 28, len - 28);
|
||||
return 1;
|
||||
}
|
||||
if (proto == IPPROTO_ICMP) {
|
||||
if (len < 28) return 0;
|
||||
uint8_t icmp_type = buf[20];
|
||||
if (icmp_type != 8) return 0;
|
||||
uint32_t src_ip, dst_ip;
|
||||
memcpy(&src_ip, buf + 12, 4); memcpy(&dst_ip, buf + 16, 4);
|
||||
uint16_t icmp_id, icmp_seq;
|
||||
memcpy(&icmp_id, buf + 24, 2); memcpy(&icmp_seq, buf + 26, 2);
|
||||
icmp_proxy_send_to_exit(p->inst, p->via_node_id, dst_ip, src_ip, icmp_id, icmp_seq, buf + 28, len - 28);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Помощник: передача данных из очереди to_lwip в lwIP TCP
|
||||
// ====================================================================
|
||||
static void tcp_proxy_client_feed_from_transport(struct tcp_proxy_client_conn *pc) {
|
||||
if (!pc->to_lwip || !pc->pcb) return;
|
||||
if (pc->rem_closed) return;
|
||||
int sent_any = 0;
|
||||
uint32_t q_pre = queue_entry_count(pc->to_lwip);
|
||||
while (1) {
|
||||
uint16_t space = tcp_sndbuf(pc->pcb);
|
||||
if (space < TCP_MSS / 2) break;
|
||||
struct ll_entry *e = queue_data_get(pc->to_lwip);
|
||||
if (!e) break;
|
||||
if (e->len <= space) {
|
||||
err_t ret = tcp_write(pc->pcb, e->dgram, e->len, TCP_WRITE_FLAG_COPY);
|
||||
if (ret == LERR_OK) { sent_any = 1; queue_dgram_free(e); queue_entry_free(e); }
|
||||
else { queue_data_put_first(pc->to_lwip, e); break; }
|
||||
} else {
|
||||
queue_data_put_first(pc->to_lwip, e);
|
||||
break;
|
||||
}
|
||||
queue_resume_callback(pc->to_lwip);
|
||||
}
|
||||
queue_resume_callback(pc->to_lwip);
|
||||
if (sent_any) {
|
||||
uint32_t unsent = 0; struct tcp_seg* s;
|
||||
for (s = pc->pcb->unsent; s; s = s->next) unsent++;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "PROXY FEED exit->client sid=%08x fed=%u q=%u->%u snd_wnd=%u cwnd=%u unsent=%u",
|
||||
pc->stream_id, sent_any, q_pre, queue_entry_count(pc->to_lwip), pc->pcb->snd_wnd, pc->pcb->cwnd, unsent);
|
||||
tcp_output(pc->pcb);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// lwIP TCP коллбэки
|
||||
// ====================================================================
|
||||
static err_t tcp_proxy_client_accept_cb(void *arg, struct tcp_pcb *newpcb, err_t err) {
|
||||
struct tcp_proxy_client *p = (struct tcp_proxy_client *)arg;
|
||||
if (err != LERR_OK || !newpcb) return LERR_ABRT;
|
||||
|
||||
struct tcp_proxy_client_conn *pc = u_calloc(1, sizeof(struct tcp_proxy_client_conn));
|
||||
if (!pc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy client: accept alloc failed"); return LERR_MEM; }
|
||||
pc->proxy = p; pc->pcb = newpcb; pc->stream_id = ++p->next_stream_id;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < p->mapping_count; i++) {
|
||||
if (p->mappings[i].local_port == newpcb->local_port) {
|
||||
struct in_addr ra; ra.s_addr = inet_addr(p->mappings[i].remote_ip);
|
||||
memcpy(pc->dest_ip, &ra.s_addr, 4);
|
||||
pc->dest_port = htons(p->mappings[i].remote_port);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == p->mapping_count) {
|
||||
memcpy(pc->dest_ip, &newpcb->local_ip, 4);
|
||||
pc->dest_port = htons(newpcb->local_port);
|
||||
}
|
||||
|
||||
tcp_arg(newpcb, pc);
|
||||
tcp_recv(newpcb, tcp_proxy_client_recv_cb);
|
||||
tcp_sent(newpcb, tcp_proxy_client_sent_cb);
|
||||
tcp_err(newpcb, tcp_proxy_client_err_cb);
|
||||
tcp_poll(newpcb, tcp_proxy_client_poll_cb, 2);
|
||||
tcp_nagle_disable(newpcb);
|
||||
|
||||
pc->to_lwip = queue_new(p->ua, 0, 0, 0, "to_lwip");
|
||||
|
||||
if (tcp_proxy_client_send_connect(pc) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy client: send_connect failed sid=%08x to %d.%d.%d.%d:%d", pc->stream_id, pc->dest_ip[0], pc->dest_ip[1], pc->dest_ip[2], pc->dest_ip[3], ntohs(pc->dest_port)); tcp_arg(newpcb, NULL); tcp_abort(newpcb); queue_free(pc->to_lwip); u_free(pc); return LERR_MEM; }
|
||||
|
||||
pc->next = p->conns; p->conns = pc; p->conn_count++;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client: new conn sid=%08x local_port=%u -> %d.%d.%d.%d:%d total_conns=%d snd_wnd=%u mss=%u",
|
||||
pc->stream_id, newpcb->local_port, pc->dest_ip[0], pc->dest_ip[1], pc->dest_ip[2], pc->dest_ip[3], ntohs(pc->dest_port), p->conn_count, newpcb->snd_wnd, newpcb->mss);
|
||||
return LERR_OK;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Backpressure: waiter callback при освобождении normalizer очереди
|
||||
// ====================================================================
|
||||
static void tcp_proxy_client_tx_waiter_cb(struct ll_queue* q, void* arg) {
|
||||
(void)q;
|
||||
struct tcp_proxy_client_conn* pc = (struct tcp_proxy_client_conn*)arg;
|
||||
if (!pc || !pc->proxy || pc->rem_closed || !pc->tx_buf) return;
|
||||
int ret = tcp_proxy_client_send_data(pc, pc->tx_buf, pc->tx_len);
|
||||
if (ret == 0) {
|
||||
if (pc->pcb) tcp_recved(pc->pcb, pc->tx_len);
|
||||
u_free(pc->tx_buf); pc->tx_buf = NULL; pc->tx_len = 0;
|
||||
if (pc->fin_local && !pc->close_sent && !pc->close_pending) {
|
||||
if (pc->fin_remote || pc->rem_closed)
|
||||
tcp_proxy_client_send_close(pc);
|
||||
else
|
||||
tcp_proxy_client_send_fin(pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static err_t tcp_proxy_client_recv_cb(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) {
|
||||
struct tcp_proxy_client_conn *pc = (struct tcp_proxy_client_conn *)arg;
|
||||
if (!pc || !pc->proxy || pc->rem_closed) { if (p) pbuf_free(p); return LERR_OK; }
|
||||
|
||||
if (p == NULL || err != LERR_OK) {
|
||||
pc->fin_local = 1;
|
||||
if (p == NULL && err == ERR_OK) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY FIN sid=%08x pcb_state=%u sndbuf=%u cwnd=%u unsent=%p unacked=%p",
|
||||
pc->stream_id, pcb->state, pcb->snd_buf, pcb->cwnd,
|
||||
(void*)pcb->unsent, (void*)pcb->unacked);
|
||||
{ uint16_t wnd_gap = TCP_WND_MAX(pcb) - pcb->rcv_wnd;
|
||||
if (wnd_gap > 0) tcp_recved(pcb, wnd_gap); }
|
||||
if (!pc->tx_buf) {
|
||||
tcp_proxy_client_send_fin(pc);
|
||||
if (pc->fin_remote && !pc->close_sent && !pc->close_pending)
|
||||
tcp_proxy_client_send_close(pc);
|
||||
}
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY ERROR recv sid=%08x pcb_state=%u err=%d",
|
||||
pc->stream_id, pcb->state, err);
|
||||
pc->error = 1;
|
||||
tcp_proxy_client_send_error(pc);
|
||||
}
|
||||
return LERR_OK;
|
||||
}
|
||||
|
||||
uint16_t len = p->tot_len;
|
||||
if (pc->error || pc->rem_closed) {
|
||||
tcp_recved(pcb, len); pbuf_free(p); return LERR_OK;
|
||||
}
|
||||
|
||||
if (pc->tx_buf) { pbuf_free(p); return LERR_OK; }
|
||||
|
||||
uint8_t *data = u_malloc(len);
|
||||
if (data) {
|
||||
pbuf_copy_partial(p, data, len, 0);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "PROXY RECV client->exit sid=%08x len=%u", pc->stream_id, len);
|
||||
int ret = tcp_proxy_client_send_data(pc, data, len);
|
||||
if (ret == 0) { tcp_recved(pcb, len); u_free(data); }
|
||||
else {
|
||||
pc->tx_buf = data; pc->tx_len = len;
|
||||
etcp_router_waiter_register(pc->proxy->inst, pc->proxy->via_node_id, &pc->tx_waiter,
|
||||
tcp_proxy_client_tx_waiter_cb, pc);
|
||||
}
|
||||
} else DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY recv malloc(%u) failed sid=%08x", len, pc->stream_id);
|
||||
pbuf_free(p);
|
||||
return LERR_OK;
|
||||
}
|
||||
|
||||
static err_t tcp_proxy_client_sent_cb(void *arg, struct tcp_pcb *pcb, uint16_t len) {
|
||||
(void)len;
|
||||
struct tcp_proxy_client_conn *pc = (struct tcp_proxy_client_conn *)arg;
|
||||
if (!pc || !pc->proxy || pc->rem_closed) return LERR_OK;
|
||||
tcp_proxy_client_feed_from_transport(pc);
|
||||
return LERR_OK;
|
||||
}
|
||||
|
||||
static void tcp_proxy_client_err_cb(void *arg, err_t err) {
|
||||
struct tcp_proxy_client_conn *pc = (struct tcp_proxy_client_conn *)arg;
|
||||
if (!pc || !pc->proxy || pc->rem_closed) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY tcp_proxy_client_err_cb: pc=%p proxy=%p rem_closed=%d err=%d", (void*)pc, pc ? (void*)pc->proxy : NULL, pc ? pc->rem_closed : 0, err); return; }
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy client: error %d sid=%08x pcb_state=%u fin_local=%d rem_closed=%d",
|
||||
err, pc->stream_id, pc->pcb ? pc->pcb->state : 0, pc->fin_local, pc->rem_closed);
|
||||
pc->pcb = NULL;// pcb уже уничтожен стеком lwip
|
||||
pc->error = 1;
|
||||
if (tcp_proxy_client_send_error(pc) < 0) DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY send_error failed sid=%08x", pc->stream_id);
|
||||
}
|
||||
|
||||
static err_t tcp_proxy_client_poll_cb(void *arg, struct tcp_pcb *pcb) {
|
||||
struct tcp_proxy_client_conn *pc = (struct tcp_proxy_client_conn *)arg;
|
||||
if (!pc || !pc->proxy || pc->rem_closed) return LERR_OK;
|
||||
|
||||
if (pc->error) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY CLEANUP error sid=%08x", pc->stream_id);
|
||||
if (pc->pcb) {
|
||||
tcp_arg(pc->pcb, NULL);
|
||||
tcp_recv(pc->pcb, NULL);
|
||||
tcp_sent(pc->pcb, NULL);
|
||||
tcp_err(pc->pcb, NULL);
|
||||
tcp_poll(pc->pcb, NULL, 0);
|
||||
tcp_abort(pc->pcb);
|
||||
pc->pcb = NULL;
|
||||
}
|
||||
tcp_proxy_client_conn_free(pc);
|
||||
return LERR_OK;
|
||||
}
|
||||
|
||||
if (pc->close_pending) {
|
||||
if (pc->error) tcp_proxy_client_send_error(pc);
|
||||
else tcp_proxy_client_send_close(pc);
|
||||
}
|
||||
return LERR_OK;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Обеспечить динамический listen pcb для прозрачного исходящего TCP проксирования
|
||||
// ====================================================================
|
||||
static void tcp_proxy_client_ensure_outbound_listen(struct tcp_proxy_client* p, uint16_t dport_net) {
|
||||
uint16_t dport_host = ntohs(dport_net);
|
||||
struct tcp_pcb* lp = p->lwip->listen_pcbs;
|
||||
while (lp) { if (lp->local_port == dport_host) return; lp = lp->next; }
|
||||
struct tcp_pcb* lpcb = tcp_new(p->lwip);
|
||||
if (!lpcb) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy client: tcp_new failed for dynamic listen port %u", dport_host); return; }
|
||||
tcp_bind(lpcb, INADDR_ANY, dport_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);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client: dynamic listen on port %u", dport_host);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Ввод: IP пакет → lwip_tcp (из TUN output_queue)
|
||||
// ====================================================================
|
||||
static void tcp_proxy_client_tun_input(struct ll_queue* q, void* arg) {
|
||||
struct tcp_proxy_client* p = (struct tcp_proxy_client*)arg;
|
||||
struct ll_entry* entry = queue_data_get(q);
|
||||
if (!entry) return;
|
||||
if (entry->dgram && entry->len > 1) {
|
||||
uint8_t* ip = entry->dgram + 1; size_t len = entry->len - 1;
|
||||
if (len > 20 && len <= 2000) {
|
||||
if (!tcp_proxy_client_handle_non_tcp(p, ip, len)) {
|
||||
uint8_t proto = ip[9];
|
||||
if (proto == IPPROTO_TCP) {
|
||||
uint16_t ip_hdr_len = (ip[0] & 0x0F) * 4;
|
||||
uint16_t ip_total = ((uint16_t)ip[2] << 8) | ip[3];
|
||||
if (ip_hdr_len >= 20 && ip_total >= ip_hdr_len && len >= ip_total) {
|
||||
uint16_t dport_net; memcpy(&dport_net, ip + ip_hdr_len + 2, 2);
|
||||
tcp_proxy_client_ensure_outbound_listen(p, dport_net);
|
||||
uint32_t src_ip, dst_ip;
|
||||
memcpy(&src_ip, ip + 12, 4); memcpy(&dst_ip, ip + 16, 4);
|
||||
uint16_t tcp_len = ip_total - ip_hdr_len;
|
||||
struct pbuf *pb = pbuf_alloc(PBUF_RAW, tcp_len);
|
||||
if (pb) { pbuf_take(pb, ip + ip_hdr_len, tcp_len); lwip_tcp_input(p->lwip, pb, src_ip, dst_ip); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
queue_dgram_free(entry); queue_entry_free(entry); queue_resume_callback(q);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Очистка tcp_proxy_client_conn
|
||||
// ====================================================================
|
||||
static void tcp_proxy_client_conn_free(struct tcp_proxy_client_conn *pc) {
|
||||
if (!pc) return;
|
||||
struct tcp_proxy_client *p = pc->proxy;
|
||||
uint32_t pending = pc->to_lwip ? queue_entry_count(pc->to_lwip) : 0;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY FREE sid=%08x total_conns=%d to_lwip_q=%u",
|
||||
pc->stream_id, p->conn_count, pending);
|
||||
if (pc->close_pending && p && p->inst) {
|
||||
pc->close_pending = 0;
|
||||
uint8_t subcmd = pc->error ? TCP_PROXY_SUBCMD_ERROR : TCP_PROXY_SUBCMD_CLOSE;
|
||||
tcp_proxy_client_send_msg(p->inst, p->via_node_id, subcmd, pc->stream_id, NULL, 0, 1);
|
||||
}
|
||||
struct tcp_proxy_client_conn **prev = &p->conns;
|
||||
while (*prev) { if (*prev == pc) { *prev = pc->next; p->conn_count--; break; } prev = &(*prev)->next; }
|
||||
if (pc->to_lwip) {
|
||||
struct ll_entry *e;
|
||||
while ((e = queue_data_get(pc->to_lwip))) { queue_dgram_free(e); queue_entry_free(e); }
|
||||
queue_free(pc->to_lwip); pc->to_lwip = NULL;
|
||||
}
|
||||
if (pc->tx_buf) { u_free(pc->tx_buf); pc->tx_buf = NULL; pc->tx_len = 0; }
|
||||
if (pc->proxy && pc->proxy->inst) etcp_router_waiter_cancel(pc->proxy->inst, pc->proxy->via_node_id, &pc->tx_waiter);
|
||||
u_free(pc);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Обработчики входящих сообщений (сторона клиента)
|
||||
// ====================================================================
|
||||
static struct tcp_proxy_client_conn* tcp_proxy_client_find_conn(struct tcp_proxy_client* p, uint32_t stream_id) {
|
||||
struct tcp_proxy_client_conn* pc;
|
||||
for (pc = p->conns; pc; pc = pc->next) if (pc->stream_id == stream_id) return pc;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void tcp_proxy_client_handle_data(struct tcp_proxy_client* p, struct ETCP_CONN* conn, uint32_t stream_id, struct ll_entry* entry) {
|
||||
struct tcp_proxy_client_conn* pc = tcp_proxy_client_find_conn(p, stream_id);
|
||||
if (!pc || pc->rem_closed || pc->error) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY DATA sid=%08x — no conn/closed, dropping", stream_id);
|
||||
queue_dgram_free(entry); queue_entry_free(entry); return;
|
||||
}
|
||||
size_t data_len = entry->len - TCP_PROXY_HDR_SIZE;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "PROXY DATA <- sid=%08x len=%zu", stream_id, data_len);
|
||||
if (data_len > 0) {
|
||||
struct ll_entry* e = tcp_proxy_client_entry_from_data(pc->proxy->entry_pool, entry->dgram + TCP_PROXY_HDR_SIZE, (uint16_t)data_len);
|
||||
if (e) queue_data_put(pc->to_lwip, e);
|
||||
tcp_proxy_client_feed_from_transport(pc);
|
||||
}
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
}
|
||||
|
||||
static void tcp_proxy_client_handle_close(struct tcp_proxy_client* p, uint32_t stream_id) {
|
||||
struct tcp_proxy_client_conn* pc = tcp_proxy_client_find_conn(p, stream_id);
|
||||
if (!pc) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "PROXY CLOSE sid=%08x — no conn, dropping", stream_id); return; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY REM_CLOSED sid=%08x fin_local=%d pcb_state=%u", stream_id, pc->fin_local, pc->pcb ? pc->pcb->state : 0);
|
||||
pc->rem_closed = 1;
|
||||
if (pc->tx_buf) { u_free(pc->tx_buf); pc->tx_buf = NULL; pc->tx_len = 0; }
|
||||
etcp_router_waiter_cancel(pc->proxy->inst, pc->proxy->via_node_id, &pc->tx_waiter);
|
||||
if (pc->pcb) {
|
||||
tcp_arg(pc->pcb, NULL);
|
||||
tcp_recv(pc->pcb, NULL);
|
||||
tcp_sent(pc->pcb, NULL);
|
||||
tcp_err(pc->pcb, NULL);
|
||||
tcp_poll(pc->pcb, NULL, 0);
|
||||
tcp_close(pc->pcb);
|
||||
pc->pcb = NULL;
|
||||
}
|
||||
tcp_proxy_client_conn_free(pc);
|
||||
}
|
||||
|
||||
static void tcp_proxy_client_handle_error(struct tcp_proxy_client* p, uint32_t stream_id) {
|
||||
struct tcp_proxy_client_conn* pc = tcp_proxy_client_find_conn(p, stream_id);
|
||||
if (!pc) { DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY ERROR sid=%08x — no conn, dropping", stream_id); return; }
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY ERROR from exit sid=%08x fin_local=%d", stream_id, pc->fin_local);
|
||||
if (pc->pcb) {
|
||||
tcp_arg(pc->pcb, NULL);
|
||||
tcp_recv(pc->pcb, NULL);
|
||||
tcp_sent(pc->pcb, NULL);
|
||||
tcp_err(pc->pcb, NULL);
|
||||
tcp_poll(pc->pcb, NULL, 0);
|
||||
tcp_close(pc->pcb);
|
||||
pc->pcb = NULL;
|
||||
}
|
||||
pc->rem_closed = 1;
|
||||
if (pc->tx_buf) { u_free(pc->tx_buf); pc->tx_buf = NULL; pc->tx_len = 0; }
|
||||
etcp_router_waiter_cancel(pc->proxy->inst, pc->proxy->via_node_id, &pc->tx_waiter);
|
||||
tcp_proxy_client_conn_free(pc);
|
||||
}
|
||||
|
||||
static void tcp_proxy_client_handle_fin(struct tcp_proxy_client* p, uint32_t stream_id) {
|
||||
struct tcp_proxy_client_conn* pc = tcp_proxy_client_find_conn(p, stream_id);
|
||||
if (!pc || !pc->pcb) return;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY FIN FROM exit sid=%08x pcb_state=%u — shutdown write (send FIN to local)", stream_id, pc->pcb->state);
|
||||
pc->fin_remote = 1;
|
||||
if (pc->pcb->state != TIME_WAIT && pc->pcb->state != CLOSED) {
|
||||
tcp_shutdown(pc->pcb, 0, 1);
|
||||
tcp_sent(pc->pcb, NULL);
|
||||
tcp_poll(pc->pcb, NULL, 0);
|
||||
}
|
||||
if (pc->fin_local && !pc->close_sent && !pc->close_pending)
|
||||
tcp_proxy_client_send_close(pc);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// ETCP коллбэк клиентской стороны (принимает DATA/CLOSE/ERROR/FIN от сервера)
|
||||
// ====================================================================
|
||||
void tcp_proxy_client_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
struct UTUN_INSTANCE* inst = conn ? conn->instance : NULL;
|
||||
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_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",
|
||||
(unsigned long long)peer_id);
|
||||
if (proxy) {
|
||||
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); }
|
||||
return;
|
||||
}
|
||||
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_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);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Публичное API
|
||||
// ====================================================================
|
||||
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,
|
||||
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; }
|
||||
|
||||
int need_tun = tun_name && tun_name[0] && tun_ip && tun_ip[0];
|
||||
if (!need_tun && !socks_enabled && !http_proxy_enabled) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: no proxy mode enabled"); return NULL;
|
||||
}
|
||||
|
||||
struct tcp_proxy_client* p = u_calloc(1, sizeof(struct tcp_proxy_client));
|
||||
if (!p) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: u_calloc failed"); return NULL; }
|
||||
p->inst = inst; p->ua = ua; p->next_stream_id = 1;
|
||||
p->via_node_id = via_node_id;
|
||||
p->mappings = mappings; p->mapping_count = mapping_count;
|
||||
p->socks_enabled = socks_enabled;
|
||||
p->http_proxy_enabled = http_proxy_enabled;
|
||||
|
||||
p->entry_pool = memory_pool_init(sizeof(struct ll_entry), "entry_pool");
|
||||
if (!p->entry_pool) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: memory_pool_init failed"); u_free(p); return NULL; }
|
||||
|
||||
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; }
|
||||
|
||||
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_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_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: 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;
|
||||
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;
|
||||
if (pc->pcb && pc->pcb->state != CLOSED) {
|
||||
tcp_arg(pc->pcb, NULL);
|
||||
tcp_recv(pc->pcb, NULL);
|
||||
tcp_sent(pc->pcb, NULL);
|
||||
tcp_err(pc->pcb, NULL);
|
||||
tcp_poll(pc->pcb, NULL, 0);
|
||||
tcp_abort(pc->pcb);
|
||||
}
|
||||
pc->pcb = NULL;
|
||||
if (pc->to_lwip) {
|
||||
struct ll_entry *e;
|
||||
while ((e = queue_data_get(pc->to_lwip))) { queue_dgram_free(e); queue_entry_free(e); }
|
||||
queue_free(pc->to_lwip); pc->to_lwip = NULL;
|
||||
}
|
||||
if (pc->tx_buf) { u_free(pc->tx_buf); pc->tx_buf = NULL; pc->tx_len = 0; }
|
||||
if (p->inst) etcp_router_waiter_cancel(p->inst, p->via_node_id, &pc->tx_waiter);
|
||||
u_free(pc); pc = next;
|
||||
}
|
||||
p->conns = NULL;
|
||||
|
||||
if (p->lwip) { lwip_tcp_destroy(p->lwip); p->lwip = NULL; }
|
||||
|
||||
if (p->tun) tun_close(p->tun);
|
||||
if (p->entry_pool) memory_pool_destroy(p->entry_pool);
|
||||
u_free(p);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// tcp_proxy_client.h — TCP прокси-клиент: стек lwIP TCP → ETCP → удалённый exit узел
|
||||
#ifndef TCP_PROXY_CLIENT_H
|
||||
#define TCP_PROXY_CLIENT_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "../lib/socket_compat.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
|
||||
struct UASYNC;
|
||||
struct UTUN_INSTANCE;
|
||||
struct ETCP_CONN;
|
||||
struct tun_if;
|
||||
struct ll_queue;
|
||||
struct ll_entry;
|
||||
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;
|
||||
|
||||
struct tcp_proxy_client_conn {
|
||||
struct tcp_proxy_client_conn* next;
|
||||
struct tcp_proxy_client* proxy;
|
||||
struct tcp_pcb* pcb;
|
||||
|
||||
uint32_t stream_id;
|
||||
|
||||
struct ll_queue* to_lwip; // DATA от exit → lwIP (управление потоком)
|
||||
|
||||
uint8_t fin_local; // локальная сторона отправила FIN (lwIP)
|
||||
uint8_t fin_remote; // exit отправил FIN (назначение закрылось)
|
||||
uint8_t rem_closed; // exit отправил CLOSE
|
||||
uint8_t error; // ошибка, немедленная очистка
|
||||
uint8_t close_sent; // отправили CLOSE/ERROR в exit
|
||||
uint8_t close_pending; // CLOSE/ERROR не доставлен, ждём повтора
|
||||
|
||||
uint8_t* tx_buf; // буфер при backpressure (lwIP→ETCP send fail)
|
||||
uint16_t tx_len;
|
||||
struct queue_waiter_handle tx_waiter;
|
||||
|
||||
uint8_t dest_ip[4];
|
||||
uint16_t dest_port;
|
||||
};
|
||||
|
||||
struct tcp_proxy_client {
|
||||
struct UTUN_INSTANCE* inst;
|
||||
struct UASYNC* ua;
|
||||
struct tun_if* tun;
|
||||
struct tcp_proxy_client_conn* conns;
|
||||
int conn_count;
|
||||
struct memory_pool* entry_pool;
|
||||
uint32_t next_stream_id;
|
||||
uint64_t via_node_id;
|
||||
struct lwip_tcp_ctx* lwip;
|
||||
|
||||
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,
|
||||
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);
|
||||
|
||||
#endif // TCP_PROXY_CLIENT_H
|
||||
@@ -0,0 +1,559 @@
|
||||
// tcp_proxy_server.c — TCP прокси-сервер (exit node)
|
||||
#include "tcp_proxy_server.h"
|
||||
#include "udp_proxy.h"
|
||||
#include "icmp_proxy.h"
|
||||
#include "etcp.h"
|
||||
#include "etcp_api.h"
|
||||
#include "etcp_router.h"
|
||||
#include "utun_instance.h"
|
||||
#include "config_parser.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/mem.h"
|
||||
#include "../lib/tcp_io.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
static struct tcp_proxy_server* g_tcp_proxy_server_ctx = NULL;
|
||||
|
||||
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_flushed_cb(struct tcp_conn* tc, void* arg);
|
||||
static void on_closed_cb(struct tcp_conn* tc, void* arg);
|
||||
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);
|
||||
static void send_error(struct tcp_proxy_server_conn* rc);
|
||||
void tcp_proxy_server_conn_free(struct tcp_proxy_server_conn* rc);
|
||||
|
||||
static inline int write_pending(struct tcp_conn* tc) {
|
||||
return tc && (tc->write_buf || tc->write_queue->head);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Отправка сообщений через 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, "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_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);
|
||||
}
|
||||
|
||||
static void close_retry_cb(void* arg) {
|
||||
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
|
||||
if (!rc) return;
|
||||
rc->close_timer = NULL;
|
||||
if (!rc->close_pending) return;
|
||||
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
|
||||
if (!inst) return;
|
||||
uint8_t subcmd = (rc->tc && rc->tc->error) ? TCP_PROXY_SUBCMD_ERROR : TCP_PROXY_SUBCMD_CLOSE;
|
||||
if (send_msg(inst, rc->peer_node_id, subcmd, rc->stream_id, NULL, 0, 1) < 0) {
|
||||
rc->close_backoff = rc->close_backoff < 5000 ? rc->close_backoff * 2 : 5000;
|
||||
rc->close_timer = uasync_set_timeout(rc->ua, rc->close_backoff, rc, close_retry_cb, "tps_close_retry");
|
||||
return;
|
||||
}
|
||||
rc->close_pending = 0;
|
||||
}
|
||||
|
||||
static void send_close(struct tcp_proxy_server_conn* rc) {
|
||||
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
|
||||
if (!inst) return;
|
||||
if (send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_CLOSE, rc->stream_id, NULL, 0, 1) < 0) {
|
||||
rc->close_pending = 1;
|
||||
rc->close_backoff = 50;
|
||||
if (!rc->close_timer)
|
||||
rc->close_timer = uasync_set_timeout(rc->ua, rc->close_backoff, rc, close_retry_cb, "tps_close_retry");
|
||||
}
|
||||
}
|
||||
|
||||
static void send_error(struct tcp_proxy_server_conn* rc) {
|
||||
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
|
||||
if (!inst) return;
|
||||
if (send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_ERROR, rc->stream_id, NULL, 0, 1) < 0) {
|
||||
rc->close_pending = 1;
|
||||
rc->close_backoff = 50;
|
||||
if (!rc->close_timer)
|
||||
rc->close_timer = uasync_set_timeout(rc->ua, rc->close_backoff, rc, close_retry_cb, "tps_close_retry");
|
||||
}
|
||||
}
|
||||
|
||||
static void send_fin(struct tcp_proxy_server_conn* rc) {
|
||||
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
|
||||
if (!inst) return;
|
||||
send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_FIN, rc->stream_id, NULL, 0, 1);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Коллбэки tcp_io
|
||||
// ====================================================================
|
||||
|
||||
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_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) {
|
||||
send_close(rc); tcp_conn_push_close(tc);
|
||||
} else if (pend_w) {
|
||||
tcp_conn_set_flushed(tc, on_flushed_cb);
|
||||
} else if (pend_r) {
|
||||
rc->dst_fin_deferred = 1;
|
||||
} else {
|
||||
send_fin(rc);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_flushed_cb(struct tcp_conn* tc, void* arg) {
|
||||
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
|
||||
if (rc->cli_closed) return;
|
||||
if (tc->fin_local) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FLUSHED fd=%d sid=%08x fin_local=%d — both FINs, closing",
|
||||
(int)tc->sock, rc->stream_id, tc->fin_local);
|
||||
send_close(rc); tcp_conn_push_close(tc); return;
|
||||
}
|
||||
send_fin(rc);
|
||||
}
|
||||
|
||||
static void on_closed_cb(struct tcp_conn* tc, void* arg) {
|
||||
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:CLOSED fd=%d sid=%08x — freeing",
|
||||
tc ? (int)tc->sock : -1, rc->stream_id);
|
||||
tcp_proxy_server_conn_free(rc);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
int ent_f = tc->entry_pool->free_count;
|
||||
int dat_f = tc->data_pool->free_count;
|
||||
|
||||
int rcv_buf = 0, snd_buf = 0;
|
||||
socklen_t optlen = sizeof(int);
|
||||
getsockopt(tc->sock, SOL_SOCKET, SO_RCVBUF, &rcv_buf, &optlen);
|
||||
getsockopt(tc->sock, SOL_SOCKET, SO_SNDBUF, &snd_buf, &optlen);
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET,
|
||||
"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",
|
||||
(int)tc->sock, rc->stream_id, tc->connected, tc->fin_remote,
|
||||
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",
|
||||
ent_f, dat_f,
|
||||
(size_t)rcv_buf, (size_t)snd_buf);
|
||||
|
||||
rc->diag_timer = uasync_set_timeout(rc->ua, 10000, rc, diag_timer_cb, "tps_diag");
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Дрейн read_queue → ETCP (автозабор + backpressure)
|
||||
// ====================================================================
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
queue_entry_free(e);
|
||||
e = queue_data_get(q);
|
||||
} while (e);
|
||||
queue_resume_callback(q);
|
||||
return;
|
||||
}
|
||||
int ret = send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_DATA, rc->stream_id, e->dgram, e->len, 0);
|
||||
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 {
|
||||
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 || 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");
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Управление жизненным циклом коннекта
|
||||
// ====================================================================
|
||||
|
||||
static int conn_total(struct tcp_proxy_server_conn* rc) {
|
||||
if (!rc || !rc->ctx) return 0;
|
||||
int n = 0; struct tcp_proxy_server_conn* c;
|
||||
for (c = rc->ctx->conns; c; c = c->next) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
void tcp_proxy_server_conn_free(struct tcp_proxy_server_conn* rc) {
|
||||
if (!rc) return;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FREE enter rc=%p freed=%d sid=%08x", (void*)rc, rc->freed, rc->stream_id);
|
||||
if (rc->freed) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:FREE double rc=%p — IGNORED", (void*)rc);
|
||||
return;
|
||||
}
|
||||
rc->freed = 1;
|
||||
int total = conn_total(rc);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FREE fd=%d sid=%08x total=%d cli_closed=%d fin=%d error=%d",
|
||||
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;
|
||||
uint8_t subcmd = (rc->tc && rc->tc->error) ? TCP_PROXY_SUBCMD_ERROR : TCP_PROXY_SUBCMD_CLOSE;
|
||||
send_msg(rc->ctx->inst, rc->peer_node_id, subcmd, rc->stream_id, NULL, 0, 1);
|
||||
}
|
||||
if (rc->ctx) {
|
||||
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->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->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);
|
||||
}
|
||||
|
||||
struct tcp_proxy_server_conn* tcp_proxy_server_find_conn(struct tcp_proxy_server* ctx, uint32_t stream_id) {
|
||||
struct tcp_proxy_server_conn* c;
|
||||
for (c = ctx->conns; c; c = c->next) if (c->stream_id == stream_id) return c;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Входные точки из ETCP-диспетчера
|
||||
// ====================================================================
|
||||
|
||||
int tcp_proxy_server_handle_connect(struct UTUN_INSTANCE* inst, struct ll_entry* entry, uint32_t stream_id, uint64_t src_node_id) {
|
||||
if (!inst || !inst->tcp_proxy_server.enabled) { if (entry) { queue_dgram_free(entry); queue_entry_free(entry); } return -1; }
|
||||
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
|
||||
if (entry->len < TCP_PROXY_CONNECT_HDR_SIZE) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: CONNECT too short len=%u", entry->len); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
|
||||
uint8_t* dest_ip = entry->dgram + TCP_PROXY_HDR_SIZE;
|
||||
uint16_t dest_port = 0; memcpy(&dest_port, dest_ip + 4, 2);
|
||||
|
||||
struct tcp_proxy_server_conn* rc = u_calloc(1, sizeof(struct tcp_proxy_server_conn));
|
||||
if (!rc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: u_calloc failed sid=%08x", stream_id); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
|
||||
rc->ctx = ctx; rc->stream_id = stream_id; rc->peer_node_id = src_node_id;
|
||||
memcpy(rc->dest_ip, dest_ip, 4); rc->dest_port = dest_port;
|
||||
rc->ua = inst->ua;
|
||||
|
||||
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: socket() failed"); u_free(rc); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
|
||||
ctx->conn_count++;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:NEW fd=%d sid=%08x dest=%d.%d.%d.%d:%d total=%d",
|
||||
(int)sock, stream_id, dest_ip[0],dest_ip[1],dest_ip[2],dest_ip[3],ntohs(dest_port), ctx->conn_count);
|
||||
socket_set_nonblocking(sock);
|
||||
|
||||
rc->tc = tcp_conn_create(inst->ua, sock, 1500, 8192, 4, 0, inst->config->global.tcp_recv_buf, on_fin_cb, on_error_cb, rc);
|
||||
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);
|
||||
queue_set_waiter_defer(rc->tc->read_queue, 1);
|
||||
|
||||
struct sockaddr_in addr; memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET; memcpy(&addr.sin_addr.s_addr, dest_ip, 4); addr.sin_port = dest_port;
|
||||
int ret = connect(sock, (struct sockaddr*)&addr, sizeof(addr));
|
||||
if (ret < 0 && errno != EINPROGRESS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: connect() to %d.%d.%d.%d:%d failed: %s",
|
||||
dest_ip[0], dest_ip[1], dest_ip[2], dest_ip[3], ntohs(dest_port), strerror(errno));
|
||||
send_msg(inst, src_node_id, TCP_PROXY_SUBCMD_ERROR, stream_id, NULL, 0, 1);
|
||||
ctx->conn_count--; tcp_conn_destroy(rc->tc); u_free(rc);
|
||||
queue_dgram_free(entry); queue_entry_free(entry); return -1;
|
||||
}
|
||||
|
||||
rc->next = ctx->conns; ctx->conns = rc;
|
||||
rc->diag_timer = uasync_set_timeout(rc->ua, 10000, rc, diag_timer_cb, "tps_diag");
|
||||
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tcp_proxy_server_handle_data(struct UTUN_INSTANCE* inst, struct ETCP_CONN* conn, struct ll_entry* entry, uint32_t stream_id) {
|
||||
(void)conn;
|
||||
if (!inst) { queue_dgram_free(entry); queue_entry_free(entry); return -1; }
|
||||
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->sock == SOCKET_INVALID || rc->tc->error || rc->cli_closed) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TPS handle_data: no/closed conn sid=%08x, dropping", stream_id);
|
||||
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) {
|
||||
memcpy(buf, entry->dgram + TCP_PROXY_HDR_SIZE, data_len);
|
||||
e->dgram = buf; e->len = (uint16_t)data_len;
|
||||
queue_data_put(rc->tc->write_queue, e);
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_server: handle_data alloc failed sid=%08x", stream_id);
|
||||
if (e) queue_entry_free(e);
|
||||
if (buf) memory_pool_free(rc->tc->data_pool, buf);
|
||||
}
|
||||
}
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tcp_proxy_server_handle_close(struct UTUN_INSTANCE* inst, uint32_t stream_id) {
|
||||
if (!inst) return;
|
||||
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
|
||||
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(ctx, stream_id);
|
||||
if (!rc) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy server: CLOSE sid=%08x — no conn", stream_id); return; }
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:CLOSE_RECV fd=%d sid=%08x total=%d fin=%d write_pend=%d",
|
||||
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;
|
||||
if (!rc->tc) { tcp_proxy_server_conn_free(rc); return; }
|
||||
tcp_conn_pause_read(rc->tc);
|
||||
queue_set_callback(rc->tc->read_queue, NULL, NULL);
|
||||
{
|
||||
struct ll_entry* e;
|
||||
while ((e = queue_data_get(rc->tc->read_queue))) {
|
||||
memory_pool_free(rc->tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
}
|
||||
}
|
||||
if (rc->tc->connected) tcp_conn_push_close(rc->tc); else tcp_proxy_server_conn_free(rc);
|
||||
}
|
||||
|
||||
void tcp_proxy_server_handle_error(struct UTUN_INSTANCE* inst, uint32_t stream_id) {
|
||||
if (!inst) return;
|
||||
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
|
||||
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(ctx, stream_id);
|
||||
if (!rc) { DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy server: ERROR sid=%08x — no conn", stream_id); return; }
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:ERROR_RECV fd=%d sid=%08x total=%d fin=%d write_pend=%d",
|
||||
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);
|
||||
}
|
||||
|
||||
void tcp_proxy_server_handle_fin(struct UTUN_INSTANCE* inst, uint32_t stream_id) {
|
||||
if (!inst) return;
|
||||
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
|
||||
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(ctx, stream_id);
|
||||
if (!rc || !rc->tc || !rc->tc->connected) return;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FIN_RECV fd=%d sid=%08x — pushing FIN to wq",
|
||||
(int)rc->tc->sock, stream_id);
|
||||
tcp_conn_push_fin(rc->tc);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// ETCP коллбэк серверной стороны (принимает CONNECT/DATA/CLOSE/ERROR/FIN от клиента)
|
||||
// ====================================================================
|
||||
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_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",
|
||||
(unsigned long long)peer_id);
|
||||
if (inst && inst->tcp_proxy_server.enabled) {
|
||||
struct tcp_proxy_server_conn *rc, *next;
|
||||
for (rc = inst->tcp_proxy_server.conns; rc; rc = next) {
|
||||
next = rc->next;
|
||||
if (rc->peer_node_id == peer_id) tcp_proxy_server_conn_free(rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
|
||||
return;
|
||||
}
|
||||
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);
|
||||
return;
|
||||
}
|
||||
if (inst && inst->tcp_proxy_server.enabled) {
|
||||
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(&inst->tcp_proxy_server, stream_id);
|
||||
if (rc) {
|
||||
if (subcmd == TCP_PROXY_SUBCMD_DATA) { tcp_proxy_server_handle_data(inst, conn, entry, stream_id); return; }
|
||||
if (subcmd == TCP_PROXY_SUBCMD_CLOSE) { tcp_proxy_server_handle_close(inst, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
if (subcmd == TCP_PROXY_SUBCMD_ERROR) { tcp_proxy_server_handle_error(inst, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
if (subcmd == TCP_PROXY_SUBCMD_FIN) { tcp_proxy_server_handle_fin(inst, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
}
|
||||
}
|
||||
if (subcmd == TCP_PROXY_SUBCMD_DATA || subcmd == TCP_PROXY_SUBCMD_CLOSE || subcmd == TCP_PROXY_SUBCMD_FIN || subcmd == TCP_PROXY_SUBCMD_ERROR) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "PROXY subcmd=%02x sid=%08x — server conn not found, silent drop", subcmd, stream_id);
|
||||
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);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Инициализация / деинициализация
|
||||
// ====================================================================
|
||||
|
||||
int tcp_proxy_server_init(struct UTUN_INSTANCE* inst) {
|
||||
if (!inst) return -1;
|
||||
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
if (!inst->config) return 0;
|
||||
ctx->enabled = inst->config->global.tcp_proxy_server_enabled;
|
||||
ctx->inst = inst;
|
||||
if (!ctx->enabled) return 0;
|
||||
g_tcp_proxy_server_ctx = ctx;
|
||||
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);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy server initialized node=%016llx", (unsigned long long)inst->node_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tcp_proxy_server_destroy(struct UTUN_INSTANCE* inst) {
|
||||
if (!inst) return;
|
||||
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy server destroying: conn_count=%d", ctx->conn_count);
|
||||
struct tcp_proxy_server_conn* rc = ctx->conns;
|
||||
while (rc) { struct tcp_proxy_server_conn* next = rc->next; tcp_proxy_server_conn_free(rc); rc = next; }
|
||||
ctx->conns = NULL; ctx->enabled = 0;
|
||||
if (g_tcp_proxy_server_ctx == ctx) g_tcp_proxy_server_ctx = NULL;
|
||||
udp_proxy_destroy(inst);
|
||||
icmp_proxy_destroy(inst);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy server destroyed");
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// tcp_proxy_server.h — TCP прокси-сервер (exit node)
|
||||
#ifndef TCP_PROXY_SERVER_H
|
||||
#define TCP_PROXY_SERVER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "../lib/socket_compat.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/tcp_io.h"
|
||||
|
||||
struct UTUN_INSTANCE;
|
||||
struct UASYNC;
|
||||
struct ll_entry;
|
||||
struct ETCP_CONN;
|
||||
|
||||
#define TCP_PROXY_SUBCMD_CONNECT 0x01
|
||||
#define TCP_PROXY_SUBCMD_DATA 0x03
|
||||
#define TCP_PROXY_SUBCMD_CLOSE 0x04
|
||||
#define TCP_PROXY_SUBCMD_ERROR 0x05
|
||||
#define TCP_PROXY_SUBCMD_FIN 0x06
|
||||
|
||||
#define TCP_PROXY_HDR_SIZE 6 // svc_id(1)+subcmd(1)+stream_id(4)
|
||||
#define TCP_PROXY_CONNECT_HDR_SIZE 12 // HDR_SIZE + dest_ip(4)+dest_port(2)
|
||||
|
||||
struct tcp_proxy_server_conn {
|
||||
struct tcp_proxy_server_conn* next;
|
||||
struct tcp_proxy_server* ctx;
|
||||
struct tcp_conn* tc;
|
||||
uint32_t stream_id;
|
||||
uint64_t peer_node_id;
|
||||
uint8_t dest_ip[4];
|
||||
uint16_t dest_port;
|
||||
|
||||
uint8_t cli_closed; // client sent CLOSE
|
||||
uint8_t close_pending; // CLOSE/ERROR не доставлен, ретрай
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
struct tcp_proxy_server {
|
||||
int enabled;
|
||||
int conn_count;
|
||||
struct tcp_proxy_server_conn* conns;
|
||||
struct UTUN_INSTANCE* inst;
|
||||
};
|
||||
|
||||
int tcp_proxy_server_init(struct UTUN_INSTANCE* inst);
|
||||
void tcp_proxy_server_destroy(struct UTUN_INSTANCE* inst);
|
||||
|
||||
struct tcp_proxy_server_conn* tcp_proxy_server_find_conn(struct tcp_proxy_server* ctx, uint32_t stream_id);
|
||||
void tcp_proxy_server_conn_free(struct tcp_proxy_server_conn* rc);
|
||||
|
||||
void tcp_proxy_server_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry);
|
||||
|
||||
int tcp_proxy_server_handle_connect(struct UTUN_INSTANCE* inst, struct ll_entry* entry,
|
||||
uint32_t stream_id, uint64_t src_node_id);
|
||||
int tcp_proxy_server_handle_data(struct UTUN_INSTANCE* inst, struct ETCP_CONN* conn, struct ll_entry* entry, uint32_t stream_id);
|
||||
void tcp_proxy_server_handle_close(struct UTUN_INSTANCE* inst, uint32_t stream_id);
|
||||
void tcp_proxy_server_handle_error(struct UTUN_INSTANCE* inst, uint32_t stream_id);
|
||||
void tcp_proxy_server_handle_fin(struct UTUN_INSTANCE* inst, uint32_t stream_id);
|
||||
|
||||
#endif
|
||||
@@ -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
|
||||
@@ -64,7 +64,7 @@ static void flow_read_cb(socket_t sock, void* arg) {
|
||||
memcpy(e->dgram + UDP_PROXY_HDR_SIZE, buf, n);
|
||||
e->len = UDP_PROXY_HDR_SIZE + n;
|
||||
|
||||
etcp_route_send(g_udp_ctx->inst, f->client_node_id, e);
|
||||
etcp_route_send(g_udp_ctx->inst, f->client_node_id, e, 0);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
@@ -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);
|
||||
@@ -169,7 +169,7 @@ int udp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
|
||||
memcpy(e->dgram + 20, &dst_port, 2);
|
||||
if (payload_len > 0) memcpy(e->dgram + UDP_PROXY_HDR_SIZE, payload, payload_len);
|
||||
e->len = UDP_PROXY_HDR_SIZE + payload_len;
|
||||
return etcp_route_send(inst, exit_node_id, e);
|
||||
return etcp_route_send(inst, exit_node_id, e, 0);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
@@ -179,7 +179,7 @@ int udp_proxy_deliver_reply(struct UTUN_INSTANCE* inst,
|
||||
uint32_t src_ip, uint16_t src_port,
|
||||
uint32_t dst_ip, uint16_t dst_port,
|
||||
const uint8_t* payload, size_t payload_len) {
|
||||
if (!inst || !inst->tcp_proxy || !inst->tcp_proxy->tun) return -1;
|
||||
if (!inst || !inst->tcp_proxy_client || !inst->tcp_proxy_client->tun) return -1;
|
||||
|
||||
// Собрать IP/UDP ответный пакет
|
||||
size_t ip_len = 20 + 8 + payload_len;
|
||||
@@ -208,7 +208,7 @@ int udp_proxy_deliver_reply(struct UTUN_INSTANCE* inst,
|
||||
e->dgram = u_malloc(1 + ip_len);
|
||||
e->dgram[0] = 4; memcpy(e->dgram + 1, pkt, ip_len); e->len = 1 + ip_len;
|
||||
u_free(pkt);
|
||||
queue_data_put(inst->tcp_proxy->tun->input_queue, e);
|
||||
queue_data_put(inst->tcp_proxy_client->tun->input_queue, e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -242,10 +242,10 @@ int udp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua) {
|
||||
ctx->inst = inst; ctx->ua = ua;
|
||||
ctx->flow_timeout_tb = UDP_FLOW_TIMEOUT_TB;
|
||||
ctx->expire_timer = NULL;
|
||||
ctx->is_exit = inst->remote_proxy.enabled;
|
||||
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;
|
||||
File renamed without changes.
Loaded 100 of 587 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user