Compare commits
48
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
487cb8718e | ||
|
|
42d96f118d | ||
|
|
8962f58ea4 | ||
|
|
1f541d6432 | ||
|
|
e66156eb49 | ||
|
|
2cd38988e6 | ||
|
|
d58b532741 | ||
|
|
4716e54069 | ||
|
|
aa73e9d8cb | ||
|
|
993c265125 | ||
|
|
5ca0bbf9e7 | ||
|
|
0ef7ee597c | ||
|
|
7d460ca156 | ||
|
|
b738b2ff1b | ||
|
|
cc5289e37c | ||
|
|
0c5f25c96d | ||
|
|
b53758bdbe | ||
|
|
f5b1c3e789 | ||
|
|
0edd21e335 | ||
|
|
e0c38676e7 | ||
|
|
fefd2bcc52 | ||
|
|
8427e18c98 | ||
|
|
7329fa65f1 | ||
|
|
de8cce521a | ||
|
|
f06ebd2ab4 | ||
|
|
a97a38d867 | ||
|
|
aff3b2d00f | ||
|
|
ccdbab95a8 | ||
|
|
a01e2f0899 | ||
|
|
dfdec06a17 | ||
|
|
393a641e18 | ||
|
|
53d41d5c62 | ||
|
|
c60ba6eaec | ||
|
|
c5123e4baf | ||
|
|
928c2fe057 | ||
|
|
b5b6875bc5 | ||
|
|
7c953c1929 | ||
|
|
1c09ad2e5a | ||
|
|
3284dffaaf | ||
|
|
c893285d71 | ||
|
|
75028f278a | ||
|
|
7fc1b5419f | ||
|
|
dee9889423 | ||
|
|
35871891db | ||
|
|
373e4b33c7 | ||
|
|
090e2f28e4 | ||
|
|
a2c7f1e6cb | ||
|
|
21da640ebe |
No files matched your search
@@ -113,3 +113,4 @@ build_win.log
|
||||
/wintun.dll
|
||||
src/lwip_orig/
|
||||
lib/timeout/
|
||||
utun.pid
|
||||
@@ -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
|
||||
@@ -490,7 +500,7 @@ void lottie_animation_destroy(Lottie_Animation *anim);
|
||||
- **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`
|
||||
|
||||
@@ -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"
|
||||
+8
-4
@@ -130,11 +130,11 @@ void queue_free(struct ll_queue* q) {
|
||||
// ВАЖНО: Не освобождаем элементы в очереди - они должны быть извлечены отдельно
|
||||
// Это упрощает архитектуру и предотвращает double-u_free
|
||||
|
||||
// Освободить список waiters (сбросить handle->internal)
|
||||
// Освободить список waiters (сбросить handle->internal, отменить отложенные вызовы)
|
||||
struct queue_waiter* w = q->waiter_head;
|
||||
while (w) {
|
||||
struct queue_waiter* next = w->next;
|
||||
if (w->handle) w->handle->internal = NULL;
|
||||
if (w->handle) { w->handle->internal = NULL; if (w->handle->call_soon_id) { uasync_call_soon_cancel(q->ua, w->handle->call_soon_id); w->handle->call_soon_id = NULL; } }
|
||||
u_free(w);
|
||||
w = next;
|
||||
}
|
||||
@@ -305,14 +305,17 @@ static uint32_t make_hash(const void* data, uint16_t len) {// алгоритм F
|
||||
|
||||
static void waiter_defer_cb(void* arg) {
|
||||
struct queue_waiter_handle* h = (struct queue_waiter_handle*)arg;
|
||||
h->defer_cb(h->defer_q, h->defer_arg);
|
||||
h->call_soon_id = NULL;
|
||||
if (h->defer_cb) h->defer_cb(h->defer_q, h->defer_arg);
|
||||
else DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "waiter_defer_cb: NULL callback (stale?) q=%p", (void*)h->defer_q);
|
||||
}
|
||||
|
||||
static inline void queue_waiter_call(struct ll_queue* q, queue_threshold_callback_fn cb, void* arg,
|
||||
struct queue_waiter_handle* h) {
|
||||
if (q->waiter_defer) {
|
||||
if (h->call_soon_id) { DEBUG_WARN(DEBUG_CATEGORY_LL_QUEUE, "queue_waiter_call: overwriting existing call_soon_id=%p", h->call_soon_id); uasync_call_soon_cancel(q->ua, h->call_soon_id); }
|
||||
h->defer_q = q; h->defer_cb = cb; h->defer_arg = arg;
|
||||
uasync_call_soon(q->ua, h, waiter_defer_cb);
|
||||
h->call_soon_id = uasync_call_soon(q->ua, h, waiter_defer_cb);
|
||||
} else {
|
||||
cb(q, arg);
|
||||
}
|
||||
@@ -692,6 +695,7 @@ int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h,
|
||||
|
||||
void queue_waiter_cancel(struct ll_queue* q, struct queue_waiter_handle* h) {
|
||||
if (!q || !h || !h->internal) return;
|
||||
if (h->call_soon_id) { uasync_call_soon_cancel(q->ua, h->call_soon_id); h->call_soon_id = NULL; }
|
||||
|
||||
struct queue_waiter* waiter = h->internal;
|
||||
struct queue_waiter* prev = NULL;
|
||||
|
||||
@@ -117,6 +117,7 @@ struct queue_waiter_handle {
|
||||
struct ll_queue* defer_q;
|
||||
queue_threshold_callback_fn defer_cb;
|
||||
void* defer_arg;
|
||||
void* call_soon_id; ///< хендл отложенного вызова (для отмены)
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -89,6 +89,7 @@ int socket_set_nonblocking(socket_t sock) {
|
||||
}
|
||||
|
||||
int socket_close_wrapper(socket_t sock) {
|
||||
if (sock == 0) return 0; // never close fd 0
|
||||
return close(sock);
|
||||
}
|
||||
|
||||
|
||||
+30
-15
@@ -32,7 +32,7 @@ struct tcp_conn* tcp_conn_create(
|
||||
void (*on_error)(struct tcp_conn* tc, int err, void* arg),
|
||||
void* arg)
|
||||
{
|
||||
if (!ua || sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: invalid args ua=%p", ua); return NULL; }
|
||||
if (!ua || sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: invalid args ua=%p", (void*)ua); return NULL; }
|
||||
|
||||
struct tcp_conn* tc = u_calloc(1, sizeof(struct tcp_conn));
|
||||
if (!tc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: u_calloc failed"); return NULL; }
|
||||
@@ -104,8 +104,17 @@ struct tcp_conn* tcp_conn_create(
|
||||
return tc;
|
||||
}
|
||||
|
||||
static void tcp_conn_deferred_free(void* arg) {
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
if (tc->entry_pool) memory_pool_destroy(tc->entry_pool);
|
||||
if (tc->data_pool) memory_pool_destroy(tc->data_pool);
|
||||
u_free(tc);
|
||||
}
|
||||
|
||||
void tcp_conn_destroy(struct tcp_conn* tc) {
|
||||
if (!tc) return;
|
||||
if (tc->destroyed) return;
|
||||
tc->destroyed = 1;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_destroy: fd=%d connected=%d error=%d fin_remote=%d fin_local=%d closed=%d",
|
||||
(int)tc->sock, tc->connected, tc->error, tc->fin_remote, tc->fin_local, tc->closed);
|
||||
|
||||
@@ -127,14 +136,12 @@ void tcp_conn_destroy(struct tcp_conn* tc) {
|
||||
if (e->dgram && e->dgram != &tcp_fin_sentinel) memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e); queue_resume_callback(tc->write_queue);
|
||||
}
|
||||
queue_free(tc->read_queue);
|
||||
queue_free(tc->write_queue);
|
||||
queue_free(tc->read_queue); tc->read_queue = NULL;
|
||||
queue_free(tc->write_queue); tc->write_queue = NULL;
|
||||
|
||||
if (tc->write_buf) memory_pool_free(tc->data_pool, tc->write_buf);
|
||||
if (tc->sock != SOCKET_INVALID) socket_close_wrapper(tc->sock);
|
||||
memory_pool_destroy(tc->entry_pool);
|
||||
memory_pool_destroy(tc->data_pool);
|
||||
u_free(tc);
|
||||
if (tc->sock != SOCKET_INVALID) { socket_close_wrapper(tc->sock); tc->sock = SOCKET_INVALID; }
|
||||
uasync_call_soon(tc->ua, tc, tcp_conn_deferred_free);
|
||||
}
|
||||
|
||||
static void tcp_conn_handle_error(struct tcp_conn* tc, int err)
|
||||
@@ -157,13 +164,15 @@ static void tcp_conn_handle_error(struct tcp_conn* tc, int err)
|
||||
}
|
||||
}
|
||||
|
||||
if (tc->socket_id) {
|
||||
if (tc->sock != SOCKET_INVALID) {
|
||||
uasync_remove_socket_t(tc->ua, tc->sock);
|
||||
tc->socket_id = NULL;
|
||||
}
|
||||
if (tc->sock != SOCKET_INVALID) {
|
||||
#if HAS_EPOLL
|
||||
if (tc->ua && tc->ua->use_epoll && tc->ua->epoll_fd >= 0)
|
||||
epoll_ctl(tc->ua->epoll_fd, EPOLL_CTL_DEL, (int)tc->sock, NULL);
|
||||
#endif
|
||||
socket_close_wrapper(tc->sock);
|
||||
tc->sock = SOCKET_INVALID;
|
||||
tc->sock = SOCKET_INVALID; // ДО on_error: read/write в том же epoll event увидят INVALID
|
||||
}
|
||||
|
||||
queue_set_callback(tc->write_queue, NULL, NULL);
|
||||
@@ -183,7 +192,7 @@ static void tcp_conn_handle_error(struct tcp_conn* tc, int err)
|
||||
static void read_cb(socket_t sock, void* arg) {
|
||||
(void)sock;
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
if (!tc || tc->sock == SOCKET_INVALID) return;
|
||||
if (!tc || tc->sock == SOCKET_INVALID || !tc->read_queue) return;
|
||||
|
||||
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
|
||||
uint8_t* buf = memory_pool_alloc(tc->data_pool);
|
||||
@@ -200,8 +209,10 @@ static void read_cb(socket_t sock, void* arg) {
|
||||
if (n > 0) {
|
||||
e->dgram = buf;
|
||||
e->len = (uint16_t)n;
|
||||
queue_data_put(tc->read_queue, e);
|
||||
if (tc->read_queue->count >= tc->read_high_water && !tc->read_paused) {
|
||||
struct ll_queue* rq = tc->read_queue;
|
||||
queue_data_put(rq, e);
|
||||
// tc мог быть уничтожен через callback chain — read_queue обнулён
|
||||
if (rq && tc->read_queue && tc->read_queue->count >= tc->read_high_water && !tc->read_paused) {
|
||||
uasync_set_socket_read(tc->ua, tc->socket_id, 0);
|
||||
tc->read_paused = 1;
|
||||
queue_waiter_wait(tc->read_queue, &tc->read_waiter, resume_read_cb, tc);
|
||||
@@ -366,7 +377,7 @@ static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
|
||||
static void write_cb(socket_t sock, void* arg) {
|
||||
(void)sock;
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
if (!tc || tc->sock == SOCKET_INVALID) return;
|
||||
if (!tc || tc->sock == SOCKET_INVALID || !tc->write_queue) return;
|
||||
|
||||
if (!tc->connected) {
|
||||
int err = 0;
|
||||
@@ -384,6 +395,10 @@ static void write_cb(socket_t sock, void* arg) {
|
||||
if (flush_write_buf(tc)) return;
|
||||
if (!tc->write_buf) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: write_cb flushed fd=%d wmon=%d (resume WQ cb)", (int)tc->sock, tc->write_monitor);
|
||||
if (tc->write_queue->waiter_defer) {
|
||||
uasync_set_socket_write(tc->ua, tc->socket_id, 0);
|
||||
tc->write_monitor = 0;
|
||||
}
|
||||
queue_resume_callback(tc->write_queue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ struct tcp_conn {
|
||||
uint8_t fin_remote; // FIN получен от удалённой стороны (recv == 0)
|
||||
uint8_t fin_local; // FIN отправлен удалённой стороне (shutdown SHUT_WR)
|
||||
uint8_t closed; // сокет полностью закрыт (close)
|
||||
uint8_t destroyed; // 1 = tcp_conn_destroy вызван, tc ожидает отложенного free
|
||||
|
||||
// Частичная отправка (из data_pool, не в очереди — досылается первой)
|
||||
uint8_t* write_buf;
|
||||
|
||||
+611
-603
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,27 @@
|
||||
UTUN
|
||||
|
||||
Идея: объединить узлы в единую локальную сеть.
|
||||
Чтобы узлы сами находили оптимальные линки между собой, пробивали NAT где это можно, где нельзя - выбирали посредника с хорошей связью; чтобы подсто было добавлять новые узлы и адреса узлов сразу виделись во всей сети.
|
||||
|
||||
Потенциальные варианты использования:
|
||||
- локальная сеть. хочу объединить офис, сотрудников (включая их работчие подсети), телефоны, офисы, сервер доступа итд в одно адресное пространство.
|
||||
- чат с файлообменником. узлы сети - это мемберы группы и одновременно сети.
|
||||
- CDN. узлы - потребители контента - это мемберы сети, которые распространяют трафик.
|
||||
|
||||
То есть задача utun - обеспечить связность группы узлов и адаптацию работы всей сети к подключению-отключении отдельных узлов.
|
||||
|
||||
Механизмы:
|
||||
1. Пробитие nat.
|
||||
2. автообновление списка узлов и их адресов для подключения
|
||||
3. шифрование трафика и маскировка handshake.
|
||||
handshake просиходит без сигнатур по которым можно опреедлить тип сервиса, весь трафик выглядлит как случайный набор байт случайного размера
|
||||
4. многоканальные подключения, агрегирование каналов, исключение мёртвых каналов
|
||||
подключиться к узлу можно через несколько линков. UTUN будет объединять пропускную способность каналов, наблюдая за состоянием каналов, не допуская перегрузки и обеспечивая быстрые переповторы через другие каналы при потерях или большой задержке.
|
||||
5. динамическая маршрутизация. Узлы могут назначать себе IP адреса и при подключении к сети анонсировать свои адреса/подсети.
|
||||
это максимально упрощает добавление нового узла в сеть.
|
||||
6. автоматический роутинг между узлами: узлы маршрутизируют трафик между собой.
|
||||
используя статические подключения, прописанные в конфиге.
|
||||
7. прямые подключения и поиск лучших подключений
|
||||
узлы могут подключаться друг к другу напрямую, либо если оба узла за строгим NAT - выбирать оптимального посредника.
|
||||
Узлы определяют свой тип подключения и NAT. Например у ростелеком - EIM NAT который можно использовать и для входящих подклюений.
|
||||
Можно использовать IPv6.
|
||||
@@ -45,6 +45,7 @@ utun_CORE_SOURCES = \
|
||||
etcp_router.c \
|
||||
proxy/tcp_proxy_server.c \
|
||||
proxy/udp_proxy.c \
|
||||
proxy/socks_proxy.c \
|
||||
proxy/icmp_proxy.c \
|
||||
lwip_tcp/lwip_pbuf.c \
|
||||
lwip_tcp/lwip_tcp.c \
|
||||
|
||||
+39
-6
@@ -357,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;
|
||||
@@ -377,6 +381,10 @@ static int parse_global(const char *key, const char *value, struct global_config
|
||||
global->keepalive_interval = atoi(value);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "keepalive_adaptive") == 0) {
|
||||
global->keepalive_adaptive = atoi(value);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "bbr_max_cwnd") == 0) {
|
||||
global->bbr_max_cwnd = atoi(value);
|
||||
return 0;
|
||||
@@ -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, bbr_max_cwnd, debug_level, log_file, db_path, enable_timestamp, enable_function_names, enable_file_lines, enable_colors, tun_test_mode", filename, line_num, key);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown global option '%s'. Valid: my_node_name, my_private_key, my_public_key, my_node_id, tun_ifname, tun_ip, mtu, keepalive_timeout, keepalive_interval, 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;
|
||||
}
|
||||
|
||||
@@ -472,6 +480,22 @@ static int parse_tcp_proxy_client(const char *key, const char *value, struct glo
|
||||
global->tcp_proxy_client_via_node_id = strtoull(value, NULL, 16);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "socks_enabled") == 0) {
|
||||
global->tcp_proxy_client_socks_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "socks_addr") == 0) {
|
||||
strncpy(global->tcp_proxy_client_socks_addr, value, sizeof(global->tcp_proxy_client_socks_addr) - 1);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "http_proxy_enabled") == 0) {
|
||||
global->tcp_proxy_client_http_proxy_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "http_proxy_addr") == 0) {
|
||||
strncpy(global->tcp_proxy_client_http_proxy_addr, value, sizeof(global->tcp_proxy_client_http_proxy_addr) - 1);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "forward") == 0) {
|
||||
if (global->tcp_proxy_client_mapping_count >= MAX_TCP_PROXY_CLIENT_MAPPINGS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Too many tcp_proxy_client forward rules (max %d)", MAX_TCP_PROXY_CLIENT_MAPPINGS);
|
||||
@@ -508,7 +532,7 @@ static int parse_tcp_proxy_client(const char *key, const char *value, struct glo
|
||||
global->tcp_proxy_client_mapping_count++;
|
||||
return 0;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown tcp_proxy_client option '%s'. Valid: enabled, tun_name, tun_ip, mtu, via_node, forward", filename, line_num, key);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown tcp_proxy_client option '%s'. Valid: enabled, tun_name, tun_ip, mtu, via_node, forward, socks_enabled, socks_addr, http_proxy_enabled, http_proxy_addr", filename, line_num, key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -741,6 +765,7 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
cfg->global.name[0] = '\0';
|
||||
cfg->global.keepalive_timeout = 2000; // Default 2 seconds
|
||||
cfg->global.keepalive_interval = 200; // Default 0.2 s
|
||||
cfg->global.keepalive_adaptive = 1; // Default: adaptive enabled
|
||||
cfg->global.bbr_max_cwnd = 1048576; // Default 1MB (was INFLIGHT_LIM_MAX)
|
||||
cfg->global.tcp_recv_buf = 0; // 0 = OS default
|
||||
cfg->global.firewall_rules = NULL;
|
||||
@@ -916,10 +941,18 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
cfg->clients = cur_client;
|
||||
}
|
||||
|
||||
// Set default TUN interface name if not provided
|
||||
if (cfg->global.tun_ifname[0] == '\0') {
|
||||
snprintf(cfg->global.tun_ifname, sizeof(cfg->global.tun_ifname), "tun0");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Auto-generated TUN interface name: %s", cfg->global.tun_ifname);
|
||||
// Post-parse defaults: tun_enabled=1 if not explicitly disabled
|
||||
if (!cfg->global.tun_enabled && cfg->global.tun_ifname[0] == '\0' && cfg->global.tun_ip.family == AF_UNSPEC) {
|
||||
// No TUN config at all — treat as explicitly off (for non-root SOCKS/HTTP-only mode)
|
||||
} else if (!cfg->global.tun_enabled) {
|
||||
// TUN was explicitly disabled via tun_enabled=no
|
||||
} else {
|
||||
// TUN enabled (default) — set default ifname if not provided
|
||||
cfg->global.tun_enabled = 1;
|
||||
if (cfg->global.tun_ifname[0] == '\0') {
|
||||
snprintf(cfg->global.tun_ifname, sizeof(cfg->global.tun_ifname), "tun0");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Auto-generated TUN interface name: %s", cfg->global.tun_ifname);
|
||||
}
|
||||
}
|
||||
|
||||
return cfg;
|
||||
|
||||
@@ -95,6 +95,7 @@ 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;
|
||||
@@ -121,6 +122,7 @@ struct global_config {
|
||||
int tun_test_mode; // test mode: 1 = don't open real TUN, queues only
|
||||
int keepalive_timeout; // keepalive timeout in ms (default: 2000)
|
||||
int keepalive_interval; // keepalive interval in ms (default: 200)
|
||||
int keepalive_adaptive; // 1 = adaptive period (default), 0 = fixed interval
|
||||
int bbr_max_cwnd; // BBR cwnd cap in bytes (default: 100000)
|
||||
|
||||
// Firewall configuration
|
||||
@@ -161,6 +163,10 @@ struct global_config {
|
||||
uint64_t tcp_proxy_client_via_node_id; // через этот узел проксируются все forward-правила
|
||||
struct tcp_proxy_client_mapping_config tcp_proxy_client_mappings[MAX_TCP_PROXY_CLIENT_MAPPINGS];
|
||||
int tcp_proxy_client_mapping_count;
|
||||
int tcp_proxy_client_socks_enabled;
|
||||
char tcp_proxy_client_socks_addr[64]; // e.g. "127.0.0.1:1080"
|
||||
int tcp_proxy_client_http_proxy_enabled;
|
||||
char tcp_proxy_client_http_proxy_addr[64]; // e.g. "127.0.0.1:8080"
|
||||
|
||||
// TCP proxy server (exit node) configuration ([tcp_proxy_server] section)
|
||||
int tcp_proxy_server_enabled;
|
||||
|
||||
+38
-23
@@ -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);
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#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"
|
||||
@@ -90,6 +91,7 @@ static void client_write_callback(socket_t fd, void* arg);
|
||||
static void client_except_callback(socket_t fd, void* arg);
|
||||
static void handle_client_data(struct control_server* server, struct control_client* client);
|
||||
static void close_client(struct control_server* server, struct control_client* client);
|
||||
static void control_client_idle_cb(void* arg);
|
||||
|
||||
static void send_conn_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
|
||||
static void send_socket_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
|
||||
@@ -424,6 +426,7 @@ static void accept_callback(socket_t fd, void* arg) {
|
||||
}
|
||||
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,
|
||||
@@ -603,6 +606,7 @@ static void client_write_callback(socket_t fd, void* arg) {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -633,9 +637,19 @@ static int control_client_send(struct control_server* server, struct control_cli
|
||||
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",
|
||||
@@ -765,6 +779,27 @@ static void handle_client_data(struct control_server* server, struct control_cli
|
||||
if (strncmp(cmd->action, "nat", 3) == 0) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Action 'nat' received, requesting NAT check for all links");
|
||||
route_bgp_request_nat_check_all(server->instance->bgp);
|
||||
} else if (strncmp(cmd->action, "nodes", 5) == 0) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Action 'nodes' received, dumping all BGP nodes");
|
||||
uint16_t text_max = (ETCPMON_MAX_MSG_SIZE > 1024) ? ETCPMON_MAX_MSG_SIZE - 512 : 1024;
|
||||
char* text = (char*)u_malloc(text_max);
|
||||
if (text) {
|
||||
int text_len = route_node_format_all(server->instance->bgp, text, text_max);
|
||||
if (text_len < 0) text_len = 0;
|
||||
text[text_len] = '\0';
|
||||
uint16_t rsp_size = ETCPMON_ACTION_RESULT_SIZE(text_len);
|
||||
uint8_t* buffer = (uint8_t*)u_malloc(rsp_size);
|
||||
if (buffer) {
|
||||
struct etcpmon_msg_header* rsp_hdr = (struct etcpmon_msg_header*)buffer;
|
||||
etcpmon_build_header(rsp_hdr, sizeof(struct etcpmon_rsp_action_result) + text_len + 1,
|
||||
ETCPMON_RSP_ACTION_RESULT, req_seq);
|
||||
struct etcpmon_rsp_action_result* rsp = (struct etcpmon_rsp_action_result*)(buffer + sizeof(*rsp_hdr));
|
||||
rsp->code = 0;
|
||||
memcpy(buffer + sizeof(*rsp_hdr) + sizeof(*rsp), text, text_len + 1);
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
}
|
||||
u_free(text);
|
||||
}
|
||||
} else {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Unknown action received: '%s'", cmd->action);
|
||||
}
|
||||
@@ -821,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) {
|
||||
@@ -1150,6 +1189,29 @@ static void send_metrics(struct control_server* server, struct control_client* c
|
||||
memset(&rsp->tun, 0, sizeof(rsp->tun));
|
||||
}
|
||||
|
||||
/* Fill router congestion metrics — aggregate over all router_conns */
|
||||
memset(&rsp->router, 0, sizeof(rsp->router));
|
||||
if (instance->router_conns) {
|
||||
struct ll_entry* re = instance->router_conns->head;
|
||||
while (re) {
|
||||
struct ETCP_ROUTER_CONN* rc = (struct ETCP_ROUTER_CONN*)re;
|
||||
rsp->router.total_inflight += (uint32_t)(rc->tx_seq - rc->tx_acked);
|
||||
if (rc->send_q) rsp->router.total_send_q += (uint32_t)queue_entry_count(rc->send_q);
|
||||
if (rc->recv_q) rsp->router.total_recv_q += (uint32_t)queue_entry_count(rc->recv_q);
|
||||
rsp->router.pkts_sent += rc->c_pkts_sent;
|
||||
rsp->router.pkts_send_err += rc->c_pkts_send_err;
|
||||
rsp->router.pkts_rcvd += rc->c_pkts_rcvd;
|
||||
rsp->router.ack_sent += rc->c_ack_sent;
|
||||
rsp->router.ack_recv += rc->c_ack_recv;
|
||||
rsp->router.dup_dropped += rc->c_dup_dropped;
|
||||
rsp->router.oob_dropped += rc->c_oob_dropped;
|
||||
rsp->router.stale_ack += rc->c_stale_ack;
|
||||
rsp->router.sign_fail += rc->c_sign_fail;
|
||||
re = re->next;
|
||||
}
|
||||
rsp->router.total_conns = (uint32_t)queue_entry_count(instance->router_conns);
|
||||
}
|
||||
|
||||
/* Fill link metrics */
|
||||
struct etcpmon_link_metrics* link_info = (struct etcpmon_link_metrics*)(buffer + sizeof(*hdr) + sizeof(*rsp));
|
||||
link = conn->links;
|
||||
|
||||
@@ -40,6 +40,7 @@ struct control_client {
|
||||
uint64_t selected_peer_id; /* 0 = none selected */
|
||||
|
||||
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 */
|
||||
};
|
||||
|
||||
+11
-6
@@ -70,7 +70,7 @@ static int inflight_seq_cmp(const void* a, const void* b) {
|
||||
|
||||
static void feed_dgram_to_asm(struct ETCP_CONN* etcp, struct PKTNORM* pn,
|
||||
uint8_t* dgram, uint16_t dgram_len,
|
||||
uint8_t** asm_buf, uint16_t* asm_len, uint16_t* asm_cap,
|
||||
uint8_t** asm_buf, uint16_t* asm_len, uint32_t* asm_cap,
|
||||
uint32_t* returned) {
|
||||
uint32_t need = (uint32_t)*asm_len + dgram_len;
|
||||
if (need >= ASM_BUF_MAX_SIZE) {
|
||||
@@ -78,9 +78,10 @@ static void feed_dgram_to_asm(struct ETCP_CONN* etcp, struct PKTNORM* pn,
|
||||
return;
|
||||
}
|
||||
if (need > *asm_cap) {
|
||||
uint16_t new_cap = *asm_cap ? *asm_cap : 256;
|
||||
uint32_t new_cap = *asm_cap ? *asm_cap : 256;
|
||||
while (new_cap < need) new_cap *= 2;
|
||||
uint8_t* new_buf = u_realloc(*asm_buf, new_cap);
|
||||
if (new_cap > 0xFFFF) new_cap = 0xFFFF;
|
||||
uint8_t* new_buf = u_realloc(*asm_buf, (uint16_t)new_cap);
|
||||
if (!new_buf) return;
|
||||
*asm_buf = new_buf;
|
||||
*asm_cap = new_cap;
|
||||
@@ -138,7 +139,7 @@ static void etcp_return_inflight_to_normalizer(struct ETCP_CONN* etcp) {
|
||||
|
||||
uint8_t* asm_buf = NULL;
|
||||
uint16_t asm_len = 0;
|
||||
uint16_t asm_cap = 0;
|
||||
uint32_t asm_cap = 0;
|
||||
uint32_t returned_packets = 0;
|
||||
|
||||
for (int i = 0; i < inflight_count; i++) {
|
||||
@@ -374,7 +375,10 @@ void etcp_conn_reset(struct ETCP_CONN* etcp) {
|
||||
etcp->last_rr_link = NULL;
|
||||
|
||||
// Return unconfirmed inflight data to normalizer input (до нормалайзера)
|
||||
if (etcp->normalizer) etcp_return_inflight_to_normalizer(etcp);
|
||||
if (etcp->normalizer) {
|
||||
etcp->normalizer->input->callback_suspended = 1;
|
||||
etcp_return_inflight_to_normalizer(etcp);
|
||||
}
|
||||
|
||||
// Clear queues (keep queue structures)
|
||||
clear_queue(etcp->input_queue);
|
||||
@@ -1178,8 +1182,9 @@ void etcp_output_try_assembly(struct ETCP_CONN* etcp) {
|
||||
// Add to output_queue using the same ETCP_FRAGMENT structure
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] moving packet id=%u to output_queue (qlen=%d)", etcp->log_name,
|
||||
next_expected_id, etcp->output_queue->count);
|
||||
uint16_t pkt_len = rx_pkt->ll.len;
|
||||
if (queue_data_put(etcp->output_queue, (struct ll_entry*)rx_pkt) == 0) {
|
||||
delivered_bytes += rx_pkt->ll.len;
|
||||
delivered_bytes += pkt_len;
|
||||
delivered_count++;
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to add packet id=%u to output_queue", etcp->log_name,
|
||||
|
||||
+3
-3
@@ -52,14 +52,14 @@ void etcp_int_recv(struct ll_queue* queue, void* arg) {
|
||||
if (!queue || !conn) return;
|
||||
struct ll_entry* e = queue_data_get(queue);
|
||||
if (!e) { queue_resume_callback(queue); return; }
|
||||
if (!e->dgram || e->len == 0) { queue_entry_free(e); queue_dgram_free(e); queue_resume_callback(queue); return; }
|
||||
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) { queue_entry_free(e); queue_dgram_free(e); queue_resume_callback(queue); return; }
|
||||
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_entry_free(e); queue_dgram_free(e); }
|
||||
else { queue_dgram_free(e); queue_entry_free(e); }
|
||||
queue_resume_callback(queue);
|
||||
}
|
||||
@@ -324,10 +324,11 @@ static void keepalive_timer_cb(void* arg) {
|
||||
}
|
||||
}
|
||||
|
||||
// Adaptive keepalive period
|
||||
// Adaptive keepalive period (only if adaptive enabled)
|
||||
if (link->pkt_sent_since_keepalive)
|
||||
link->ka_period_ms = KA_PERIOD_MIN_MS; // data flowing → reset to 200ms
|
||||
else {
|
||||
link->ka_period_ms = KA_PERIOD_MIN_MS;
|
||||
else if (link->etcp->instance && link->etcp->instance->config &&
|
||||
link->etcp->instance->config->global.keepalive_adaptive) {
|
||||
uint32_t next = (uint32_t)link->ka_period_ms * 105 / 100 + 1;
|
||||
link->ka_period_ms = next > KA_PERIOD_MAX_MS ? KA_PERIOD_MAX_MS : (uint16_t)next;
|
||||
}
|
||||
@@ -739,9 +740,7 @@ void etcp_socket_remove(struct ETCP_SOCKET* conn) {
|
||||
size_t i = 0;
|
||||
while (i < conn->num_channels) {
|
||||
struct ETCP_LINK* l = conn->links[i];
|
||||
etcp_link_close(l);
|
||||
conn->links[i] = NULL; // обнулить после — etcp_link_close обращается к массиву через remove_link
|
||||
i++;
|
||||
etcp_link_close(l); // remove_link inside shifts elements left → next at same i
|
||||
}
|
||||
u_free(conn->links);
|
||||
|
||||
|
||||
+85
-19
@@ -58,7 +58,7 @@ static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t*
|
||||
size_t sig_len = is_signed ? SC_SIGN_SIZE : 0;
|
||||
size_t total_len = SVC_ROUTE_HDR_SIZE + pl_len + sig_len;
|
||||
uint8_t* dgram = u_malloc(total_len);
|
||||
if (!dgram) { if (!seq_flags) rconn->tx_seq--; return -1; }
|
||||
if (!dgram) { if (!seq_flags) rconn->tx_seq--; rconn->c_pkts_send_err++; return -1; }
|
||||
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)dgram;
|
||||
hdr->cmd = ETCP_ID_SVC_ROUTE;
|
||||
hdr->dst_node_id = rconn->remote_node_id;
|
||||
@@ -91,29 +91,29 @@ static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t*
|
||||
if (!conn) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router_send_one: no route to %016llx svc_id=%u",
|
||||
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
|
||||
u_free(dgram); if (!seq_flags) rconn->tx_seq--; return -1;
|
||||
u_free(dgram); if (!seq_flags) rconn->tx_seq--; rconn->c_pkts_send_err++; return -1;
|
||||
}
|
||||
|
||||
if (is_signed) {
|
||||
struct sc_stream_sign_state sign_state;
|
||||
if (sc_stream_sign_init(&conn->crypto_ctx, &sign_state) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_send_one: sign_init failed svc_id=%u", rconn->svc_id);
|
||||
u_free(dgram); if (!seq_flags) rconn->tx_seq--; return -1;
|
||||
u_free(dgram); if (!seq_flags) rconn->tx_seq--; rconn->c_pkts_send_err++; return -1;
|
||||
}
|
||||
size_t data_len = SVC_ROUTE_HDR_SIZE + pl_len;
|
||||
if (sc_stream_sign_update(&sign_state, dgram, data_len) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_send_one: sign_update failed svc_id=%u", rconn->svc_id);
|
||||
sc_stream_sign_cleanup(&sign_state); u_free(dgram); if (!seq_flags) rconn->tx_seq--; return -1;
|
||||
sc_stream_sign_cleanup(&sign_state); u_free(dgram); if (!seq_flags) rconn->tx_seq--; rconn->c_pkts_send_err++; return -1;
|
||||
}
|
||||
size_t actual_sig_len = SC_SIGN_SIZE;
|
||||
if (sc_stream_sign_final(&sign_state, dgram + data_len, &actual_sig_len) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_send_one: sign_final failed svc_id=%u", rconn->svc_id);
|
||||
u_free(dgram); if (!seq_flags) rconn->tx_seq--; return -1;
|
||||
u_free(dgram); if (!seq_flags) rconn->tx_seq--; rconn->c_pkts_send_err++; return -1;
|
||||
}
|
||||
}
|
||||
|
||||
struct ll_entry* entry = queue_entry_new(0);
|
||||
if (!entry) { u_free(dgram); if (!seq_flags) rconn->tx_seq--; return -1; }
|
||||
if (!entry) { u_free(dgram); if (!seq_flags) rconn->tx_seq--; rconn->c_pkts_send_err++; return -1; }
|
||||
entry->dgram = dgram;
|
||||
entry->len = total_len;
|
||||
|
||||
@@ -123,7 +123,16 @@ static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t*
|
||||
(int32_t)(rconn->tx_seq - rconn->tx_acked),
|
||||
is_signed,
|
||||
(unsigned long long)rconn->remote_node_id);
|
||||
return etcp_send(conn, entry);
|
||||
int ret = etcp_send(conn, entry);
|
||||
if (ret != 0) {
|
||||
if (!seq_flags) rconn->tx_seq--;
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
rconn->c_pkts_send_err++;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_send_one: etcp_send failed ret=%d svc_id=%u seq=%u", ret, rconn->svc_id, seq);
|
||||
} else {
|
||||
rconn->c_pkts_sent++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Отправка без conn (RST для неизвестного src)
|
||||
@@ -143,7 +152,11 @@ static void router_send_to(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t svc
|
||||
entry->len = SVC_ROUTE_HDR_SIZE;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router_send_to: seq_flags=0x%08x → %016llx svc_id=%u",
|
||||
seq_flags, (unsigned long long)dst, svc_id);
|
||||
etcp_send(conn, entry);
|
||||
int ret = etcp_send(conn, entry);
|
||||
if (ret != 0) {
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_send_to: etcp_send failed ret=%d", ret);
|
||||
}
|
||||
}
|
||||
|
||||
static void router_send_close_to_service(struct ETCP_ROUTER_CONN* rconn) {
|
||||
@@ -199,8 +212,11 @@ static void router_drain_send_q(struct ETCP_ROUTER_CONN* rconn) {
|
||||
if ((int32_t)(rconn->tx_seq - rconn->tx_acked) >= ROUTER_MAX_INFLIGHT) break;
|
||||
struct ll_entry* e = queue_data_get(rconn->send_q);
|
||||
if (!e) { rconn->send_blocked = 0; break; }
|
||||
router_send_one(rconn, e->dgram, e->len);
|
||||
u_check(e, "drain:after_get", "router");
|
||||
int s_err = router_send_one(rconn, e->dgram, e->len);
|
||||
u_check(e, "drain:after_send", "router");
|
||||
queue_dgram_free(e); queue_entry_free(e);
|
||||
if (s_err != 0 && (int32_t)(rconn->tx_seq - rconn->tx_acked) >= ROUTER_MAX_INFLIGHT) break;
|
||||
}
|
||||
if (rconn->send_blocked && !rconn->send_resume_timer)
|
||||
rconn->send_resume_timer = uasync_set_timeout(rconn->inst->ua,
|
||||
@@ -232,6 +248,7 @@ static int router_enqueue_send(struct ETCP_ROUTER_CONN* rconn, const uint8_t* pa
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router_send_q: queued svc_id=%u inflight=%d send_q=%d",
|
||||
rconn->svc_id, (int32_t)(rconn->tx_seq - rconn->tx_acked), queue_entry_count(rconn->send_q));
|
||||
queue_data_put(rconn->send_q, qe);
|
||||
u_check(qe, "enqueue_send:after_put", "router");
|
||||
rconn->send_blocked = 1;
|
||||
if (!rconn->send_resume_timer)
|
||||
rconn->send_resume_timer = uasync_set_timeout(rconn->inst->ua,
|
||||
@@ -245,8 +262,6 @@ static int router_enqueue_send(struct ETCP_ROUTER_CONN* rconn, const uint8_t* pa
|
||||
|
||||
static void router_ack_do_send(struct ETCP_ROUTER_CONN* rconn) {
|
||||
router_send_ack(rconn);
|
||||
rconn->last_sent_ack_seq = rconn->rx_seq;
|
||||
rconn->last_ack_sent_tb = get_time_tb();
|
||||
}
|
||||
|
||||
static void router_send_ack(struct ETCP_ROUTER_CONN* rconn) {
|
||||
@@ -255,6 +270,7 @@ static void router_send_ack(struct ETCP_ROUTER_CONN* rconn) {
|
||||
if (!conn) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router_ack: no route to %016llx svc_id=%u",
|
||||
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
|
||||
rconn->last_ack_sent_tb = get_time_tb();
|
||||
return;
|
||||
}
|
||||
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)u_malloc(SVC_ROUTE_HDR_SIZE);
|
||||
@@ -273,7 +289,15 @@ static void router_send_ack(struct ETCP_ROUTER_CONN* rconn) {
|
||||
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "ACK_SEND: svc_id=%u rx_seq=%u → %016llx",
|
||||
rconn->svc_id, rconn->rx_seq, (unsigned long long)rconn->remote_node_id);
|
||||
etcp_send(conn, entry);
|
||||
int ret = etcp_send(conn, entry);
|
||||
if (ret != 0) {
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_ack: etcp_send failed ret=%d svc_id=%u rx_seq=%u", ret, rconn->svc_id, rconn->rx_seq);
|
||||
return;
|
||||
}
|
||||
rconn->c_ack_sent++;
|
||||
rconn->last_sent_ack_seq = rconn->rx_seq;
|
||||
rconn->last_ack_sent_tb = get_time_tb();
|
||||
}
|
||||
|
||||
static void router_schedule_ack(struct ETCP_ROUTER_CONN* rconn) {
|
||||
@@ -338,6 +362,7 @@ static void router_deliver(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* con
|
||||
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "router_deliver: svc_id=%u len=%zu from remote=%016llx",
|
||||
rconn->svc_id, payload_len, (unsigned long long)rconn->remote_node_id);
|
||||
rconn->c_pkts_rcvd++;
|
||||
cb(conn, svc_entry);
|
||||
}
|
||||
|
||||
@@ -415,6 +440,10 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
|
||||
}
|
||||
if (sc_stream_sign_verify(&vfy_state, sig, SC_SIGN_SIZE) != SC_OK) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: signature verification FAILED for node %016llx svc_id=%u — dropping", (unsigned long long)hdr->src_node_id, hdr->svc_id);
|
||||
{
|
||||
struct ETCP_ROUTER_CONN* sr = router_conn_find(inst, hdr->src_node_id, hdr->svc_id);
|
||||
if (sr) sr->c_sign_fail++;
|
||||
}
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
return;
|
||||
}
|
||||
@@ -440,9 +469,11 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
|
||||
rconn->svc_id, hdr->seq, (int32_t)(rconn->tx_seq - rconn->tx_acked),
|
||||
queue_entry_count(rconn->send_q),
|
||||
(unsigned long long)hdr->src_node_id);
|
||||
rconn->c_ack_recv++;
|
||||
} else {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: stale ACK seq=%u tx_acked=%u from %016llx, ignoring",
|
||||
hdr->seq, rconn->tx_acked, (unsigned long long)hdr->src_node_id);
|
||||
rconn->c_stale_ack++;
|
||||
}
|
||||
rconn->last_dgram_ts = get_current_timestamp();
|
||||
if (rconn->send_blocked) router_drain_send_q(rconn);
|
||||
@@ -455,6 +486,7 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
|
||||
if (!rconn) {
|
||||
rconn = etcp_router_conn_get(inst, hdr->src_node_id, hdr->svc_id);
|
||||
if (!rconn) { queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
rconn->peer_sess_id = (hdr->flags >> ROUTER_SESS_ID_SHIFT) & 0x3;
|
||||
}
|
||||
|
||||
// Обнаружение перезапуска peer'а: новый sess_id или START-флаг
|
||||
@@ -486,12 +518,19 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
|
||||
rconn->last_dgram_ts = get_current_timestamp();
|
||||
uint32_t seq = hdr->seq;
|
||||
|
||||
// Синхронизация после авто-создания/рестарта: принимаем первый пришедший seq
|
||||
if (!rconn->peer_sync_done) {
|
||||
rconn->peer_sync_done = 1;
|
||||
rconn->rx_seq = seq;
|
||||
}
|
||||
|
||||
// Проверка границ (32-bit circular, аналог ETCP MAX_INFLIGHT_SIZE)
|
||||
{
|
||||
int32_t d = (int32_t)(seq - rconn->rx_seq);
|
||||
if (d > ROUTER_MAX_INFLIGHT || d < -ROUTER_MAX_INFLIGHT) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router: seq=%u out of bounds, rx_seq=%u (d=%d), dropping",
|
||||
seq, rconn->rx_seq, d);
|
||||
rconn->c_oob_dropped++;
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
return;
|
||||
}
|
||||
@@ -501,6 +540,7 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
|
||||
if (((int32_t)(rconn->rx_seq - seq) > 0) ||
|
||||
queue_find_data_by_index(rconn->recv_q, &seq)) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router: dup seq=%u rx_seq=%u, dropping", seq, rconn->rx_seq);
|
||||
rconn->c_dup_dropped++;
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
return;
|
||||
}
|
||||
@@ -510,7 +550,7 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
|
||||
if (!qe) { queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
*(uint32_t*)qe->data = seq;
|
||||
qe->dgram = u_malloc(pl_len);
|
||||
if (!qe->dgram) { queue_entry_free(qe); queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
if (!qe->dgram) { queue_dgram_free(qe); queue_entry_free(qe); queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
qe->len = pl_len;
|
||||
memcpy(qe->dgram, pl, pl_len);
|
||||
|
||||
@@ -535,7 +575,11 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
|
||||
}
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "etcp_router: forwarding svc_id=%u → %016llx via %s",
|
||||
hdr->svc_id, (unsigned long long)hdr->dst_node_id, next->log_name);
|
||||
etcp_send(next, entry);
|
||||
int fwd_err = etcp_send(next, entry);
|
||||
if (fwd_err != 0) {
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "etcp_router: transit etcp_send failed ret=%d", fwd_err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,13 +743,17 @@ void etcp_router_conn_restart(struct UTUN_INSTANCE* inst, uint64_t remote_node_i
|
||||
}
|
||||
}
|
||||
|
||||
if (rconn->ack_timer) { uasync_cancel_timeout(inst->ua, rconn->ack_timer); rconn->ack_timer = NULL; }
|
||||
if (rconn->idle_ack_timer) { uasync_cancel_timeout(inst->ua, rconn->idle_ack_timer); rconn->idle_ack_timer = NULL; }
|
||||
if (rconn->send_resume_timer) { uasync_cancel_timeout(inst->ua, rconn->send_resume_timer); rconn->send_resume_timer = NULL; }
|
||||
|
||||
struct ll_entry* f;
|
||||
if (rconn->send_q) {
|
||||
while ((f = queue_data_get(rconn->send_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
|
||||
while ((f = queue_data_get(rconn->send_q)) != NULL) { u_check(f, "restart:send_q", "router"); queue_dgram_free(f); queue_entry_free(f); }
|
||||
queue_free(rconn->send_q);
|
||||
}
|
||||
if (rconn->recv_q) {
|
||||
while ((f = queue_data_get(rconn->recv_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
|
||||
while ((f = queue_data_get(rconn->recv_q)) != NULL) { u_check(f, "restart:recv_q", "router"); queue_dgram_free(f); queue_entry_free(f); }
|
||||
queue_free(rconn->recv_q);
|
||||
}
|
||||
rconn->send_q = queue_new(inst->ua, 0, 0, 0, "router_send_q");
|
||||
@@ -716,10 +764,19 @@ void etcp_router_conn_restart(struct UTUN_INSTANCE* inst, uint64_t remote_node_i
|
||||
rconn->rx_seq = 0;
|
||||
rconn->tx_acked = 0;
|
||||
rconn->last_sent_ack_seq = 0;
|
||||
rconn->sess_id = (rconn->sess_id + 1) & 0x3;
|
||||
rconn->last_ack_sent_tb = 0;
|
||||
rconn->peer_sess_id = 0;
|
||||
rconn->start_sent = 0;
|
||||
rconn->send_blocked = 0;
|
||||
rconn->peer_sync_done = 0;
|
||||
rconn->c_pkts_sent = 0;
|
||||
rconn->c_pkts_send_err = 0;
|
||||
rconn->c_pkts_rcvd = 0;
|
||||
rconn->c_ack_sent = 0;
|
||||
rconn->c_ack_recv = 0;
|
||||
rconn->c_dup_dropped = 0;
|
||||
rconn->c_oob_dropped = 0;
|
||||
rconn->c_stale_ack = 0;
|
||||
rconn->c_sign_fail = 0;
|
||||
}
|
||||
|
||||
void etcp_router_on_send_ready(struct UTUN_INSTANCE* inst, uint64_t node_id, uint8_t svc_id,
|
||||
@@ -766,8 +823,17 @@ struct ETCP_ROUTER_CONN* etcp_router_conn_get(struct UTUN_INSTANCE* inst,
|
||||
rconn->send_blocked = 0;
|
||||
rconn->send_resume_timer = NULL;
|
||||
rconn->sess_id = 0;
|
||||
rconn->peer_sess_id = 0;
|
||||
rconn->start_sent = 0;
|
||||
rconn->peer_sync_done = 0;
|
||||
rconn->c_pkts_sent = 0;
|
||||
rconn->c_pkts_send_err = 0;
|
||||
rconn->c_pkts_rcvd = 0;
|
||||
rconn->c_ack_sent = 0;
|
||||
rconn->c_ack_recv = 0;
|
||||
rconn->c_dup_dropped = 0;
|
||||
rconn->c_oob_dropped = 0;
|
||||
rconn->c_stale_ack = 0;
|
||||
rconn->c_sign_fail = 0;
|
||||
rconn->recv_q = queue_new(inst->ua, ROUTER_RECVQ_HASH_SIZE, 0, 4, "router_recv_q");
|
||||
if (!rconn->recv_q) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_conn_get: queue_new(recv_q) failed"); queue_entry_free(&rconn->ll); return NULL; }
|
||||
rconn->send_q = queue_new(inst->ua, 0, 0, 0, "router_send_q");
|
||||
|
||||
@@ -56,6 +56,17 @@ struct ETCP_ROUTER_CONN {
|
||||
uint8_t sess_id; // наш session id (0-3)
|
||||
uint8_t peer_sess_id; // последний sess_id от peer'а
|
||||
uint8_t start_sent; // 0 = нужно отправить START в первом data
|
||||
uint8_t peer_sync_done; // 0 = rx_seq ещё не синхронизирован с удалённым seq (авто-создание/рестарт)
|
||||
|
||||
uint32_t c_pkts_sent; // успешные отправки данных
|
||||
uint32_t c_pkts_send_err; // ошибки отправки
|
||||
uint32_t c_pkts_rcvd; // получено и доставлено данных
|
||||
uint32_t c_ack_sent; // отправлено ACK
|
||||
uint32_t c_ack_recv; // получено ACK (последовательных)
|
||||
uint32_t c_dup_dropped; // дропнуто дубликатов seq
|
||||
uint32_t c_oob_dropped; // дропнуто out-of-bounds seq
|
||||
uint32_t c_stale_ack; // устаревших ACK
|
||||
uint32_t c_sign_fail; // signature verification failures
|
||||
};
|
||||
|
||||
#define ROUTER_CONN_HASH_SIZE 256
|
||||
|
||||
+5
-5
@@ -51,7 +51,7 @@ static void nat_transport_client_tun_out_cb(struct ll_queue* q, void* arg) {
|
||||
if (ret != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT client: etcp_route_send to provider %016llx failed",
|
||||
(unsigned long long)tr->nat_via_node_id);
|
||||
queue_entry_free(new_entry); queue_dgram_free(new_entry);
|
||||
queue_dgram_free(new_entry); queue_entry_free(new_entry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,21 +94,21 @@ static void nat_transport_provider_tun_out_cb(struct ll_queue* q, void* arg) {
|
||||
if (send_ret != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT provider: etcp_route_send back to node %016llx failed",
|
||||
(unsigned long long)entry->src_node_id);
|
||||
queue_entry_free(new_entry); queue_dgram_free(new_entry);
|
||||
queue_dgram_free(new_entry); queue_entry_free(new_entry);
|
||||
}
|
||||
}
|
||||
|
||||
// ETCP_ID_NAT receive via etcp_router: CLIENT gets response, PROVIDER gets request
|
||||
static void nat_transport_etcp_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
if (!conn || !entry || !entry->dgram || entry->len < NAT_SVC_HDR_SIZE) {
|
||||
if (entry) { queue_entry_free(entry); queue_dgram_free(entry); }
|
||||
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
|
||||
return;
|
||||
}
|
||||
struct UTUN_INSTANCE* inst = conn->instance;
|
||||
if (!inst) { queue_entry_free(entry); queue_dgram_free(entry); return; }
|
||||
if (!inst) { queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
struct nat_transport_ctx* tr = &inst->nat_tr;
|
||||
struct eim_nat_ctx* ctx = &inst->nat;
|
||||
if (!ctx->initialized) { queue_entry_free(entry); queue_dgram_free(entry); return; }
|
||||
if (!ctx->initialized) { queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
|
||||
uint64_t src_node_id;
|
||||
memcpy(&src_node_id, entry->dgram + 1, 8);
|
||||
|
||||
@@ -394,11 +394,12 @@ static void pn_unpacker_cb(struct ll_queue* q, void* arg) {
|
||||
ptr += cp;
|
||||
|
||||
if (pn->recvpart->len == pn->recvpart->memlen) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_NORMALIZER, "unpacked dgram (size=%d)", pn->recvpart->len);
|
||||
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP, "NORM->", pn->recvpart->dgram, pn->recvpart->len);
|
||||
uint32_t recv_len = pn->recvpart->len;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_NORMALIZER, "unpacked dgram (size=%d)", recv_len);
|
||||
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP)) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_DUMP, "NORM->", pn->recvpart->dgram, recv_len);
|
||||
queue_data_put(pn->output, pn->recvpart);
|
||||
pn->out_total_pkts++;
|
||||
pn->out_total_bytes += pn->recvpart->len;
|
||||
pn->out_total_bytes += recv_len;
|
||||
pn->recvpart = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ drop:
|
||||
// Сторона клиента: принять REPLY, доставить echo ответ в TUN
|
||||
// ====================================================================
|
||||
static void client_handle_reply(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
if (entry->len < ICMP_PROXY_HDR_SIZE + 1) { queue_entry_free(entry); queue_dgram_free(entry); return; }
|
||||
if (entry->len < ICMP_PROXY_HDR_SIZE + 1) { queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
uint32_t src_ip;
|
||||
uint32_t orig_src_ip;
|
||||
uint16_t echo_id, echo_seq;
|
||||
@@ -194,7 +194,7 @@ static void client_handle_reply(struct ETCP_CONN* conn, struct ll_entry* entry)
|
||||
// ====================================================================
|
||||
void icmp_proxy_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
if (!entry || !entry->dgram || entry->len < 2) {
|
||||
if (entry) { queue_entry_free(entry); queue_dgram_free(entry); }
|
||||
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
|
||||
return;
|
||||
}
|
||||
uint8_t subcmd = entry->dgram[1];
|
||||
|
||||
@@ -0,0 +1,487 @@
|
||||
// socks_proxy.c — SOCKS5 / HTTP CONNECT proxy (client side)
|
||||
#include "socks_proxy.h"
|
||||
#include "tcp_proxy_server.h"
|
||||
#include "etcp.h"
|
||||
#include "etcp_api.h"
|
||||
#include "etcp_router.h"
|
||||
#include "utun_instance.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/memory_pool.h"
|
||||
#include "../lib/tcp_io.h"
|
||||
#include "../lib/mem.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
static void on_accept_cb(socket_t sock, void* arg);
|
||||
static void on_read_cb(struct ll_queue* q, void* arg);
|
||||
static void on_fin_cb(struct tcp_conn* tc, void* arg);
|
||||
static void on_error_cb(struct tcp_conn* tc, int err, void* arg);
|
||||
static void on_closed_cb(struct tcp_conn* tc, void* arg);
|
||||
static void tx_waiter_cb(struct ll_queue* q, void* arg);
|
||||
|
||||
static int send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd, uint32_t sid, const uint8_t* data, size_t len, int force);
|
||||
|
||||
struct listen_ctx {
|
||||
socket_t listen_sock;
|
||||
void* socket_id;
|
||||
struct UASYNC* ua;
|
||||
struct UTUN_INSTANCE* inst;
|
||||
uint64_t via_node_id;
|
||||
int is_http;
|
||||
struct socks_proxy_conn** conns;
|
||||
int* conn_count;
|
||||
uint32_t* next_stream_id;
|
||||
};
|
||||
|
||||
// ====================================================================
|
||||
// Отправка сообщений через ETCP
|
||||
// ====================================================================
|
||||
static int send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd, uint32_t sid, const uint8_t* data, size_t len, int force) {
|
||||
struct ll_entry* e = queue_entry_new(0);
|
||||
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: queue_entry_new failed subcmd=%02x sid=%08x", subcmd, sid); return -1; }
|
||||
e->dgram = u_malloc(TCP_PROXY_HDR_SIZE + len);
|
||||
if (!e->dgram) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: malloc(%zu) failed", TCP_PROXY_HDR_SIZE + len); queue_entry_free(e); return -1; }
|
||||
e->dgram[0] = ETCP_ID_TCP_PROXY;
|
||||
e->dgram[1] = subcmd;
|
||||
memcpy(e->dgram + 2, &sid, 4);
|
||||
if (len > 0) memcpy(e->dgram + TCP_PROXY_HDR_SIZE, data, len);
|
||||
if (TCP_PROXY_HDR_SIZE + len > UINT16_MAX) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: msg too large len=%zu subcmd=%02x", len, subcmd);
|
||||
queue_dgram_free(e); queue_entry_free(e); return -1;
|
||||
}
|
||||
e->len = (uint16_t)(TCP_PROXY_HDR_SIZE + len);
|
||||
return etcp_route_send(inst, dst, e, force);
|
||||
}
|
||||
|
||||
static int send_connect(struct socks_proxy_conn* c) {
|
||||
uint8_t buf[6];
|
||||
memcpy(buf, c->dest_ip, 4); memcpy(buf + 4, &c->dest_port, 2);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCKS proxy: CONNECT sid=%08x to %d.%d.%d.%d:%d via_node=%016llx %s",
|
||||
c->stream_id, c->dest_ip[0], c->dest_ip[1], c->dest_ip[2], c->dest_ip[3],
|
||||
ntohs(c->dest_port), (unsigned long long)c->via_node_id, c->is_http ? "http" : "socks");
|
||||
return send_msg(c->inst, c->via_node_id, TCP_PROXY_SUBCMD_CONNECT, c->stream_id, buf, 6, 1);
|
||||
}
|
||||
|
||||
static int send_data(struct socks_proxy_conn* c, const uint8_t* data, uint16_t len) {
|
||||
return send_msg(c->inst, c->via_node_id, TCP_PROXY_SUBCMD_DATA, c->stream_id, data, len, 0);
|
||||
}
|
||||
|
||||
static void send_close(struct socks_proxy_conn* c) {
|
||||
if (send_msg(c->inst, c->via_node_id, TCP_PROXY_SUBCMD_CLOSE, c->stream_id, NULL, 0, 1) < 0) c->close_pending = 1;
|
||||
else { c->close_pending = 0; c->close_sent = 1; }
|
||||
}
|
||||
|
||||
static void send_error(struct socks_proxy_conn* c) {
|
||||
if (send_msg(c->inst, c->via_node_id, TCP_PROXY_SUBCMD_ERROR, c->stream_id, NULL, 0, 1) < 0) c->close_pending = 1;
|
||||
else { c->close_pending = 0; c->close_sent = 1; }
|
||||
}
|
||||
|
||||
static void send_fin(struct socks_proxy_conn* c) {
|
||||
send_msg(c->inst, c->via_node_id, TCP_PROXY_SUBCMD_FIN, c->stream_id, NULL, 0, 1);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Запись ответа в TCP клиенту через write_queue
|
||||
// ====================================================================
|
||||
static int write_to_client(struct socks_proxy_conn* c, const uint8_t* data, uint16_t len) {
|
||||
struct ll_entry* e = queue_entry_new_from_pool(c->tc->entry_pool);
|
||||
uint8_t* buf = memory_pool_alloc(c->tc->data_pool);
|
||||
if (!e || !buf) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: write_to_client alloc failed sid=%08x", c->stream_id);
|
||||
if (e) queue_entry_free(e); if (buf) memory_pool_free(c->tc->data_pool, buf);
|
||||
return -1;
|
||||
}
|
||||
if (len > c->tc->data_pool->object_size) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: write_to_client len=%u > pool_sz=%zu sid=%08x",
|
||||
len, c->tc->data_pool->object_size, c->stream_id);
|
||||
queue_entry_free(e); memory_pool_free(c->tc->data_pool, buf);
|
||||
return -1;
|
||||
}
|
||||
memcpy(buf, data, len);
|
||||
e->dgram = buf; e->len = len;
|
||||
queue_data_put(c->tc->write_queue, e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// SOCKS5 handshake
|
||||
// ====================================================================
|
||||
static void process_socks_greeting(struct socks_proxy_conn* c) {
|
||||
if (c->buf_len < 3) return;
|
||||
uint8_t ver = c->buf[0], nmethods = c->buf[1];
|
||||
if (ver != 5 || c->buf_len < (uint16_t)(2 + nmethods)) return;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "socks_proxy: greeting ver=%d nmethods=%d", ver, nmethods);
|
||||
uint8_t reply[] = { 0x05, 0x00 };
|
||||
write_to_client(c, reply, 2);
|
||||
c->buf_len = 0;
|
||||
c->state = SOCKS_STATE_REQUEST;
|
||||
}
|
||||
|
||||
static void process_socks_request(struct socks_proxy_conn* c) {
|
||||
if (c->buf_len < 10) return;
|
||||
uint8_t ver = c->buf[0], cmd = c->buf[1], atyp = c->buf[3];
|
||||
if (ver != 5) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad ver=%d", ver); goto error; }
|
||||
if (cmd != 1) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: unsupported cmd=%d (only CONNECT supported)", cmd); goto error; }
|
||||
|
||||
uint16_t need;
|
||||
if (atyp == 1) need = 10; // IPv4: 4+2=6 more bytes
|
||||
else if (atyp == 3) { // domain: 1+len+2
|
||||
if (c->buf_len < 5) return;
|
||||
need = (uint16_t)(5 + c->buf[4] + 2);
|
||||
}
|
||||
else if (atyp == 4) need = 22; // IPv6: 16+2=18 more
|
||||
else { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: unsupported atyp=%d", atyp); goto error; }
|
||||
|
||||
if (c->buf_len < need) return;
|
||||
|
||||
if (atyp == 1) {
|
||||
memcpy(c->dest_ip, c->buf + 4, 4);
|
||||
memcpy(&c->dest_port, c->buf + 8, 2);
|
||||
} else if (atyp == 4) {
|
||||
// Извлекаем первые 4 байта IPv6 в dest_ip (для упрощения: IPv6 mapped IPv4 или реальный IPv6)
|
||||
memcpy(c->dest_ip, c->buf + 12, 4);
|
||||
memcpy(&c->dest_port, c->buf + 20, 2);
|
||||
// TODO: proper IPv6 support
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: IPv6 unsupported, using last 4 bytes of addr");
|
||||
} else { // atyp == 3 (domain)
|
||||
uint8_t dlen = c->buf[4];
|
||||
char domain[256]; memcpy(domain, c->buf + 5, dlen); domain[dlen] = '\0';
|
||||
memcpy(&c->dest_port, c->buf + 5 + dlen, 2);
|
||||
// resolve domain
|
||||
struct hostent* he = gethostbyname(domain);
|
||||
if (!he || he->h_addrtype != AF_INET) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: DNS failed for %s", domain);
|
||||
goto error;
|
||||
}
|
||||
memcpy(c->dest_ip, he->h_addr, 4);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: resolved %s → %d.%d.%d.%d:%d",
|
||||
domain, c->dest_ip[0], c->dest_ip[1], c->dest_ip[2], c->dest_ip[3], ntohs(c->dest_port));
|
||||
}
|
||||
|
||||
c->buf_len = 0;
|
||||
c->state = SOCKS_STATE_RELAY; // relay immediately, no waiting for first DATA
|
||||
uint8_t reply[] = { 0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
write_to_client(c, reply, 10);
|
||||
if (send_connect(c) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: send_connect failed sid=%08x", c->stream_id);
|
||||
tcp_conn_push_close(c->tc);
|
||||
}
|
||||
return;
|
||||
|
||||
error: {
|
||||
uint8_t err_reply[] = { 0x05, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
write_to_client(c, err_reply, 10);
|
||||
tcp_conn_push_close(c->tc);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// HTTP CONNECT handshake
|
||||
// ====================================================================
|
||||
static void process_http_request(struct socks_proxy_conn* c) {
|
||||
// Ищем "\r\n\r\n" или первую "\r\n" для строки CONNECT
|
||||
char* end = memmem(c->buf, c->buf_len, "\r\n\r\n", 4);
|
||||
if (!end) { end = memmem(c->buf, c->buf_len, "\r\n", 2); if (!end) return; }
|
||||
|
||||
uint16_t line_len = (uint16_t)((uint8_t*)end - c->buf);
|
||||
if (line_len < 8) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: http request too short"); goto error; }
|
||||
|
||||
// Парсим "CONNECT host:port HTTP/1.1"
|
||||
char line[512]; if (line_len > sizeof(line) - 1) line_len = sizeof(line) - 1;
|
||||
memcpy(line, c->buf, line_len); line[line_len] = '\0';
|
||||
|
||||
char host_port[256];
|
||||
if (sscanf(line, "CONNECT %255s HTTP/", host_port) != 1) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad http connect: %s", line);
|
||||
goto error;
|
||||
}
|
||||
|
||||
char* colon = strrchr(host_port, ':');
|
||||
if (!colon) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: no port in %s", host_port); goto error; }
|
||||
*colon = '\0'; int port = atoi(colon + 1);
|
||||
if (port <= 0 || port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad port %d", port); goto error; }
|
||||
|
||||
struct hostent* he = gethostbyname(host_port);
|
||||
if (!he || he->h_addrtype != AF_INET) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: DNS failed for %s", host_port);
|
||||
uint8_t resp[] = "HTTP/1.1 502 Bad Gateway\r\n\r\n";
|
||||
write_to_client(c, resp, (uint16_t)strlen((char*)resp)); tcp_conn_push_close(c->tc); return;
|
||||
}
|
||||
memcpy(c->dest_ip, he->h_addr, 4); c->dest_port = htons((uint16_t)port);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: HTTP CONNECT %s → %d.%d.%d.%d:%d",
|
||||
host_port, c->dest_ip[0], c->dest_ip[1], c->dest_ip[2], c->dest_ip[3], port);
|
||||
|
||||
c->buf_len = 0;
|
||||
c->state = HTTP_STATE_RELAY; // reply immediately, no waiting
|
||||
uint8_t resp[] = "HTTP/1.1 200 Connection Established\r\n\r\n";
|
||||
write_to_client(c, resp, (uint16_t)strlen((char*)resp));
|
||||
if (send_connect(c) < 0) {
|
||||
tcp_conn_push_close(c->tc);
|
||||
}
|
||||
return;
|
||||
|
||||
error: {
|
||||
uint8_t resp[] = "HTTP/1.1 400 Bad Request\r\n\r\n";
|
||||
write_to_client(c, resp, (uint16_t)strlen((char*)resp)); tcp_conn_push_close(c->tc);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// tcp_io read callback — парсинг рукопожатия + релей данных
|
||||
// ====================================================================
|
||||
static void on_read_cb(struct ll_queue* q, void* arg) {
|
||||
struct socks_proxy_conn* c = (struct socks_proxy_conn*)arg;
|
||||
struct ll_entry* e = queue_data_get(q);
|
||||
if (!e) { queue_resume_callback(q); return; }
|
||||
|
||||
if (c->rem_closed) {
|
||||
queue_dgram_free(e); queue_entry_free(e); queue_resume_callback(q); return;
|
||||
}
|
||||
|
||||
if (c->state == SOCKS_STATE_GREETING || c->state == SOCKS_STATE_REQUEST ||
|
||||
c->state == HTTP_STATE_REQUEST) {
|
||||
// накапливаем данные для рукопожатия
|
||||
uint16_t space = sizeof(c->buf) - c->buf_len;
|
||||
if (space < e->len) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: handshake buffer overflow sid=%08x", c->stream_id);
|
||||
memory_pool_free(c->tc->data_pool, e->dgram); queue_entry_free(e);
|
||||
tcp_conn_push_close(c->tc); queue_resume_callback(q); return;
|
||||
}
|
||||
memcpy(c->buf + c->buf_len, e->dgram, e->len);
|
||||
c->buf_len += e->len;
|
||||
memory_pool_free(c->tc->data_pool, e->dgram); queue_entry_free(e);
|
||||
queue_resume_callback(q);
|
||||
|
||||
if (c->is_http) {
|
||||
process_http_request(c);
|
||||
} else {
|
||||
if (c->state == SOCKS_STATE_GREETING) process_socks_greeting(c);
|
||||
if (c->state == SOCKS_STATE_REQUEST) process_socks_request(c);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// RELAY = релей данных в ETCP
|
||||
int ret = send_data(c, e->dgram, e->len);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "SOCKS PROXY SEND sid=%08x len=%u", c->stream_id, e->len);
|
||||
if (ret == 0) {
|
||||
memory_pool_free(c->tc->data_pool, e->dgram); queue_entry_free(e);
|
||||
queue_resume_callback(q);
|
||||
} else {
|
||||
memory_pool_free(c->tc->data_pool, e->dgram); queue_entry_free(e);
|
||||
etcp_router_waiter_register(c->inst, c->via_node_id, &c->tx_waiter, tx_waiter_cb, c);
|
||||
}
|
||||
}
|
||||
|
||||
static void tx_waiter_cb(struct ll_queue* q, void* arg) {
|
||||
(void)q;
|
||||
struct socks_proxy_conn* c = (struct socks_proxy_conn*)arg;
|
||||
if (c->rem_closed || c->close_sent) return;
|
||||
queue_resume_callback(c->tc->read_queue);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// tcp_io: FIN / Error / Closed
|
||||
// ====================================================================
|
||||
static void on_fin_cb(struct tcp_conn* tc, void* arg) {
|
||||
struct socks_proxy_conn* c = (struct socks_proxy_conn*)arg;
|
||||
if (c->rem_closed || c->close_sent) return;
|
||||
if (!tc->fin_local && !tc->write_buf && !tc->write_queue->head) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "socks_proxy: local FIN → relay FIN sid=%08x", c->stream_id);
|
||||
send_fin(c);
|
||||
if (c->fin_remote && !c->close_sent && !c->close_pending) send_close(c);
|
||||
} else {
|
||||
// данные ещё в write_queue, отложим FIN
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "socks_proxy: local FIN deferred (wq=%d wbuf=%s) sid=%08x",
|
||||
tc->write_queue->count, tc->write_buf ? "y" : "n", c->stream_id);
|
||||
tcp_conn_set_flushed(tc, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_error_cb(struct tcp_conn* tc, int err, void* arg) {
|
||||
(void)err;
|
||||
struct socks_proxy_conn* c = (struct socks_proxy_conn*)arg;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: tcp error sid=%08x err=%d", c->stream_id, err);
|
||||
if (!c->close_sent && !c->close_pending) send_error(c);
|
||||
socks_proxy_conn_free(c);
|
||||
}
|
||||
|
||||
static void on_closed_cb(struct tcp_conn* tc, void* arg) {
|
||||
struct socks_proxy_conn* c = (struct socks_proxy_conn*)arg;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "socks_proxy: tcp closed sid=%08x", c->stream_id);
|
||||
if (!c->close_sent && !c->close_pending) send_close(c);
|
||||
socks_proxy_conn_free(c);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Accept callback для слушающего сокета
|
||||
// ====================================================================
|
||||
static void on_accept_cb(socket_t sock, void* arg) {
|
||||
struct listen_ctx* ctx = (struct listen_ctx*)arg;
|
||||
struct sockaddr_in addr; socklen_t alen = sizeof(addr);
|
||||
socket_t csock = accept(sock, (struct sockaddr*)&addr, &alen);
|
||||
if (csock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: accept failed errno=%d", errno); return; }
|
||||
socket_set_nonblocking(csock);
|
||||
|
||||
struct socks_proxy_conn* c = u_calloc(1, sizeof(struct socks_proxy_conn));
|
||||
if (!c) { socket_close_wrapper(csock); DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: u_calloc failed"); return; }
|
||||
c->stream_id = ++(*ctx->next_stream_id);
|
||||
c->is_http = (uint8_t)ctx->is_http;
|
||||
c->ua = ctx->ua; c->inst = ctx->inst; c->via_node_id = ctx->via_node_id;
|
||||
c->state = ctx->is_http ? HTTP_STATE_REQUEST : SOCKS_STATE_GREETING;
|
||||
c->head = ctx->conns; c->count = ctx->conn_count;
|
||||
|
||||
c->tc = tcp_conn_create(ctx->ua, csock, 4096, 4096, 8, 0, 0, on_fin_cb, on_error_cb, c);
|
||||
if (!c->tc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: tcp_conn_create failed"); u_free(c); socket_close_wrapper(csock); return; }
|
||||
c->tc->on_closed = on_closed_cb;
|
||||
queue_set_callback(c->tc->read_queue, on_read_cb, c);
|
||||
queue_set_waiter_defer(c->tc->read_queue, 1);
|
||||
|
||||
c->next = *ctx->conns; *ctx->conns = c; (*ctx->conn_count)++;
|
||||
|
||||
char ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &addr.sin_addr, ip, sizeof(ip));
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: accepted %s conn from %s:%d sid=%08x total=%d",
|
||||
ctx->is_http ? "http" : "socks", ip, ntohs(addr.sin_port), c->stream_id, *ctx->conn_count);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Публичное API для tcp_proxy_client (etcp dispatch)
|
||||
// ====================================================================
|
||||
struct socks_proxy_conn* socks_proxy_find_conn(struct socks_proxy_conn* head, uint32_t stream_id) {
|
||||
struct socks_proxy_conn* c;
|
||||
for (c = head; c; c = c->next) if (c->stream_id == stream_id) return c;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int socks_proxy_handle_etcp(struct socks_proxy_conn** head, int* count,
|
||||
uint32_t stream_id, uint8_t subcmd,
|
||||
const uint8_t* data, size_t data_len) {
|
||||
struct socks_proxy_conn* c = socks_proxy_find_conn(*head, stream_id);
|
||||
if (!c) return 0;
|
||||
|
||||
if (subcmd == TCP_PROXY_SUBCMD_DATA) {
|
||||
if (data_len > 0) {
|
||||
if (data_len > c->tc->data_pool->object_size) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: data_len=%zu > pool_sz=%zu, dropping sid=%08x",
|
||||
data_len, c->tc->data_pool->object_size, stream_id);
|
||||
return 1;
|
||||
}
|
||||
struct ll_entry* e = queue_entry_new_from_pool(c->tc->entry_pool);
|
||||
uint8_t* buf = memory_pool_alloc(c->tc->data_pool);
|
||||
if (e && buf) {
|
||||
memcpy(buf, data, data_len); e->dgram = buf; e->len = (uint16_t)data_len;
|
||||
queue_data_put(c->tc->write_queue, e);
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: handle_data alloc failed sid=%08x", stream_id);
|
||||
if (e) queue_entry_free(e); if (buf) memory_pool_free(c->tc->data_pool, buf);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (subcmd == TCP_PROXY_SUBCMD_CLOSE) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: REM_CLOSED sid=%08x", stream_id);
|
||||
c->rem_closed = 1;
|
||||
etcp_router_waiter_cancel(c->inst, c->via_node_id, &c->tx_waiter);
|
||||
tcp_conn_push_close(c->tc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (subcmd == TCP_PROXY_SUBCMD_ERROR) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: ERROR from exit sid=%08x", stream_id);
|
||||
c->rem_closed = 1;
|
||||
etcp_router_waiter_cancel(c->inst, c->via_node_id, &c->tx_waiter);
|
||||
tcp_conn_push_close(c->tc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (subcmd == TCP_PROXY_SUBCMD_FIN) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: FIN from exit sid=%08x", stream_id);
|
||||
c->fin_remote = 1;
|
||||
tcp_conn_push_fin(c->tc);
|
||||
if (c->tc->fin_local && !c->close_sent && !c->close_pending) send_close(c);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void socks_proxy_conn_free(struct socks_proxy_conn* c) {
|
||||
if (!c) return;
|
||||
if (c->freed) return;
|
||||
c->freed = 1;
|
||||
struct socks_proxy_conn** head = c->head; int* count = c->count;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: FREE sid=%08x state=%d total=%d", c->stream_id, c->state, count ? *count : 0);
|
||||
if (c->close_pending && c->inst) {
|
||||
c->close_pending = 0;
|
||||
uint8_t subcmd = c->tc && c->tc->error ? TCP_PROXY_SUBCMD_ERROR : TCP_PROXY_SUBCMD_CLOSE;
|
||||
send_msg(c->inst, c->via_node_id, subcmd, c->stream_id, NULL, 0, 1);
|
||||
}
|
||||
if (head) { struct socks_proxy_conn** prev = head; while (*prev) { if (*prev == c) { *prev = c->next; if (count) (*count)--; break; } prev = &(*prev)->next; } }
|
||||
if (c->tc) { tcp_conn_destroy(c->tc); c->tc = NULL; }
|
||||
if (c->inst) etcp_router_waiter_cancel(c->inst, c->via_node_id, &c->tx_waiter);
|
||||
u_free(c);
|
||||
}
|
||||
|
||||
void socks_proxy_conn_free_all(struct socks_proxy_conn** head, int* count) {
|
||||
while (*head) { struct socks_proxy_conn* next = (*head)->next; socks_proxy_conn_free(*head); }
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Слушающий сокет
|
||||
// ====================================================================
|
||||
struct listen_ctx* socks_proxy_init_listen(struct UASYNC* ua, const char* addr_str,
|
||||
struct socks_proxy_conn** conns, int* count, uint32_t* next_stream_id,
|
||||
struct UTUN_INSTANCE* inst, uint64_t via_node_id, int is_http) {
|
||||
char ip[64], *colon = strrchr(addr_str, ':');
|
||||
if (!colon) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad addr '%s' (need IP:PORT)", addr_str); return NULL; }
|
||||
size_t ip_len = (size_t)(colon - addr_str);
|
||||
if (ip_len > sizeof(ip) - 1) ip_len = sizeof(ip) - 1;
|
||||
memcpy(ip, addr_str, ip_len); ip[ip_len] = '\0';
|
||||
int port = atoi(colon + 1);
|
||||
if (port <= 0 || port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad port %d", port); return NULL; }
|
||||
|
||||
struct listen_ctx* ctx = u_calloc(1, sizeof(struct listen_ctx));
|
||||
if (!ctx) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: u_calloc failed"); return NULL; }
|
||||
ctx->ua = ua; ctx->inst = inst; ctx->via_node_id = via_node_id;
|
||||
ctx->is_http = is_http; ctx->conns = conns; ctx->conn_count = count;
|
||||
ctx->next_stream_id = next_stream_id;
|
||||
|
||||
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: socket() failed errno=%d", errno); u_free(ctx); return NULL; }
|
||||
socket_set_nonblocking(sock);
|
||||
socket_set_reuseaddr(sock, 1);
|
||||
|
||||
struct sockaddr_in addr; memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET; addr.sin_port = htons((uint16_t)port);
|
||||
if (inet_pton(AF_INET, ip, &addr.sin_addr) != 1) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: inet_pton(%s) failed", ip); socket_close_wrapper(sock); u_free(ctx); return NULL; }
|
||||
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bind(%s:%d) failed errno=%d", ip, port, errno); socket_close_wrapper(sock); u_free(ctx); return NULL; }
|
||||
if (listen(sock, 32) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: listen() failed errno=%d", errno); socket_close_wrapper(sock); u_free(ctx); return NULL; }
|
||||
|
||||
ctx->listen_sock = sock;
|
||||
ctx->socket_id = uasync_add_socket_t(ua, sock, on_accept_cb, NULL, NULL, ctx);
|
||||
if (!ctx->socket_id) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: uasync_add_socket_t failed"); socket_close_wrapper(sock); u_free(ctx); return NULL; }
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: %s listening on %s:%d sock=%d",
|
||||
is_http ? "HTTP" : "SOCKS", ip, port, (int)sock);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void socks_proxy_close_listen(struct UASYNC* ua, struct listen_ctx* ctx, socket_t* sock_out) {
|
||||
if (!ctx) return;
|
||||
if (ctx->socket_id) uasync_remove_socket_t(ua, ctx->listen_sock);
|
||||
socket_close_wrapper(ctx->listen_sock);
|
||||
if (sock_out) *sock_out = SOCKET_INVALID;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: listener closed sock=%d", (int)ctx->listen_sock);
|
||||
u_free(ctx);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// socks_proxy.h — SOCKS5 / HTTP CONNECT proxy (client side)
|
||||
#ifndef SOCKS_PROXY_H
|
||||
#define SOCKS_PROXY_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "../lib/socket_compat.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
|
||||
struct UASYNC;
|
||||
struct UTUN_INSTANCE;
|
||||
struct ll_entry;
|
||||
struct ll_queue;
|
||||
struct tcp_conn;
|
||||
|
||||
enum {
|
||||
SOCKS_STATE_GREETING = 0, // ждём SOCKS5 greeting (ver + methods)
|
||||
SOCKS_STATE_REQUEST, // ждём SOCKS5 CONNECT request
|
||||
SOCKS_STATE_CONNECTING, // отправили ETCP CONNECT, ждём ответ от exit
|
||||
SOCKS_STATE_RELAY // релей данных
|
||||
};
|
||||
|
||||
enum {
|
||||
HTTP_STATE_REQUEST = 0, // ждём "CONNECT host:port HTTP/1.1\r\n"
|
||||
HTTP_STATE_CONNECTING, // отправили ETCP CONNECT, ждём ответ от exit
|
||||
HTTP_STATE_RELAY // релей данных
|
||||
};
|
||||
|
||||
struct socks_proxy_conn {
|
||||
struct socks_proxy_conn* next;
|
||||
struct socks_proxy_conn** head; // указатель на голову списка (для удаления)
|
||||
int* count; // указатель на счётчик списка
|
||||
struct tcp_conn* tc;
|
||||
struct UASYNC* ua;
|
||||
struct UTUN_INSTANCE* inst;
|
||||
uint64_t via_node_id;
|
||||
uint32_t stream_id;
|
||||
uint8_t dest_ip[4];
|
||||
uint16_t dest_port;
|
||||
uint8_t fin_remote;
|
||||
uint8_t rem_closed;
|
||||
uint8_t close_sent;
|
||||
uint8_t close_pending;
|
||||
uint8_t is_http;
|
||||
uint8_t state;
|
||||
uint8_t freed;
|
||||
uint8_t buf[1024];
|
||||
uint16_t buf_len;
|
||||
struct queue_waiter_handle tx_waiter;
|
||||
};
|
||||
|
||||
struct listen_ctx;
|
||||
|
||||
// Создать слушающий сокет для SOCKS/HTTP proxy. addr_str = "IP:PORT".
|
||||
// listen_ctx должен быть освобождён через socks_proxy_close_listen.
|
||||
// Возвращает listen_ctx при успехе, NULL при ошибке.
|
||||
struct listen_ctx* socks_proxy_init_listen(struct UASYNC* ua, const char* addr_str,
|
||||
struct socks_proxy_conn** conns, int* count, uint32_t* next_stream_id,
|
||||
struct UTUN_INSTANCE* inst, uint64_t via_node_id, int is_http);
|
||||
|
||||
// Закрыть слушающий сокет и освободить listen_ctx.
|
||||
// sock_out получает значение сокета (для последующего close).
|
||||
void socks_proxy_close_listen(struct UASYNC* ua, struct listen_ctx* ctx, socket_t* sock_out);
|
||||
|
||||
// Найти соединение по stream_id
|
||||
struct socks_proxy_conn* socks_proxy_find_conn(struct socks_proxy_conn* head, uint32_t stream_id);
|
||||
|
||||
// Обработать входящее ETCP сообщение (DATA/CLOSE/ERROR/FIN)
|
||||
// Возвращает 1 если обработано, 0 если stream_id не найден
|
||||
int socks_proxy_handle_etcp(struct socks_proxy_conn** head, int* count,
|
||||
uint32_t stream_id, uint8_t subcmd,
|
||||
const uint8_t* data, size_t data_len);
|
||||
|
||||
// Освободить одно соединение (удаляет из списка)
|
||||
void socks_proxy_conn_free(struct socks_proxy_conn* c);
|
||||
|
||||
// Освободить все соединения в списке
|
||||
void socks_proxy_conn_free_all(struct socks_proxy_conn** head, int* count);
|
||||
|
||||
#endif // SOCKS_PROXY_H
|
||||
@@ -1,5 +1,6 @@
|
||||
// tcp_proxy_client.c — TCP прокси-клиент: стек lwIP TCP → ETCP → удалённый exit узел
|
||||
#include "tcp_proxy_client.h"
|
||||
#include "socks_proxy.h"
|
||||
#include "lwip_tcp/lwip_tcp.h"
|
||||
#include "lwip_tcp/lwip_tcp_priv.h"
|
||||
#include "lwip_tcp/lwip_tcp_opts.h"
|
||||
@@ -525,6 +526,8 @@ void tcp_proxy_client_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* en
|
||||
struct tcp_proxy_client_conn *pc, *next;
|
||||
for (pc = proxy->conns; pc; pc = next) { next = pc->next; tcp_proxy_client_conn_free(pc); }
|
||||
proxy->conns = NULL; proxy->conn_count = 0;
|
||||
socks_proxy_conn_free_all(&proxy->socks_conns, &proxy->socks_conn_count);
|
||||
socks_proxy_conn_free_all(&proxy->http_conns, &proxy->http_conn_count);
|
||||
}
|
||||
}
|
||||
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
|
||||
@@ -534,6 +537,17 @@ void tcp_proxy_client_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* en
|
||||
uint32_t stream_id; memcpy(&stream_id, entry->dgram + 2, 4);
|
||||
|
||||
if (proxy) {
|
||||
size_t data_len = entry->len > TCP_PROXY_HDR_SIZE ? entry->len - TCP_PROXY_HDR_SIZE : 0;
|
||||
// Пробуем SOCKS/HTTP first (у них приоритет — могут быть без TUN)
|
||||
if (proxy->socks_enabled && socks_proxy_handle_etcp(&proxy->socks_conns, &proxy->socks_conn_count,
|
||||
stream_id, subcmd, entry->dgram + TCP_PROXY_HDR_SIZE, data_len)) {
|
||||
queue_dgram_free(entry); queue_entry_free(entry); return;
|
||||
}
|
||||
if (proxy->http_proxy_enabled && socks_proxy_handle_etcp(&proxy->http_conns, &proxy->http_conn_count,
|
||||
stream_id, subcmd, entry->dgram + TCP_PROXY_HDR_SIZE, data_len)) {
|
||||
queue_dgram_free(entry); queue_entry_free(entry); return;
|
||||
}
|
||||
// Существующие lwIP conns
|
||||
if (subcmd == TCP_PROXY_SUBCMD_DATA) { tcp_proxy_client_handle_data(proxy, conn, stream_id, entry); return; }
|
||||
if (subcmd == TCP_PROXY_SUBCMD_CLOSE) { tcp_proxy_client_handle_close(proxy, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
if (subcmd == TCP_PROXY_SUBCMD_ERROR) { tcp_proxy_client_handle_error(proxy, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
@@ -553,66 +567,90 @@ void tcp_proxy_client_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* en
|
||||
struct tcp_proxy_client* tcp_proxy_client_create(struct UTUN_INSTANCE* inst, struct UASYNC* ua,
|
||||
const char* tun_name, const char* tun_ip, int mtu, int test_mode,
|
||||
struct tcp_proxy_client_mapping_config* mappings, int mapping_count,
|
||||
uint64_t via_node_id)
|
||||
uint64_t via_node_id,
|
||||
int socks_enabled, const char* socks_addr,
|
||||
int http_proxy_enabled, const char* http_proxy_addr)
|
||||
{
|
||||
if (!ua) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: ua is NULL"); return NULL; }
|
||||
if (!tun_name || !tun_ip) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: tun name/ip required"); return NULL; }
|
||||
|
||||
int need_tun = tun_name && tun_name[0] && tun_ip && tun_ip[0];
|
||||
if (!need_tun && !socks_enabled && !http_proxy_enabled) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: no proxy mode enabled"); return NULL;
|
||||
}
|
||||
|
||||
struct tcp_proxy_client* p = u_calloc(1, sizeof(struct tcp_proxy_client));
|
||||
if (!p) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: u_calloc failed"); return NULL; }
|
||||
p->inst = inst; p->ua = ua; p->next_stream_id = 1;
|
||||
p->via_node_id = via_node_id;
|
||||
p->mappings = mappings; p->mapping_count = mapping_count;
|
||||
p->socks_enabled = socks_enabled;
|
||||
p->http_proxy_enabled = http_proxy_enabled;
|
||||
|
||||
p->entry_pool = memory_pool_init(sizeof(struct ll_entry), "entry_pool");
|
||||
if (!p->entry_pool) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: memory_pool_init failed"); u_free(p); return NULL; }
|
||||
|
||||
p->tun = tun_init_nat(ua, tun_name, tun_ip, mtu > 0 ? mtu : 1500, test_mode);
|
||||
if (!p->tun) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "tcp_proxy_client: failed to create TUN %s", tun_name); memory_pool_destroy(p->entry_pool); u_free(p); return NULL; }
|
||||
queue_set_callback(p->tun->output_queue, tcp_proxy_client_tun_input, p);
|
||||
if (need_tun) {
|
||||
p->tun = tun_init_nat(ua, tun_name, tun_ip, mtu > 0 ? mtu : 1500, test_mode);
|
||||
if (!p->tun) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "tcp_proxy_client: failed to create TUN %s", tun_name); memory_pool_destroy(p->entry_pool); u_free(p); return NULL; }
|
||||
queue_set_callback(p->tun->output_queue, tcp_proxy_client_tun_input, p);
|
||||
|
||||
p->lwip = lwip_tcp_init(ua, tcp_proxy_client_output_cb, p);
|
||||
if (!p->lwip) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: lwip_tcp_init failed");
|
||||
tun_close(p->tun);
|
||||
memory_pool_destroy(p->entry_pool); u_free(p); return NULL;
|
||||
}
|
||||
p->lwip = lwip_tcp_init(ua, tcp_proxy_client_output_cb, p);
|
||||
if (!p->lwip) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: lwip_tcp_init failed"); tun_close(p->tun); memory_pool_destroy(p->entry_pool); u_free(p); return NULL; }
|
||||
|
||||
if (mapping_count > 0) {
|
||||
int j; for (j = 0; j < mapping_count; j++) {
|
||||
uint16_t port_net = htons(mappings[j].local_port);
|
||||
struct tcp_pcb *lpcb = tcp_new(p->lwip);
|
||||
if (!lpcb) continue;
|
||||
tcp_bind(lpcb, INADDR_ANY, port_net);
|
||||
struct tcp_pcb *listen_pcb = tcp_listen(lpcb);
|
||||
if (listen_pcb) {
|
||||
tcp_arg(listen_pcb, p);
|
||||
tcp_accept(listen_pcb, tcp_proxy_client_accept_cb);
|
||||
if (mapping_count > 0) {
|
||||
int j; for (j = 0; j < mapping_count; j++) {
|
||||
uint16_t port_net = htons(mappings[j].local_port);
|
||||
struct tcp_pcb *lpcb = tcp_new(p->lwip);
|
||||
if (!lpcb) continue;
|
||||
tcp_bind(lpcb, INADDR_ANY, port_net);
|
||||
struct tcp_pcb *listen_pcb = tcp_listen(lpcb);
|
||||
if (listen_pcb) { tcp_arg(listen_pcb, p); tcp_accept(listen_pcb, tcp_proxy_client_accept_cb); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (socks_enabled && socks_addr && socks_addr[0]) {
|
||||
p->socks_listen = socks_proxy_init_listen(ua, socks_addr, &p->socks_conns, &p->socks_conn_count,
|
||||
&p->next_stream_id, inst, via_node_id, 0);
|
||||
if (!p->socks_listen) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client: SOCKS init failed on %s", socks_addr); }
|
||||
}
|
||||
|
||||
if (http_proxy_enabled && http_proxy_addr && http_proxy_addr[0]) {
|
||||
p->http_listen = socks_proxy_init_listen(ua, http_proxy_addr, &p->http_conns, &p->http_conn_count,
|
||||
&p->next_stream_id, inst, via_node_id, 1);
|
||||
if (!p->http_listen) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client: HTTP proxy init failed on %s", http_proxy_addr); }
|
||||
}
|
||||
|
||||
if (inst) {
|
||||
if (etcp_router_bind(inst, ETCP_ID_TCP_PROXY_CLIENT, tcp_proxy_client_router_recv_cb) != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy client: etcp_router_bind failed");
|
||||
} else {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client: etcp_router bind registered for ID=0x%02x", ETCP_ID_TCP_PROXY_CLIENT);
|
||||
udp_proxy_init(inst, ua);
|
||||
icmp_proxy_init(inst, ua);
|
||||
if (need_tun) { udp_proxy_init(inst, ua); icmp_proxy_init(inst, ua); }
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TCP proxy client created: mappings=%d via_node=%016llx", mapping_count, (unsigned long long)via_node_id);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TCP proxy client created: tun=%s socks=%s(http=%s) mappings=%d via_node=%016llx",
|
||||
need_tun ? "yes" : "no", socks_enabled ? "yes" : "no", http_proxy_enabled ? "yes" : "no",
|
||||
mapping_count, (unsigned long long)via_node_id);
|
||||
return p;
|
||||
}
|
||||
|
||||
void tcp_proxy_client_destroy(struct tcp_proxy_client* p) {
|
||||
if (!p) return;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client destroying: conns=%d", p->conn_count);
|
||||
int total = p->conn_count + p->socks_conn_count + p->http_conn_count;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client destroying: lwip=%d socks=%d http=%d",
|
||||
p->conn_count, p->socks_conn_count, p->http_conn_count);
|
||||
if (p->inst) etcp_router_unbind(p->inst, ETCP_ID_TCP_PROXY_CLIENT);
|
||||
udp_proxy_destroy(p->inst);
|
||||
icmp_proxy_destroy(p->inst);
|
||||
|
||||
if (p->socks_listen) socks_proxy_close_listen(p->ua, p->socks_listen, NULL);
|
||||
socks_proxy_conn_free_all(&p->socks_conns, &p->socks_conn_count);
|
||||
|
||||
if (p->http_listen) socks_proxy_close_listen(p->ua, p->http_listen, NULL);
|
||||
socks_proxy_conn_free_all(&p->http_conns, &p->http_conn_count);
|
||||
|
||||
struct tcp_proxy_client_conn* pc = p->conns;
|
||||
while (pc) {
|
||||
struct tcp_proxy_client_conn* next = pc->next;
|
||||
|
||||
@@ -17,6 +17,8 @@ struct memory_pool;
|
||||
struct tcp_proxy_client_mapping_config;
|
||||
struct tcp_proxy_server;
|
||||
struct lwip_tcp_ctx;
|
||||
struct socks_proxy_conn;
|
||||
struct listen_ctx;
|
||||
|
||||
struct tcp_pcb;
|
||||
|
||||
@@ -57,12 +59,26 @@ struct tcp_proxy_client {
|
||||
|
||||
struct tcp_proxy_client_mapping_config* mappings; // указатель на конфиг
|
||||
int mapping_count;
|
||||
|
||||
// SOCKS proxy
|
||||
int socks_enabled;
|
||||
struct socks_proxy_conn* socks_conns;
|
||||
int socks_conn_count;
|
||||
struct listen_ctx* socks_listen;
|
||||
|
||||
// HTTP CONNECT proxy
|
||||
int http_proxy_enabled;
|
||||
struct socks_proxy_conn* http_conns;
|
||||
int http_conn_count;
|
||||
struct listen_ctx* http_listen;
|
||||
};
|
||||
|
||||
struct tcp_proxy_client* tcp_proxy_client_create(struct UTUN_INSTANCE* inst, struct UASYNC* ua,
|
||||
const char* tun_name, const char* tun_ip, int mtu, int test_mode,
|
||||
struct tcp_proxy_client_mapping_config* mappings, int mapping_count,
|
||||
uint64_t via_node_id);
|
||||
uint64_t via_node_id,
|
||||
int socks_enabled, const char* socks_addr,
|
||||
int http_proxy_enabled, const char* http_proxy_addr);
|
||||
void tcp_proxy_client_destroy(struct tcp_proxy_client* p);
|
||||
void tcp_proxy_client_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry);
|
||||
|
||||
|
||||
@@ -57,6 +57,10 @@ static int send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd,
|
||||
e->dgram[1] = subcmd;
|
||||
memcpy(e->dgram + 2, &sid, 4);
|
||||
if (len > 0) memcpy(e->dgram + TCP_PROXY_HDR_SIZE, data, len);
|
||||
if (TCP_PROXY_HDR_SIZE + len > UINT16_MAX) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_server: msg too large len=%zu subcmd=%02x", len, subcmd);
|
||||
queue_dgram_free(e); queue_entry_free(e); return -1;
|
||||
}
|
||||
e->len = TCP_PROXY_HDR_SIZE + len;
|
||||
return etcp_route_send(inst, dst, e, force);
|
||||
}
|
||||
@@ -253,10 +257,10 @@ void tcp_proxy_server_conn_free(struct tcp_proxy_server_conn* rc) {
|
||||
struct tcp_proxy_server_conn** prev = &rc->ctx->conns;
|
||||
while (*prev) { if (*prev == rc) { *prev = rc->next; rc->ctx->conn_count--; break; } prev = &(*prev)->next; }
|
||||
}
|
||||
if (rc->tc) { tcp_conn_destroy(rc->tc); rc->tc = NULL; }
|
||||
if (rc->ctx && rc->ctx->inst) etcp_router_cancel_send_ready(rc->ctx->inst, rc->peer_node_id, ETCP_ID_TCP_PROXY_CLIENT, &rc->pause_waiter);
|
||||
if (rc->close_timer) { uasync_cancel_timeout(rc->ua, rc->close_timer); rc->close_timer = NULL; }
|
||||
if (rc->diag_timer) { uasync_cancel_timeout(rc->ua, rc->diag_timer); rc->diag_timer = NULL; }
|
||||
if (rc->ctx && rc->ctx->inst) etcp_router_cancel_send_ready(rc->ctx->inst, rc->peer_node_id, ETCP_ID_TCP_PROXY_CLIENT, &rc->pause_waiter);
|
||||
if (rc->tc) { tcp_conn_destroy(rc->tc); rc->tc = NULL; }
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FREE u_free rc=%p", (void*)rc);
|
||||
u_free(rc);
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ drop:
|
||||
// Сторона клиента: принять REPLY, доставить в TUN
|
||||
// ====================================================================
|
||||
static void client_handle_reply(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
if (entry->len < UDP_PROXY_HDR_SIZE + 1) { queue_entry_free(entry); queue_dgram_free(entry); return; }
|
||||
if (entry->len < UDP_PROXY_HDR_SIZE + 1) { queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
// svc_id уже обработан диспетчером etcp_router
|
||||
uint32_t src_ip, dst_ip;
|
||||
uint16_t src_port, dst_port;
|
||||
@@ -136,7 +136,7 @@ static void client_handle_reply(struct ETCP_CONN* conn, struct ll_entry* entry)
|
||||
// ====================================================================
|
||||
void udp_proxy_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
if (!entry || !entry->dgram || entry->len < 2) {
|
||||
if (entry) { queue_entry_free(entry); queue_dgram_free(entry); }
|
||||
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
|
||||
return;
|
||||
}
|
||||
uint8_t subcmd = entry->dgram[1];
|
||||
|
||||
+38
-34
@@ -441,14 +441,12 @@ void route_bgp_remove_conn(struct ETCP_CONN* conn) {
|
||||
struct ROUTE_TABLE* rt = conn->instance->rt;
|
||||
|
||||
// Remove this connection from all nodes' path lists
|
||||
bool need_withdraw = false;
|
||||
int nodes_removed = 0;
|
||||
struct ll_entry* node_entry = bgp->nodes ? bgp->nodes->head : NULL;
|
||||
while (node_entry) {
|
||||
struct ll_entry* next = node_entry->next;
|
||||
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)node_entry;
|
||||
if (route_bgp_remove_path(nq, conn) == 1) {
|
||||
need_withdraw = true;
|
||||
if (rt) route_delete(rt, nq);
|
||||
if (conn->instance && conn->instance->control_srv)
|
||||
control_server_notify_node_removed(conn->instance->control_srv, nq->node.node_id);
|
||||
@@ -460,6 +458,8 @@ void route_bgp_remove_conn(struct ETCP_CONN* conn) {
|
||||
if (entry) { queue_remove_data(bgp->nodes, entry); queue_entry_free(entry); }
|
||||
nodes_removed++;
|
||||
etcp_router_conn_close_all_for_node(bgp->instance, key);
|
||||
uint64_t wd_src = (key == conn->peer_node_id) ? conn->instance->node_id : conn->peer_node_id;
|
||||
route_bgp_broadcast_withdraw(bgp, key, wd_src, NULL);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Removed node %016llx after link down", (unsigned long long)key);
|
||||
}
|
||||
node_entry = next;
|
||||
@@ -477,12 +477,8 @@ void route_bgp_remove_conn(struct ETCP_CONN* conn) {
|
||||
e = e->next;
|
||||
}
|
||||
|
||||
if (need_withdraw) {
|
||||
route_bgp_broadcast_withdraw(bgp, conn->peer_node_id, conn->instance->node_id, NULL);
|
||||
}
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP peer removed: %s nodes=%d withdraw=%d",
|
||||
conn->log_name, nodes_removed, need_withdraw);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP peer removed: %s nodes=%d",
|
||||
conn->log_name, nodes_removed);
|
||||
}
|
||||
|
||||
|
||||
@@ -495,14 +491,23 @@ struct ETCP_CONN* route_bgp_find_conn_for_node(struct ROUTE_BGP* bgp, uint64_t n
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(bgp, node_id);
|
||||
if (!nq || !nq->paths || !nq->paths->head) return NULL;
|
||||
struct ll_entry* e = nq->paths->head;
|
||||
struct NODEINFO_PATH* best = NULL;
|
||||
uint8_t best_hops = 255;
|
||||
struct NODEINFO_PATH* best_live = NULL;
|
||||
uint8_t best_live_hops = 255;
|
||||
struct NODEINFO_PATH* best_any = NULL;
|
||||
uint8_t best_any_hops = 255;
|
||||
while (e) {
|
||||
struct NODEINFO_PATH* path = (struct NODEINFO_PATH*)e;
|
||||
if (path->conn && path->hop_count < best_hops) { best = path; best_hops = path->hop_count; }
|
||||
if (path->conn) {
|
||||
if (path->conn->links_up && path->hop_count < best_live_hops) { best_live = path; best_live_hops = path->hop_count; }
|
||||
if (path->hop_count < best_any_hops) { best_any = path; best_any_hops = path->hop_count; }
|
||||
}
|
||||
e = e->next;
|
||||
}
|
||||
return best ? best->conn : NULL;
|
||||
if (!best_live && best_any) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "find_conn_for_node %016llx: no live paths, fallback to best (hops=%d links_up=%d)",
|
||||
(unsigned long long)node_id, best_any_hops, best_any->conn->links_up);
|
||||
}
|
||||
return best_live ? best_live->conn : (best_any ? best_any->conn : NULL);
|
||||
}
|
||||
|
||||
int route_bgp_add_path(struct NODEINFO_Q* nq, struct ETCP_CONN* conn, uint64_t* hop_list, uint8_t hop_count) {
|
||||
@@ -745,8 +750,19 @@ int route_bgp_process_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, co
|
||||
struct NODEINFO_Q* nodeinfo1 = nodeinfo_find_by_id(bgp, node_id);
|
||||
uint8_t new_ver = ni->ver;
|
||||
|
||||
if (node_id == bgp->instance->node_id) return 0;
|
||||
|
||||
if (nodeinfo1 && (int8_t)(nodeinfo1->last_ver-new_ver)>=0) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "NODEINFO ignored old ver: node=%016llx ver=%d<=%d from=%s",
|
||||
int new_hops = ni->hop_count + 1;
|
||||
if (new_hops <= MAX_HOPS) {
|
||||
uint64_t hop_list[MAX_HOPS];
|
||||
uint64_t* pkt_hops = (uint64_t*)((uint8_t*)ni + sizeof(struct NODEINFO) + dyn_size);
|
||||
memcpy(hop_list, pkt_hops, ni->hop_count * 8);
|
||||
hop_list[ni->hop_count] = from->peer_node_id;
|
||||
route_bgp_remove_path_by_hop(nodeinfo1, from->peer_node_id);
|
||||
route_bgp_add_path(nodeinfo1, from, hop_list, new_hops);
|
||||
}
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "NODEINFO old ver: node=%016llx ver=%d<=%d from=%s (paths updated)",
|
||||
(unsigned long long)node_id, new_ver, nodeinfo1->last_ver, from->log_name);
|
||||
return 0;
|
||||
}
|
||||
@@ -774,6 +790,7 @@ int route_bgp_process_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, co
|
||||
|
||||
if (need_alloc) {
|
||||
if (nodeinfo1) {
|
||||
if (bgp->instance->rt) route_delete(bgp->instance->rt, nodeinfo1);
|
||||
queue_remove_data(bgp->nodes, &nodeinfo1->ll);
|
||||
queue_free(paths);
|
||||
queue_entry_free(&nodeinfo1->ll);
|
||||
@@ -796,22 +813,6 @@ int route_bgp_process_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, co
|
||||
memcpy(&nodeinfo1->node, ni, sizeof(struct NODEINFO) + dyn_size);
|
||||
}
|
||||
|
||||
/* Если это наш local_node (по node_id), обновляем его данные */
|
||||
if (node_id == bgp->instance->node_id && bgp->local_node) {
|
||||
uint8_t* dyn_src = (uint8_t*)&nodeinfo1->node + sizeof(struct NODEINFO);
|
||||
uint8_t* dyn_dst = (uint8_t*)&bgp->local_node->node + sizeof(struct NODEINFO);
|
||||
int dyn_sz = nodeinfo1->node.node_name_len +
|
||||
nodeinfo1->node.local_v4_sockets * sizeof(struct NODEINFO_IPV4_SOCKET_META) +
|
||||
nodeinfo1->node.local_v4_addrs * sizeof(struct NODEINFO_IPV4_ADDR) +
|
||||
nodeinfo1->node.local_v6_sockets * sizeof(struct NODEINFO_IPV6_SOCKET_META) +
|
||||
nodeinfo1->node.local_v6_addrs * sizeof(struct NODEINFO_IPV6_ADDR) +
|
||||
nodeinfo1->node.local_v4_subnets * sizeof(struct NODEINFO_IPV4_SUBNET) +
|
||||
nodeinfo1->node.local_v6_subnets * sizeof(struct NODEINFO_IPV6_SUBNET);
|
||||
memcpy(dyn_dst, dyn_src, dyn_sz);
|
||||
bgp->local_node->node.ver = nodeinfo1->node.ver;
|
||||
bgp->local_node->last_ver = new_ver;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Updated local_node from remote NODEINFO: node=%016llx", (unsigned long long)node_id);
|
||||
}
|
||||
nodeinfo1->paths = paths;
|
||||
nodeinfo1->last_ver = new_ver;
|
||||
|
||||
@@ -843,10 +844,12 @@ int route_bgp_process_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, co
|
||||
struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)e->data;
|
||||
if (item->conn) {
|
||||
uint64_t id=item->conn->peer_node_id;
|
||||
int found=0;
|
||||
for (int i=0; i<hop_count; i++) if (hop_list[i]==id) found=1;
|
||||
if (found==0) route_bgp_send_nodeinfo(nodeinfo1, item->conn);
|
||||
else DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Skip send NODEINFO to node %016llx (loop prevention)",id);
|
||||
if (id != node_id) {
|
||||
int found=0;
|
||||
for (int i=0; i<hop_count; i++) if (hop_list[i]==id) found=1;
|
||||
if (found==0) route_bgp_send_nodeinfo(nodeinfo1, item->conn);
|
||||
else DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Skip send NODEINFO to node %016llx (loop prevention)",id);
|
||||
}
|
||||
}
|
||||
e = e->next;
|
||||
}
|
||||
@@ -878,7 +881,7 @@ int route_bgp_process_withdraw(struct ROUTE_BGP* bgp, struct ETCP_CONN* sender,
|
||||
return 0;
|
||||
}
|
||||
int ret=route_bgp_remove_path_by_hop(nq, wd_source);
|
||||
if (ret>0 || !nq->paths || (nq->paths && queue_entry_count(nq->paths) == 0)) {
|
||||
if (ret == 1) {
|
||||
if (bgp->instance && bgp->instance->rt) route_delete(bgp->instance->rt, nq);
|
||||
if (bgp->instance->control_srv) control_server_notify_node_removed(bgp->instance->control_srv, node_id);
|
||||
nq->dirty = 1;
|
||||
@@ -954,6 +957,7 @@ static void route_bgp_add_to_senders(struct ROUTE_BGP* bgp, struct ETCP_CONN* co
|
||||
|
||||
static bool route_bgp_should_send_to(const struct NODEINFO_Q* nq, uint64_t target_id) {
|
||||
if (!nq || !nq->paths) return false;
|
||||
if (target_id == nq->node.node_id) return false;
|
||||
struct ll_entry* e = nq->paths->head;
|
||||
while (e) {
|
||||
struct NODEINFO_PATH* path = (struct NODEINFO_PATH*)e;
|
||||
|
||||
+37
-25
@@ -20,6 +20,7 @@
|
||||
#define CONN_MAX_SOCKET_CANDIDATES 8
|
||||
|
||||
struct conn_probe_ctx {
|
||||
struct conn_probe_ctx* next; /* linked list in nq->connectivity.probe_list */
|
||||
struct UTUN_INSTANCE* instance;
|
||||
struct NODEINFO_Q* nq;
|
||||
uint8_t addr_type; // ADDR_TYPE_*
|
||||
@@ -245,33 +246,37 @@ static void conn_probe_single_cb(int success, uint16_t rtt, void* arg,
|
||||
}
|
||||
|
||||
static void conn_probe_finish(struct conn_probe_ctx* ctx, int ok) {
|
||||
if (!ctx || !ctx->nq) return;
|
||||
struct NODE_CONNECTIVITY* c = &ctx->nq->connectivity;
|
||||
if (!ctx) return;
|
||||
if (ctx->nq) {
|
||||
struct NODE_CONNECTIVITY* c = &ctx->nq->connectivity;
|
||||
struct conn_probe_ctx** p = (struct conn_probe_ctx**)&c->probe_list;
|
||||
while (*p) { if (*p == ctx) { *p = ctx->next; break; } p = &(*p)->next; }
|
||||
|
||||
switch (ctx->addr_type) {
|
||||
case ADDR_TYPE_INTERFACE:
|
||||
c->interface_status = ok ? PROBE_RESULT_REACHABLE : (c->interface_status == PROBE_RESULT_UNKNOWN ? PROBE_RESULT_UNREACHABLE : c->interface_status);
|
||||
if (ok && ctx->best_across_sockets < c->interface_min_rtt) c->interface_min_rtt = ctx->best_across_sockets;
|
||||
c->interface_probe_time = get_time_tb();
|
||||
break;
|
||||
case ADDR_TYPE_NAT:
|
||||
c->nat_status = ok ? PROBE_RESULT_REACHABLE : (c->nat_status == PROBE_RESULT_UNKNOWN ? PROBE_RESULT_UNREACHABLE : c->nat_status);
|
||||
if (ok && ctx->best_across_sockets < c->nat_min_rtt) c->nat_min_rtt = ctx->best_across_sockets;
|
||||
c->nat_probe_time = get_time_tb();
|
||||
break;
|
||||
case ADDR_TYPE_REAL:
|
||||
c->real_status = ok ? PROBE_RESULT_REACHABLE : (c->real_status == PROBE_RESULT_UNKNOWN ? PROBE_RESULT_UNREACHABLE : c->real_status);
|
||||
if (ok && ctx->best_across_sockets < c->real_min_rtt) c->real_min_rtt = ctx->best_across_sockets;
|
||||
c->real_probe_time = get_time_tb();
|
||||
break;
|
||||
}
|
||||
switch (ctx->addr_type) {
|
||||
case ADDR_TYPE_INTERFACE:
|
||||
c->interface_status = ok ? PROBE_RESULT_REACHABLE : (c->interface_status == PROBE_RESULT_UNKNOWN ? PROBE_RESULT_UNREACHABLE : c->interface_status);
|
||||
if (ok && ctx->best_across_sockets < c->interface_min_rtt) c->interface_min_rtt = ctx->best_across_sockets;
|
||||
c->interface_probe_time = get_time_tb();
|
||||
break;
|
||||
case ADDR_TYPE_NAT:
|
||||
c->nat_status = ok ? PROBE_RESULT_REACHABLE : (c->nat_status == PROBE_RESULT_UNKNOWN ? PROBE_RESULT_UNREACHABLE : c->nat_status);
|
||||
if (ok && ctx->best_across_sockets < c->nat_min_rtt) c->nat_min_rtt = ctx->best_across_sockets;
|
||||
c->nat_probe_time = get_time_tb();
|
||||
break;
|
||||
case ADDR_TYPE_REAL:
|
||||
c->real_status = ok ? PROBE_RESULT_REACHABLE : (c->real_status == PROBE_RESULT_UNKNOWN ? PROBE_RESULT_UNREACHABLE : c->real_status);
|
||||
if (ok && ctx->best_across_sockets < c->real_min_rtt) c->real_min_rtt = ctx->best_across_sockets;
|
||||
c->real_probe_time = get_time_tb();
|
||||
break;
|
||||
}
|
||||
|
||||
if (c->pending_count > 0) c->pending_count--;
|
||||
if (c->pending_count == 0) {
|
||||
c->probe_status = PROBE_STATUS_DONE;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "connectivity probe DONE for node %016llx: intf=%d nat=%d real=%d",
|
||||
(unsigned long long)ctx->nq->node.node_id,
|
||||
c->interface_status, c->nat_status, c->real_status);
|
||||
if (c->pending_count > 0) c->pending_count--;
|
||||
if (c->pending_count == 0) {
|
||||
c->probe_status = PROBE_STATUS_DONE;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "connectivity probe DONE for node %016llx: intf=%d nat=%d real=%d",
|
||||
(unsigned long long)ctx->nq->node.node_id,
|
||||
c->interface_status, c->nat_status, c->real_status);
|
||||
}
|
||||
}
|
||||
|
||||
u_free(ctx);
|
||||
@@ -349,6 +354,8 @@ void route_connectivity_probe_node(struct UTUN_INSTANCE* instance, struct NODEIN
|
||||
ctx->timeout_ms = CONN_PROBE_TIMEOUT_MS;
|
||||
ctx->best_across_sockets = 65535;
|
||||
|
||||
ctx->next = (struct conn_probe_ctx*)nq->connectivity.probe_list;
|
||||
nq->connectivity.probe_list = ctx;
|
||||
pend++;
|
||||
conn_probe_start_series(ctx);
|
||||
}
|
||||
@@ -407,6 +414,8 @@ void route_connectivity_probe_node(struct UTUN_INSTANCE* instance, struct NODEIN
|
||||
ctx->timeout_ms = CONN_PROBE_TIMEOUT_MS;
|
||||
ctx->best_across_sockets = 65535;
|
||||
|
||||
ctx->next = (struct conn_probe_ctx*)nq->connectivity.probe_list;
|
||||
nq->connectivity.probe_list = ctx;
|
||||
pend++;
|
||||
conn_probe_start_series(ctx);
|
||||
}
|
||||
@@ -428,6 +437,9 @@ void route_connectivity_cancel_node(struct UTUN_INSTANCE* instance, struct NODEI
|
||||
if (!instance || !nq) return;
|
||||
nq->connectivity.probe_status = PROBE_STATUS_NONE;
|
||||
nq->connectivity.pending_count = 0;
|
||||
struct conn_probe_ctx* ctx = (struct conn_probe_ctx*)nq->connectivity.probe_list;
|
||||
while (ctx) { ctx->nq = NULL; ctx = ctx->next; }
|
||||
nq->connectivity.probe_list = NULL;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "connectivity probe cancelled for node %016llx", (unsigned long long)nq->node.node_id);
|
||||
}
|
||||
|
||||
|
||||
+1
-4
@@ -21,10 +21,7 @@ static uint32_t prefix_to_mask(uint8_t prefix) {
|
||||
}
|
||||
|
||||
static bool routes_overlap(uint32_t net1, uint8_t pre1, uint32_t net2, uint8_t pre2) {
|
||||
uint32_t mask1 = prefix_to_mask(pre1);
|
||||
uint32_t mask2 = prefix_to_mask(pre2);
|
||||
if ((net1 & mask2) == net2 && pre1 >= pre2) return true;
|
||||
if ((net2 & mask1) == net1 && pre2 >= pre1) return true;
|
||||
if (pre1 == pre2 && net1 == net2) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+306
-1
@@ -10,6 +10,7 @@
|
||||
#include "config_parser.h"
|
||||
#include "route_node.h"
|
||||
#include "route_bgp.h"
|
||||
#include "route_lib.h"
|
||||
#include "etcp_debug.h"
|
||||
|
||||
static const uint8_t* node_dyn_start(const struct NODEINFO* info) {
|
||||
@@ -128,6 +129,303 @@ struct NODEINFO_Q* nodeinfo_find_by_id(struct ROUTE_BGP* bgp, uint64_t node_id)
|
||||
return e ? (struct NODEINFO_Q*)e : NULL;
|
||||
}
|
||||
|
||||
static int is_node_connectivity_active(struct NODE_CONNECTIVITY* c) {
|
||||
return c->probe_status != PROBE_STATUS_NONE
|
||||
|| c->interface_status != PROBE_RESULT_UNKNOWN
|
||||
|| c->nat_status != PROBE_RESULT_UNKNOWN
|
||||
|| c->real_status != PROBE_RESULT_UNKNOWN
|
||||
|| c->probe_start_time != 0
|
||||
|| c->ping_req_time != 0;
|
||||
}
|
||||
|
||||
static const char* probe_status_str(uint8_t s) {
|
||||
switch (s) { case 0: return "NONE"; case 1: return "IN_PROGRESS"; case 2: return "DONE"; default: return "?"; }
|
||||
}
|
||||
static const char* probe_result_str(uint8_t s) {
|
||||
switch (s) { case 0: return "UNKNOWN"; case 1: return "REACHABLE"; case 2: return "UNREACHABLE"; default: return "?"; }
|
||||
}
|
||||
static const char* conn_mgr_type_str(uint8_t t) {
|
||||
switch (t) { case 1: return "DIRECT"; case 2: return "REVERSE"; case 3: return "INDIRECT"; default: return "NONE"; }
|
||||
}
|
||||
|
||||
void route_node_dump_all(struct ROUTE_BGP* bgp) {
|
||||
if (!bgp || !bgp->nodes) return;
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "=== BGP Nodes dump: %d nodes ===", queue_entry_count(bgp->nodes));
|
||||
|
||||
int idx = 0;
|
||||
struct ll_entry* e = bgp->nodes->head;
|
||||
while (e) {
|
||||
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)e;
|
||||
struct NODEINFO* ni = &nq->node;
|
||||
int is_local = (bgp->local_node == nq);
|
||||
const char* name_s = (ni->node_name_len > 0) ? (const char*)((const uint8_t*)ni + sizeof(struct NODEINFO)) : "";
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "--- Node #%d: id=%016llx name=\"%.*s\" ver=%u dirty=%u alien=%u%s ---",
|
||||
idx, (unsigned long long)ni->node_id, ni->node_name_len, name_s,
|
||||
ni->ver, nq->dirty, nq->alien, is_local ? " [LOCAL]" : "");
|
||||
|
||||
if (nq->best_socket) DEBUG_INFO(DEBUG_CATEGORY_BGP, " best_socket=%p", (void*)nq->best_socket);
|
||||
|
||||
log_dump(DEBUG_LEVEL_INFO, DEBUG_CATEGORY_BGP, " pubkey ", ni->public_key, SC_PUBKEY_SIZE);
|
||||
log_dump(DEBUG_LEVEL_INFO, DEBUG_CATEGORY_BGP, " ed25519", ni->ed25519_public_key, SC_PUBKEY_SIZE);
|
||||
|
||||
const struct NODEINFO_IPV4_SOCKET_META* v4sm;
|
||||
int v4sm_cnt = get_node_v4_sockets_meta(nq, &v4sm);
|
||||
if (v4sm_cnt > 0) for (int i = 0; i < v4sm_cnt; i++) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, " v4sock[%d]: id=%u type=%u", i, v4sm[i].id, v4sm[i].type);
|
||||
}
|
||||
|
||||
const struct NODEINFO_IPV4_ADDR* v4a;
|
||||
int v4a_cnt = get_node_v4_addrs(nq, &v4a);
|
||||
if (v4a_cnt > 0) {
|
||||
const char* atn[] = {"INTERFACE","NAT","REAL"};
|
||||
for (int i = 0; i < v4a_cnt; i++) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, " v4addr[%d]: %s:%u %s sock=%u",
|
||||
i, ip_to_str(v4a[i].addr, AF_INET).str, v4a[i].port,
|
||||
(v4a[i].type<=2)?atn[v4a[i].type]:"?", v4a[i].socket_id);
|
||||
}
|
||||
}
|
||||
|
||||
const struct NODEINFO_IPV6_SOCKET_META* v6sm;
|
||||
int v6sm_cnt = get_node_v6_sockets_meta(nq, &v6sm);
|
||||
if (v6sm_cnt > 0) for (int i = 0; i < v6sm_cnt; i++) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, " v6sock[%d]: id=%u type=%u", i, v6sm[i].id, v6sm[i].type);
|
||||
}
|
||||
|
||||
const struct NODEINFO_IPV6_ADDR* v6a;
|
||||
int v6a_cnt = get_node_v6_addrs(nq, &v6a);
|
||||
if (v6a_cnt > 0) {
|
||||
for (int i = 0; i < v6a_cnt; i++) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, " v6addr[%d]: %s:%u type=%u sock=%u",
|
||||
i, ip_to_str(v6a[i].addr, AF_INET6).str, v6a[i].port,
|
||||
v6a[i].type, v6a[i].socket_id);
|
||||
}
|
||||
}
|
||||
|
||||
const struct NODEINFO_IPV4_SUBNET* sub4;
|
||||
int sub4_cnt = get_node_routes(nq, &sub4);
|
||||
if (sub4_cnt > 0) for (int i = 0; i < sub4_cnt; i++) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, " v4sub[%d]: %s/%d", i,
|
||||
ip_to_str(sub4[i].addr, AF_INET).str, sub4[i].prefix_length);
|
||||
}
|
||||
|
||||
const struct NODEINFO_IPV6_SUBNET* sub6;
|
||||
int sub6_cnt = get_node_v6_routes(nq, &sub6);
|
||||
if (sub6_cnt > 0) for (int i = 0; i < sub6_cnt; i++) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, " v6sub[%d]: %s/%d", i,
|
||||
ip_to_str(sub6[i].addr, AF_INET6).str, sub6[i].prefix_length);
|
||||
}
|
||||
|
||||
if (ni->tranzit_nodes > 0) {
|
||||
const uint8_t* dyn = node_dyn_start(ni);
|
||||
dyn = skip_name(dyn, ni->node_name_len);
|
||||
dyn = skip_v4_sockets_meta(dyn, ni->local_v4_sockets);
|
||||
dyn = skip_v4_addrs(dyn, ni->local_v4_addrs);
|
||||
dyn = skip_v6_sockets_meta(dyn, ni->local_v6_sockets);
|
||||
dyn = skip_v6_addrs(dyn, ni->local_v6_addrs);
|
||||
dyn = skip_v4_subnets(dyn, ni->local_v4_subnets);
|
||||
const struct NODEINFO_IPV6_SUBNET* sub6_dyn = (const struct NODEINFO_IPV6_SUBNET*)dyn;
|
||||
dyn += ni->local_v6_subnets * sizeof(struct NODEINFO_IPV6_SUBNET);
|
||||
const struct NODEINFO_TRANZIT_NODE* tr = (const struct NODEINFO_TRANZIT_NODE*)dyn;
|
||||
for (uint8_t i = 0; i < ni->tranzit_nodes; i++) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, " tranzit[%d]: node=%016llx rtt=%u linkq=%u",
|
||||
i, (unsigned long long)tr[i].node_id, tr[i].rtt, tr[i].link_q);
|
||||
}
|
||||
}
|
||||
|
||||
int path_idx = 0;
|
||||
if (nq->paths) {
|
||||
struct ll_entry* pe = nq->paths->head;
|
||||
while (pe) {
|
||||
struct NODEINFO_PATH* path = (struct NODEINFO_PATH*)pe;
|
||||
uint64_t* hop = (uint64_t*)((uint8_t*)path + sizeof(struct NODEINFO_PATH));
|
||||
char hop_buf[256] = "";
|
||||
int hop_pos = 0;
|
||||
for (uint8_t hi = 0; hi < path->hop_count && hop_pos < (int)sizeof(hop_buf)-20; hi++) {
|
||||
hop_pos += snprintf(hop_buf + hop_pos, sizeof(hop_buf) - hop_pos,
|
||||
"%s%016llx", hi ? "->" : "", (unsigned long long)hop[hi]);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, " path[%d]: conn=%s hop_count=%u hops=[%s]",
|
||||
path_idx, path->conn ? path->conn->log_name : "NULL",
|
||||
path->hop_count, hop_buf);
|
||||
path_idx++;
|
||||
pe = pe->next;
|
||||
}
|
||||
}
|
||||
|
||||
struct NODE_CONNECTIVITY* c = &nq->connectivity;
|
||||
if (is_node_connectivity_active(c)) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, " connectivity: probe=%s iface=%s(%u) nat=%s(%u) real=%s(%u) pend=%u iface_rtt=%u nat_rtt=%u real_rtt=%u start_tb=%llu ping_req_tb=%llu",
|
||||
probe_status_str(c->probe_status),
|
||||
probe_result_str(c->interface_status), c->interface_min_rtt,
|
||||
probe_result_str(c->nat_status), c->nat_min_rtt,
|
||||
probe_result_str(c->real_status), c->real_min_rtt,
|
||||
c->pending_count, c->interface_min_rtt, c->nat_min_rtt, c->real_min_rtt,
|
||||
(unsigned long long)c->probe_start_time, (unsigned long long)c->ping_req_time);
|
||||
}
|
||||
|
||||
if (nq->conn_mgr_type != CONN_TYPE_NONE) {
|
||||
char interm_buf[200] = "";
|
||||
int ipos = 0;
|
||||
for (uint8_t i = 0; i < nq->conn_mgr_intermediariy_count && ipos < (int)sizeof(interm_buf)-19; i++) {
|
||||
ipos += snprintf(interm_buf + ipos, sizeof(interm_buf) - ipos,
|
||||
"%s%016llx", i ? "," : "", (unsigned long long)nq->conn_mgr_intermediaries[i]);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, " conn_mgr: type=%s intermediates=[%s]",
|
||||
conn_mgr_type_str(nq->conn_mgr_type), interm_buf);
|
||||
}
|
||||
|
||||
e = e->next;
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
int route_node_format_all(struct ROUTE_BGP* bgp, char* buf, size_t buf_size) {
|
||||
if (!bgp || !bgp->nodes || !buf || buf_size == 0) return 0;
|
||||
int pos = 0;
|
||||
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0,
|
||||
"=== BGP Nodes dump: %d nodes ===\n", queue_entry_count(bgp->nodes));
|
||||
|
||||
int idx = 0;
|
||||
struct ll_entry* e = bgp->nodes->head;
|
||||
while (e && (size_t)pos < buf_size) {
|
||||
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)e;
|
||||
struct NODEINFO* ni = &nq->node;
|
||||
int is_local = (bgp->local_node == nq);
|
||||
const char* name_s = (ni->node_name_len > 0) ? (const char*)((const uint8_t*)ni + sizeof(struct NODEINFO)) : "";
|
||||
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0,
|
||||
"--- Node #%d: id=%016llx name=\"%.*s\" ver=%u dirty=%u alien=%u%s ---\n",
|
||||
idx, (unsigned long long)ni->node_id, ni->node_name_len, name_s,
|
||||
ni->ver, nq->dirty, nq->alien, is_local ? " [LOCAL]" : "");
|
||||
|
||||
if (nq->best_socket)
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0,
|
||||
" best_socket=%p\n", (void*)nq->best_socket);
|
||||
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, " pubkey: ");
|
||||
for (int k = 0; k < SC_PUBKEY_SIZE; k++)
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "%02x", ni->public_key[k]);
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "\n");
|
||||
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, " ed25519: ");
|
||||
for (int k = 0; k < SC_PUBKEY_SIZE; k++)
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "%02x", ni->ed25519_public_key[k]);
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "\n");
|
||||
|
||||
#define FMT_ADD(fmt, ...) \
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, fmt, ##__VA_ARGS__)
|
||||
|
||||
const struct NODEINFO_IPV4_SOCKET_META* v4sm;
|
||||
int v4sm_cnt = get_node_v4_sockets_meta(nq, &v4sm);
|
||||
for (int i = 0; i < v4sm_cnt; i++)
|
||||
FMT_ADD(" v4sock[%d]: id=%u type=%u\n", i, v4sm[i].id, v4sm[i].type);
|
||||
|
||||
const struct NODEINFO_IPV4_ADDR* v4a;
|
||||
int v4a_cnt = get_node_v4_addrs(nq, &v4a);
|
||||
if (v4a_cnt > 0) {
|
||||
const char* atn[] = {"INTERFACE","NAT","REAL"};
|
||||
for (int i = 0; i < v4a_cnt; i++)
|
||||
FMT_ADD(" v4addr[%d]: %s:%u %s sock=%u\n",
|
||||
i, ip_to_str(v4a[i].addr, AF_INET).str, v4a[i].port,
|
||||
(v4a[i].type<=2)?atn[v4a[i].type]:"?", v4a[i].socket_id);
|
||||
}
|
||||
|
||||
const struct NODEINFO_IPV6_SOCKET_META* v6sm;
|
||||
int v6sm_cnt = get_node_v6_sockets_meta(nq, &v6sm);
|
||||
for (int i = 0; i < v6sm_cnt; i++)
|
||||
FMT_ADD(" v6sock[%d]: id=%u type=%u\n", i, v6sm[i].id, v6sm[i].type);
|
||||
|
||||
const struct NODEINFO_IPV6_ADDR* v6a;
|
||||
int v6a_cnt = get_node_v6_addrs(nq, &v6a);
|
||||
for (int i = 0; i < v6a_cnt; i++)
|
||||
FMT_ADD(" v6addr[%d]: %s:%u type=%u sock=%u\n",
|
||||
i, ip_to_str(v6a[i].addr, AF_INET6).str, v6a[i].port,
|
||||
v6a[i].type, v6a[i].socket_id);
|
||||
|
||||
const struct NODEINFO_IPV4_SUBNET* sub4;
|
||||
int sub4_cnt = get_node_routes(nq, &sub4);
|
||||
for (int i = 0; i < sub4_cnt; i++)
|
||||
FMT_ADD(" v4sub[%d]: %s/%d\n", i,
|
||||
ip_to_str(sub4[i].addr, AF_INET).str, sub4[i].prefix_length);
|
||||
|
||||
const struct NODEINFO_IPV6_SUBNET* sub6;
|
||||
int sub6_cnt = get_node_v6_routes(nq, &sub6);
|
||||
for (int i = 0; i < sub6_cnt; i++)
|
||||
FMT_ADD(" v6sub[%d]: %s/%d\n", i,
|
||||
ip_to_str(sub6[i].addr, AF_INET6).str, sub6[i].prefix_length);
|
||||
|
||||
if (ni->tranzit_nodes > 0) {
|
||||
const uint8_t* dyn = node_dyn_start(ni);
|
||||
dyn = skip_name(dyn, ni->node_name_len);
|
||||
dyn = skip_v4_sockets_meta(dyn, ni->local_v4_sockets);
|
||||
dyn = skip_v4_addrs(dyn, ni->local_v4_addrs);
|
||||
dyn = skip_v6_sockets_meta(dyn, ni->local_v6_sockets);
|
||||
dyn = skip_v6_addrs(dyn, ni->local_v6_addrs);
|
||||
dyn = skip_v4_subnets(dyn, ni->local_v4_subnets);
|
||||
dyn += ni->local_v6_subnets * sizeof(struct NODEINFO_IPV6_SUBNET);
|
||||
const struct NODEINFO_TRANZIT_NODE* tr = (const struct NODEINFO_TRANZIT_NODE*)dyn;
|
||||
for (uint8_t i = 0; i < ni->tranzit_nodes; i++)
|
||||
FMT_ADD(" tranzit[%d]: node=%016llx rtt=%u linkq=%u\n",
|
||||
i, (unsigned long long)tr[i].node_id, tr[i].rtt, tr[i].link_q);
|
||||
}
|
||||
|
||||
if (nq->paths) {
|
||||
int path_idx = 0;
|
||||
struct ll_entry* pe = nq->paths->head;
|
||||
while (pe) {
|
||||
struct NODEINFO_PATH* path = (struct NODEINFO_PATH*)pe;
|
||||
uint64_t* hop = (uint64_t*)((uint8_t*)path + sizeof(struct NODEINFO_PATH));
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0,
|
||||
" path[%d]: conn=%s hop_count=%u hops=[",
|
||||
path_idx, path->conn ? path->conn->log_name : "NULL", path->hop_count);
|
||||
for (uint8_t hi = 0; hi < path->hop_count; hi++)
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0,
|
||||
"%s%016llx", hi ? "->" : "", (unsigned long long)hop[hi]);
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "]\n");
|
||||
path_idx++;
|
||||
pe = pe->next;
|
||||
}
|
||||
}
|
||||
|
||||
struct NODE_CONNECTIVITY* c = &nq->connectivity;
|
||||
if (is_node_connectivity_active(c)) {
|
||||
FMT_ADD(" connectivity: probe=%s iface=%s(%u) nat=%s(%u) real=%s(%u) pend=%u iface_rtt=%u nat_rtt=%u real_rtt=%u start_tb=%llu ping_req_tb=%llu\n",
|
||||
probe_status_str(c->probe_status),
|
||||
probe_result_str(c->interface_status), c->interface_min_rtt,
|
||||
probe_result_str(c->nat_status), c->nat_min_rtt,
|
||||
probe_result_str(c->real_status), c->real_min_rtt,
|
||||
c->pending_count, c->interface_min_rtt, c->nat_min_rtt, c->real_min_rtt,
|
||||
(unsigned long long)c->probe_start_time, (unsigned long long)c->ping_req_time);
|
||||
}
|
||||
|
||||
if (nq->conn_mgr_type != CONN_TYPE_NONE) {
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0,
|
||||
" conn_mgr: type=%s intermediates=[",
|
||||
conn_mgr_type_str(nq->conn_mgr_type));
|
||||
for (uint8_t i = 0; i < nq->conn_mgr_intermediariy_count; i++)
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0,
|
||||
"%s%016llx", i ? "," : "",
|
||||
(unsigned long long)nq->conn_mgr_intermediaries[i]);
|
||||
pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "]\n");
|
||||
}
|
||||
|
||||
#undef FMT_ADD
|
||||
e = e->next;
|
||||
idx++;
|
||||
}
|
||||
|
||||
if ((size_t)pos >= buf_size) {
|
||||
pos = (int)buf_size - 14;
|
||||
if (pos < 0) pos = 0;
|
||||
pos += snprintf(buf + pos, buf_size - pos, "\n[TRUNCATED]\n");
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BGP* bgp) {
|
||||
if (!instance || !bgp) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_update_my_nodeinfo: invalid args");
|
||||
@@ -189,7 +487,10 @@ int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BG
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
if (bgp->local_node) u_free(bgp->local_node);
|
||||
if (bgp->local_node) {
|
||||
if (instance->rt) route_delete(instance->rt, bgp->local_node);
|
||||
u_free(bgp->local_node);
|
||||
}
|
||||
bgp->local_node = u_calloc(1, sizeof(struct NODEINFO_Q) + dyn);
|
||||
if (!bgp->local_node) return -1;
|
||||
bgp->local_node->node.node_id = instance->node_id;
|
||||
@@ -311,6 +612,10 @@ int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BG
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "my_nodeinfo updated: v4s=%d v4a=%d v6s=%d v6a=%d v4sub=%d v6sub=%d ver=%d",
|
||||
sock_count, addr_count, sock6_count, addr6_count, vc, vc6, bgp->local_node->node.ver);
|
||||
if (instance->rt) {
|
||||
if (!route_insert(instance->rt, bgp->local_node))
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_bgp_update_my_nodeinfo: failed to re-insert local routes after update");
|
||||
}
|
||||
} else {
|
||||
bgp->local_node->last_ver = bgp->local_node->node.ver;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ struct NODE_CONNECTIVITY {
|
||||
uint64_t nat_probe_time;
|
||||
uint64_t real_probe_time;
|
||||
uint64_t ping_req_time; // время последнего полученного ping request от этого узла (0.1ms tb)
|
||||
void* probe_list; // linked list of active conn_probe_ctx (для cancel)
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -187,4 +188,7 @@ int get_node_v6_addrs(struct NODEINFO_Q *node, const struct NODEINFO_IPV6_ADDR *
|
||||
|
||||
struct NODEINFO_Q* nodeinfo_find_by_id(struct ROUTE_BGP* bgp, uint64_t node_id);
|
||||
|
||||
void route_node_dump_all(struct ROUTE_BGP* bgp);
|
||||
int route_node_format_all(struct ROUTE_BGP* bgp, char* buf, size_t buf_size);
|
||||
|
||||
#endif // ROUTE_NODE_H
|
||||
+5
-1
@@ -72,6 +72,10 @@ static int stcp_decrypt_and_check(uint8_t *plaintext, size_t plaintext_len,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stcp_conn_deferred_free(void* arg) {
|
||||
u_free((struct stcp_conn*)arg);
|
||||
}
|
||||
|
||||
static void stcp_conn_do_close(struct stcp_conn *c, int err) {
|
||||
if (c->state == STCP_STATE_CLOSED || c->state == STCP_STATE_ERROR) return;
|
||||
int prev = c->state;
|
||||
@@ -82,7 +86,7 @@ static void stcp_conn_do_close(struct stcp_conn *c, int err) {
|
||||
if (c->send_buf) { u_free(c->send_buf); c->send_buf = NULL; c->send_len = 0; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "stcp_conn closed is_server=%d prev_state=%d err=%d", c->is_server, prev, err);
|
||||
if (c->on_close) { void (*cb)(struct stcp_conn*, int, void*) = c->on_close; cb(c, err, c->close_arg); }
|
||||
else if (prev == STCP_STATE_HS_SERVER_WAIT) { sc_stream_cleanup(&c->stream_send); sc_stream_cleanup(&c->stream_recv); if (c->allocated) u_free(c); }
|
||||
else if (prev == STCP_STATE_HS_SERVER_WAIT) { sc_stream_cleanup(&c->stream_send); sc_stream_cleanup(&c->stream_recv); if (c->allocated) uasync_call_soon(c->ua, c, stcp_conn_deferred_free); }
|
||||
}
|
||||
|
||||
static void stcp_conn_send_message(struct stcp_conn *c, const uint8_t *data, size_t len) {
|
||||
|
||||
+4
-3
@@ -142,7 +142,7 @@ struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int mtu = config->global.mtu > 0 ? config->global.mtu : TUN_MTU_DEFAULT;
|
||||
int mtu = TUN_MTU_DEFAULT;
|
||||
|
||||
if (!test_mode) {
|
||||
if (tun_platform_init(tun, tun->ifname, ip_str, mtu) != 0) {
|
||||
@@ -223,7 +223,8 @@ fail:
|
||||
struct tun_if* tun_init_nat(struct UASYNC* ua, const char* ifname, const char* ip_addr_str, int mtu, int test_mode)
|
||||
{
|
||||
if (!ua || !ifname || !ip_addr_str) return NULL;
|
||||
if (mtu <= 0) mtu = TUN_MTU_DEFAULT;
|
||||
(void)mtu;
|
||||
int tun_mtu = TUN_MTU_DEFAULT;
|
||||
|
||||
struct tun_if* tun = u_calloc(1, sizeof(struct tun_if));
|
||||
if (!tun) return NULL;
|
||||
@@ -238,7 +239,7 @@ struct tun_if* tun_init_nat(struct UASYNC* ua, const char* ifname, const char* i
|
||||
snprintf(ip_str, sizeof(ip_str), "%s/32", ip_addr_str);
|
||||
|
||||
if (!test_mode) {
|
||||
if (tun_platform_init(tun, tun->ifname, ip_str, mtu) != 0) {
|
||||
if (tun_platform_init(tun, tun->ifname, ip_str, tun_mtu) != 0) {
|
||||
u_free(tun);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
+14
-10
@@ -95,7 +95,7 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
|
||||
return -1;
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Routing module created");
|
||||
if (g_tun_init_enabled) {
|
||||
if (g_tun_init_enabled && config->global.tun_enabled) {
|
||||
instance->tun = tun_init(ua, config);
|
||||
if (!instance->tun) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to initialize TUN device");
|
||||
@@ -126,8 +126,7 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
|
||||
} else {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP module initialized");
|
||||
if (instance->rt && instance->bgp->local_node) {
|
||||
route_bgp_update_my_nodeinfo(instance,instance->bgp);
|
||||
if (route_insert(instance->rt,instance->bgp->local_node)) DEBUG_INFO(DEBUG_CATEGORY_ROUTING,"Added local routes"); else DEBUG_WARN(DEBUG_CATEGORY_ROUTING,"Failed to add local routes");
|
||||
route_bgp_update_my_nodeinfo(instance, instance->bgp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,16 +161,21 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
|
||||
}
|
||||
|
||||
// TCP proxy client (from [tcp_proxy] config section)
|
||||
if (config->global.tcp_proxy_client_enabled) {
|
||||
const char* tun_name = config->global.tcp_proxy_client_tun_name[0] ? config->global.tcp_proxy_client_tun_name : "tun_tcp";
|
||||
const char* tun_ip = config->global.tcp_proxy_client_tun_ip[0] ? config->global.tcp_proxy_client_tun_ip : "10.99.0.1";
|
||||
int mtu = config->global.tcp_proxy_client_mtu > 0 ? config->global.tcp_proxy_client_mtu : 1500;
|
||||
if (config->global.tcp_proxy_client_enabled || config->global.tcp_proxy_client_socks_enabled || config->global.tcp_proxy_client_http_proxy_enabled) {
|
||||
const char* tun_name = config->global.tcp_proxy_client_tun_name;
|
||||
const char* tun_ip = config->global.tcp_proxy_client_tun_ip;
|
||||
int mtu = config->global.tcp_proxy_client_mtu;
|
||||
instance->tcp_proxy_client = tcp_proxy_client_create(instance, ua, tun_name, tun_ip, mtu, g_tun_init_enabled ? 0 : 1,
|
||||
config->global.tcp_proxy_client_mappings, config->global.tcp_proxy_client_mapping_count,
|
||||
config->global.tcp_proxy_client_via_node_id);
|
||||
config->global.tcp_proxy_client_via_node_id,
|
||||
config->global.tcp_proxy_client_socks_enabled, config->global.tcp_proxy_client_socks_addr,
|
||||
config->global.tcp_proxy_client_http_proxy_enabled, config->global.tcp_proxy_client_http_proxy_addr);
|
||||
if (instance->tcp_proxy_client) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TCP proxy client enabled: TUN=%s IP=%s MTU=%d mappings=%d",
|
||||
tun_name, tun_ip, mtu, config->global.tcp_proxy_client_mapping_count);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TCP proxy client enabled: TUN=%s IP=%s MTU=%d socks=%s http=%s mappings=%d",
|
||||
tun_name, tun_ip, mtu,
|
||||
config->global.tcp_proxy_client_socks_enabled ? config->global.tcp_proxy_client_socks_addr : "off",
|
||||
config->global.tcp_proxy_client_http_proxy_enabled ? config->global.tcp_proxy_client_http_proxy_addr : "off",
|
||||
config->global.tcp_proxy_client_mapping_count);
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create TCP proxy client");
|
||||
return -1;
|
||||
|
||||
@@ -43,11 +43,14 @@ check_PROGRAMS = \
|
||||
test_udp_proxy \
|
||||
test_icmp_proxy \
|
||||
test_tcp_proxy_client \
|
||||
test_socks_http_proxy \
|
||||
test_bgp_route_exchange \
|
||||
test_bgp_triangle \
|
||||
test_conn_mgr \
|
||||
test_bbr_integration \
|
||||
test_intensive_memory_pool \
|
||||
test_tcp_io \
|
||||
test_uasync_socket_race \
|
||||
bench_timeout_heap \
|
||||
bench_uasync_timeouts
|
||||
|
||||
@@ -111,6 +114,7 @@ ETCP_FULL_OBJS = \
|
||||
$(top_builddir)/src/utun-etcp_router.o \
|
||||
$(top_builddir)/src/proxy/utun-tcp_proxy_server.o \
|
||||
$(top_builddir)/src/proxy/utun-udp_proxy.o \
|
||||
$(top_builddir)/src/proxy/utun-socks_proxy.o \
|
||||
$(top_builddir)/src/proxy/utun-icmp_proxy.o \
|
||||
$(top_builddir)/src/lwip_tcp/utun-lwip_pbuf.o \
|
||||
$(top_builddir)/src/lwip_tcp/utun-lwip_tcp.o \
|
||||
@@ -176,6 +180,9 @@ test_ipv6_sockets_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS
|
||||
test_tcp_proxy_client_SOURCES = test_tcp_proxy_client.c
|
||||
test_tcp_proxy_client_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_socks_http_proxy_SOURCES = test_socks_http_proxy.c
|
||||
test_socks_http_proxy_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_lwip_tcp_SOURCES = test_lwip_tcp.c
|
||||
test_lwip_tcp_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_lwip_tcp_LDADD = \
|
||||
@@ -208,6 +215,7 @@ test_radix_LDADD = $(COMMON_LIBS)
|
||||
test_route6_lib_SOURCES = test_route6_lib.c
|
||||
test_route6_lib_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_route6_lib_LDADD = $(top_builddir)/src/utun-route6_lib.o \
|
||||
$(top_builddir)/src/utun-route_lib.o \
|
||||
$(top_builddir)/src/utun-route_node.o \
|
||||
$(top_builddir)/src/utun-etcp_debug.o \
|
||||
$(COMMON_LIBS)
|
||||
@@ -274,6 +282,10 @@ test_tcp_io_SOURCES = test_tcp_io.c
|
||||
test_tcp_io_CFLAGS = -I$(top_srcdir)/lib
|
||||
test_tcp_io_LDADD = $(COMMON_LIBS)
|
||||
|
||||
test_uasync_socket_race_SOURCES = test_uasync_socket_race.c
|
||||
test_uasync_socket_race_CFLAGS = -I$(top_srcdir)/lib
|
||||
test_uasync_socket_race_LDADD = $(COMMON_LIBS)
|
||||
|
||||
test_memory_pool_and_config_SOURCES = test_memory_pool_and_config.c
|
||||
test_memory_pool_and_config_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_memory_pool_and_config_LDADD = $(COMMON_LIBS)
|
||||
@@ -310,6 +322,10 @@ test_bgp_route_exchange_SOURCES = test_bgp_route_exchange.c
|
||||
test_bgp_route_exchange_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_bgp_route_exchange_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_bgp_triangle_SOURCES = test_bgp_triangle.c
|
||||
test_bgp_triangle_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_bgp_triangle_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_conn_mgr_SOURCES = test_conn_mgr.c
|
||||
test_conn_mgr_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_conn_mgr_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
@@ -107,6 +107,7 @@ static int create_temp_configs(void) {
|
||||
"my_node_id=0x3333333333333333\n"
|
||||
"tun_ip=10.100.0.3/24\n"
|
||||
"tun_ifname=tun102\n"
|
||||
"keepalive_adaptive=0\n"
|
||||
"\n"
|
||||
"[routing]\n"
|
||||
"my_subnet=192.168.30.0/24\n"
|
||||
@@ -128,6 +129,7 @@ static int create_temp_configs(void) {
|
||||
"my_node_id=0x2222222222222222\n"
|
||||
"tun_ip=10.100.0.2/24\n"
|
||||
"tun_ifname=tun101\n"
|
||||
"keepalive_adaptive=0\n"
|
||||
"\n"
|
||||
"[routing]\n"
|
||||
"my_subnet=192.168.20.0/24\n"
|
||||
@@ -159,6 +161,7 @@ static int create_temp_configs(void) {
|
||||
"my_node_id=0x1111111111111111\n"
|
||||
"tun_ip=10.100.0.1/24\n"
|
||||
"tun_ifname=tun100\n"
|
||||
"keepalive_adaptive=0\n"
|
||||
"\n"
|
||||
"[routing]\n"
|
||||
"my_subnet=192.168.10.0/24\n"
|
||||
@@ -288,8 +291,8 @@ static int cond_bgp_c_to_a_gone(void) {
|
||||
// -------------------- Main --------------------
|
||||
int main(void) {
|
||||
debug_config_init();
|
||||
debug_set_level(DEBUG_LEVEL_INFO);
|
||||
debug_set_categories(DEBUG_CATEGORY_BGP | DEBUG_CATEGORY_ETCP | DEBUG_CATEGORY_CONNECTION);
|
||||
debug_set_level(DEBUG_LEVEL_ERROR);
|
||||
debug_set_categories(DEBUG_CATEGORY_BGP);
|
||||
utun_instance_set_tun_init_enabled(0);
|
||||
|
||||
if (create_temp_configs() != 0) return 1;
|
||||
|
||||
@@ -0,0 +1,485 @@
|
||||
/**
|
||||
* @file test_bgp_triangle.c
|
||||
* @brief Тест треугольной BGP-маршрутизации с цепочкой D и листом E (5 узлов)
|
||||
*
|
||||
* Топология:
|
||||
* E
|
||||
* │
|
||||
* A ── B ── C ── D
|
||||
* │ │
|
||||
* └─────┘
|
||||
*
|
||||
* A↔B, A↔C, B↔C — треугольник (проверка fix#1: альтернативные пути при старой версии)
|
||||
* C↔D — транзитивная цепочка
|
||||
* B↔E — лист (проверка каскадного удаления)
|
||||
*
|
||||
* Фазы:
|
||||
* 1. Все линки up — BGP-обмен, проверка полного состояния
|
||||
* 2. Kill A–C — C/D/E только через B
|
||||
* 3. Restore A–C — C шлёт NODEINFO напрямую (та же версия). Fix#1: путь [C] добавлен
|
||||
* 4. Kill A–B — C/D выживают через A–C, B/E живут через C
|
||||
* 5. Kill A–C — A изолирован, все узлы удалены
|
||||
* 6. Restore A–C + A–B — полное восстановление
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <time.h>
|
||||
#include "../lib/platform_compat.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "../src/etcp.h"
|
||||
#include "../src/etcp_connections.h"
|
||||
#include "../src/config_parser.h"
|
||||
#include "../src/config_updater.h"
|
||||
#include "../src/utun_instance.h"
|
||||
#include "../src/routing.h"
|
||||
#include "../src/route_lib.h"
|
||||
#include "../src/route_bgp.h"
|
||||
#include "../src/route_node.h"
|
||||
#include "../src/dummynet.h"
|
||||
#include "../src/tun_if.h"
|
||||
#include "../src/secure_channel.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
|
||||
#define TEST_TIMEOUT_TB 200000
|
||||
#define PHASE_TIMEOUT_TB 100000
|
||||
#define POLL_INTERVAL_MS 5
|
||||
|
||||
#define NODE_ID_A 0x1111111111111111ULL
|
||||
#define NODE_ID_B 0x2222222222222222ULL
|
||||
#define NODE_ID_C 0x3333333333333333ULL
|
||||
#define NODE_ID_D 0x4444444444444444ULL
|
||||
#define NODE_ID_E 0x5555555555555555ULL
|
||||
|
||||
static struct UTUN_INSTANCE* inst_a = NULL;
|
||||
static struct UTUN_INSTANCE* inst_b = NULL;
|
||||
static struct UTUN_INSTANCE* inst_c = NULL;
|
||||
static struct UTUN_INSTANCE* inst_d = NULL;
|
||||
static struct UTUN_INSTANCE* inst_e = NULL;
|
||||
static struct UASYNC* ua = NULL;
|
||||
static int test_phase = 0;
|
||||
static void* timeout_id = NULL;
|
||||
|
||||
static struct dummynet_filter* df_ab = NULL;
|
||||
static struct dummynet_filter* df_ac = NULL;
|
||||
|
||||
/* ================================================================
|
||||
* Config generator
|
||||
* ================================================================ */
|
||||
|
||||
struct lnk { const char* local_srv; int remote_port; };
|
||||
struct cli { const char* name; const char* peer_pubkey_hex; int lnk_cnt; struct lnk lnks[2]; };
|
||||
struct srv { const char* name; int port; };
|
||||
struct ncfg {
|
||||
uint64_t node_id; const char* tun_ip; const char* my_subnet;
|
||||
const char* priv_hex; const char* pub_hex;
|
||||
int srv_cnt; struct srv srvs[4];
|
||||
int cli_cnt; struct cli clis[3];
|
||||
};
|
||||
|
||||
static void build_node_config(char* buf, size_t size, const struct ncfg* c) {
|
||||
int off = snprintf(buf, size,
|
||||
"[global]\n"
|
||||
"my_node_id=0x%llx\n"
|
||||
"my_private_key=%s\n"
|
||||
"my_public_key=%s\n"
|
||||
"tun_ip=%s\n"
|
||||
"tun_ifname=tun99\n"
|
||||
"keepalive_adaptive=0\n"
|
||||
"\n"
|
||||
"[routing]\n"
|
||||
"my_subnet=%s\n"
|
||||
"\n",
|
||||
(unsigned long long)c->node_id, c->priv_hex, c->pub_hex, c->tun_ip, c->my_subnet);
|
||||
for (int i = 0; i < c->srv_cnt; i++)
|
||||
off += snprintf(buf + off, size - off,
|
||||
"[server: %s]\naddr=127.0.0.1:%d\ntype=public\n\n", c->srvs[i].name, c->srvs[i].port);
|
||||
for (int i = 0; i < c->cli_cnt; i++) {
|
||||
off += snprintf(buf + off, size - off,
|
||||
"[client: %s]\nkeepalive=1\npeer_public_key=%s\n", c->clis[i].name, c->clis[i].peer_pubkey_hex);
|
||||
for (int j = 0; j < c->clis[i].lnk_cnt; j++)
|
||||
off += snprintf(buf + off, size - off,
|
||||
"link=%s:127.0.0.1:%d\n", c->clis[i].lnks[j].local_srv, c->clis[i].lnks[j].remote_port);
|
||||
off += snprintf(buf + off, size - off, "\n");
|
||||
}
|
||||
off += snprintf(buf + off, size - off, "[allowed_keys]\nallow_all=1\n");
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Verification helpers — проверка внутренних структур
|
||||
* ================================================================ */
|
||||
|
||||
static int peer_in_nodes(struct UTUN_INSTANCE* inst, uint64_t node_id) {
|
||||
if (!inst || !inst->bgp || !inst->bgp->nodes) return 0;
|
||||
return nodeinfo_find_by_id(inst->bgp, node_id) != NULL;
|
||||
}
|
||||
|
||||
static int node_path_count(struct UTUN_INSTANCE* inst, uint64_t node_id) {
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(inst->bgp, node_id);
|
||||
if (!nq || !nq->paths) return 0;
|
||||
return queue_entry_count(nq->paths);
|
||||
}
|
||||
|
||||
static int node_path_has_peer(struct UTUN_INSTANCE* inst, uint64_t node_id, uint64_t peer_id) {
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(inst->bgp, node_id);
|
||||
if (!nq || !nq->paths) return 0;
|
||||
struct ll_entry* e = nq->paths->head;
|
||||
while (e) {
|
||||
struct NODEINFO_PATH* path = (struct NODEINFO_PATH*)e;
|
||||
uint64_t* hop = (uint64_t*)((uint8_t*)path + sizeof(struct NODEINFO_PATH));
|
||||
for (uint8_t i = 0; i < path->hop_count; i++)
|
||||
if (hop[i] == peer_id) return 1;
|
||||
e = e->next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int node_version(struct UTUN_INSTANCE* inst, uint64_t node_id) {
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(inst->bgp, node_id);
|
||||
return nq ? nq->last_ver : -1;
|
||||
}
|
||||
|
||||
static int route_count_for_node(struct UTUN_INSTANCE* inst, uint64_t node_id) {
|
||||
if (!inst || !inst->rt) return 0;
|
||||
uint64_t expected_be = htobe64(node_id);
|
||||
int cnt = 0;
|
||||
for (size_t i = 0; i < inst->rt->count; i++)
|
||||
if (inst->rt->entries[i].v_node_info &&
|
||||
inst->rt->entries[i].v_node_info->node.node_id == expected_be) cnt++;
|
||||
return cnt;
|
||||
}
|
||||
|
||||
static int count_initialized_links(struct UTUN_INSTANCE* inst) {
|
||||
int n = 0;
|
||||
if (!inst) return 0;
|
||||
struct ETCP_CONN* conn = inst->connections;
|
||||
while (conn) {
|
||||
struct ETCP_LINK* l = conn->links;
|
||||
while (l) { if (l->initialized) n++; l = l->next; }
|
||||
conn = conn->next;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static struct ETCP_LINK* find_client_link(struct UTUN_INSTANCE* inst, int idx) {
|
||||
if (!inst) return NULL;
|
||||
struct ETCP_CONN* conn = inst->connections;
|
||||
while (conn) {
|
||||
struct ETCP_LINK* l = conn->links;
|
||||
while (l) {
|
||||
if (l->is_server == 0) { if (idx == 0) return l; idx--; }
|
||||
l = l->next;
|
||||
}
|
||||
conn = conn->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void test_timeout_cb(void* arg) {
|
||||
(void)arg;
|
||||
test_phase = 2;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "test_bgp_triangle: overall timeout");
|
||||
}
|
||||
|
||||
static void fail(const char* msg) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: %s", msg);
|
||||
test_phase = 2;
|
||||
}
|
||||
|
||||
#define PHASE(name) do { \
|
||||
if (test_phase != 0) break; \
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "=== PHASE: %s ===", name); \
|
||||
} while(0)
|
||||
|
||||
#define ASSERT(cond, msg) do { if (!(cond)) { fail(msg); goto cleanup; } } while(0)
|
||||
|
||||
static int wait_for(const char* desc, int (*cond)(void), int timeout_tb) {
|
||||
uint64_t start = get_time_tb();
|
||||
while (!cond() && (get_time_tb() - start) < (uint64_t)timeout_tb && test_phase == 0)
|
||||
uasync_poll(ua, POLL_INTERVAL_MS);
|
||||
if (!cond() && test_phase == 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "timeout waiting for: %s", desc);
|
||||
test_phase = 2;
|
||||
return 0;
|
||||
}
|
||||
return test_phase == 0;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Condition callbacks for wait_for
|
||||
* ================================================================ */
|
||||
|
||||
static int cond_links_init(void) {
|
||||
return count_initialized_links(inst_a) >= 2 &&
|
||||
count_initialized_links(inst_b) >= 2 &&
|
||||
count_initialized_links(inst_c) >= 1 &&
|
||||
count_initialized_links(inst_e) >= 1;
|
||||
}
|
||||
|
||||
static int cond_d_in_a(void) { return peer_in_nodes(inst_a, NODE_ID_D); }
|
||||
static int cond_d_gone_a(void) { return !peer_in_nodes(inst_a, NODE_ID_D); }
|
||||
static int cond_e_in_a(void) { return peer_in_nodes(inst_a, NODE_ID_E); }
|
||||
static int cond_e_gone_a(void) { return !peer_in_nodes(inst_a, NODE_ID_E); }
|
||||
static int cond_c_in_a(void) { return peer_in_nodes(inst_a, NODE_ID_C); }
|
||||
static int cond_c_gone_a(void) { return !peer_in_nodes(inst_a, NODE_ID_C); }
|
||||
static int cond_c_two_paths(void) {
|
||||
return node_path_count(inst_a, NODE_ID_C) >= 2 &&
|
||||
node_path_has_peer(inst_a, NODE_ID_C, NODE_ID_C) &&
|
||||
node_path_has_peer(inst_a, NODE_ID_C, NODE_ID_B);
|
||||
}
|
||||
|
||||
static int cond_c_one_path_B(void) {
|
||||
return node_path_count(inst_a, NODE_ID_C) == 1 &&
|
||||
node_path_has_peer(inst_a, NODE_ID_C, NODE_ID_B);
|
||||
}
|
||||
|
||||
static int cond_b_gone_a(void) { return !peer_in_nodes(inst_a, NODE_ID_B); }
|
||||
|
||||
static int cond_c_one_path(void) { return node_path_count(inst_a, NODE_ID_C) == 1; }
|
||||
|
||||
/* ================================================================
|
||||
* Main
|
||||
* ================================================================ */
|
||||
|
||||
int main(void) {
|
||||
debug_config_init();
|
||||
debug_set_level(DEBUG_LEVEL_ERROR);
|
||||
debug_set_categories(DEBUG_CATEGORY_BGP);
|
||||
utun_instance_set_tun_init_enabled(0);
|
||||
|
||||
/* ---------- Pre-generate 5 keypairs ---------- */
|
||||
struct SC_MYKEYS keys[5];
|
||||
char pub_hex[5][SC_PUBKEY_SIZE * 2 + 1];
|
||||
char priv_hex[5][SC_PRIVKEY_SIZE * 2 + 1];
|
||||
for (int i = 0; i < 5; i++) {
|
||||
sc_generate_keypair(&keys[i]);
|
||||
bytes_to_hex(keys[i].public_key, SC_PUBKEY_SIZE, pub_hex[i], sizeof(pub_hex[i]));
|
||||
bytes_to_hex(keys[i].private_key, SC_PRIVKEY_SIZE, priv_hex[i], sizeof(priv_hex[i]));
|
||||
}
|
||||
|
||||
/* ---------- Ports ---------- */
|
||||
int base = 41000 + (getpid() % 15000);
|
||||
int p_ab_a = base++, p_ab_b = base++; // A↔B
|
||||
int p_ac_a = base++, p_ac_c = base++; // A↔C
|
||||
int p_bc_b = base++, p_bc_c = base++; // B↔C (C's second server for B)
|
||||
int p_be_b = base++, p_be_e = base++; // B↔E
|
||||
int p_cd_c = base++, p_cd_d = base++; // C↔D
|
||||
|
||||
/* ---------- Build config strings ---------- */
|
||||
char cfg_a[2048], cfg_b[2048], cfg_c[2048], cfg_d[2048], cfg_e[2048];
|
||||
|
||||
build_node_config(cfg_a, sizeof(cfg_a), &(struct ncfg){
|
||||
.node_id = NODE_ID_A, .tun_ip = "10.100.0.1/24", .my_subnet = "192.168.10.0/24",
|
||||
.priv_hex = priv_hex[0], .pub_hex = pub_hex[0],
|
||||
.srv_cnt = 2, .srvs = {{"a_srv_b", p_ab_a}, {"a_srv_c", p_ac_a}},
|
||||
.cli_cnt = 2, .clis = {
|
||||
{"to_b", pub_hex[1], 1, {{"a_srv_b", p_ab_b}}},
|
||||
{"to_c", pub_hex[2], 1, {{"a_srv_c", p_ac_c}}},
|
||||
}
|
||||
});
|
||||
|
||||
build_node_config(cfg_b, sizeof(cfg_b), &(struct ncfg){
|
||||
.node_id = NODE_ID_B, .tun_ip = "10.100.0.2/24", .my_subnet = "192.168.20.0/24",
|
||||
.priv_hex = priv_hex[1], .pub_hex = pub_hex[1],
|
||||
.srv_cnt = 3, .srvs = {{"b_srv", p_ab_b}, {"b_cli_c", p_bc_b}, {"b_cli_e", p_be_b}},
|
||||
.cli_cnt = 2, .clis = {
|
||||
{"to_c", pub_hex[2], 1, {{"b_cli_c", p_bc_c}}},
|
||||
{"to_e", pub_hex[4], 1, {{"b_cli_e", p_be_e}}},
|
||||
}
|
||||
});
|
||||
|
||||
build_node_config(cfg_c, sizeof(cfg_c), &(struct ncfg){
|
||||
.node_id = NODE_ID_C, .tun_ip = "10.100.0.3/24", .my_subnet = "192.168.30.0/24",
|
||||
.priv_hex = priv_hex[2], .pub_hex = pub_hex[2],
|
||||
.srv_cnt = 3, .srvs = {{"c_srv_a", p_ac_c}, {"c_srv_b", p_bc_c}, {"c_cli_d", p_cd_c}},
|
||||
.cli_cnt = 1, .clis = {
|
||||
{"to_d", pub_hex[3], 1, {{"c_cli_d", p_cd_d}}},
|
||||
}
|
||||
});
|
||||
|
||||
build_node_config(cfg_d, sizeof(cfg_d), &(struct ncfg){
|
||||
.node_id = NODE_ID_D, .tun_ip = "10.100.0.4/24", .my_subnet = "192.168.40.0/24",
|
||||
.priv_hex = priv_hex[3], .pub_hex = pub_hex[3],
|
||||
.srv_cnt = 1, .srvs = {{"d_srv", p_cd_d}},
|
||||
.cli_cnt = 0, .clis = {},
|
||||
});
|
||||
|
||||
build_node_config(cfg_e, sizeof(cfg_e), &(struct ncfg){
|
||||
.node_id = NODE_ID_E, .tun_ip = "10.100.0.5/24", .my_subnet = "192.168.50.0/24",
|
||||
.priv_hex = priv_hex[4], .pub_hex = pub_hex[4],
|
||||
.srv_cnt = 1, .srvs = {{"e_srv", p_be_e}},
|
||||
.cli_cnt = 0, .clis = {},
|
||||
});
|
||||
|
||||
/* ---------- Create instances ---------- */
|
||||
ua = uasync_create();
|
||||
ASSERT(ua, "uasync_create");
|
||||
|
||||
inst_a = utun_instance_create_from_str(ua, cfg_a); ASSERT(inst_a, "inst_a");
|
||||
inst_b = utun_instance_create_from_str(ua, cfg_b); ASSERT(inst_b, "inst_b");
|
||||
inst_c = utun_instance_create_from_str(ua, cfg_c); ASSERT(inst_c, "inst_c");
|
||||
inst_d = utun_instance_create_from_str(ua, cfg_d); ASSERT(inst_d, "inst_d");
|
||||
inst_e = utun_instance_create_from_str(ua, cfg_e); ASSERT(inst_e, "inst_e");
|
||||
|
||||
ASSERT(utun_instance_init(inst_a) == 0, "init a");
|
||||
ASSERT(utun_instance_init(inst_b) == 0, "init b");
|
||||
ASSERT(utun_instance_init(inst_c) == 0, "init c");
|
||||
ASSERT(utun_instance_init(inst_d) == 0, "init d");
|
||||
ASSERT(utun_instance_init(inst_e) == 0, "init e");
|
||||
|
||||
/* ---------- Dummynet filters (2 managed links) ---------- */
|
||||
struct ETCP_LINK* ab_link = find_client_link(inst_a, 0);
|
||||
struct ETCP_LINK* ac_link = find_client_link(inst_a, 1);
|
||||
ASSERT(ab_link && ac_link, "A links not found");
|
||||
|
||||
df_ab = dummynet_filter_create(ua); ASSERT(df_ab, "df_ab");
|
||||
df_ac = dummynet_filter_create(ua); ASSERT(df_ac, "df_ac");
|
||||
|
||||
timeout_id = uasync_set_timeout(ua, TEST_TIMEOUT_TB, NULL, test_timeout_cb, "test_timeout");
|
||||
|
||||
/* ================================================================
|
||||
* Phase 1: Initial BGP exchange — all 5 nodes visible
|
||||
* ================================================================ */
|
||||
PHASE("1: Initial BGP — all nodes visible");
|
||||
ASSERT(wait_for("links init", cond_links_init, PHASE_TIMEOUT_TB), "links init timeout");
|
||||
ASSERT(wait_for("D in A nodes", cond_d_in_a, PHASE_TIMEOUT_TB), "D not in A");
|
||||
ASSERT(wait_for("E in A nodes", cond_e_in_a, PHASE_TIMEOUT_TB), "E not in A");
|
||||
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_B), "B missing");
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_C), "C missing");
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_D), "D missing");
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_E), "E missing");
|
||||
{
|
||||
int pb = node_path_count(inst_a, NODE_ID_B);
|
||||
int pc = node_path_count(inst_a, NODE_ID_C);
|
||||
int pd = node_path_count(inst_a, NODE_ID_D);
|
||||
int pe = node_path_count(inst_a, NODE_ID_E);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 1: B_paths=%d C_paths=%d D_paths=%d E_paths=%d", pb, pc, pd, pe);
|
||||
ASSERT(pb >= 1, "B: expected >=1");
|
||||
ASSERT(pc >= 1, "C: expected >=1");
|
||||
ASSERT(pd >= 1, "D: expected >=1");
|
||||
ASSERT(pe >= 1, "E: expected >=1");
|
||||
}
|
||||
ASSERT(route_count_for_node(inst_a, NODE_ID_B) > 0, "B routes missing");
|
||||
ASSERT(route_count_for_node(inst_a, NODE_ID_C) > 0, "C routes missing");
|
||||
ASSERT(route_count_for_node(inst_a, NODE_ID_D) > 0, "D routes missing");
|
||||
ASSERT(route_count_for_node(inst_a, NODE_ID_E) > 0, "E routes missing");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 1 PASSED");
|
||||
|
||||
/* ================================================================
|
||||
* Phase 2: Kill A–C — C/D/E only through B
|
||||
* ================================================================ */
|
||||
PHASE("2: Kill A–C — C/D/E via B only");
|
||||
dummynet_filter_attach(df_ac, ac_link);
|
||||
dummynet_filter_set_loss(df_ac, 1000);
|
||||
ASSERT(wait_for("C: 1 path via B after A-C kill", cond_c_one_path_B, PHASE_TIMEOUT_TB), "C paths not 1");
|
||||
{
|
||||
int pc = node_path_count(inst_a, NODE_ID_C);
|
||||
int pd = node_path_count(inst_a, NODE_ID_D);
|
||||
int pe = node_path_count(inst_a, NODE_ID_E);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 2: C_paths=%d D_paths=%d E_paths=%d", pc, pd, pe);
|
||||
ASSERT(cond_c_in_a(), "C missing after A-C kill");
|
||||
ASSERT(cond_d_in_a(), "D missing after A-C kill");
|
||||
ASSERT(cond_e_in_a(), "E missing after A-C kill");
|
||||
ASSERT(pd == 1, "D: expected 1 path");
|
||||
ASSERT(pe >= 1, "E: expected >=1");
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 2 PASSED");
|
||||
|
||||
/* ================================================================
|
||||
* Phase 3: Restore A–C — C sends NODEINFO directly (same ver).
|
||||
* Fix#1: path [C] added despite old version.
|
||||
* ================================================================ */
|
||||
PHASE("3: Restore A–C");
|
||||
dummynet_filter_set_loss(df_ac, 0);
|
||||
ASSERT(wait_for("C: >=2 paths after A-C restore", cond_c_two_paths, PHASE_TIMEOUT_TB), "C paths not >=2");
|
||||
{
|
||||
int cp = node_path_count(inst_a, NODE_ID_C);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 3 PASSED (C paths=%d)", cp);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Phase 4: Kill A–B — B/E survive via C relay, C/D via A-C
|
||||
* ================================================================ */
|
||||
PHASE("4: Kill A–B — B/E survive via C");
|
||||
dummynet_filter_attach(df_ab, ab_link);
|
||||
dummynet_filter_set_loss(df_ab, 1000);
|
||||
/* After A-B kill: direct paths removed, B/C/E survive via C relay */
|
||||
ASSERT(wait_for("A-B paths removed", cond_c_one_path, PHASE_TIMEOUT_TB), "A-B paths not removed");
|
||||
{
|
||||
int pb = node_path_count(inst_a, NODE_ID_B);
|
||||
int pe = node_path_count(inst_a, NODE_ID_E);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 4: B_paths=%d E_paths=%d", pb, pe);
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_B), "B missing — should survive via C");
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_E), "E missing — should survive via C");
|
||||
ASSERT(cond_c_in_a(), "C missing");
|
||||
ASSERT(cond_d_in_a(), "D missing");
|
||||
ASSERT(node_path_count(inst_a, NODE_ID_C) == 1, "C: expected 1 path via A-C");
|
||||
ASSERT(node_path_has_peer(inst_a, NODE_ID_C, NODE_ID_C), "C: missing direct path");
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 4 PASSED");
|
||||
|
||||
/* ================================================================
|
||||
* Phase 5: Kill A–C — A fully isolated (A-B already dead)
|
||||
* ================================================================ */
|
||||
PHASE("5: Kill A–C — A isolated");
|
||||
dummynet_filter_set_loss(df_ac, 1000);
|
||||
ASSERT(wait_for("C gone from A", cond_c_gone_a, PHASE_TIMEOUT_TB), "C still in A");
|
||||
ASSERT(wait_for("D gone from A", cond_d_gone_a, PHASE_TIMEOUT_TB), "D still in A");
|
||||
ASSERT(!peer_in_nodes(inst_a, NODE_ID_B), "B should be gone");
|
||||
ASSERT(!peer_in_nodes(inst_a, NODE_ID_C), "C should be gone");
|
||||
ASSERT(!peer_in_nodes(inst_a, NODE_ID_D), "D should be gone");
|
||||
ASSERT(!peer_in_nodes(inst_a, NODE_ID_E), "E should be gone");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 5 PASSED");
|
||||
|
||||
/* ================================================================
|
||||
* Phase 6: Restore A–C + A–B — full recovery
|
||||
* ================================================================ */
|
||||
PHASE("6: Full restore — both links back");
|
||||
dummynet_filter_set_loss(df_ac, 0);
|
||||
dummynet_filter_set_loss(df_ab, 0);
|
||||
ASSERT(wait_for("D in A after restore", cond_d_in_a, 2 * PHASE_TIMEOUT_TB), "D not back");
|
||||
ASSERT(wait_for("E in A after restore", cond_e_in_a, PHASE_TIMEOUT_TB), "E not back");
|
||||
ASSERT(wait_for("C: >=2 paths after restore", cond_c_two_paths, PHASE_TIMEOUT_TB), "C paths not >=2");
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_B), "B missing after restore");
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_C), "C missing after restore");
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_D), "D missing after restore");
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_E), "E missing after restore");
|
||||
ASSERT(node_path_count(inst_a, NODE_ID_C) >= 2, "C: expected >=2 paths");
|
||||
ASSERT(node_path_count(inst_a, NODE_ID_D) >= 1, "D: expected >=1");
|
||||
ASSERT(node_path_count(inst_a, NODE_ID_B) >= 1, "B: expected >=1");
|
||||
ASSERT(node_path_count(inst_a, NODE_ID_E) >= 1, "E: expected >=1");
|
||||
ASSERT(route_count_for_node(inst_a, NODE_ID_B) > 0, "B routes");
|
||||
ASSERT(route_count_for_node(inst_a, NODE_ID_C) > 0, "C routes");
|
||||
ASSERT(route_count_for_node(inst_a, NODE_ID_D) > 0, "D routes");
|
||||
ASSERT(route_count_for_node(inst_a, NODE_ID_E) > 0, "E routes");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 6 PASSED");
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "=== ALL PHASES PASSED ===");
|
||||
test_phase = 1;
|
||||
|
||||
cleanup:
|
||||
if (df_ab) { dummynet_filter_detach(df_ab); dummynet_filter_destroy(df_ab); }
|
||||
if (df_ac) { dummynet_filter_detach(df_ac); dummynet_filter_destroy(df_ac); }
|
||||
if (timeout_id) uasync_cancel_timeout(ua, timeout_id);
|
||||
if (inst_a) { inst_a->running = 0; utun_instance_destroy(inst_a); }
|
||||
if (inst_b) { inst_b->running = 0; utun_instance_destroy(inst_b); }
|
||||
if (inst_c) { inst_c->running = 0; utun_instance_destroy(inst_c); }
|
||||
if (inst_d) { inst_d->running = 0; utun_instance_destroy(inst_d); }
|
||||
if (inst_e) { inst_e->running = 0; utun_instance_destroy(inst_e); }
|
||||
if (ua) { uasync_destroy(ua, 0); ua = NULL; }
|
||||
|
||||
printf("=== %s ===\n", test_phase == 1 ? "TEST PASSED" : "TEST FAILED");
|
||||
return test_phase == 1 ? 0 : 1;
|
||||
}
|
||||
@@ -105,7 +105,7 @@ static struct ETCP_CONN* first_conn(struct UTUN_INSTANCE* inst) {
|
||||
static void srv_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
(void)conn;
|
||||
if (!entry || !entry->dgram || entry->len < 10) { // svc_id(1) + subcmd(1) + seq(4) + data_len(4)
|
||||
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];
|
||||
@@ -123,7 +123,7 @@ static void srv_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
} else {
|
||||
fwd_rcvd++;
|
||||
}
|
||||
queue_entry_free(entry); queue_dgram_free(entry);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
|
||||
// Send reply back
|
||||
if (!g_test_done && reply_sent < TOTAL_PACKETS) {
|
||||
@@ -139,7 +139,7 @@ static void srv_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
etcp_route_send(srv, client_node_id, re, 0); reply_sent++; }
|
||||
}
|
||||
} else {
|
||||
queue_entry_free(entry); queue_dgram_free(entry);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ static void srv_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
static void cli_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
(void)conn;
|
||||
if (!entry || !entry->dgram || entry->len < 10) {
|
||||
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];
|
||||
@@ -166,7 +166,7 @@ static void cli_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
reply_rcvd++;
|
||||
}
|
||||
}
|
||||
queue_entry_free(entry); queue_dgram_free(entry);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
}
|
||||
|
||||
// ======================== Loopback test (no ETCP) ========================
|
||||
@@ -178,7 +178,7 @@ static void loop_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
loop_rcvd++;
|
||||
if (entry->dgram[1] == 0xAA && entry->dgram[2] == 0xBB && entry->dgram[3] == 0xCC) loop_ok = 1;
|
||||
}
|
||||
if (entry) { queue_entry_free(entry); queue_dgram_free(entry); }
|
||||
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
|
||||
}
|
||||
|
||||
static int test_loopback(void) {
|
||||
@@ -250,7 +250,7 @@ static void monitor(void* arg) {
|
||||
struct ll_entry* e = queue_entry_new(0);
|
||||
if (e) { e->dgram = u_malloc(10 + data_len); memcpy(e->dgram, buf, 10 + data_len); e->len = 10 + data_len;
|
||||
if (etcp_route_send(cli, server_node_id, e, 0) == 0) fwd_sent++;
|
||||
else { queue_entry_free(e); queue_dgram_free(e); }
|
||||
else { queue_dgram_free(e); queue_entry_free(e); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ static void test_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
return;
|
||||
}
|
||||
if (entry->dgram[1] != rx.marker) { rx.errors++; } else { rx.delivered++; rx.last_seq = rx.expected_seq; rx.expected_seq++; }
|
||||
queue_entry_free(entry); queue_dgram_free(entry);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
}
|
||||
|
||||
static void rx_reset(uint8_t marker) {
|
||||
@@ -434,11 +434,13 @@ static int test_out_of_bounds(void) {
|
||||
|
||||
rx_reset(0xEE);
|
||||
uint8_t data[] = { TEST_SVC_ID, 0xEE, 0 };
|
||||
/* first packet syncs rx_seq (peer_sync_done), then OOB check works */
|
||||
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 0);
|
||||
uint32_t bad_seq = ROUTER_MAX_INFLIGHT + 2;
|
||||
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, bad_seq, data + 1, 2, 0, 0);
|
||||
|
||||
if (rx.delivered != 0) FAIL("out-of-bounds delivered");
|
||||
if (c->rx_seq != 0) FAIL("rx_seq changed on bounds drop");
|
||||
if (rx.delivered != 1) FAIL("first packet not delivered");
|
||||
if (c->rx_seq != 1) FAIL("rx_seq should advance to 1 after first");
|
||||
|
||||
etcp_router_conn_close(c);
|
||||
TEARDOWN();
|
||||
@@ -525,12 +527,8 @@ static int test_ack_timer(void) {
|
||||
uint8_t data[] = { TEST_SVC_ID, 0x55, 0 };
|
||||
inject(recv_cb, &inst, TEST_REMOTE_NODE, TEST_SVC_ID, 0, data + 1, 2, 0, 0);
|
||||
|
||||
if (c->ack_timer == NULL && c->last_sent_ack_seq != 1) FAIL("ack not sent (no timer, no immediate)");
|
||||
if (c->ack_timer != NULL && c->last_sent_ack_seq != 0) FAIL("last_sent_ack_seq changed before timer fired");
|
||||
|
||||
// Cancel timer manually (it would fire in real event loop)
|
||||
if (c->ack_timer) uasync_cancel_timeout(ua, c->ack_timer);
|
||||
c->ack_timer = NULL;
|
||||
if (c->ack_timer != NULL) FAIL("ack_timer set without BGP route");
|
||||
if (c->last_sent_ack_seq != 0) FAIL("last_sent_ack_seq changed without BGP route");
|
||||
|
||||
etcp_router_conn_close(c);
|
||||
TEARDOWN();
|
||||
|
||||
@@ -85,7 +85,7 @@ static void* g_to_id = NULL;
|
||||
static void cli_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
(void)conn;
|
||||
if (!entry || !entry->dgram || entry->len < ICMP_PROXY_HDR_SIZE + 1) {
|
||||
if (entry) { queue_entry_free(entry); queue_dgram_free(entry); } return;
|
||||
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); } return;
|
||||
}
|
||||
if (entry->dgram[1] == ICMP_PROXY_SUBCMD_REPLY) {
|
||||
uint16_t rid, rseq;
|
||||
@@ -105,7 +105,7 @@ static void cli_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
g_done = -1;
|
||||
}
|
||||
}
|
||||
queue_entry_free(entry); queue_dgram_free(entry);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
}
|
||||
|
||||
static void monitor(void* arg) {
|
||||
|
||||
@@ -285,7 +285,7 @@ static void test_stress(void) {
|
||||
|
||||
struct saved_route sr[MAX_SAVED];
|
||||
int nsr = 0;
|
||||
struct NODEINFO_Q *nodes[STRESS_NODES];
|
||||
struct NODEINFO_Q *nodes[STRESS_NODES + STRESS_OPS];
|
||||
|
||||
// create nodes with random subnets
|
||||
int n_created = 0;
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
// test_socks_http_proxy.c — SOCKS5 + HTTP CONNECT proxy integration test
|
||||
// 2 workers (1 SOCKS + 1 HTTP), 25 reqs each, files up to 100KB
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include "../lib/platform_compat.h"
|
||||
#include "test_utils.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/mem.h"
|
||||
#include "../src/etcp.h"
|
||||
#include "../src/etcp_connections.h"
|
||||
#include "../src/etcp_api.h"
|
||||
#include "../src/etcp_router.h"
|
||||
#include "../src/config_parser.h"
|
||||
#include "../src/utun_instance.h"
|
||||
#include "../src/routing.h"
|
||||
#include "../src/tun_if.h"
|
||||
|
||||
#define TIMEOUT_MS 120000
|
||||
#define WORKERS 2
|
||||
#define REQS_PER_WORKER 25
|
||||
|
||||
static const int file_sizes[] = { 1024, 10*1024, 50*1024, 100*1024 };
|
||||
static const int file_counts[] = { 8, 7, 5, 5 }; // sum = 25
|
||||
static const char* file_names[] = { "f_1k.bin", "f_10k.bin", "f_50k.bin", "f_100k.bin" };
|
||||
static const int num_files = 4;
|
||||
|
||||
static struct UTUN_INSTANCE* g_cli = NULL;
|
||||
static struct UTUN_INSTANCE* g_srv = NULL;
|
||||
static struct UASYNC* g_ua = NULL;
|
||||
static int g_ok = 0, g_done = 0, g_phase = 0;
|
||||
static pid_t g_http_pid = 0;
|
||||
static pid_t g_workers[WORKERS];
|
||||
static int g_http_port = 0, g_cli_port = 0, g_srv_port = 0;
|
||||
static int g_socks_port = 0, g_http_proxy_port = 0;
|
||||
static char g_tmpdir[256];
|
||||
static void* g_mon_id = NULL;
|
||||
|
||||
static int alloc_port(void) {
|
||||
int s = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (s < 0) return -1;
|
||||
struct sockaddr_in a; memset(&a, 0, sizeof(a));
|
||||
a.sin_family = AF_INET; a.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||
for (int i = 0; i < 200; i++) {
|
||||
a.sin_port = htons((uint16_t)(20000 + (rand() % 40000)));
|
||||
if (bind(s, (struct sockaddr*)&a, sizeof(a)) == 0) { close(s); return ntohs(a.sin_port); }
|
||||
}
|
||||
close(s); return -1;
|
||||
}
|
||||
|
||||
static void gen_file(const char* path, int size, int seed) {
|
||||
FILE* f = fopen(path, "wb");
|
||||
if (!f) return;
|
||||
for (int i = 0; i < size; i++) { uint8_t b = (uint8_t)((i ^ seed) & 0xFF); fwrite(&b, 1, 1, f); }
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
static int start_http_server(int port, const char* dir) {
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) return -1;
|
||||
if (pid == 0) {
|
||||
char port_str[16]; snprintf(port_str, sizeof(port_str), "%d", port);
|
||||
execlp("python3", "python3", "-m", "http.server", port_str, "--directory", dir, (char*)NULL);
|
||||
_exit(1);
|
||||
}
|
||||
g_http_pid = pid;
|
||||
usleep(300000);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char* make_cfg_server(void) {
|
||||
static char buf[1024];
|
||||
snprintf(buf, sizeof(buf),
|
||||
"[global]\ntun_enabled=no\n"
|
||||
"my_node_id=0x2222000000000001\n"
|
||||
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
|
||||
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
|
||||
"[server: s1]\naddr=127.0.0.1:%d\ntype=public\n"
|
||||
"[allowed_keys]\nallow_all=1\n"
|
||||
"[tcp_proxy_server]\nenabled=yes\n", g_srv_port);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static char* make_cfg_client(void) {
|
||||
static char buf[1024];
|
||||
snprintf(buf, sizeof(buf),
|
||||
"[global]\ntun_enabled=no\n"
|
||||
"my_node_id=0x1111000000000001\n"
|
||||
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
|
||||
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
|
||||
"[server: s1]\naddr=127.0.0.1:%d\ntype=public\n"
|
||||
"[client: c1]\nkeepalive=1\nlink=s1:127.0.0.1:%d\n"
|
||||
"peer_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
|
||||
"[tcp_proxy_client]\n"
|
||||
"enabled=yes\n"
|
||||
"socks_enabled=yes\n"
|
||||
"socks_addr=127.0.0.1:%d\n"
|
||||
"http_proxy_enabled=yes\n"
|
||||
"http_proxy_addr=127.0.0.1:%d\n"
|
||||
"via_node=0x2222000000000001\n",
|
||||
g_cli_port, g_srv_port, g_socks_port, g_http_proxy_port);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int conn_ready(struct UTUN_INSTANCE* inst) {
|
||||
if (!inst) return 0;
|
||||
for (struct ETCP_CONN* c = inst->connections; c; c = c->next)
|
||||
for (struct ETCP_LINK* l = c->links; l; l = l->next)
|
||||
if (l->initialized && c->crypto_ctx.initialized) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int worker_main(const char* type, const char* proxy, const char* url_base, const char* tmpdir) {
|
||||
for (int fi = 0; fi < num_files; fi++) {
|
||||
int count = file_counts[fi];
|
||||
char expected[512];
|
||||
snprintf(expected, sizeof(expected), "%s/%s", tmpdir, file_names[fi]);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
char cmd[1024], out[512], url[512];
|
||||
snprintf(out, sizeof(out), "%s/out_%d_%s_%d.bin", tmpdir, (int)getpid(), file_names[fi], i);
|
||||
snprintf(url, sizeof(url), "%s/%s", url_base, file_names[fi]);
|
||||
if (strcmp(type, "socks") == 0)
|
||||
snprintf(cmd, sizeof(cmd), "curl -s --connect-timeout 10 --max-time 30 --socks5-hostname %s -o %s %s 2>/dev/null", proxy, out, url);
|
||||
else
|
||||
snprintf(cmd, sizeof(cmd), "curl -s --connect-timeout 10 --max-time 30 --proxytunnel -x %s -o %s %s 2>/dev/null", proxy, out, url);
|
||||
|
||||
int rc = system(cmd);
|
||||
if (rc != 0) { fprintf(stderr, "[FAIL] worker %s %s iter %d curl exit %d\n", type, file_names[fi], i, WEXITSTATUS(rc)); return 1; }
|
||||
|
||||
char cmp_cmd[1024];
|
||||
snprintf(cmp_cmd, sizeof(cmp_cmd), "cmp -s %s %s", out, expected);
|
||||
if (system(cmp_cmd) != 0) { fprintf(stderr, "[FAIL] worker %s %s iter %d mismatch\n", type, file_names[fi], i); return 1; }
|
||||
unlink(out);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void monitor(void* arg) {
|
||||
(void)arg;
|
||||
if (g_done) return;
|
||||
|
||||
if (g_phase == 0) {
|
||||
if (conn_ready(g_cli) && conn_ready(g_srv)) {
|
||||
printf(" ETCP connected, launching %d workers...\n", WORKERS); fflush(stdout);
|
||||
g_phase = 1;
|
||||
char url[128]; snprintf(url, sizeof(url), "http://127.0.0.1:%d", g_http_port);
|
||||
char socks_proxy[64]; snprintf(socks_proxy, sizeof(socks_proxy), "127.0.0.1:%d", g_socks_port);
|
||||
char http_proxy[64]; snprintf(http_proxy, sizeof(http_proxy), "http://127.0.0.1:%d", g_http_proxy_port);
|
||||
|
||||
for (int i = 0; i < WORKERS; i++) {
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) { printf("[FAIL] fork worker %d: %s\n", i, strerror(errno)); g_done = -1; return; }
|
||||
if (pid == 0) {
|
||||
int rc = worker_main(i < WORKERS/2 ? "socks" : "http", i < WORKERS/2 ? socks_proxy : http_proxy, url, g_tmpdir);
|
||||
_exit(rc);
|
||||
}
|
||||
g_workers[i] = pid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (g_phase == 1) {
|
||||
int done_count = 0, fail = 0;
|
||||
for (int i = 0; i < WORKERS; i++) {
|
||||
if (g_workers[i] <= 0) { done_count++; continue; }
|
||||
int status; pid_t r = waitpid(g_workers[i], &status, WNOHANG);
|
||||
if (r == 0) continue;
|
||||
g_workers[i] = 0; done_count++;
|
||||
if (WIFEXITED(status) && WEXITSTATUS(status) != 0) fail++;
|
||||
}
|
||||
if (done_count >= WORKERS) {
|
||||
if (fail) { printf("[FAIL] %d/%d workers failed\n", fail, WORKERS); g_done = -1; }
|
||||
else { g_ok = 1; g_done = 1; }
|
||||
}
|
||||
}
|
||||
|
||||
if (!g_done) g_mon_id = uasync_set_timeout(g_ua, 500, NULL, monitor, "mon");
|
||||
}
|
||||
|
||||
static void test_timeout(void* arg) {
|
||||
(void)arg;
|
||||
if (!g_done) { printf("[FAIL] timeout\n"); g_done = -1; }
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("=== test_socks_http_proxy ===\n"); fflush(stdout);
|
||||
|
||||
// Резервируем fd 0
|
||||
{ int fd = open("/dev/null", O_RDONLY); if (fd > 0 && fd != 0) { dup2(fd, 0); close(fd); } else if (fd < 0) { close(0); open("/dev/null", O_RDONLY); } }
|
||||
|
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); debug_set_categories(DEBUG_CATEGORY_ALL);
|
||||
utun_instance_set_tun_init_enabled(0);
|
||||
srand((unsigned)time(NULL));
|
||||
|
||||
g_http_port = alloc_port(); g_cli_port = alloc_port(); g_srv_port = alloc_port();
|
||||
g_socks_port = alloc_port(); g_http_proxy_port = alloc_port();
|
||||
if (g_http_port < 0 || g_cli_port < 0 || g_srv_port < 0 || g_socks_port < 0 || g_http_proxy_port < 0) {
|
||||
printf("[FAIL] port allocation\n"); return 1;
|
||||
}
|
||||
printf(" ports: http=%d socks=%d http_proxy=%d\n", g_http_port, g_socks_port, g_http_proxy_port);
|
||||
|
||||
strcpy(g_tmpdir, "/tmp/utun_test_XXXXXX");
|
||||
if (test_mkdtemp(g_tmpdir) < 0) { printf("[FAIL] mkdtemp: %s\n", strerror(errno)); return 1; }
|
||||
|
||||
int seed = (int)time(NULL) ^ (int)getpid();
|
||||
for (int fi = 0; fi < num_files; fi++) {
|
||||
char path[512]; snprintf(path, sizeof(path), "%s/%s", g_tmpdir, file_names[fi]);
|
||||
gen_file(path, file_sizes[fi], seed + fi);
|
||||
}
|
||||
printf(" test files: 1k, 10k, 50k, 100k\n"); fflush(stdout);
|
||||
|
||||
if (start_http_server(g_http_port, g_tmpdir) < 0) { printf("[FAIL] start http server\n"); goto cleanup; }
|
||||
|
||||
g_ua = uasync_create();
|
||||
if (!g_ua) { printf("[FAIL] uasync_create\n"); goto cleanup; }
|
||||
|
||||
g_srv = utun_instance_create_from_str(g_ua, make_cfg_server());
|
||||
g_cli = utun_instance_create_from_str(g_ua, make_cfg_client());
|
||||
if (!g_srv || !g_cli) { printf("[FAIL] instance create\n"); goto cleanup; }
|
||||
|
||||
if (utun_instance_init(g_srv) < 0 || utun_instance_init(g_cli) < 0) { printf("[FAIL] instance init\n"); goto cleanup; }
|
||||
printf(" waiting for ETCP...\n"); fflush(stdout);
|
||||
|
||||
g_mon_id = uasync_set_timeout(g_ua, 500, NULL, monitor, "mon");
|
||||
void* to_id = uasync_set_timeout(g_ua, TIMEOUT_MS * 10, NULL, test_timeout, "to");
|
||||
|
||||
while (!g_done) uasync_poll(g_ua, 50);
|
||||
|
||||
if (to_id) uasync_cancel_timeout(g_ua, to_id);
|
||||
|
||||
if (g_ok) printf("[PASS] test_socks_http_proxy — %d reqs, %d workers, up to 100KB\n", WORKERS * REQS_PER_WORKER, WORKERS);
|
||||
else printf("[FAIL] test_socks_http_proxy\n");
|
||||
|
||||
cleanup:
|
||||
for (int i = 0; i < WORKERS; i++) if (g_workers[i] > 0) { kill(g_workers[i], SIGKILL); waitpid(g_workers[i], NULL, 0); }
|
||||
if (g_mon_id) uasync_cancel_timeout(g_ua, g_mon_id);
|
||||
if (g_http_pid > 0) { kill(g_http_pid, SIGKILL); waitpid(g_http_pid, NULL, 0); }
|
||||
if (g_cli) { g_cli->running = 0; utun_instance_destroy(g_cli); }
|
||||
if (g_srv) { g_srv->running = 0; utun_instance_destroy(g_srv); }
|
||||
if (g_ua) uasync_destroy(g_ua, 0);
|
||||
{ char buf[512]; snprintf(buf, sizeof(buf), "rm -rf %s", g_tmpdir); system(buf); }
|
||||
return g_ok ? 0 : 1;
|
||||
}
|
||||
@@ -26,7 +26,7 @@ int main(void) {
|
||||
if (!ua) { printf("[FAIL] uasync_create\n"); return 1; }
|
||||
struct tcp_proxy_client_mapping_config m = {.local_port = 9090, .remote_ip = "127.0.0.1", .remote_port = 9999};
|
||||
|
||||
struct tcp_proxy_client* p = tcp_proxy_client_create(NULL, ua, "tun_tcp", "10.99.0.1", 1500, 1, &m, 1, 0);
|
||||
struct tcp_proxy_client* p = tcp_proxy_client_create(NULL, ua, "tun_tcp", "10.99.0.1", 1500, 1, &m, 1, 0, 0, NULL, 0, NULL);
|
||||
if (!p) { printf("[FAIL] tcp_proxy_client_create with mapping\n"); uasync_destroy(ua, 0); return 1; }
|
||||
|
||||
tcp_proxy_client_destroy(p);
|
||||
@@ -39,7 +39,7 @@ int main(void) {
|
||||
struct UASYNC* ua = uasync_create();
|
||||
if (!ua) { printf("[FAIL] uasync_create\n"); return 1; }
|
||||
|
||||
struct tcp_proxy_client* p = tcp_proxy_client_create(NULL, ua, "tun_tcp", "10.99.0.1", 1500, 1, NULL, 0, 0);
|
||||
struct tcp_proxy_client* p = tcp_proxy_client_create(NULL, ua, "tun_tcp", "10.99.0.1", 1500, 1, NULL, 0, 0, 0, NULL, 0, NULL);
|
||||
if (!p) { printf("[FAIL] tcp_proxy_client_create without mapping\n"); uasync_destroy(ua, 0); return 1; }
|
||||
|
||||
tcp_proxy_client_destroy(p);
|
||||
|
||||
@@ -76,11 +76,11 @@ done: close(cli); close(srv); _exit(0);
|
||||
|
||||
static void start_test(void) {
|
||||
struct tcp_proxy_client_mapping_config m = {.local_port=9090,.remote_ip="127.0.0.1",.remote_port=g_echo_port};
|
||||
g_proxy_b = tcp_proxy_client_create(g_b, g_ua, NULL, NULL, 0, 0, &m, 1, 0, g_pair[1], 0xCCCC000000000001ULL);
|
||||
g_proxy_b = tcp_proxy_client_create(g_b, g_ua, "tun_tcp", "10.99.0.1", 0, 0, &m, 1, 0xCCCC000000000001ULL, 0, NULL, 0, NULL);
|
||||
if (!g_proxy_b) { printf("[FAIL] proxy_b create\n"); g_done=-1; return; }
|
||||
g_b->tcp_proxy = g_proxy_b;
|
||||
g_b->tcp_proxy_client = g_proxy_b;
|
||||
|
||||
g_cli = tcp_proxy_client_create(NULL, g_ua, NULL, NULL, 0, 0, NULL, 0, 0, g_pair[0], 0);
|
||||
g_cli = tcp_proxy_client_create(NULL, g_ua, "tun_tcp", "10.99.0.1", 0, 0, NULL, 0, 0, 0, NULL, 0, NULL);
|
||||
if (!g_cli) { printf("[FAIL] cli create\n"); g_done=-1; return; }
|
||||
|
||||
g_conn_idx = tcp_proxy_active_open(g_cli, "10.99.0.100", 9090);
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
// test_uasync_socket_race.c — тест гонки fd-reuse в epoll при быстром accept/close/accept
|
||||
// 4 дочерних процесса, каждый 200 connect+send+close. Сервер на uasync+tcp_io.
|
||||
// Проверяет что нет use-after-free при двойном epoll-событии (ERROR→free tc, WRITE→stale).
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/wait.h>
|
||||
#include <fcntl.h>
|
||||
#include "../lib/tcp_io.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
|
||||
#define CHILDREN 4
|
||||
#define ITER_PER_CHILD 200
|
||||
#define TIMEOUT_MS 30000
|
||||
|
||||
static struct UASYNC* g_ua = NULL;
|
||||
static int g_ok = 0, g_done = 0;
|
||||
static int g_conn_count = 0, g_read_count = 0, g_err_count = 0;
|
||||
static pid_t g_children[CHILDREN];
|
||||
static int g_port = 0;
|
||||
static void* g_mon_id = NULL;
|
||||
|
||||
static void on_read_cb(struct ll_queue* q, void* arg) {
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
struct ll_entry* e = queue_data_get(q);
|
||||
if (!e) { queue_resume_callback(q); return; }
|
||||
if (e->len == 3 && memcmp(e->dgram, "OK\n", 3) == 0) g_read_count++;
|
||||
memory_pool_free(tc->data_pool, e->dgram); queue_entry_free(e);
|
||||
queue_resume_callback(q);
|
||||
}
|
||||
|
||||
static void on_error_cb(struct tcp_conn* tc, int err, void* arg) {
|
||||
(void)err; (void)arg;
|
||||
g_err_count++;
|
||||
tcp_conn_destroy(tc);
|
||||
}
|
||||
|
||||
static void on_fin_cb(struct tcp_conn* tc, void* arg) {
|
||||
(void)arg;
|
||||
tcp_conn_push_close(tc);
|
||||
}
|
||||
|
||||
static void on_closed_cb(struct tcp_conn* tc, void* arg) {
|
||||
(void)arg;
|
||||
tcp_conn_destroy(tc);
|
||||
}
|
||||
|
||||
static void on_accept_cb(int fd, void* arg) {
|
||||
(void)arg;
|
||||
struct sockaddr_in addr; socklen_t alen = sizeof(addr);
|
||||
int csock = accept(fd, (struct sockaddr*)&addr, &alen);
|
||||
if (csock < 0) return;
|
||||
if (csock == 0) { int r = open("/dev/null", O_RDONLY); if (r > 0 && r != 0) { dup2(r, 0); close(r); } return; }
|
||||
socket_set_nonblocking(csock);
|
||||
struct tcp_conn* tc = tcp_conn_create(g_ua, csock, 512, 512, 4, 0, 0, on_fin_cb, on_error_cb, NULL);
|
||||
if (!tc) { socket_close_wrapper(csock); return; }
|
||||
tc->on_closed = on_closed_cb;
|
||||
queue_set_callback(tc->read_queue, on_read_cb, tc);
|
||||
g_conn_count++;
|
||||
}
|
||||
|
||||
static int child_main(int port, int id, int count) {
|
||||
(void)id;
|
||||
for (int i = 0; i < count; i++) {
|
||||
int s = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (s < 0) return 1;
|
||||
struct sockaddr_in a; memset(&a, 0, sizeof(a));
|
||||
a.sin_family = AF_INET; a.sin_port = htons((uint16_t)port);
|
||||
inet_pton(AF_INET, "127.0.0.1", &a.sin_addr);
|
||||
if (connect(s, (struct sockaddr*)&a, sizeof(a)) < 0) { close(s); return 1; }
|
||||
if (send(s, "OK\n", 3, 0) != 3) { close(s); return 1; }
|
||||
close(s);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void monitor(void* arg) {
|
||||
(void)arg;
|
||||
int alive = 0;
|
||||
for (int i = 0; i < CHILDREN; i++) {
|
||||
if (g_children[i] <= 0) continue;
|
||||
int status; pid_t r = waitpid(g_children[i], &status, WNOHANG);
|
||||
if (r == 0) { alive++; continue; }
|
||||
if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { g_done = -1; return; }
|
||||
g_children[i] = 0;
|
||||
}
|
||||
if (alive == 0) { g_ok = 1; g_done = 1; }
|
||||
if (!g_done) g_mon_id = uasync_set_timeout(g_ua, 500, NULL, monitor, "mon");
|
||||
}
|
||||
|
||||
static void test_timeout(void* arg) {
|
||||
(void)arg;
|
||||
printf("[FAIL] timeout conn=%d read=%d err=%d\n", g_conn_count, g_read_count, g_err_count);
|
||||
g_done = -1;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("=== test_uasync_socket_race ===\n"); fflush(stdout);
|
||||
|
||||
debug_config_init();
|
||||
for (int i = 1; i < DEBUG_CATEGORY_COUNT; i++) debug_set_category_level(i, DEBUG_LEVEL_ERROR);
|
||||
srand((unsigned)getpid());
|
||||
|
||||
// Резервируем fd 0: dup на высокий fd, close(0), /dev/null на 0.
|
||||
// Гарантирует что accept()/socket() никогда не вернут 0.
|
||||
{ int fd = open("/dev/null", O_RDONLY); if (fd == 0) { /* уже ок */ } else if (fd > 0) { dup2(fd, 0); close(fd); } else { close(0); open("/dev/null", O_RDONLY); } }
|
||||
|
||||
g_port = 25000 + (rand() % 10000);
|
||||
g_ua = uasync_create();
|
||||
if (!g_ua) { printf("[FAIL] uasync_create\n"); return 1; }
|
||||
|
||||
int lsock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (lsock < 0) { printf("[FAIL] socket\n"); goto cleanup; }
|
||||
socket_set_reuseaddr(lsock, 1);
|
||||
socket_set_nonblocking(lsock);
|
||||
struct sockaddr_in la; memset(&la, 0, sizeof(la));
|
||||
la.sin_family = AF_INET; la.sin_port = htons((uint16_t)g_port);
|
||||
la.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||
if (bind(lsock, (struct sockaddr*)&la, sizeof(la)) < 0) { printf("[FAIL] bind: %s\n", strerror(errno)); goto cleanup; }
|
||||
if (listen(lsock, 32) < 0) { printf("[FAIL] listen: %s\n", strerror(errno)); goto cleanup; }
|
||||
uasync_add_socket(g_ua, lsock, on_accept_cb, NULL, NULL, NULL);
|
||||
|
||||
for (int i = 0; i < CHILDREN; i++) {
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) { printf("[FAIL] fork\n"); goto cleanup; }
|
||||
if (pid == 0) { _exit(child_main(g_port, i, ITER_PER_CHILD)); }
|
||||
g_children[i] = pid;
|
||||
}
|
||||
printf(" %d children × %d iterations on port %d\n", CHILDREN, ITER_PER_CHILD, g_port); fflush(stdout);
|
||||
|
||||
g_mon_id = uasync_set_timeout(g_ua, 100, NULL, monitor, "mon");
|
||||
void* to_id = uasync_set_timeout(g_ua, TIMEOUT_MS * 10, NULL, test_timeout, "to");
|
||||
|
||||
while (!g_done) uasync_poll(g_ua, 50);
|
||||
|
||||
if (to_id) uasync_cancel_timeout(g_ua, to_id);
|
||||
|
||||
int expected = CHILDREN * ITER_PER_CHILD;
|
||||
if (g_ok && g_conn_count == expected && g_read_count == expected) {
|
||||
printf("[PASS] test_uasync_socket_race — %d/%d/%d conn/read/err\n", g_conn_count, g_read_count, g_err_count);
|
||||
} else {
|
||||
printf("[FAIL] test_uasync_socket_race — expected %d, got %d/%d/%d\n", expected, g_conn_count, g_read_count, g_err_count);
|
||||
g_ok = 0;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
for (int i = 0; i < CHILDREN; i++) if (g_children[i] > 0) { kill(g_children[i], SIGKILL); waitpid(g_children[i], NULL, 0); }
|
||||
if (g_mon_id) uasync_cancel_timeout(g_ua, g_mon_id);
|
||||
if (g_ua) uasync_destroy(g_ua, 0);
|
||||
return g_ok ? 0 : 1;
|
||||
}
|
||||
@@ -95,7 +95,7 @@ static void udp_echo_cb(socket_t sock, void* arg) {
|
||||
static void cli_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
(void)conn;
|
||||
if (!entry || !entry->dgram || entry->len < UDP_PROXY_HDR_SIZE + 1) {
|
||||
if (entry) { queue_entry_free(entry); queue_dgram_free(entry); } return;
|
||||
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); } return;
|
||||
}
|
||||
// svc_id(1) + subcmd(1) + sender_node_id(8) + src_ip(4) + src_port(2) + dst_ip(4) + dst_port(2) + payload
|
||||
size_t payload_len = entry->len - UDP_PROXY_HDR_SIZE;
|
||||
@@ -108,7 +108,7 @@ static void cli_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
g_done = -1;
|
||||
}
|
||||
}
|
||||
queue_entry_free(entry); queue_dgram_free(entry);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
}
|
||||
|
||||
static void monitor(void* arg) {
|
||||
|
||||
@@ -355,6 +355,11 @@ int etcpmon_client_request_metrics(struct etcpmon_client* client) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void action_auto_callback(void* arg, uint8_t msg_type, const uint8_t* payload, uint16_t payload_size) {
|
||||
(void)arg; (void)msg_type; (void)payload; (void)payload_size;
|
||||
/* legacy callback is called via on_action_result in handle_response */
|
||||
}
|
||||
|
||||
int etcpmon_client_send_action(struct etcpmon_client* client, const char* action) {
|
||||
if (!client || client->sock == INVALID_SOCKET || !action) return -1;
|
||||
|
||||
@@ -363,37 +368,8 @@ int etcpmon_client_send_action(struct etcpmon_client* client, const char* action
|
||||
strncpy(cmd.action, action, sizeof(cmd.action) - 1);
|
||||
cmd.action[sizeof(cmd.action) - 1] = '\0';
|
||||
|
||||
struct etcpmon_msg_header hdr;
|
||||
etcpmon_build_header(&hdr, sizeof(cmd), ETCPMON_CMD_ACTION, 0);
|
||||
|
||||
if (client->log_file) {
|
||||
log_hex_data(client->log_file, "TX", (const uint8_t*)&hdr, sizeof(hdr));
|
||||
fprintf(client->log_file, "%llu: [LOG] CMD_ACTION '%s'\n",
|
||||
(unsigned long long)get_timestamp_ms(), cmd.action);
|
||||
fflush(client->log_file);
|
||||
}
|
||||
|
||||
int sent = send(client->sock, (const char*)&hdr, sizeof(hdr), 0);
|
||||
if (sent != sizeof(hdr)) {
|
||||
if (client->log_file) {
|
||||
fprintf(client->log_file, "%llu: [ERROR] Failed to send CMD_ACTION header: %d\n",
|
||||
(unsigned long long)get_timestamp_ms(), WSAGetLastError());
|
||||
fflush(client->log_file);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
sent = send(client->sock, (const char*)&cmd, sizeof(cmd), 0);
|
||||
if (sent != sizeof(cmd)) {
|
||||
if (client->log_file) {
|
||||
fprintf(client->log_file, "%llu: [ERROR] Failed to send CMD_ACTION payload: %d\n",
|
||||
(unsigned long long)get_timestamp_ms(), WSAGetLastError());
|
||||
fflush(client->log_file);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return etcpmon_client_send_request(client, ETCPMON_CMD_ACTION, &cmd, sizeof(cmd),
|
||||
action_auto_callback, NULL);
|
||||
}
|
||||
|
||||
/* Send request with callback - queued version */
|
||||
@@ -589,13 +565,13 @@ static void handle_response(struct etcpmon_client* client, uint8_t msg_type,
|
||||
if (payload_size >= sizeof(struct etcpmon_rsp_error)) {
|
||||
struct etcpmon_rsp_error* rsp = (struct etcpmon_rsp_error*)payload;
|
||||
const char* msg = (const char*)(payload + sizeof(*rsp));
|
||||
|
||||
|
||||
if (client->log_file) {
|
||||
fprintf(client->log_file, "%llu: [LOG] RSP_ERROR code=%d msg='%s'\n",
|
||||
fprintf(client->log_file, "%llu: [LOG] RSP_ERROR code=%d msg='%s'\n",
|
||||
(unsigned long long)get_timestamp_ms(), rsp->error_code, msg);
|
||||
fflush(client->log_file);
|
||||
}
|
||||
|
||||
|
||||
if (client->on_error) {
|
||||
client->on_error(msg, client->user_data);
|
||||
}
|
||||
@@ -603,6 +579,23 @@ static void handle_response(struct etcpmon_client* client, uint8_t msg_type,
|
||||
break;
|
||||
}
|
||||
|
||||
case ETCPMON_RSP_ACTION_RESULT: {
|
||||
if (payload_size >= sizeof(struct etcpmon_rsp_action_result)) {
|
||||
const char* text = (const char*)(payload + sizeof(struct etcpmon_rsp_action_result));
|
||||
|
||||
if (client->log_file) {
|
||||
fprintf(client->log_file, "%llu: [LOG] RSP_ACTION_RESULT len=%u\n",
|
||||
(unsigned long long)get_timestamp_ms(), payload_size);
|
||||
fflush(client->log_file);
|
||||
}
|
||||
|
||||
if (client->on_action_result) {
|
||||
client->on_action_result(text, client->user_data);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ETCPMON_RSP_DEBUG_CONFIG: {
|
||||
if (payload_size >= 2) {
|
||||
client->debug_global_level = payload[0];
|
||||
@@ -692,7 +685,8 @@ static void handle_response_with_callback(struct etcpmon_client* client, uint8_t
|
||||
* Это позволяет использовать и старый код, и новый асинхронный send_request одновременно */
|
||||
if (seq_id == 0 || msg_type == ETCPMON_RSP_CONN_LIST ||
|
||||
msg_type == ETCPMON_RSP_SOCKET_LIST || msg_type == ETCPMON_RSP_DEBUG_CONFIG ||
|
||||
msg_type == ETCPMON_RSP_METRICS || msg_type == ETCPMON_RSP_ERROR) {
|
||||
msg_type == ETCPMON_RSP_METRICS || msg_type == ETCPMON_RSP_ERROR ||
|
||||
msg_type == ETCPMON_RSP_ACTION_RESULT) {
|
||||
handle_response(client, msg_type, payload, payload_size);
|
||||
}
|
||||
}
|
||||
@@ -856,6 +850,7 @@ void etcpmon_client_set_callbacks(struct etcpmon_client* client,
|
||||
struct etcpmon_link_metrics*,
|
||||
uint8_t, void*),
|
||||
void (*on_error)(const char*, void*),
|
||||
void (*on_action_result)(const char*, void*),
|
||||
void* user_data) {
|
||||
if (!client) return;
|
||||
|
||||
@@ -865,6 +860,7 @@ void etcpmon_client_set_callbacks(struct etcpmon_client* client,
|
||||
client->on_socket_list = on_socket_list;
|
||||
client->on_metrics = on_metrics;
|
||||
client->on_error = on_error;
|
||||
client->on_action_result = on_action_result;
|
||||
client->user_data = user_data;
|
||||
}
|
||||
|
||||
|
||||
@@ -105,6 +105,7 @@ struct etcpmon_client {
|
||||
struct etcpmon_link_metrics* links,
|
||||
uint8_t links_count, void* user_data);
|
||||
void (*on_error)(const char* msg, void* user_data);
|
||||
void (*on_action_result)(const char* text, void* user_data);
|
||||
|
||||
void* user_data;
|
||||
uint64_t last_metrics_request_ms;
|
||||
@@ -182,6 +183,7 @@ void etcpmon_client_set_callbacks(struct etcpmon_client* client,
|
||||
struct etcpmon_link_metrics*,
|
||||
uint8_t, void*),
|
||||
void (*on_error)(const char*, void*),
|
||||
void (*on_action_result)(const char*, void*),
|
||||
void* user_data);
|
||||
|
||||
/* Get connection name by peer_id */
|
||||
|
||||
+181
-4
@@ -20,13 +20,14 @@
|
||||
//#pragma comment(lib, "user32.lib")
|
||||
//#pragma comment(lib, "gdi32.lib")
|
||||
#define WINDOW_WIDTH 1000
|
||||
#define WINDOW_HEIGHT 1300
|
||||
#define WINDOW_HEIGHT 1400
|
||||
#define WINDOW_CLIENT_W 1250
|
||||
#define UPDATE_INTERVAL 10 /* 50ms → 20 samples per second */
|
||||
/* Global app pointer for callbacks */
|
||||
static struct etcpmon_app* g_app = NULL;
|
||||
/* Forward declarations */
|
||||
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
|
||||
static LRESULT CALLBACK ActionResultWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
|
||||
static void CreateControls(struct etcpmon_app* app);
|
||||
static void OnConnect(struct etcpmon_app* app);
|
||||
static void OnDisconnect(struct etcpmon_app* app);
|
||||
@@ -47,6 +48,7 @@ static void on_metrics(struct etcpmon_rsp_metrics* metrics,
|
||||
struct etcpmon_link_metrics* links,
|
||||
uint8_t links_count, void* user_data);
|
||||
static void on_error(const char* msg, void* user_data);
|
||||
static void on_action_result(const char* text, void* user_data);
|
||||
|
||||
int etcpmon_gui_init(struct etcpmon_app* app, HINSTANCE hInstance) {
|
||||
if (!app) return -1;
|
||||
@@ -97,6 +99,20 @@ int etcpmon_gui_init(struct etcpmon_app* app, HINSTANCE hInstance) {
|
||||
wcexGraph.hIconSm = NULL;
|
||||
|
||||
RegisterClassExA(&wcexGraph);
|
||||
|
||||
/* Register ActionResult window class */
|
||||
WNDCLASSEXA wcexAR;
|
||||
memset(&wcexAR, 0, sizeof(wcexAR));
|
||||
wcexAR.cbSize = sizeof(wcexAR);
|
||||
wcexAR.style = CS_HREDRAW | CS_VREDRAW;
|
||||
wcexAR.lpfnWndProc = ActionResultWndProc;
|
||||
wcexAR.hInstance = hInstance;
|
||||
wcexAR.hIcon = LoadIcon(NULL, IDI_APPLICATION);
|
||||
wcexAR.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||
wcexAR.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
|
||||
wcexAR.lpszClassName = "ETCPMonActionResult";
|
||||
wcexAR.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
|
||||
RegisterClassExA(&wcexAR);
|
||||
|
||||
/* Initialize Winsock */
|
||||
WSADATA wsaData;
|
||||
@@ -141,6 +157,7 @@ HWND etcpmon_gui_create_window(struct etcpmon_app* app) {
|
||||
etcpmon_client_set_callbacks(app->client,
|
||||
on_connected, on_disconnected,
|
||||
on_conn_list, on_socket_list, on_metrics, on_error,
|
||||
on_action_result,
|
||||
app);
|
||||
}
|
||||
|
||||
@@ -484,7 +501,7 @@ static void CreateControls(struct etcpmon_app* app) {
|
||||
mx + 130+50, my - 2, 45, 18, hWnd, (HMENU)IDC_EDIT_RT_BGP_NODES, hInst, NULL);
|
||||
|
||||
/* Links list */
|
||||
y = 725;
|
||||
y = 710;
|
||||
CreateWindowExA(0, "STATIC", "Links:",
|
||||
WS_CHILD | WS_VISIBLE,
|
||||
10, y, 100, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
|
||||
@@ -494,10 +511,10 @@ static void CreateControls(struct etcpmon_app* app) {
|
||||
10, y + 20, WINDOW_WIDTH - 20, 160, hWnd, (HMENU)IDC_LIST_LINKS, hInst, NULL);
|
||||
|
||||
/* Queues & Errors group - below Links */
|
||||
y = 915;
|
||||
y = 900;
|
||||
CreateWindowExA(0, "BUTTON", "Queues & Errors",
|
||||
WS_CHILD | WS_VISIBLE | BS_GROUPBOX,
|
||||
10, y, WINDOW_WIDTH - 20, 335, hWnd, (HMENU)IDC_STATIC_QUEUES, hInst, NULL);
|
||||
10, y, WINDOW_WIDTH - 20, 280, hWnd, (HMENU)IDC_STATIC_QUEUES, hInst, NULL);
|
||||
|
||||
int qy = y + 22;
|
||||
int q_col1 = 20;
|
||||
@@ -733,6 +750,52 @@ static void CreateControls(struct etcpmon_app* app) {
|
||||
q_col1 + 85 + i * 65, qy - 2, 60, 18, hWnd, (HMENU)((UINT_PTR)(IDC_EDIT_DEBUG_0 + i)), hInst, NULL);
|
||||
}
|
||||
|
||||
/* ========================================================================
|
||||
* Router Congestion group — below Queues & Errors
|
||||
* ======================================================================== */
|
||||
{
|
||||
int ry = 1181;
|
||||
int r_col1 = 20, r_col2 = 260, r_col3 = 500, r_col4 = 740;
|
||||
int lw = 85, ew = 70; /* label width, edit width */
|
||||
CreateWindowExA(0, "BUTTON", "Router Congestion",
|
||||
WS_CHILD | WS_VISIBLE | BS_GROUPBOX,
|
||||
10, ry, WINDOW_WIDTH - 20, 140, hWnd, (HMENU)IDC_GB_ROUTER, hInst, NULL);
|
||||
/* Row 1 */
|
||||
int ry1 = ry + 22;
|
||||
CreateWindowExA(0, "STATIC", "Conns:", WS_CHILD | WS_VISIBLE, r_col1, ry1, 50, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
|
||||
app->hEditRtrConns = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, r_col1 + 55, ry1 - 2, 50, 18, hWnd, (HMENU)IDC_EDIT_RTR_CONNS, hInst, NULL);
|
||||
CreateWindowExA(0, "STATIC", "Inflight:", WS_CHILD | WS_VISIBLE, r_col2, ry1, 55, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
|
||||
app->hEditRtrInflight = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, r_col2 + 60, ry1 - 2, 70, 18, hWnd, (HMENU)IDC_EDIT_RTR_INFLIGHT, hInst, NULL);
|
||||
CreateWindowExA(0, "STATIC", "SendQ:", WS_CHILD | WS_VISIBLE, r_col3, ry1, 55, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
|
||||
app->hEditRtrSendQ = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, r_col3 + 55, ry1 - 2, 70, 18, hWnd, (HMENU)IDC_EDIT_RTR_SENDQ, hInst, NULL);
|
||||
CreateWindowExA(0, "STATIC", "RecvQ:", WS_CHILD | WS_VISIBLE, r_col4, ry1, 55, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
|
||||
app->hEditRtrRecvQ = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, r_col4 + 55, ry1 - 2, 70, 18, hWnd, (HMENU)IDC_EDIT_RTR_RECVQ, hInst, NULL);
|
||||
/* Row 2 */
|
||||
int ry2 = ry + 47;
|
||||
CreateWindowExA(0, "STATIC", "Pkts Sent:", WS_CHILD | WS_VISIBLE, r_col1, ry2, lw, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
|
||||
app->hEditRtrPktsSent = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, r_col1 + lw, ry2 - 2, ew, 18, hWnd, (HMENU)IDC_EDIT_RTR_PKTS_SENT, hInst, NULL);
|
||||
CreateWindowExA(0, "STATIC", "Send Err:", WS_CHILD | WS_VISIBLE, r_col2, ry2, lw, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
|
||||
app->hEditRtrSendErr = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, r_col2 + lw, ry2 - 2, ew, 18, hWnd, (HMENU)IDC_EDIT_RTR_SEND_ERR, hInst, NULL);
|
||||
CreateWindowExA(0, "STATIC", "Pkts Rcvd:", WS_CHILD | WS_VISIBLE, r_col3, ry2, lw, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
|
||||
app->hEditRtrPktsRcvd = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, r_col3 + lw, ry2 - 2, ew, 18, hWnd, (HMENU)IDC_EDIT_RTR_PKTS_RCVD, hInst, NULL);
|
||||
CreateWindowExA(0, "STATIC", "SignFail:", WS_CHILD | WS_VISIBLE, r_col4, ry2, lw, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
|
||||
app->hEditRtrSignFail = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, r_col4 + lw, ry2 - 2, ew, 18, hWnd, (HMENU)IDC_EDIT_RTR_SIGN_FAIL, hInst, NULL);
|
||||
/* Row 3 */
|
||||
int ry3 = ry + 72;
|
||||
CreateWindowExA(0, "STATIC", "ACK Sent:", WS_CHILD | WS_VISIBLE, r_col1, ry3, lw, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
|
||||
app->hEditRtrAckSent = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, r_col1 + lw, ry3 - 2, ew, 18, hWnd, (HMENU)IDC_EDIT_RTR_ACK_SENT, hInst, NULL);
|
||||
CreateWindowExA(0, "STATIC", "ACK Rcvd:", WS_CHILD | WS_VISIBLE, r_col2, ry3, lw, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
|
||||
app->hEditRtrAckRecv = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, r_col2 + lw, ry3 - 2, ew, 18, hWnd, (HMENU)IDC_EDIT_RTR_ACK_RECV, hInst, NULL);
|
||||
CreateWindowExA(0, "STATIC", "Stale ACK:", WS_CHILD | WS_VISIBLE, r_col3, ry3, lw, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
|
||||
app->hEditRtrStaleAck = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, r_col3 + lw, ry3 - 2, ew, 18, hWnd, (HMENU)IDC_EDIT_RTR_STALE_ACK, hInst, NULL);
|
||||
/* Row 4 */
|
||||
int ry4 = ry + 97;
|
||||
CreateWindowExA(0, "STATIC", "Dup Drop:", WS_CHILD | WS_VISIBLE, r_col1, ry4, lw, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
|
||||
app->hEditRtrDupDrop = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, r_col1 + lw, ry4 - 2, ew, 18, hWnd, (HMENU)IDC_EDIT_RTR_DUP_DROP, hInst, NULL);
|
||||
CreateWindowExA(0, "STATIC", "OOB Drop:", WS_CHILD | WS_VISIBLE, r_col2, ry4, lw, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
|
||||
app->hEditRtrOobDrop = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, r_col2 + lw, ry4 - 2, ew, 18, hWnd, (HMENU)IDC_EDIT_RTR_OOB_DROP, hInst, NULL);
|
||||
}
|
||||
|
||||
/* ========================================================================
|
||||
* Debug Levels section — right column
|
||||
* ======================================================================== */
|
||||
@@ -1302,6 +1365,85 @@ static void on_error(const char* msg, void* user_data) {
|
||||
struct etcpmon_app* app = (struct etcpmon_app*)user_data;
|
||||
etcpmon_gui_set_status(app, msg);
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK ActionResultWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
||||
switch (message) {
|
||||
case WM_CREATE: {
|
||||
HWND hEdit = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "",
|
||||
WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_READONLY | WS_VSCROLL | ES_AUTOVSCROLL,
|
||||
0, 0, 0, 0, hWnd, (HMENU)1, ((CREATESTRUCTA*)lParam)->hInstance, NULL);
|
||||
HDC hdc = GetDC(hWnd);
|
||||
int logpix = GetDeviceCaps(hdc, LOGPIXELSY);
|
||||
ReleaseDC(hWnd, hdc);
|
||||
HFONT hFont = CreateFontA(-MulDiv(10, logpix, 72),
|
||||
0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
|
||||
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
|
||||
CLEARTYPE_QUALITY, FIXED_PITCH | FF_MODERN,
|
||||
"Consolas");
|
||||
SendMessage(hEdit, WM_SETFONT, (WPARAM)hFont, TRUE);
|
||||
SetWindowLongPtrA(hWnd, GWLP_USERDATA, (LONG_PTR)hEdit);
|
||||
SetPropA(hWnd, "Font", (HANDLE)hFont);
|
||||
break;
|
||||
}
|
||||
case WM_SIZE:
|
||||
if (GetWindowLongPtrA(hWnd, GWLP_USERDATA))
|
||||
MoveWindow((HWND)GetWindowLongPtrA(hWnd, GWLP_USERDATA), 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
|
||||
break;
|
||||
case WM_CLOSE:
|
||||
ShowWindow(hWnd, SW_HIDE);
|
||||
return 0;
|
||||
case WM_DESTROY: {
|
||||
HFONT hFont = (HFONT)GetPropA(hWnd, "Font");
|
||||
if (hFont) DeleteObject(hFont);
|
||||
RemovePropA(hWnd, "Font");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return DefWindowProcA(hWnd, message, wParam, lParam);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void on_action_result(const char* text, void* user_data) {
|
||||
struct etcpmon_app* app = (struct etcpmon_app*)user_data;
|
||||
if (!app || !app->hWndMain || !text || !text[0]) return;
|
||||
|
||||
if (!IsWindow(app->hActionResultWnd)) {
|
||||
RECT rc;
|
||||
GetWindowRect(app->hWndMain, &rc);
|
||||
int w = rc.right - rc.left;
|
||||
int h = rc.bottom - rc.top;
|
||||
|
||||
app->hActionResultWnd = CreateWindowExA(0, "ETCPMonActionResult", "Action Result",
|
||||
WS_OVERLAPPEDWINDOW | WS_SIZEBOX,
|
||||
rc.left, rc.top, w, h,
|
||||
NULL, NULL, app->hInstance, NULL);
|
||||
|
||||
app->hActionResultEdit = (HWND)GetWindowLongPtrA(app->hActionResultWnd, GWLP_USERDATA);
|
||||
}
|
||||
ShowWindow(app->hActionResultWnd, SW_RESTORE);
|
||||
SetForegroundWindow(app->hActionResultWnd);
|
||||
UpdateWindow(app->hActionResultWnd);
|
||||
|
||||
if (IsWindow(app->hActionResultEdit)) {
|
||||
size_t tlen = strlen(text);
|
||||
char* crlf = (char*)malloc(tlen * 2 + 1);
|
||||
if (crlf) {
|
||||
size_t out = 0;
|
||||
for (size_t i = 0; i < tlen; i++) {
|
||||
if (text[i] == '\n') crlf[out++] = '\r';
|
||||
crlf[out++] = text[i];
|
||||
}
|
||||
crlf[out] = '\0';
|
||||
SendMessageA(app->hActionResultEdit, EM_SETSEL, -1, -1);
|
||||
SendMessageA(app->hActionResultEdit, EM_REPLACESEL, FALSE, (LPARAM)crlf);
|
||||
free(crlf);
|
||||
}
|
||||
|
||||
int lines = (int)SendMessage(app->hActionResultEdit, EM_GETLINECOUNT, 0, 0);
|
||||
SendMessage(app->hActionResultEdit, EM_LINESCROLL, 0, lines);
|
||||
}
|
||||
}
|
||||
int etcpmon_gui_run(struct etcpmon_app* app) {
|
||||
if (!app || !app->hWndMain) return -1;
|
||||
ShowWindow(app->hWndMain, SW_SHOW);
|
||||
@@ -1332,6 +1474,12 @@ int etcpmon_gui_run(struct etcpmon_app* app) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (hFocus == app->hEditAction) {
|
||||
if (msg.wParam == VK_RETURN) {
|
||||
OnAction(app);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessageA(&msg);
|
||||
@@ -1484,6 +1632,21 @@ void etcpmon_gui_update_metrics(struct etcpmon_app* app,
|
||||
UpdateEditIfChanged(hMain, IDC_EDIT_RT_BGP_SENDERS, "%u", metrics->tun.rt_bgp_senders);
|
||||
UpdateEditIfChanged(hMain, IDC_EDIT_RT_BGP_NODES, "%u", metrics->tun.rt_bgp_nodes);
|
||||
|
||||
/* Router Congestion Metrics */
|
||||
UpdateEditIfChanged(hMain, IDC_EDIT_RTR_CONNS, "%u", metrics->router.total_conns);
|
||||
UpdateEditIfChanged(hMain, IDC_EDIT_RTR_INFLIGHT, "%u", metrics->router.total_inflight);
|
||||
UpdateEditIfChanged(hMain, IDC_EDIT_RTR_SENDQ, "%u", metrics->router.total_send_q);
|
||||
UpdateEditIfChanged(hMain, IDC_EDIT_RTR_RECVQ, "%u", metrics->router.total_recv_q);
|
||||
UpdateEditIfChanged(hMain, IDC_EDIT_RTR_PKTS_SENT, "%u", metrics->router.pkts_sent);
|
||||
UpdateEditIfChanged(hMain, IDC_EDIT_RTR_SEND_ERR, "%u", metrics->router.pkts_send_err);
|
||||
UpdateEditIfChanged(hMain, IDC_EDIT_RTR_PKTS_RCVD, "%u", metrics->router.pkts_rcvd);
|
||||
UpdateEditIfChanged(hMain, IDC_EDIT_RTR_ACK_SENT, "%u", metrics->router.ack_sent);
|
||||
UpdateEditIfChanged(hMain, IDC_EDIT_RTR_ACK_RECV, "%u", metrics->router.ack_recv);
|
||||
UpdateEditIfChanged(hMain, IDC_EDIT_RTR_DUP_DROP, "%u", metrics->router.dup_dropped);
|
||||
UpdateEditIfChanged(hMain, IDC_EDIT_RTR_OOB_DROP, "%u", metrics->router.oob_dropped);
|
||||
UpdateEditIfChanged(hMain, IDC_EDIT_RTR_STALE_ACK, "%u", metrics->router.stale_ack);
|
||||
UpdateEditIfChanged(hMain, IDC_EDIT_RTR_SIGN_FAIL, "%u", metrics->router.sign_fail);
|
||||
|
||||
/* Queue Metrics */
|
||||
UpdateEditIfChanged(hMain, IDC_EDIT_Q_IN_Q_BYTES, "%u", metrics->etcp.input_queue_bytes);
|
||||
UpdateEditIfChanged(hMain, IDC_EDIT_Q_IN_Q_PKTS, "%u", metrics->etcp.input_queue_packets);
|
||||
@@ -1655,6 +1818,20 @@ void etcpmon_gui_clear_metrics(struct etcpmon_app* app) {
|
||||
SetDlgItemTextA(app->hWndMain, IDC_EDIT_RT_BGP_SENDERS, "");
|
||||
SetDlgItemTextA(app->hWndMain, IDC_EDIT_RT_BGP_NODES, "");
|
||||
|
||||
SetDlgItemTextA(app->hWndMain, IDC_EDIT_RTR_CONNS, "");
|
||||
SetDlgItemTextA(app->hWndMain, IDC_EDIT_RTR_INFLIGHT, "");
|
||||
SetDlgItemTextA(app->hWndMain, IDC_EDIT_RTR_SENDQ, "");
|
||||
SetDlgItemTextA(app->hWndMain, IDC_EDIT_RTR_RECVQ, "");
|
||||
SetDlgItemTextA(app->hWndMain, IDC_EDIT_RTR_PKTS_SENT, "");
|
||||
SetDlgItemTextA(app->hWndMain, IDC_EDIT_RTR_SEND_ERR, "");
|
||||
SetDlgItemTextA(app->hWndMain, IDC_EDIT_RTR_PKTS_RCVD, "");
|
||||
SetDlgItemTextA(app->hWndMain, IDC_EDIT_RTR_ACK_SENT, "");
|
||||
SetDlgItemTextA(app->hWndMain, IDC_EDIT_RTR_ACK_RECV, "");
|
||||
SetDlgItemTextA(app->hWndMain, IDC_EDIT_RTR_DUP_DROP, "");
|
||||
SetDlgItemTextA(app->hWndMain, IDC_EDIT_RTR_OOB_DROP, "");
|
||||
SetDlgItemTextA(app->hWndMain, IDC_EDIT_RTR_STALE_ACK, "");
|
||||
SetDlgItemTextA(app->hWndMain, IDC_EDIT_RTR_SIGN_FAIL, "");
|
||||
|
||||
SetDlgItemTextA(app->hWndMain, IDC_EDIT_Q_IN_Q_BYTES, "");
|
||||
SetDlgItemTextA(app->hWndMain, IDC_EDIT_Q_IN_Q_PKTS, "");
|
||||
SetDlgItemTextA(app->hWndMain, IDC_EDIT_Q_IN_SEND_BYTES, "");
|
||||
|
||||
@@ -81,6 +81,22 @@ extern "C" {
|
||||
#define IDC_EDIT_RT_BGP_SENDERS 315
|
||||
#define IDC_EDIT_RT_BGP_NODES 316
|
||||
|
||||
/* Router Congestion control IDs */
|
||||
#define IDC_GB_ROUTER 350
|
||||
#define IDC_EDIT_RTR_CONNS 351
|
||||
#define IDC_EDIT_RTR_INFLIGHT 352
|
||||
#define IDC_EDIT_RTR_SENDQ 353
|
||||
#define IDC_EDIT_RTR_RECVQ 354
|
||||
#define IDC_EDIT_RTR_PKTS_SENT 355
|
||||
#define IDC_EDIT_RTR_SEND_ERR 356
|
||||
#define IDC_EDIT_RTR_PKTS_RCVD 357
|
||||
#define IDC_EDIT_RTR_ACK_SENT 358
|
||||
#define IDC_EDIT_RTR_ACK_RECV 359
|
||||
#define IDC_EDIT_RTR_DUP_DROP 360
|
||||
#define IDC_EDIT_RTR_OOB_DROP 361
|
||||
#define IDC_EDIT_RTR_STALE_ACK 362
|
||||
#define IDC_EDIT_RTR_SIGN_FAIL 363
|
||||
|
||||
/* Link list control ID */
|
||||
#define IDC_LIST_LINKS 400
|
||||
|
||||
@@ -212,6 +228,10 @@ struct etcpmon_app {
|
||||
HWND hEditAction;
|
||||
HWND hBtnAction;
|
||||
|
||||
/* Action result popup */
|
||||
HWND hActionResultWnd;
|
||||
HWND hActionResultEdit;
|
||||
|
||||
/* ETCP metrics controls */
|
||||
HWND hEditEtcpName;
|
||||
HWND hEditEtcpRttLast;
|
||||
@@ -248,6 +268,21 @@ struct etcpmon_app {
|
||||
HWND hEditRtBgpSenders;
|
||||
HWND hEditRtBgpNodes;
|
||||
|
||||
/* Router Congestion controls */
|
||||
HWND hEditRtrConns;
|
||||
HWND hEditRtrInflight;
|
||||
HWND hEditRtrSendQ;
|
||||
HWND hEditRtrRecvQ;
|
||||
HWND hEditRtrPktsSent;
|
||||
HWND hEditRtrSendErr;
|
||||
HWND hEditRtrPktsRcvd;
|
||||
HWND hEditRtrAckSent;
|
||||
HWND hEditRtrAckRecv;
|
||||
HWND hEditRtrDupDrop;
|
||||
HWND hEditRtrOobDrop;
|
||||
HWND hEditRtrStaleAck;
|
||||
HWND hEditRtrSignFail;
|
||||
|
||||
/* Links list */
|
||||
HWND hListLinks;
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ extern "C" {
|
||||
#define NAT_VERIFIED_EIM 5 /* EIM NAT */
|
||||
#define NAT_VERIFIED_STRICT 6 /* strict NAT */
|
||||
#define NAT_VERIFIED_DIRECT 7
|
||||
#define ETCPMON_MAX_MSG_SIZE 4096
|
||||
#define ETCPMON_MAX_MSG_SIZE 32768
|
||||
#define ETCPMON_MAX_CONN_NAME 32
|
||||
#define ETCPMON_MAX_CONNECTIONS 250
|
||||
#define ETCPMON_MAX_LINKS 16
|
||||
@@ -63,6 +63,7 @@ extern "C" {
|
||||
#define ETCPMON_RSP_NODE_INFO 0x85 /* Single node info (payload: raw BGP_NODEINFO_PACKET) */
|
||||
#define ETCPMON_RSP_NODE_INFO_END 0x86 /* End of node list (no payload) */
|
||||
#define ETCPMON_RSP_NODE_REMOVED 0x88 /* Node removed (payload: uint64_t node_id) */
|
||||
#define ETCPMON_RSP_ACTION_RESULT 0x89 /* Action result text response */
|
||||
#define ETCPMON_RSP_ERROR 0xFF /* Error response */
|
||||
|
||||
/* Error codes for RSP_ERROR */
|
||||
@@ -288,9 +289,27 @@ struct etcpmon_tun_metrics {
|
||||
uint32_t rt_bgp_nodes; /* BGP nodes count (ROUTE_BGP) */
|
||||
};
|
||||
|
||||
/* Router congestion metrics — aggregated over all router_conns */
|
||||
struct etcpmon_router_metrics {
|
||||
uint32_t total_conns;
|
||||
uint32_t total_inflight;
|
||||
uint32_t total_send_q;
|
||||
uint32_t total_recv_q;
|
||||
uint32_t pkts_sent;
|
||||
uint32_t pkts_send_err;
|
||||
uint32_t pkts_rcvd;
|
||||
uint32_t ack_sent;
|
||||
uint32_t ack_recv;
|
||||
uint32_t dup_dropped;
|
||||
uint32_t oob_dropped;
|
||||
uint32_t stale_ack;
|
||||
uint32_t sign_fail;
|
||||
};
|
||||
|
||||
struct etcpmon_rsp_metrics {
|
||||
struct etcpmon_etcp_metrics etcp; /* ETCP connection metrics */
|
||||
struct etcpmon_tun_metrics tun; /* TUN interface metrics */
|
||||
struct etcpmon_etcp_metrics etcp; /* ETCP connection metrics */
|
||||
struct etcpmon_tun_metrics tun; /* TUN interface metrics */
|
||||
struct etcpmon_router_metrics router; /* Router congestion metrics (aggregated) */
|
||||
/* Followed by etcp.links_count * struct etcpmon_link_metrics */
|
||||
};
|
||||
|
||||
@@ -309,6 +328,12 @@ struct etcpmon_cmd_set_debug {
|
||||
uint8_t levels[ETCPMON_MAX_DEBUG_CATEGORIES]; /* Actual length = category_count */
|
||||
};
|
||||
|
||||
/* RSP_ACTION_RESULT: Text result of an action command */
|
||||
struct etcpmon_rsp_action_result {
|
||||
uint8_t code; /* 0=success, 1=error */
|
||||
/* Followed by null-terminated UTF-8 text string */
|
||||
};
|
||||
|
||||
/* RSP_ERROR: Error response */
|
||||
struct etcpmon_rsp_error {
|
||||
uint8_t error_code; /* Error code */
|
||||
@@ -340,6 +365,10 @@ struct etcpmon_rsp_error {
|
||||
#define ETCPMON_ERROR_SIZE(msg_len) \
|
||||
(sizeof(struct etcpmon_msg_header) + sizeof(struct etcpmon_rsp_error) + (msg_len) + 1)
|
||||
|
||||
/* Calculate size of action result response */
|
||||
#define ETCPMON_ACTION_RESULT_SIZE(text_len) \
|
||||
(sizeof(struct etcpmon_msg_header) + sizeof(struct etcpmon_rsp_action_result) + (text_len) + 1)
|
||||
|
||||
/* ============================================================================
|
||||
* Utility Functions (optional, for both client and server)
|
||||
* ============================================================================ */
|
||||
|
||||
+16
-3
@@ -1,5 +1,7 @@
|
||||
[global]
|
||||
tun_ip=10.0.0.1 # IP адрес tun интерфейса. чтобы к нему могли обращаться другие узлу его надо вписать в секции [routing] my_subnet
|
||||
#tun_enabled=yes # создавать TUN интерфейс (по умолчанию yes). Установите no для работы без root (только SOCKS/HTTP прокси)
|
||||
tun_ifname=tun0 # имя TUN интерфейса (по умолчанию tun0)
|
||||
mtu=1500 # UDP MTU for all connections (default - 1500)
|
||||
db_path= # путь к LMDB-базе nodeinfo (например /var/lib/utun/nodes.db). Если не задан — база не используется
|
||||
|
||||
@@ -79,17 +81,28 @@ allow=all
|
||||
|
||||
# --- TCP Proxy Client (локальный TCP/UDP/ICMP прокси) ---
|
||||
# Проксирует входящие TCP (через lwIP), UDP и ICMP ping через указанный via_node.
|
||||
# Все три протокола идут через один и тот же удалённый узел.
|
||||
# Также поддерживает SOCKS5 и HTTP CONNECT proxy без TUN (напрямую через ETCP).
|
||||
# Режимы можно включать раздельно или одновременно.
|
||||
#[tcp_proxy_client]
|
||||
#enabled=yes
|
||||
|
||||
## --- TUN режим (прозрачный) ---
|
||||
#tun_name=tun_tcp # имя TUN интерфейса (по умолчанию tun_tcp)
|
||||
#tun_ip=10.99.0.1 # IP адрес TUN интерфейса
|
||||
#mtu=1500 # MTU
|
||||
#eim_timeout=300 # таймаут EIM маппингов (сек)
|
||||
#via_node=0xABCD000000000001 # узел для проксирования (или свой node_id для локального)
|
||||
#forward=8000 -> 10.0.0.50:80
|
||||
#forward=2222 -> 10.0.0.50:22
|
||||
|
||||
## --- SOCKS5 режим (прямой) ---
|
||||
#socks_enabled=yes
|
||||
#socks_addr=127.0.0.1:1080 # адрес для SOCKS5 сервера (bind)
|
||||
|
||||
## --- HTTP CONNECT режим (прямой) ---
|
||||
#http_proxy_enabled=yes
|
||||
#http_proxy_addr=127.0.0.1:8080 # адрес для HTTP CONNECT сервера (bind)
|
||||
|
||||
#via_node=0xABCD000000000001 # узел для проксирования (общий для всех режимов)
|
||||
|
||||
# --- TCP Proxy Server (удаленный exit node) ---
|
||||
# Принимает CONNECT-запросы от других нод и открывает OS сокеты к адресатам.
|
||||
#[tcp_proxy_server]
|
||||
|
||||
Reference in new issue
Block a user