18 Commits
Author SHA1 Message Date
Evgeny 55fa5c7759 fix: add retry_timer fallback in tcp_proxy_server for backpressure stall
waiter-based backpressure (commit ba14e31e) could starve HTTPS CONNECT
worker under concurrent 4-worker load — send_q never drained below threshold
fast enough for the waiter to fire before curl timed out (exit=28).

Add retry_timer (500ms, force=1) alongside etcp_router_on_send_ready
waiter: waiter gives instant wake when send_q drains, timer guarantees
delivery via force=1 if waiter hasn't fired in time.

Also improve socks_proxy DEBUG diagnostics in backpressure branches.
2026-07-04 19:32:31 +03:00
Evgeny 0b5a710fe6 etcp_connect + BGP_READY callbacks + router reconnect test
- etcp_connect.c: async connect API with EARLY/LATE/BGP_READY callbacks
- etcp_connect.h: declaration (via etcp_api.h)
- connect_deliver: fix to preserve unfired callback nodes
- etcp_set_routing_exchange_state: fires bgp_ready_cbk on TABLE_COMPLETE
- route_bgp: TABLE_COMPLETE handler + route_bgp_send_table_complete
- etcp.c: connections_count tracking, cleanup on close
- test_etcp_router_reconnect: a-b-c topology with b restart (etcp_connect)
- test_etcp_connect: unit tests for async connect API
- etcp_router: inflight/retrans/close improvements
- Various proxy/NAT/router cleanups
2026-07-04 18:51:01 +03:00
Evgeny f1f533a552 move CLOSE/RST flags from hdr->seq to hdr->flags
- ROUTER_SEQ_CLOSE_FLAG (0x80000000) → ROUTER_FLAG_CLOSE (0x02 in flags)
- ROUTER_SEQ_RST_FLAG (0x40000000) → ROUTER_FLAG_RST (0x40 in flags, already existed)
- router_send_one_flags: uint32_t seq_flags → uint8_t flag_bits
- router_send_to: uint32_t seq_flags → uint8_t flag_bits, hdr->seq=0, hdr->flags=flag_bits
- etcp_router_recv_cb: check hdr->flags instead of hdr->seq for CLOSE/RST
2026-07-03 20:38:11 +03:00
Evgeny 03490cfc00 refactor: extract etcp_router_recv_cb into 5 functions + fix send_q threshold
- router_verify_signature — Ed25519 signature verification
- router_handle_ack — ACK processing + drain on send_blocked
- router_forward_transit — packet forwarding
- router_check_peer_restart — session restart + RST detection
- router_handle_data_packet — reorder/assembly/dedup
- free_entry helper
- etcp_router_recv_cb: 198 → 42 lines
- ROUTER_MAX_SEND_Q_PACKETS threshold: MAX/2 → MAX-1 (min wait)
2026-07-03 19:27:36 +03:00
Evgeny ba14e31e09 fix: remove retry_timer, use waiter-based round-robin on send_q for fairness
- tcp_proxy_server: drain_cb uses etcp_router_on_send_ready instead of retry_timer
- socks_proxy: on_read_cb + tx_waiter_cb use etcp_router_on_send_ready instead of waiter_register
- Result: FIFO round-robin via ll_queue waiters on send_q, all streams get fair share
- No timers — wakeup is event-driven via queue_data_get → check_waiters
- TCP backpressure: read_queue callback_suspended prevents over-reading
2026-07-03 16:23:56 +03:00
Evgeny 5b850ea5da test: add profiling (MB/s per file per worker) to socks_http_proxy 2026-07-03 15:54:09 +03:00
Evgeny 2fba5c31c4 fix: backpressure data loss in socks/http proxy + comprehensive test
socks_proxy (client side):
- tx_buf buffering on send_data failure in RELAY path, retry in tx_waiter_cb
- process_http_request: version_str null-term fix (memcpy(vlen)+explicit \0)
- process_http_request: assemble hdr+body+extra into heap pkt, atomic send
- socks_proxy_handle_etcp: alloc failure logs write_queue state + byte counters

tcp_proxy_server (exit node):
- read_queue_drain_cb: tx_buf buffering on backpressure, retry_timer (500ms, force=1)
- on_fin_cb: defer FIN when read_queue+tx_buf has pending data (dst_fin_deferred)
- handle_data + drain_cb: per-stream byte counters (sent/recv/relayed/drain_count)
- on_fin_cb/on_error_cb: detailed state logging (counters, queues, buffers)

test_socks_http_proxy:
- 4 workers: socks_ipv4, socks_domain (DNS resolution via SOCKS), http_proxy, https_connect
- 4 file sizes: 1KB, 100KB, 1MB, 10MB — deterministic content, byte-by-byte cmp -s
- HTTPS server with self-signed cert, POST/echo, HEAD, OPTIONS
- 51/51 tests PASS
2026-07-03 14:29:17 +03:00
jeka 4f168c93f6 1 2026-07-03 12:48:25 +03:00
Evgeny 6c938ed2ba test_etcp_router: dummynet congestion test + timer leak fixes
- test_etcp_router: full rewrite with dummynet proxy, congestion cycles
  (fill inflight to backpressure, push 20 packets through, release shaper)
  bitmap tracking (0 loss, 0 dup, 0 corrupt checks), timing diagnostics
- Makefile.am: add utun-dummynet.o to test_etcp_router LDADD
- conn_mgr: fix cm_exch_probe timer leak (NULL callback → retry_cb),
  clean exchange_pending in cm_entry_destroy + conn_mgr_destroy
- utun_instance: add stcp_server_destroy() call in utun_instance_destroy
2026-07-03 12:48:51 +03:00
Evgeny 2cb85cbc3d socks_proxy: поддержка HTTP-прокси (GET/POST/HEAD/OPTIONS/...) — не только CONNECT
process_http_request() теперь обрабатывает любые HTTP-методы:
- CONNECT — туннель как раньше, без изменений
- GET/POST/PUT/HEAD/OPTIONS/... — парсинг абсолютного URL, DNS,
  send_connect + пересборка строки с относительным путём + тело DATA

test_socks_http_proxy: +3-й worker (http_proxy через -x без --proxytunnel),
+проверки POST (echo), HEAD (200 OK), OPTIONS (Allow). Все 78 проверок — PASS.
2026-07-02 22:04:37 +03:00
Evgeny 754c08e9c2 etcpmon: widen sockets list by ~100px (15 chars), shift action section 2026-07-02 21:26:21 +03:00
Evgeny 71ccccc2d5 etcpmon: widen sockets list +15px, shift action section right 2026-07-02 21:20:37 +03:00
Evgeny c6d1ebeeaf debug: log etcpmon socket_list NAT values in send_socket_list 2026-07-02 20:40:08 +03:00
Evgeny eea0ed0a48 debug: add NAT category logging to self-detect, init_resp nat update, and NAT_INFO receive paths 2026-07-02 20:31:32 +03:00
Evgeny 0738dcfd5a fix: self-detection uses interface_addr instead of local_addr (works with 0.0.0.0 bind); don't overwrite nat_addr in handle_init_response_client if NAT already verified 2026-07-02 20:24:32 +03:00
Evgeny 4862c30261 add CFG_SERVER_TYPE_LOCAL (type=local) and UNKNOWN semantics: PRIVATE/LOCAL hidden from nodeinfo, PRIVATE blocks incoming INIT, LOCAL accepts; UNKNOWN treated as PUBLIC; self-detection of PUBLIC IP on incoming INIT; filter LOCAL from NAT checks and connectivity 2026-07-02 19:30:08 +03:00
Evgeny 3c5d82a6ee add cross-build-win.sh for Windows cross-compilation; gitignore build-win/ build artifacts only 2026-07-02 18:59:20 +03:00
Evgeny 389ef00da6 refactor: split socket meta 'type' into config_type and nat_type — separate config (PUBLIC/NAT/PRIVATE) from detected NAT (EIM/STRICT/DIRECT) across NODEINFO, conn_mgr, route_bgp, route_node, etcpmon; win32 compat fixes (memmem, SHUT_WR, tun test_mode, tcp_proxy guards) 2026-07-02 18:58:34 +03:00
57 changed files with 3110 additions and 700 deletions

No files matched your search

+1
View File
@@ -1,5 +1,6 @@
# Build directory (out-of-tree build)
/build/
build-win/
tools/chatgui/build/
# Build outputs
+28
View File
@@ -333,6 +333,34 @@ SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
Всегда когда начинаешь диагностику ознакомься со скиллом (используй skills)
Диагностика и поиск ошибок - это в первую очередь продумывание отладочных механизмов которые покажут понятную картину и точное представление об ошибке. Рассуждать надо как диагностикой добиться точной картины. И как приавльнее добавить диагностику чтобы не спамила лишними сообщениями и была понятной, логичной и информативной.
Важное правило отладки: вся отладка сводится к тому чтобы сделать удобные логи которые наглядно показывают поведение и проблемы. без лишнего мусора, компактно и по существу. очень желательно с деталями которые сильно повышают качество отладки.
при отладке нельзя: гадать и долго пытаться разбирать код пытаяясь найти причину. гораздо надёжнее с помощью логов понято точное поведение.
Если логов много - записывай в файл и потом анализируй.
Если видишь спам-логи - подумай как их выборочно отключить чтобы не мешали.
Для отладки добавляй в DEBUG_CATEGORY_DEBUG диагностические сообщения где надо. И включи эту категорию в настройках. Убирай только после проверки (путём запуска) когда все ошибки устранены.
сообщение выводится по или - либо debug_set_level(DEBUG_LEVEL_TRACE) - выводится ВСЁ независимо от настроек по категориям. Тоесть берется max(global level, category lavel)
Твой бич - ты постоянно гадаешь и анализируешь код. Что приводит к снежному кобу ошибок и неверных гипотез. 10 раз повторяю - только логи логи логи и никакого гадания. Лоооги!!! правильные логи покажут всё с предельной точностью. Вся суть отладки - информативные логи
И смотри логи. не задавливай их grep-ом. лучше больше. единственное с чем борись - это бесполезные спам логи.
Но полезные смотри всегда, и всегда оставляй логи которые выводятся нечасто.
Частые логи - это трафик которые >100 раз повторяются. Но которые мало раз обязательно оставлять и выводить.
можешь в тесте включить логи в нужный интервал чтобы не спамить. debug_set_level(DEBUG_LEVEL_TRACE) и выключить debug_set_level(DEBUG_LEVEL_NONE)
Если видишь проблему и ее решение неочевидно то выстраивай диагностику вокруг неё пока не будет очевидно где и что происходит не так. Это базовое и обязательное требование к отладке.
Подробные логи ты должен выводить не обрезая всегда и анализировать. Если лог большой - выведи в файл и анализируй файл.
Рассуждение должно быть примерно таким:
- данные повреждаются при отправке.
- где и почему непонятно. надо продумать ключевые точки где получим максимум полезной информации и не было большого объёма вывода лога.
- где лучше? каждый пакет логировать - много, но будет предельно точная картина
- Еще хорошо бы время - так мы заодно сможем найти проблемы с производительностью, найти проблемные места застревания кода.
- Но это большой объём. Можно ли без него? можно но это будет малоэффективно и хороших альтернатив пока не видно.
- Значит логируем весь трафик, а заодно включим полную трассировку функций.
- Запустили. Записали весь процесс в файл. Теперь можно анализировать. Давай сперва посчитаю количество строк с дампом: ... 50000.
- давай сделаю простой скрипт который дамп прочитает и сохранит в файл. И сверю с оригинальным файлом
- Давай заодно возьму произвольный фрагмент лога и изучу еа предмет явных проблем. Особенно инициализацию и освобождение ресурсов.
Сколько времени занимает, нет ли заклиниваний, нет ли ошибок или странностей
### Прочие правила:
- sed для редактирования исходников - запрещено
- Проверяй на дублирование кода - не сделано ли это уже в другом месте
+284
View File
@@ -0,0 +1,284 @@
#!/bin/bash
# cross-build-win.sh — кросс-компиляция uTun для Windows 10 из Linux
# Использование: ./cross-build-win.sh [--clean]
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
step() { echo -e "${CYAN}===${NC} $*"; }
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARNING]${NC} $*"; }
fatal() { echo -e "${RED}FATAL:${NC} $*" >&2; exit 1; }
trap 'fatal "script failed at line $LINENO (exit code: $?)"' ERR
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
BUILD_DIR="$SCRIPT_DIR/build-win"
PREFIX="$BUILD_DIR/prefix"
MINGW_CC="x86_64-w64-mingw32-gcc"
MINGW_AR="x86_64-w64-mingw32-ar"
MINGW_RANLIB="x86_64-w64-mingw32-ranlib"
MINGW_PKGS="gcc-mingw-w64-x86-64-posix g++-mingw-w64-x86-64-posix"
OPENSSL_VER="3.0.16"
OPENSSL_URL="https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_VER}/openssl-${OPENSSL_VER}.tar.gz"
# -------------------------------------------------------------------
# 0. Аргументы
# -------------------------------------------------------------------
CLEAN=0
while [[ $# -gt 0 ]]; do
case "$1" in
--clean) CLEAN=1; shift ;;
*) fatal "unknown option: $1" ;;
esac
done
if [ "$CLEAN" -eq 1 ]; then
echo -e "${YELLOW}Cleaning build directory: $BUILD_DIR${NC}"
rm -rf "$BUILD_DIR"
fi
echo -e "${CYAN}==============================================${NC}"
echo -e "${CYAN} uTun Cross-Compilation for Windows 10${NC}"
echo -e "${CYAN}==============================================${NC}"
echo ""
# -------------------------------------------------------------------
# 1. Проверка системы
# -------------------------------------------------------------------
step "Checking system..."
if [ "$(uname -s)" != "Linux" ]; then
fatal "This script only runs on Linux. Detected: $(uname -s)"
fi
if [ "$(uname -m)" != "x86_64" ]; then
fatal "This script requires x86_64 architecture. Detected: $(uname -m)"
fi
if ! command -v apt-get &>/dev/null; then
fatal "apt-get not found. Only Debian/Ubuntu are supported."
fi
ok "System: Linux x86_64 with apt"
# -------------------------------------------------------------------
# 2. Установка пакетов
# -------------------------------------------------------------------
step "Installing required packages..."
need_install=()
for pkg in $MINGW_PKGS; do
if ! dpkg -s "$pkg" &>/dev/null 2>&1; then
need_install+=("$pkg")
fi
done
if ! command -v wget &>/dev/null && ! command -v curl &>/dev/null; then
need_install+=("wget")
fi
if [ ${#need_install[@]} -gt 0 ]; then
echo " Installing: ${need_install[*]}"
sudo apt-get update -qq || fatal "apt-get update failed"
sudo apt-get install -y -qq "${need_install[@]}" || fatal "apt-get install failed"
ok "Packages installed: ${need_install[*]}"
else
ok "All packages already installed"
fi
# Проверка компилятора
for tool in "$MINGW_CC" "$MINGW_AR" "$MINGW_RANLIB"; do
if ! command -v "$tool" &>/dev/null; then
fatal "$tool not found after package installation"
fi
done
ok "Cross-compiler: $($MINGW_CC --version | head -1)"
# -------------------------------------------------------------------
# 3. Кросс-компиляция OpenSSL
# -------------------------------------------------------------------
OPENSSL_SRC="$BUILD_DIR/openssl-${OPENSSL_VER}"
LIBCRYPTO="$PREFIX/lib/libcrypto.a"
if [ -f "$LIBCRYPTO" ]; then
ok "OpenSSL already built: $LIBCRYPTO"
else
step "Building OpenSSL ${OPENSSL_VER} for Windows..."
mkdir -p "$BUILD_DIR"
TARBALL="$BUILD_DIR/openssl-${OPENSSL_VER}.tar.gz"
if [ ! -f "$TARBALL" ]; then
echo " Downloading OpenSSL ${OPENSSL_VER}..."
if command -v wget &>/dev/null; then
wget -q --show-progress -O "$TARBALL" "$OPENSSL_URL" || fatal "wget download failed for $OPENSSL_URL"
else
curl -L --progress-bar -o "$TARBALL" "$OPENSSL_URL" || fatal "curl download failed for $OPENSSL_URL"
fi
ok "Downloaded: $TARBALL"
fi
if [ ! -d "$OPENSSL_SRC" ]; then
echo " Extracting..."
tar xzf "$TARBALL" -C "$BUILD_DIR" || fatal "tar extract failed"
fi
echo " Configuring OpenSSL for mingw64..."
cd "$OPENSSL_SRC"
./Configure --cross-compile-prefix=x86_64-w64-mingw32- \
mingw64 no-shared no-tests \
--prefix="$PREFIX" --libdir="$PREFIX/lib" --openssldir="$PREFIX/ssl" \
|| fatal "OpenSSL configure failed (see output above)"
ok "OpenSSL configured"
echo " Compiling OpenSSL..."
make -j"$(nproc)" || fatal "OpenSSL make failed"
echo " Installing OpenSSL..."
make install_sw >/dev/null || fatal "OpenSSL make install_sw failed"
cd "$SCRIPT_DIR"
# OpenSSL может установить в lib64/ вместо lib/ — поправим
if [ ! -f "$LIBCRYPTO" ] && [ -f "$PREFIX/lib64/libcrypto.a" ]; then
ln -sf lib64 "$PREFIX/lib" 2>/dev/null || cp -r "$PREFIX/lib64" "$PREFIX/lib" 2>/dev/null || true
fi
if [ ! -f "$LIBCRYPTO" ]; then
fatal "libcrypto.a was not found at $PREFIX/lib/ or $PREFIX/lib64/"
fi
ok "OpenSSL built: $LIBCRYPTO"
fi
# -------------------------------------------------------------------
# 4. Сборка libuasync.a
# -------------------------------------------------------------------
step "Building libuasync.a..."
LIB_OBJDIR="$BUILD_DIR/lib-objs"
mkdir -p "$LIB_OBJDIR"
# Все .c файлы из lib/Makefile.am + myip.c (реализация get_my_ip)
LIB_SOURCES=(
u_async.c ll_queue.c memory_pool.c debug_config.c timeout_heap.c
sha256.c mem.c serialize.c swm_min.c getmyip.c radix.c tcp_io.c
myip.c platform_compat.c socket_compat.c
liblmdb/mdb.c liblmdb/midl.c
)
LIB_CFLAGS="-D_ISOC99_SOURCE -DDEBUG_OUTPUT_STDERR -DUSE_OPENSSL"
LIB_CFLAGS+=" -I$SCRIPT_DIR/lib -I$SCRIPT_DIR/src -I$SCRIPT_DIR/lib/liblmdb"
LIB_CFLAGS+=" -I$PREFIX/include"
LIB_CFLAGS+=" -g -O2 -Wno-deprecated-declarations"
for src in "${LIB_SOURCES[@]}"; do
obj="$LIB_OBJDIR/$(basename "${src%.c}.o")"
echo " CC lib/$src"
"$MINGW_CC" $LIB_CFLAGS -c "$SCRIPT_DIR/lib/$src" -o "$obj" \
|| fatal "compilation failed: lib/$src"
done
"$MINGW_AR" crs "$BUILD_DIR/libuasync.a" "$LIB_OBJDIR"/*.o \
|| fatal "ar failed for libuasync.a"
"$MINGW_RANLIB" "$BUILD_DIR/libuasync.a" 2>/dev/null || true
ok "libuasync.a ($(wc -c < "$BUILD_DIR/libuasync.a") bytes)"
# -------------------------------------------------------------------
# 5. Сборка объектных файлов для utun.exe
# -------------------------------------------------------------------
step "Compiling utun source files..."
SRC_OBJDIR="$BUILD_DIR/src-objs"
mkdir -p "$SRC_OBJDIR"
# Все .c файлы из src/Makefile.am (utun_CORE_SOURCES)
SRC_SOURCES=(
utun.c utun_instance.c config_parser.c config_updater.c
route_lib.c route6_lib.c route_bgp.c route_ping.c
route_node.c route_node_lmdb.c route_connectivity.c conn_mgr.c
routing.c tun_if.c tun_route.c tun_linux.c tun_freebsd.c tun_windows.c
etcp.c etcp_connections.c etcp_bbr.c etcp_loadbalancer.c
etcp_debug.c etcp_dump.c secure_channel.c crc32.c
stcp_link.c stcp.c stcp_server.c stcp_client.c
pkt_normalizer.c packet_dump.c etcp_api.c
control_server.c msg_transport.c
firewall.c eim_nat.c nat_transport.c dummynet.c
etcp_router.c
proxy/udp_proxy.c proxy/socks_proxy.c
lwip_tcp/lwip_pbuf.c
)
SRC_CFLAGS="-DUSE_OPENSSL"
SRC_CFLAGS+=" -I$SCRIPT_DIR/src -I$SCRIPT_DIR/lib -I$SCRIPT_DIR/lib/liblmdb"
SRC_CFLAGS+=" -I$SCRIPT_DIR/tinycrypt/lib/include -I$SCRIPT_DIR/tinycrypt/lib/source"
SRC_CFLAGS+=" -I$PREFIX/include"
SRC_CFLAGS+=" -g -O2 -Wno-deprecated-declarations"
TOTAL=${#SRC_SOURCES[@]}
CURRENT=0
FAILED=()
for src in "${SRC_SOURCES[@]}"; do
CURRENT=$((CURRENT + 1))
# Имя объектного файла: заменяем / на _ (для proxy/ и lwip_tcp/)
obj_name="$(echo "$src" | tr '/' '_')"
obj="$SRC_OBJDIR/${obj_name%.c}.o"
printf " [%3d/%3d] CC src/%s\n" "$CURRENT" "$TOTAL" "$src"
if ! "$MINGW_CC" $SRC_CFLAGS -c "$SCRIPT_DIR/src/$src" -o "$obj" 2>&1; then
FAILED+=("src/$src")
fi
done
if [ ${#FAILED[@]} -gt 0 ]; then
echo ""
fatal "compilation failed for: ${FAILED[*]}"
fi
ok "All $TOTAL source files compiled"
# -------------------------------------------------------------------
# 6. Линковка utun.exe
# -------------------------------------------------------------------
step "Linking utun.exe..."
"$MINGW_CC" -o "$SCRIPT_DIR/utun.exe" \
"$SRC_OBJDIR"/*.o \
"$BUILD_DIR/libuasync.a" \
"$LIBCRYPTO" \
-lws2_32 -liphlpapi -ladvapi32 -lbcrypt -lcrypt32 -lpthread -lm \
-static \
|| fatal "linking failed (see undefined references above)"
ok "utun.exe ($(wc -c < "$SCRIPT_DIR/utun.exe") bytes)"
# -------------------------------------------------------------------
# 7. Копирование wintun.dll и финал
# -------------------------------------------------------------------
echo ""
if [ -f "$SCRIPT_DIR/lib/wintun.dll" ]; then
cp "$SCRIPT_DIR/lib/wintun.dll" "$SCRIPT_DIR/"
ok "wintun.dll copied to project root"
else
warn "wintun.dll not found in lib/ — place it manually alongside utun.exe"
fi
echo ""
echo -e "${GREEN}==============================================${NC}"
echo -e "${GREEN} Build successful!${NC}"
echo -e "${GREEN} Binary: utun.exe${NC}"
echo -e "${GREEN}==============================================${NC}"
echo ""
if [ -f "$SCRIPT_DIR/lib/wintun.dll" ]; then
echo "Run on Windows:"
echo " 1. Install Wintun driver (https://www.wintun.net)"
echo " 2. Copy utun.exe + wintun.dll to the same directory"
echo " 3. Run: utun.exe -f -p utun.pid -l utun.log"
else
echo "Run on Windows:"
echo " 1. Install Wintun driver (https://www.wintun.net)"
echo " 2. Copy utun.exe + wintun.dll to the same directory"
echo " 3. Run: utun.exe -f -p utun.pid -l utun.log"
fi
echo ""
exit 0
+31 -7
View File
@@ -22,12 +22,9 @@ utun поддерживает встроенный ipv4 роутинг, може
Есть статические подключения (которые прописаны в конфиге) и есть динамические - которые устанавливаются в процессе по необходимости.
Обмен маршрутами происходит только через статические подключения.
toto:
- задать ограничение bandwidth для каждого сокета (общее).
## etcp_connections:
- обслуживает encrypt/decrypt и установку защищенного подключения.
- обслуживает encrypt/decrypt и установку защищенного подключения per-link.
- для одного etcp соединения может испольоваться несколько подключений одновременно (load balancing / filover)
## etcp_loadbalancer:
@@ -50,9 +47,36 @@ toto:
- обеспечивает ретрансмиссии при передаче и сборку в правильной последовательности при приёме
- взаимодействует с pkt_normalizer и с udp сокетами для отправки-получения пакетов
## etcp_metric:
- обеспечивает обновление метрик каналов etcp_connections
- вызывается при получении ack
## stcp_* :
- аналогично etcp только упрощенный вариант, используя tcp в качестве транспорта.
## etcp_router:
- устанавливает связь между узлами, маршрутизирует трафик через цепочку узлов
## route_lib, route6_lib :
- управляет таблицами маршрутизации (ipv4/ipv6 lookup)
## route_node:
- хранит список доступных узлов (полученные от соседей) и их адреса/pubkeys для подключения.
- содержит апи для поиска-чтения-обновления узла
## route_bgp:
- составляет карту маршрутизации между узлами и обновляет ее при подключении-отключении узлов
## route_ping:
- модуль для определения типа nat (strict или eim) используя вспомогательный узел
Библиотеки:
## uasync
- модуль для асинхронной работы.
реализует сокеты, таймауты и сигналы из других потоков
- быстро работает с большим числом таймаутов (timeout_heap)
## ll_queue
- модуль работы с очередями, использует uasync.
- реализует fifo/lifo очереди, быстрый поиск по индексу, различные callback - сигнализация о состояниях очереди,
- congestion control: если много желающих записать -> очередь round-robin
Тесты:
# Правила работы с очередями в тестах:
+1 -1
View File
@@ -136,7 +136,7 @@ link_state: 0=just init, 1=handshake, 2=reconnect, 3=connected.
- local_link_id: uint8_t (0-255), назначается отправителем
- sock_id: uint8_t, уникальный ID сокета отправителя
- only_local: uint8_t, флаг "только локальные подключения"
- type: uint8_t, тип сокета (CFG_SERVER_TYPE_PUBLIC/NAT/PRIVATE)
- type: uint8_t, тип сокета (CFG_SERVER_TYPE_UNKNOWN=0/PUBLIC=1/NAT=2/PRIVATE=3/LOCAL=4)
- random_padding: случайные байты до размера handshake (защита от fingerprinting)
- salt + obfuscated_pubkey: публичный ключ с обфускацией (не шифруется AES,
но скрывается XOR с salt и собственным публичным ключом). Передаётся в конце пакета
+1
View File
@@ -128,6 +128,7 @@ static const struct {
{"keepalive", DEBUG_CATEGORY_KEEPALIVE},
{"etcp_route", DEBUG_CATEGORY_ETCPROUTE},
{"bbr", DEBUG_CATEGORY_BBR},
{"etcp_dump", DEBUG_CATEGORY_ETCP_DUMP},
{"all", DEBUG_CATEGORY_ALL},
{NULL, DEBUG_CATEGORY_NONE}
};
+3 -1
View File
@@ -61,7 +61,8 @@ typedef int debug_category_t;
#define DEBUG_CATEGORY_KEEPALIVE 21 // Link keepalive logic
#define DEBUG_CATEGORY_ETCPROUTE 22 // ETCP routing
#define DEBUG_CATEGORY_BBR 23 // BBR congestion control
#define DEBUG_CATEGORY_COUNT 24 // Total number of categories
#define DEBUG_CATEGORY_ETCP_DUMP 24 // ETCP packet dump
#define DEBUG_CATEGORY_COUNT 25 // Total number of categories
#define DEBUG_CATEGORY_ALL (-1) // special value for all categories
/* Debug configuration structure */
@@ -87,6 +88,7 @@ extern debug_config_t g_debug_config;
void debug_config_init(void);
/* Set debug level */
// итоговый level = max (global level - здесь задается, category level)
void debug_set_level(debug_level_t level);
/* Set debug level for specific category (0 = disabled, otherwise uses that level) */
+12
View File
@@ -30,6 +30,18 @@
// POSIX functions missing on Windows
#define strcasecmp _stricmp
#define strncasecmp _strnicmp
// memmem — not available on Windows
#define memmem compat_memmem
static inline void* compat_memmem(const void* h, size_t hl, const void* n, size_t nl) {
if (nl == 0) return (void*)h;
if (hl < nl) return NULL;
const unsigned char* hay = (const unsigned char*)h;
const unsigned char* ndl = (const unsigned char*)n;
for (size_t i = 0; i <= hl - nl; i++)
if (hay[i] == ndl[0] && memcmp(hay + i, ndl, nl) == 0) return (void*)(hay + i);
return NULL;
}
// poll() and pollfd are already defined in winsock2.h on MSYS2 UCRT64
// Only define if not already present
+4 -1
View File
@@ -13,6 +13,9 @@
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif
#ifdef _WIN32
#define SHUT_WR SD_SEND
#endif
static void read_cb(socket_t sock, void* arg);
static void write_cb(socket_t sock, void* arg);
@@ -382,7 +385,7 @@ static void write_cb(socket_t sock, void* arg) {
if (!tc->connected) {
int err = 0;
socklen_t len = sizeof(err);
if (getsockopt(tc->sock, SOL_SOCKET, SO_ERROR, &err, &len) == 0 && err == 0) {
if (getsockopt(tc->sock, SOL_SOCKET, SO_ERROR, (char*)&err, &len) == 0 && err == 0) {
tc->connected = 1;
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: connect ok fd=%d", (int)tc->sock);
} else {
+2
View File
@@ -3,6 +3,8 @@
Идея: объединить узлы в единую локальную сеть.
Чтобы узлы сами находили оптимальные линки между собой, пробивали NAT где это можно, где нельзя - выбирали посредника с хорошей связью; чтобы подсто было добавлять новые узлы и адреса узлов сразу виделись во всей сети.
Похож на libp2p но трафик превращает в случайную последовательность кторую сложно классифицировать
Потенциальные варианты использования:
- локальная сеть. хочу объединить офис, сотрудников (включая их работчие подсети), телефоны, офисы, сервер доступа итд в одно адресное пространство.
- чат с файлообменником. узлы сети - это мемберы группы и одновременно сети.
+1
View File
@@ -35,6 +35,7 @@ utun_CORE_SOURCES = \
pkt_normalizer.c \
packet_dump.c \
etcp_api.c \
etcp_connect.c \
control_server.c \
msg_transport.c \
firewall.c \
+2
View File
@@ -650,6 +650,8 @@ static int parse_server(const char *key, const char *value, struct CFG_SERVER *s
srv->type = CFG_SERVER_TYPE_NAT;
} else if (strcmp(value, "private") == 0) {
srv->type = CFG_SERVER_TYPE_PRIVATE;
} else if (strcmp(value, "local") == 0) {
srv->type = CFG_SERVER_TYPE_LOCAL;
} else {
srv->type = CFG_SERVER_TYPE_UNKNOWN;
}
+3 -2
View File
@@ -24,9 +24,10 @@ struct IP {
};
#define CFG_SERVER_TYPE_UNKNOWN 0
#define CFG_SERVER_TYPE_PUBLIC 1
#define CFG_SERVER_TYPE_NAT 2
#define CFG_SERVER_TYPE_PUBLIC 1
#define CFG_SERVER_TYPE_NAT 2
#define CFG_SERVER_TYPE_PRIVATE 3
#define CFG_SERVER_TYPE_LOCAL 4
#define CFG_IPV6_MODE_NONE 0 // normal explicit address
#define CFG_IPV6_MODE_TEMPORARY 1 // temporary IPv6 from interface
+66 -25
View File
@@ -31,11 +31,12 @@ static void cm_start_local_scan(struct CONN_MGR_ENTRY* entry);
static void cm_idle_timer_cb(void* arg);
static void cm_deliver_result(struct CONN_MGR_ENTRY* entry, int result);
static void cm_bg_ping_timer_cb(void* arg);
static int cm_nat_compatible(struct ETCP_SOCKET* our, uint8_t target_meta_type);
static int cm_nat_compatible(struct ETCP_SOCKET* our, uint8_t target_config_type, uint8_t target_nat_type);
static uint16_t cm_get_node_min_rtt(struct NODEINFO_Q* nq);
static int cm_has_direct_ip(struct NODEINFO_Q* nq);
static int cm_has_local_addr(struct NODEINFO_Q* nq);
static void cm_entry_destroy(struct CONN_MGR_ENTRY* entry);
static void cm_exchange_probe_retry_cb(void* arg);
static uint64_t cm_get_node_max_probe_time(struct NODEINFO_Q* nq);
static int cm_is_rtt_fresh(struct NODEINFO_Q* nq, uint64_t now_tb);
@@ -77,7 +78,7 @@ struct CONN_MGR* conn_mgr_init(struct UTUN_INSTANCE* instance) {
mgr->entry_capacity = 8;
mgr->entries = u_calloc(mgr->entry_capacity, sizeof(struct CONN_MGR_ENTRY));
if (!mgr->entries) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_init: entries alloc failed"); u_free(mgr); return NULL; }
etcp_router_bind(instance, ETCP_ID_CONN_MGR, conn_mgr_router_recv_handler);
etcp_router_bind(instance, ETCP_RT_ID_CONN_MGR, conn_mgr_router_recv_handler);
mgr->bg_ping_timer = uasync_set_timeout(instance->ua, CONN_MGR_BG_PING_INTERVAL_TB, mgr, cm_bg_ping_timer_cb, "conn_mgr_bg_ping");
mgr->candidate_ping_timer = uasync_set_timeout(instance->ua, CONN_MGR_CANDIDATE_PING_TB, mgr, cm_candidate_ping_timer_cb, "conn_mgr_cand_ping");
mgr->bg_ping_cycle_start_tb = get_time_tb();
@@ -90,8 +91,16 @@ void conn_mgr_destroy(struct CONN_MGR* mgr) {
if (!mgr) return;
if (mgr->bg_ping_timer) { uasync_cancel_timeout(mgr->instance->ua, mgr->bg_ping_timer); mgr->bg_ping_timer = NULL; }
if (mgr->candidate_ping_timer) { uasync_cancel_timeout(mgr->instance->ua, mgr->candidate_ping_timer); mgr->candidate_ping_timer = NULL; }
etcp_router_unbind(mgr->instance, ETCP_ID_CONN_MGR);
etcp_router_unbind(mgr->instance, ETCP_RT_ID_CONN_MGR);
for (size_t i = 0; i < mgr->entry_count; i++) cm_entry_destroy(&mgr->entries[i]);
struct cm_exchange_pending* ep = mgr->exchange_pending;
while (ep) {
struct cm_exchange_pending* next = ep->next;
if (ep->timeout_timer) uasync_cancel_timeout(mgr->instance->ua, ep->timeout_timer);
u_free(ep);
ep = next;
}
mgr->exchange_pending = NULL;
u_free(mgr->entries);
mgr->initialized = 0;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: destroyed, entries=%zu", (size_t)mgr->entry_count);
@@ -102,6 +111,16 @@ static void cm_entry_destroy(struct CONN_MGR_ENTRY* entry) {
if (!entry || entry->state == CONN_MGR_STATE_DISCONNECTED) return;
if (entry->idle_timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->idle_timer); entry->idle_timer = NULL; }
if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; }
struct cm_exchange_pending** pp = &entry->mgr->exchange_pending;
while (*pp) {
if ((*pp)->entry == entry) {
if ((*pp)->timeout_timer && (*pp)->timeout_timer != entry->main.timer) {
uasync_cancel_timeout(entry->mgr->instance->ua, (*pp)->timeout_timer);
(*pp)->timeout_timer = NULL;
}
struct cm_exchange_pending* ep = *pp; *pp = ep->next; u_free(ep);
} else pp = &(*pp)->next;
}
entry->state = CONN_MGR_STATE_DISCONNECTED;
entry->conn_type = CONN_TYPE_NONE;
}
@@ -217,7 +236,7 @@ int conn_mgr_disconnect_node(struct CONN_MGR* mgr, uint64_t node_id) {
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id);
if (!entry) return CONN_MGR_ERR_NOT_FOUND;
struct CONN_MGR_DISCONNECT pkt;
pkt.cmd = ETCP_ID_CONN_MGR;
pkt.cmd = ETCP_RT_ID_CONN_MGR;
pkt.subcmd = CONN_MGR_SUBCMD_DISCONNECT;
pkt.node_id = node_id;
struct ll_entry* qe = queue_entry_new(sizeof(pkt));
@@ -277,7 +296,7 @@ int conn_mgr_send(struct CONN_MGR* mgr, uint64_t node_id, struct ll_entry* entry
struct CONN_MGR_ENTRY* e = cm_find_entry(mgr, node_id);
if (!e || e->state != CONN_MGR_STATE_CONNECTED) { queue_entry_free(entry); return -1; }
e->last_traffic_tb = get_time_tb();
struct ETCP_ROUTER_CONN* rconn = etcp_router_conn_get(mgr->instance, node_id, ETCP_ID_CONN_MGR);
struct ETCP_ROUTER_CONN* rconn = etcp_router_conn_get(mgr->instance, node_id, ETCP_RT_ID_CONN_MGR);
if (!rconn) { queue_entry_free(entry); return -1; }
int ret = etcp_router_conn_send(rconn, entry->dgram, entry->len);
queue_entry_free(entry);
@@ -333,15 +352,13 @@ static void cm_idle_timer_cb(void* arg) {
entry->idle_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_IDLE_CHECK_INTERVAL_TB, entry, cm_idle_timer_cb, "conn_mgr_idle");
}
static int cm_nat_compatible(struct ETCP_SOCKET* our, uint8_t target_meta_type) {
uint8_t tt = target_meta_type;
if (tt == NAT_VERIFIED_DIRECT) tt = CFG_SERVER_TYPE_PUBLIC;
else if (tt >= NAT_VERIFIED_UNKNOWN) tt = CFG_SERVER_TYPE_PUBLIC;
static int cm_nat_compatible(struct ETCP_SOCKET* our, uint8_t target_config_type, uint8_t target_nat_type) {
uint8_t tt = target_nat_type >= NAT_VERIFIED_UNKNOWN ? CFG_SERVER_TYPE_PUBLIC : target_config_type;
uint8_t ot = our->type;
if (ot == CFG_SERVER_TYPE_PUBLIC) return 1;
if (ot == CFG_SERVER_TYPE_NAT && our->nat_type == NAT_TYPE_EIM && tt == CFG_SERVER_TYPE_PUBLIC) return 1;
if (ot == CFG_SERVER_TYPE_NAT && our->nat_type == NAT_TYPE_EIM && tt == CFG_SERVER_TYPE_NAT) return 1;
if (ot == CFG_SERVER_TYPE_PRIVATE && tt == CFG_SERVER_TYPE_PRIVATE) return 2;
if (ot == CFG_SERVER_TYPE_PUBLIC || ot == CFG_SERVER_TYPE_UNKNOWN) return 1;
if (ot == CFG_SERVER_TYPE_NAT && our->nat_type == NAT_VERIFIED_EIM && tt == CFG_SERVER_TYPE_PUBLIC) return 1;
if (ot == CFG_SERVER_TYPE_NAT && our->nat_type == NAT_VERIFIED_EIM && tt == CFG_SERVER_TYPE_NAT) return 1;
if ((ot == CFG_SERVER_TYPE_PRIVATE || ot == CFG_SERVER_TYPE_LOCAL) && tt == CFG_SERVER_TYPE_PRIVATE) return 2;
return 0;
}
@@ -378,7 +395,8 @@ static int cm_has_direct_ip(struct NODEINFO_Q* nq) {
if (addrs[i].type == ADDR_TYPE_REAL || addrs[i].type == ADDR_TYPE_NAT) {
for (int j = 0; j < meta_count; j++) {
if (metas[j].id == addrs[i].socket_id &&
(metas[j].type == CFG_SERVER_TYPE_PUBLIC || metas[j].type == NAT_VERIFIED_EIM || metas[j].type == NAT_VERIFIED_DIRECT))
(metas[j].config_type == CFG_SERVER_TYPE_PUBLIC || metas[j].config_type == CFG_SERVER_TYPE_UNKNOWN
|| metas[j].nat_type == NAT_VERIFIED_EIM || metas[j].nat_type == NAT_VERIFIED_DIRECT))
return 1;
}
}
@@ -395,7 +413,8 @@ static int cm_has_local_addr(struct NODEINFO_Q* nq) {
for (int i = 0; i < addr_count; i++) {
if (addrs[i].type == ADDR_TYPE_INTERFACE) {
for (int j = 0; j < meta_count; j++) {
if (metas[j].id == addrs[i].socket_id && metas[j].type == CFG_SERVER_TYPE_PRIVATE)
if (metas[j].id == addrs[i].socket_id &&
(metas[j].config_type == CFG_SERVER_TYPE_PRIVATE || metas[j].config_type == CFG_SERVER_TYPE_LOCAL))
return 1;
}
}
@@ -426,10 +445,11 @@ static void cm_start_local_scan(struct CONN_MGR_ENTRY* entry) {
for (int i = 0; i < addr_count; i++) {
if (addrs[i].type != ADDR_TYPE_INTERFACE) continue;
for (int j = 0; j < meta_count; j++) {
if (metas[j].id != addrs[i].socket_id || metas[j].type != CFG_SERVER_TYPE_PRIVATE) continue;
if (metas[j].id != addrs[i].socket_id ||
(metas[j].config_type != CFG_SERVER_TYPE_PRIVATE && metas[j].config_type != CFG_SERVER_TYPE_LOCAL)) continue;
struct ETCP_SOCKET* s = entry->mgr->instance->etcp_sockets;
while (s) {
if (s->type == CFG_SERVER_TYPE_PRIVATE && s->local_addr.ss_family == AF_INET) {
if ((s->type == CFG_SERVER_TYPE_PRIVATE || s->type == CFG_SERVER_TYPE_LOCAL) && s->local_addr.ss_family == AF_INET) {
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
@@ -475,7 +495,7 @@ static void cm_start_phase_direct(struct CONN_MGR_ENTRY* entry) {
if (metas[j].id != addrs[i].socket_id) continue;
struct ETCP_SOCKET* s = entry->mgr->instance->etcp_sockets;
while (s) {
int compat = cm_nat_compatible(s, metas[j].type);
int compat = cm_nat_compatible(s, metas[j].config_type, metas[j].nat_type);
if (compat && s->local_addr.ss_family == AF_INET) {
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
@@ -591,7 +611,8 @@ static void cm_start_phase_reverse(struct CONN_MGR_ENTRY* entry) {
if (addrs[i].type != ADDR_TYPE_REAL && addrs[i].type != ADDR_TYPE_NAT) continue;
for (int j = 0; j < meta_count; j++) {
if (metas[j].id == addrs[i].socket_id &&
(metas[j].type == CFG_SERVER_TYPE_PUBLIC || metas[j].type == NAT_VERIFIED_EIM || metas[j].type == NAT_VERIFIED_DIRECT)) {
(metas[j].config_type == CFG_SERVER_TYPE_PUBLIC || metas[j].config_type == CFG_SERVER_TYPE_UNKNOWN
|| metas[j].nat_type == NAT_VERIFIED_EIM || metas[j].nat_type == NAT_VERIFIED_DIRECT)) {
out_addrs[direct_count].type = addrs[i].type;
memcpy(out_addrs[direct_count].ip, addrs[i].addr, 4);
out_addrs[direct_count].port = htons(addrs[i].port);
@@ -606,7 +627,7 @@ static void cm_start_phase_reverse(struct CONN_MGR_ENTRY* entry) {
uint8_t* pkt = u_malloc(pkt_size);
if (!pkt) { cm_deliver_result(entry, CONN_MGR_ERR_INTERNAL); return; }
struct CONN_MGR_DIRECT_REQ* req = (struct CONN_MGR_DIRECT_REQ*)pkt;
req->cmd = ETCP_ID_CONN_MGR;
req->cmd = ETCP_RT_ID_CONN_MGR;
req->subcmd = CONN_MGR_SUBCMD_DIRECT_REQ;
req->request_id = req_id;
req->addr_count = direct_count;
@@ -669,7 +690,7 @@ static int cm_start_phase_indirect(struct CONN_MGR_ENTRY* entry) {
entry->main.phase = 3;
struct CONN_MGR_INTERM_EXCHANGE_REQ req;
memset(&req, 0, sizeof(req));
req.cmd = ETCP_ID_CONN_MGR;
req.cmd = ETCP_RT_ID_CONN_MGR;
req.subcmd = CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ;
req.request_id = req_id;
req.candidate_count = mgr->best_candidate_count > 4 ? 4 : mgr->best_candidate_count;
@@ -735,7 +756,7 @@ static void cm_compute_intermediaries(struct CONN_MGR_ENTRY* entry, struct CONN_
struct CONN_MGR_INTERM_SELECTED pkt;
memset(&pkt, 0, sizeof(pkt));
pkt.cmd = ETCP_ID_CONN_MGR;
pkt.cmd = ETCP_RT_ID_CONN_MGR;
pkt.subcmd = CONN_MGR_SUBCMD_INTERM_SELECTED;
pkt.request_id = 0;
pkt.count = sel;
@@ -756,6 +777,26 @@ static void cm_compute_intermediaries(struct CONN_MGR_ENTRY* entry, struct CONN_
cm_deliver_result(entry, CONN_MGR_OK);
}
static void cm_exchange_probe_retry_cb(void* arg) {
struct cm_exchange_pending* ep = (struct cm_exchange_pending*)arg;
struct CONN_MGR* mgr = ep->entry->mgr;
ep->timeout_timer = NULL;
uint8_t need_probe = 0;
uint64_t now_tb = get_time_tb();
for (uint8_t i = 0; i < ep->cached_resp.my_count && i < 4; i++) {
struct NODEINFO_Q* nq = nodeinfo_find_by_id(mgr->instance->bgp, ep->cached_resp.my_candidates[i].node_id);
if (!nq || !cm_is_rtt_fresh(nq, now_tb)) { need_probe++; break; }
}
if (need_probe == 0) {
cm_compute_intermediaries(ep->entry, &ep->cached_resp);
struct cm_exchange_pending** pp = &mgr->exchange_pending;
while (*pp) { if (*pp == ep) { *pp = ep->next; break; } pp = &(*pp)->next; }
u_free(ep);
} else {
ep->timeout_timer = uasync_set_timeout(mgr->instance->ua, 2000, ep, cm_exchange_probe_retry_cb, "cm_exch_probe");
}
}
static void cm_handle_interm_exchange_resp(struct CONN_MGR* mgr, const uint8_t* data, size_t len) {
const struct CONN_MGR_INTERM_EXCHANGE_RESP* resp = (const struct CONN_MGR_INTERM_EXCHANGE_RESP*)data;
size_t min_size = offsetof(struct CONN_MGR_INTERM_EXCHANGE_RESP, your_candidates);
@@ -780,7 +821,7 @@ static void cm_handle_interm_exchange_resp(struct CONN_MGR* mgr, const uint8_t*
struct cm_exchange_pending** pp = &mgr->exchange_pending;
while (*pp) { if (*pp == ep) { *pp = ep->next; break; } pp = &(*pp)->next; }
u_free(ep); return; }
uasync_set_timeout(mgr->instance->ua, 2000, ep, NULL, "cm_exch_probe");
ep->timeout_timer = uasync_set_timeout(mgr->instance->ua, 2000, ep, cm_exchange_probe_retry_cb, "cm_exch_probe");
}
static void cm_handle_interm_selected(struct CONN_MGR* mgr, const uint8_t* data, size_t len) {
@@ -854,7 +895,7 @@ static void cm_handle_direct_req(struct ETCP_CONN* conn, const uint8_t* data, si
(void)sock_id; (void)type;
struct ETCP_SOCKET* s = conn->instance->etcp_sockets;
while (s) {
if (s->type == CFG_SERVER_TYPE_PUBLIC && s->local_addr.ss_family == AF_INET) {
if ((s->type == CFG_SERVER_TYPE_PUBLIC || s->type == CFG_SERVER_TYPE_UNKNOWN) && s->local_addr.ss_family == AF_INET) {
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET;
memcpy(&sin.sin_addr.s_addr, ip, 4); sin.sin_port = port;
@@ -889,7 +930,7 @@ static void cm_handle_interm_exchange_req(struct ETCP_CONN* conn,
}
struct CONN_MGR_INTERM_EXCHANGE_RESP resp;
memset(&resp, 0, sizeof(resp));
resp.cmd = ETCP_ID_CONN_MGR;
resp.cmd = ETCP_RT_ID_CONN_MGR;
resp.subcmd = CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP;
resp.request_id = req->request_id;
resp.my_count = mgr->best_candidate_count > 4 ? 4 : mgr->best_candidate_count;
+5 -1
View File
@@ -985,6 +985,7 @@ static void send_socket_list(struct control_server* server, struct control_clien
info[i].ip[sizeof(info[i].ip) - 1] = '\0';
info[i].port = 0;
}
info[i].config_type = sock->type;
info[i].nat_type = sock->nat_type;
/* NAT address from socket (set when INIT_RESPONSE received) */
@@ -999,7 +1000,10 @@ static void send_socket_list(struct control_server* server, struct control_clien
inet_ntop(AF_INET6, &sin6->sin6_addr, info[i].nat_ip, sizeof(info[i].nat_ip));
info[i].nat_port = ntohs(sin6->sin6_port);
}
DEBUG_INFO(DEBUG_CATEGORY_NAT, "socket_list to gui: sock=%s config=%d nat_type=%d nat_ip=%s nat_port=%u",
sock->name, sock->type, sock->nat_type,
info[i].nat_ip[0] ? info[i].nat_ip : "none", info[i].nat_port);
sock = sock->next;
}
+10 -2
View File
@@ -263,12 +263,12 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
static void etcp_on_up(struct ETCP_CONN* etcp) {
// if (!etcp->initialized) return;
DEBUG_WARN(DEBUG_CATEGORY_BGP, "[%s] initialized=%d", etcp->log_name, etcp->initialized);
DEBUG_WARN(DEBUG_CATEGORY_BGP, "[%s] UP links_up=%d initialized=%d", etcp->log_name, etcp->links_up, etcp->initialized);
if (etcp->up_cbk) etcp->up_cbk(etcp, etcp->up_arg);
}
static void etcp_on_down(struct ETCP_CONN* etcp) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "[%s]", etcp->log_name);
DEBUG_WARN(DEBUG_CATEGORY_BGP, "[%s] DOWN links_up=%d", etcp->log_name, etcp->links_up);
if (etcp->down_cbk) etcp->down_cbk(etcp, etcp->down_arg);
}
@@ -338,6 +338,14 @@ void etcp_connection_close(struct ETCP_CONN* etcp) {
u_free(etcp->name);
{ struct UTUN_INSTANCE* inst = etcp->instance;
if (inst && inst->connections) {
struct ETCP_CONN** pp = &inst->connections;
while (*pp && *pp != etcp) pp = &(*pp)->next;
if (*pp) { *pp = etcp->next; if (inst->connections_count) inst->connections_count--; }
}
}
// Clear next pointer to prevent dangling references
etcp->next = NULL;
+2 -1
View File
@@ -175,7 +175,7 @@ struct ETCP_CONN {
// uint32_t total_packets_sent; // Total packets sent counter - Not used
// Flags
uint8_t routing_exchange_active; // 0 - не активен, 1 - надо инициировать обмен маршрутами (клиент), 2 - обмен маршрутами активен
uint8_t routing_exchange_active; // 0-не активен, 1-надо инициировать (клиент), 2-обмен идёт, 3-завершён, 4-пропущен (нет BGP)
uint8_t got_initial_pkt; //
uint8_t initialized; // 0 - только созданный ETCP, 1 - хотя бы один линк проинициалзирован (обмен ключами произведен)
uint32_t session_id; // случайный ID сессии (генерируется клиентом) для защиты от ложного reinit
@@ -189,6 +189,7 @@ struct ETCP_CONN {
void* up_arg; // аргумент для ready_cbk
etcp_on_conn_ready down_cbk; // callback при готовности соединения
void* down_arg; // аргумент для ready_cbk
void (*bgp_ready_cbk)(struct ETCP_CONN* conn); // вызывается когда BGP готов (завершён или пропущен)
uint32_t cnt_ack_hit_inf; // счетчик удлений из inflight
uint32_t cnt_ack_hit_sndq; // счетчик удалений inflight пакетов из sndq
+7
View File
@@ -25,6 +25,13 @@ void etcp_set_new_conn_cbk(struct UTUN_INSTANCE* inst, etcp_cbk_fn fn, void* arg
if (inst) { inst->etcp_new_conn_cbk = fn; inst->etcp_new_conn_arg = arg; }
}
void etcp_set_routing_exchange_state(struct ETCP_CONN* conn, uint8_t new_state) {
if (!conn) return;
conn->routing_exchange_active = new_state;
if (new_state >= 3 && conn->bgp_ready_cbk)
conn->bgp_ready_cbk(conn);
}
int etcp_bind(struct UTUN_INSTANCE* inst, uint8_t id, etcp_recv_fn callback) {
if (!inst || !callback || (unsigned)id >= ETCP_MAX_BINDINGS) return -1;
inst->api_bindings.callbacks[id] = callback;
+26 -10
View File
@@ -22,17 +22,20 @@
#define ETCP_MAX_BINDINGS 256
// ETCP packet IDs
// === Raw ETCP packet IDs (etcp_bind/etcp_send) ===
#define ETCP_ID_DATA 0x00 // Пакет для передачи адресату
#define ETCP_ID_ROUTE_ENTRY 0x01 // Элемент роутинг-таблицы
#define ETCP_ID_NAT 0x02 // NAT трафик между узлами
#define ETCP_ID_SVC_ROUTE 0x03 // Маршрутизируемые сервисные пакеты (etcp_router)
#define ETCP_ID_TCP_PROXY 0x04 // TCP proxy exit node (server)
#define ETCP_ID_UDP_PROXY 0x05 // UDP datagram прокси (client ↔ exit)
#define ETCP_ID_ICMP_PROXY 0x06 // ICMP echo прокси (ping через exit)
#define ETCP_ID_TCP_PROXY_CLIENT 0x07 // TCP proxy client (клиент)
#define ETCP_ID_MSG_TRANSPORT 0x10 // msg_transport — локальный IPC транспорт сообщений
#define ETCP_ID_CONN_MGR 0x11 // Connection Manager — management connections
#define ETCP_ID_ROUTE_ENTRY 0x01 // Элемент роутинг-таблицы (BGP)
#define ETCP_ID_SVC_ROUTE 0x03 // Транспорт роутера (etcp_router)
// === Router-сервисы (etcp_router_bind/etcp_route_send) ===
#define ETCP_RT_ID_DATA 0x00 // routing.c — маршрутизация данных
#define ETCP_RT_ID_NAT 0x02 // NAT трафик между узлами
#define ETCP_RT_ID_SVC_ROUTE 0x03 // etcp_router — транспорт роутера
#define ETCP_RT_ID_TCP_PROXY 0x04 // TCP proxy (клиент ↔ exit)
#define ETCP_RT_ID_UDP_PROXY 0x05 // UDP datagram прокси (client ↔ exit)
#define ETCP_RT_ID_ICMP_PROXY 0x06 // ICMP echo прокси (ping через exit)
#define ETCP_RT_ID_MSG_TRANSPORT 0x10 // msg_transport — локальный IPC транспорт сообщений
#define ETCP_RT_ID_CONN_MGR 0x11 // Connection Manager — management connections
// Forward declarations
struct ETCP_CONN;
@@ -51,8 +54,19 @@ typedef void (*etcp_recv_fn)(struct ETCP_CONN* conn, struct ll_entry* entry);
// Forward declaration
struct ETCP_CONN;
struct UTUN_INSTANCE;
struct NODEINFO_Q;
typedef void (*etcp_cbk_fn)(struct ETCP_CONN* conn, void* arg);
// ---- Background connection initialization ----
#define ETCP_CONNECT_EARLY 1
#define ETCP_CONNECT_LATE 2
#define ETCP_CONNECT_BGP_READY 4 // BGP-синхронизация завершена
typedef void (*etcp_connect_callback_t)(void* arg, struct ETCP_CONN* conn, int type);
int etcp_connect(struct UTUN_INSTANCE* instance, struct NODEINFO_Q* node,
etcp_connect_callback_t cb, void* arg, uint8_t flags);
/**
* @brief Установить callback при создании нового ETCP соединения
*
@@ -118,6 +132,8 @@ void etcp_conn_set_ready_cbk(struct ETCP_CONN* conn, etcp_cbk_fn callback_fn, vo
void etcp_conn_set_up_cbk (struct ETCP_CONN* conn, etcp_cbk_fn callback_fn, void* arg);
void etcp_conn_set_down_cbk (struct ETCP_CONN* conn, etcp_cbk_fn callback_fn, void* arg);
void etcp_set_routing_exchange_state(struct ETCP_CONN* conn, uint8_t new_state);
/**
* @brief Внутренняя функция etcp: Коллбэк для очередей output ll_queue normalizer
*
+248
View File
@@ -0,0 +1,248 @@
#include "etcp_api.h"
#include "etcp.h"
#include "utun_instance.h"
#include "route_node.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/u_async.h"
#include "../lib/socket_compat.h"
#include <string.h>
#define DEBUG_CATEGORY_ETCP_CONNECT DEBUG_CATEGORY_ETCP
struct etcp_connect_cb_node {
etcp_connect_callback_t cb;
void* arg;
uint8_t flags;
struct etcp_connect_cb_node* next;
};
struct ETCP_CONNECT {
struct ETCP_CONNECT* next;
struct UTUN_INSTANCE* instance;
uint64_t node_id;
struct ETCP_CONN* conn;
struct etcp_connect_cb_node* cb_list;
void* initial_timer;
void* settle_timer;
uint16_t min_rtt;
uint8_t early_delivered : 1;
uint8_t done : 1;
};
static struct ETCP_CONNECT* connect_find(struct UTUN_INSTANCE* inst, uint64_t node_id) {
struct ETCP_CONNECT* ctx = inst->pending_connects;
while (ctx) { if (ctx->node_id == node_id) return ctx; ctx = ctx->next; }
return NULL;
}
static void connect_deliver(struct ETCP_CONNECT* ctx, int type) {
struct etcp_connect_cb_node** pp = &ctx->cb_list;
while (*pp) {
struct etcp_connect_cb_node* node = *pp;
if (type == 0 || (node->flags & (uint8_t)type)) {
*pp = node->next;
node->cb(node->arg, (type == 0) ? NULL : ctx->conn, type);
u_free(node);
} else {
pp = &node->next;
}
}
}
static void connect_cancel(struct ETCP_CONNECT* ctx) {
if (!ctx) return;
struct UTUN_INSTANCE* inst = ctx->instance;
if (inst) {
struct ETCP_CONNECT** pp = &inst->pending_connects;
while (*pp && *pp != ctx) pp = &(*pp)->next;
if (*pp) *pp = ctx->next;
if (ctx->initial_timer) { uasync_cancel_timeout(inst->ua, ctx->initial_timer); ctx->initial_timer = NULL; }
if (ctx->settle_timer) { uasync_cancel_timeout(inst->ua, ctx->settle_timer); ctx->settle_timer = NULL; }
}
u_free(ctx);
}
static void connect_create_links_v4(struct ETCP_CONNECT* ctx, struct NODEINFO_Q* node) {
const struct NODEINFO_IPV4_ADDR* addrs; int ac = get_node_v4_addrs(node, &addrs);
for (int i = 0; i < ac; i++) {
if (addrs[i].port == 0) continue;
{ int zero = 1; for (int j = 0; j < 4; j++) if (addrs[i].addr[j] != 0) { zero = 0; break; } if (zero) continue; }
struct sockaddr_in sin; memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
memcpy(&sin.sin_addr.s_addr, addrs[i].addr, 4);
sin.sin_port = htons(addrs[i].port);
struct sockaddr_storage sa; memcpy(&sa, &sin, sizeof(sin));
struct ETCP_SOCKET* s = ctx->instance->etcp_sockets;
while (s) {
if (s->local_addr.ss_family == AF_INET)
etcp_link_new(ctx->conn, s, &sa, 0);
s = s->next;
}
}
}
static void connect_create_links_v6(struct ETCP_CONNECT* ctx, struct NODEINFO_Q* node) {
const struct NODEINFO_IPV6_ADDR* addrs; int ac = get_node_v6_addrs(node, &addrs);
for (int i = 0; i < ac; i++) {
if (addrs[i].port == 0) continue;
int zero = 1;
for (int j = 0; j < 16; j++) if (addrs[i].addr[j] != 0) { zero = 0; break; }
if (zero) continue;
struct sockaddr_in6 sin6; memset(&sin6, 0, sizeof(sin6));
sin6.sin6_family = AF_INET6;
memcpy(&sin6.sin6_addr, addrs[i].addr, 16);
sin6.sin6_port = htons(addrs[i].port);
struct sockaddr_storage sa; memcpy(&sa, &sin6, sizeof(sin6));
struct ETCP_SOCKET* s = ctx->instance->etcp_sockets;
while (s) {
if (s->local_addr.ss_family == AF_INET6)
etcp_link_new(ctx->conn, s, &sa, 0);
s = s->next;
}
}
}
static void connect_bgp_ready_cb(struct ETCP_CONN* conn) {
struct ETCP_CONNECT* ctx = connect_find(conn->instance, conn->peer_node_id);
if (!ctx || ctx->done) return;
connect_deliver(ctx, ETCP_CONNECT_BGP_READY);
}
static void connect_initial_timeout_cb(void* arg) {
struct ETCP_CONNECT* ctx = (struct ETCP_CONNECT*)arg;
if (!ctx || ctx->done) return;
ctx->done = 1;
ctx->initial_timer = NULL;
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] timeout for node 0x%016llx", (unsigned long long)ctx->node_id);
struct ETCP_CONN* conn = ctx->conn; ctx->conn = NULL;
connect_deliver(ctx, 0);
if (conn) etcp_connection_close(conn);
connect_cancel(ctx);
}
static void connect_settle_timeout_cb(void* arg) {
struct ETCP_CONNECT* ctx = (struct ETCP_CONNECT*)arg;
if (!ctx || ctx->done) return;
ctx->done = 1;
ctx->settle_timer = NULL;
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] settle for node 0x%016llx, min_rtt=%u",
(unsigned long long)ctx->node_id, ctx->min_rtt);
struct ETCP_LINK* link = ctx->conn->links;
while (link) {
struct ETCP_LINK* next = link->next;
if (!link->initialized) {
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] removing failed link=%p for node 0x%016llx",
(void*)link, (unsigned long long)ctx->node_id);
etcp_link_close(link);
}
link = next;
}
ctx->conn->ready_cbk = NULL;
ctx->conn->ready_arg = NULL;
connect_deliver(ctx, ETCP_CONNECT_LATE);
connect_cancel(ctx);
}
static void connect_ready_cb(struct ETCP_CONN* conn, void* arg) {
struct ETCP_CONNECT* ctx = (struct ETCP_CONNECT*)arg;
if (!ctx || ctx->done) return;
struct ETCP_LINK* link = conn->links;
while (link) {
if (link->initialized && link->rtt_last && link->rtt_last < ctx->min_rtt)
ctx->min_rtt = link->rtt_last;
link = link->next;
}
if (ctx->early_delivered) return;
ctx->early_delivered = 1;
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] early ready for node 0x%016llx, min_rtt=%u",
(unsigned long long)ctx->node_id, ctx->min_rtt);
connect_deliver(ctx, ETCP_CONNECT_EARLY);
if (ctx->initial_timer) { uasync_cancel_timeout(ctx->instance->ua, ctx->initial_timer); ctx->initial_timer = NULL; }
uint32_t settle_tb;
if (ctx->min_rtt == 0xFFFF) settle_tb = 20000;
else {
settle_tb = (uint32_t)ctx->min_rtt * 8;
if (settle_tb < 5000) settle_tb = 5000;
if (settle_tb > 20000) settle_tb = 20000;
}
ctx->settle_timer = uasync_set_timeout(ctx->instance->ua, (int)settle_tb, ctx,
connect_settle_timeout_cb, "etcp_connect_settle");
}
int etcp_connect(struct UTUN_INSTANCE* inst, struct NODEINFO_Q* node,
etcp_connect_callback_t cb, void* arg, uint8_t flags) {
if (!inst || !node || !cb) return -1;
uint64_t node_id = node->node.node_id;
struct ETCP_CONN* existing = inst->connections;
while (existing) {
if (existing->peer_node_id == node_id) {
struct ETCP_LINK* l = existing->links;
while (l) { if (l->link_state == 3) break; l = l->next; }
if (l) {
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] node 0x%016llx already connected, delivering immediately",
(unsigned long long)node_id);
if (flags & ETCP_CONNECT_EARLY) cb(arg, existing, ETCP_CONNECT_EARLY);
if (flags & ETCP_CONNECT_LATE) cb(arg, existing, ETCP_CONNECT_LATE);
if ((flags & ETCP_CONNECT_BGP_READY) && existing->routing_exchange_active >= 3)
cb(arg, existing, ETCP_CONNECT_BGP_READY);
return 0;
}
}
existing = existing->next;
}
struct ETCP_CONNECT* ctx = connect_find(inst, node_id);
if (ctx) {
struct etcp_connect_cb_node* cn = u_calloc(1, sizeof(struct etcp_connect_cb_node));
if (!cn) return -1;
cn->cb = cb; cn->arg = arg; cn->flags = flags; cn->next = ctx->cb_list; ctx->cb_list = cn;
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] node 0x%016llx already connecting, added cb",
(unsigned long long)node_id);
return 0;
}
struct ETCP_CONN* conn = etcp_connection_create(inst, NULL);
if (!conn) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] failed to create ETCP_CONN for node 0x%016llx",
(unsigned long long)node_id); cb(arg, NULL, 0); return -1; }
conn->next = inst->connections; inst->connections = conn; inst->connections_count++;
if (sc_init_ctx(&conn->crypto_ctx, &inst->my_keys) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] sc_init_ctx failed for node 0x%016llx", (unsigned long long)node_id);
etcp_connection_close(conn); cb(arg, NULL, 0); return -1;
}
if (sc_set_peer_public_key(&conn->crypto_ctx, node->node.public_key, 0) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] sc_set_peer_public_key failed for node 0x%016llx", (unsigned long long)node_id);
etcp_connection_close(conn); cb(arg, NULL, 0); return -1;
}
ctx = u_calloc(1, sizeof(struct ETCP_CONNECT));
if (!ctx) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] alloc failed");
etcp_connection_close(conn); cb(arg, NULL, 0); return -1; }
ctx->instance = inst; ctx->node_id = node_id; ctx->conn = conn;
ctx->min_rtt = 0xFFFF;
{
struct etcp_connect_cb_node* cn = u_calloc(1, sizeof(struct etcp_connect_cb_node));
if (!cn) { u_free(ctx); etcp_connection_close(conn); cb(arg, NULL, 0); return -1; }
cn->cb = cb; cn->arg = arg; cn->flags = flags; ctx->cb_list = cn;
}
conn->ready_cbk = connect_ready_cb;
conn->ready_arg = ctx;
conn->bgp_ready_cbk = connect_bgp_ready_cb;
connect_create_links_v4(ctx, node);
connect_create_links_v6(ctx, node);
if (!conn->links) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] no links created for node 0x%016llx",
(unsigned long long)node_id);
connect_deliver(ctx, 0);
etcp_connection_close(conn); u_free(ctx); return -1;
}
ctx->initial_timer = uasync_set_timeout(inst->ua, (int)inst->etcp_connect_timeout_tb, ctx, connect_initial_timeout_cb, "etcp_connect_init");
ctx->next = inst->pending_connects; inst->pending_connects = ctx;
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] started for node 0x%016llx", (unsigned long long)node_id);
return 0;
}
+53 -7
View File
@@ -1,4 +1,5 @@
#include "etcp_connections.h"
#include "etcp_api.h"
#include "../lib/socket_compat.h"
#include "../lib/platform_compat.h"
#include "../lib/getmyip.h"
@@ -699,7 +700,7 @@ struct ETCP_SOCKET* etcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_S
e_sock->pkt_format_errors = 0;
e_sock->type = type;
e_sock->sock_id = instance->next_socket_id++;
e_sock->nat_type = type; // initial type from config, updated after NAT detection
e_sock->nat_type = NAT_TYPE_UNKNOWN; // только результат детекции NAT, серверные сокеты стартуют с unknown
e_sock->mtu = mtu;
e_sock->only_local = only_local;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Add Socket type=%d", type);
@@ -1294,6 +1295,38 @@ static void send_init_response(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pk
link->etcp->log_name);
}
}
/* Self-detection: if my IP is non-local, I'm on a public IP */
DEBUG_INFO(DEBUG_CATEGORY_NAT, "self-detect check: socket=%s type=%d nat_type=%d if_addr=%s local_addr=%s",
e_sock->name, e_sock->type, e_sock->nat_type,
sockaddr_storage_to_str(&e_sock->interface_addr).str,
sockaddr_storage_to_str(&e_sock->local_addr).str);
if (e_sock->nat_type != NAT_VERIFIED_DIRECT && e_sock->interface_addr.ss_family == AF_INET) {
uint32_t my_ip = ((struct sockaddr_in*)&e_sock->interface_addr)->sin_addr.s_addr;
if (my_ip != 0 && !is_local_subnet(my_ip)) {
e_sock->nat_type = NAT_VERIFIED_DIRECT;
struct sockaddr_in* nat_sin = (struct sockaddr_in*)&e_sock->nat_addr;
nat_sin->sin_family = AF_INET;
nat_sin->sin_addr.s_addr = my_ip;
nat_sin->sin_port = ((struct sockaddr_in*)&e_sock->interface_addr)->sin_port;
if (link->etcp->instance->bgp) {
route_bgp_update_my_nodeinfo(link->etcp->instance->bgp->instance, link->etcp->instance->bgp);
struct ROUTE_BGP* bgp = link->etcp->instance->bgp;
if (bgp->local_node && bgp->senders_list) {
bgp->local_node->node.ver = (bgp->local_node->node.ver % 255) + 1;
bgp->local_node->dirty = 1;
struct ll_entry* se = bgp->senders_list->head;
while (se) {
struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)se->data;
if (item && item->conn) route_bgp_send_nodeinfo(bgp->local_node, item->conn);
se = se->next;
}
}
}
DEBUG_INFO(DEBUG_CATEGORY_NAT, "Self-detected PUBLIC: socket=%s ip=%s port=%u sock_id=%d",
e_sock->name, ip_to_str(&my_ip, AF_INET).str,
ntohs(nat_sin->sin_port), e_sock->sock_id);
}
}
pkt->noencrypt_len=0;
pkt->link=link;
link->recv_keepalive = 1;
@@ -1394,12 +1427,20 @@ static int handle_init_response_client(struct ETCP_SOCKET* e_sock, struct ETCP_D
link->nat_changes_count);
}
// Update socket NAT address
// Update socket NAT address (only if not already verified)
{
struct sockaddr_in* sin = (struct sockaddr_in*)&e_sock->nat_addr;
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = new_nat_ip;
sin->sin_port = htons(new_nat_port);
DEBUG_INFO(DEBUG_CATEGORY_NAT, "init_resp nat: socket=%s nat_type=%d new_ip=%s new_port=%u",
e_sock->name, e_sock->nat_type,
ip_to_str(&new_nat_ip, AF_INET).str, new_nat_port);
if (e_sock->nat_type < NAT_VERIFIED_UNKNOWN) {
struct sockaddr_in* sin = (struct sockaddr_in*)&e_sock->nat_addr;
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = new_nat_ip;
sin->sin_port = htons(new_nat_port);
} else {
DEBUG_INFO(DEBUG_CATEGORY_NAT, "init_resp nat update SKIPPED (already verified): socket=%s nat_type=%d",
e_sock->name, e_sock->nat_type);
}
}
} else {
// Legacy format without NAT info
@@ -1565,6 +1606,11 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
errorcode=4;
goto ec_fr;
}// не init
if (e_sock->type == CFG_SERVER_TYPE_PRIVATE) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "INIT rejected: socket %s (type=PRIVATE) does not accept incoming connections", e_sock->name);
errorcode=8;
goto ec_fr;
}
if (pkt_len < ETCP_INIT_REQ_V1_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "INIT REQUEST too short: pkt_len=%zu from %s", pkt_len, sockaddr_storage_to_str(&addr).str);
errorcode=7;
@@ -1965,7 +2011,7 @@ int init_connections(struct UTUN_INSTANCE* instance) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "init_connections: no peer public key configured for client %s", client->name);
}
etcp_conn->routing_exchange_active=1;// инициируем обмен маршрутами
etcp_set_routing_exchange_state(etcp_conn, 1); // инициируем обмен маршрутами
// Create links for this client
struct CFG_CLIENT_LINK* client_link = client->links;
+509 -250
View File
@@ -2,6 +2,15 @@
// Упрощённый TCP: восстановление порядка (recv_q), дедупликация, без переповторов
// ACK: периодическая отправка rx_seq (не чаще 100ms), idle-таймер для последнего seq
// Inflight контроль через send_q: при переполнении — очередь + retry-таймер, без дропов
/*
/----- ack ack <-- id
|(pause) |
in ---> {Q} -> [src, etcp] --> ... --> [dst,etcp] -> {asm_q} -> {buf q} ---> out
*/
#include "etcp_router.h"
#include "etcp.h"
#include "utun_instance.h"
@@ -22,12 +31,24 @@ static void router_schedule_ack(struct ETCP_ROUTER_CONN* rconn);
static void router_try_assembly(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* conn);
static void router_deliver(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* conn, const uint8_t* payload, size_t payload_len);
static int router_send_one(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len);
static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, uint32_t seq_flags, int is_signed);
static void router_send_to(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t svc_id, uint32_t seq_flags);
static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, uint8_t flag_bits, int is_signed);
static void router_send_to(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t svc_id, uint8_t flag_bits);
static void router_drain_send_q(struct ETCP_ROUTER_CONN* rconn);
static void router_send_resume_cb(void* arg);
static void router_no_route_retry_cb(void* arg);
static void router_send_close_to_service(struct ETCP_ROUTER_CONN* rconn);
static void router_close_and_notify(struct ETCP_ROUTER_CONN* rconn);
static void router_close_finalize(void* arg);
static void free_entry(struct ll_entry* e) { queue_dgram_free(e); queue_entry_free(e); }
static int router_verify_signature(struct UTUN_INSTANCE* inst, struct ll_entry* entry, struct SVC_ROUTE_HDR* hdr, size_t* pl_len);
static void router_handle_ack(struct UTUN_INSTANCE* inst, struct ETCP_ROUTER_CONN* rconn, uint32_t seq, uint64_t src_node_id);
static void router_forward_transit(struct UTUN_INSTANCE* inst, struct ll_entry* entry, struct SVC_ROUTE_HDR* hdr);
static int router_check_peer_restart(struct UTUN_INSTANCE* inst, struct ETCP_ROUTER_CONN** prconn, struct SVC_ROUTE_HDR* hdr);
static void router_handle_data_packet(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* conn, uint32_t seq, const uint8_t* pl, size_t pl_len);
static void router_retransmit_one(struct ETCP_ROUTER_CONN* rconn, struct ROUTER_INFLIGHT* inf);
static void router_retrans_schedule(struct ETCP_ROUTER_CONN* rconn);
static void router_retrans_timer_cb(void* arg);
static void router_incoming_q_cb(struct ll_queue* q, void* arg);
// ====================================================================
// Управление ROUTER_CONN
@@ -50,24 +71,22 @@ static int router_send_one(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payloa
return router_send_one_flags(rconn, payload, pl_len, 0, 0);
}
static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, uint32_t seq_flags, int is_signed) {
static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, uint8_t flag_bits, int is_signed) {
struct UTUN_INSTANCE* inst = rconn->inst;
uint32_t seq = (seq_flags & (ROUTER_SEQ_CLOSE_FLAG | ROUTER_SEQ_RST_FLAG))
? 0 : rconn->tx_seq++;
seq |= seq_flags;
uint32_t seq = flag_bits ? 0 : rconn->tx_seq++;
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--; rconn->c_pkts_send_err++; return -1; }
if (!dgram) { if (!flag_bits) 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->cmd = ETCP_RT_ID_SVC_ROUTE;
hdr->dst_node_id = rconn->remote_node_id;
hdr->src_node_id = inst->node_id;
hdr->seq = seq;
hdr->svc_id = rconn->svc_id;
{
uint8_t f = (rconn->sess_id << ROUTER_SESS_ID_SHIFT);
if (!rconn->start_sent && pl_len > 0 && !(seq_flags & (ROUTER_SEQ_CLOSE_FLAG | ROUTER_SEQ_RST_FLAG))) {
uint8_t f = (rconn->sess_id << ROUTER_SESS_ID_SHIFT) | flag_bits;
if (!rconn->start_sent && pl_len > 0 && !flag_bits) {
f |= ROUTER_FLAG_START;
rconn->start_sent = 1;
}
@@ -91,29 +110,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--; rconn->c_pkts_send_err++; return -1;
u_free(dgram); if (!flag_bits) rconn->tx_seq--; rconn->c_pkts_send_err++; return -1;
}
if (is_signed) {
struct sc_stream_sign_state sign_state;
if (sc_stream_sign_init(&conn->crypto_ctx, &sign_state) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_send_one: sign_init failed svc_id=%u", rconn->svc_id);
u_free(dgram); if (!seq_flags) rconn->tx_seq--; rconn->c_pkts_send_err++; return -1;
u_free(dgram); if (!flag_bits) rconn->tx_seq--; rconn->c_pkts_send_err++; return -1;
}
size_t data_len = SVC_ROUTE_HDR_SIZE + pl_len;
if (sc_stream_sign_update(&sign_state, dgram, data_len) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_send_one: sign_update failed svc_id=%u", rconn->svc_id);
sc_stream_sign_cleanup(&sign_state); u_free(dgram); if (!seq_flags) rconn->tx_seq--; rconn->c_pkts_send_err++; return -1;
sc_stream_sign_cleanup(&sign_state); u_free(dgram); if (!flag_bits) rconn->tx_seq--; rconn->c_pkts_send_err++; return -1;
}
size_t actual_sig_len = SC_SIGN_SIZE;
if (sc_stream_sign_final(&sign_state, dgram + data_len, &actual_sig_len) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_send_one: sign_final failed svc_id=%u", rconn->svc_id);
u_free(dgram); if (!seq_flags) rconn->tx_seq--; rconn->c_pkts_send_err++; return -1;
u_free(dgram); if (!flag_bits) rconn->tx_seq--; rconn->c_pkts_send_err++; return -1;
}
}
struct ll_entry* entry = queue_entry_new(0);
if (!entry) { u_free(dgram); if (!seq_flags) rconn->tx_seq--; rconn->c_pkts_send_err++; return -1; }
if (!entry) { u_free(dgram); if (!flag_bits) rconn->tx_seq--; rconn->c_pkts_send_err++; return -1; }
entry->dgram = dgram;
entry->len = total_len;
@@ -125,33 +144,54 @@ static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t*
(unsigned long long)rconn->remote_node_id);
int ret = etcp_send(conn, entry);
if (ret != 0) {
if (!seq_flags) rconn->tx_seq--;
if (!flag_bits) 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++;
if (!flag_bits && pl_len > 0) {
struct ROUTER_INFLIGHT* inf = u_malloc(sizeof(struct ROUTER_INFLIGHT));
if (inf) {
memset(&inf->ll, 0, sizeof(inf->ll));
inf->ll.size = 4;
inf->seq = seq;
*(uint32_t*)inf->ll.data = seq;
inf->last_sent_tb = get_time_tb();
inf->send_count = 1;
inf->payload = u_malloc(pl_len);
if (inf->payload) {
memcpy(inf->payload, payload, pl_len);
inf->payload_len = pl_len;
queue_data_put_with_index(rconn->inflight_q, &inf->ll);
} else {
u_free(inf);
}
if (!rconn->retrans_timer) router_retrans_schedule(rconn);
}
}
}
return ret;
}
// Отправка без conn (RST для неизвестного src)
static void router_send_to(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t svc_id, uint32_t seq_flags) {
static void router_send_to(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t svc_id, uint8_t flag_bits) {
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, dst);
if (!conn) return;
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)u_malloc(SVC_ROUTE_HDR_SIZE);
if (!hdr) return;
hdr->cmd = ETCP_ID_SVC_ROUTE;
hdr->cmd = ETCP_RT_ID_SVC_ROUTE;
hdr->dst_node_id = dst;
hdr->src_node_id = inst->node_id;
hdr->seq = seq_flags;
hdr->seq = 0;
hdr->flags = flag_bits;
hdr->svc_id = svc_id;
struct ll_entry* entry = queue_entry_new(0);
if (!entry) { u_free(hdr); return; }
entry->dgram = (uint8_t*)hdr;
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);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router_send_to: flags=0x%02x → %016llx svc_id=%u",
flag_bits, (unsigned long long)dst, svc_id);
int ret = etcp_send(conn, entry);
if (ret != 0) {
queue_dgram_free(entry); queue_entry_free(entry);
@@ -176,17 +216,27 @@ static void router_send_close_to_service(struct ETCP_ROUTER_CONN* rconn) {
}
// Закрыть conn: отправить CLOSE удалённой стороне, уведомить локальный сервис, очистить
static void router_close_finalize(void* arg) {
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)arg;
void (*cb)(void*) = rconn->close_callback;
void* cb_arg = rconn->close_callback_arg;
queue_entry_free(&rconn->ll);
if (cb) cb(cb_arg);
}
static void router_close_and_notify(struct ETCP_ROUTER_CONN* rconn) {
if (!rconn) return;
if (!rconn || rconn->closed) return;
struct UTUN_INSTANCE* inst = rconn->inst;
rconn->closed = 1;
router_send_close_to_service(rconn);
// Отправляем CLOSE удалённой стороне
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, rconn->remote_node_id);
if (conn) router_send_one_flags(rconn, NULL, 0, ROUTER_SEQ_CLOSE_FLAG, 0);
if (conn) router_send_one_flags(rconn, NULL, 0, ROUTER_FLAG_CLOSE, 0);
// Таймеры
if (rconn->ack_timer) { uasync_cancel_timeout(inst->ua, rconn->ack_timer); rconn->ack_timer = NULL; }
if (rconn->idle_ack_timer) { uasync_cancel_timeout(inst->ua, rconn->idle_ack_timer); rconn->idle_ack_timer = NULL; }
if (rconn->send_resume_timer) { uasync_cancel_timeout(inst->ua, rconn->send_resume_timer); rconn->send_resume_timer = NULL; }
if (rconn->no_route_timer) { uasync_cancel_timeout(inst->ua, rconn->no_route_timer); rconn->no_route_timer = NULL; }
if (rconn->recv_q) {
struct ll_entry* f;
while ((f = queue_data_get(rconn->recv_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
@@ -197,14 +247,31 @@ static void router_close_and_notify(struct ETCP_ROUTER_CONN* rconn) {
while ((f = queue_data_get(rconn->send_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
queue_free(rconn->send_q); rconn->send_q = NULL;
}
if (rconn->inflight_q) {
struct ll_entry* f;
while ((f = queue_data_get(rconn->inflight_q)) != NULL) {
struct ROUTER_INFLIGHT* inf = (struct ROUTER_INFLIGHT*)f;
if (inf->payload) u_free(inf->payload);
u_free(inf);
}
queue_free(rconn->inflight_q); rconn->inflight_q = NULL;
}
if (rconn->retrans_timer) { uasync_cancel_timeout(inst->ua, rconn->retrans_timer); rconn->retrans_timer = NULL; }
if (rconn->incoming_q) {
struct ll_entry* f;
while ((f = queue_data_get(rconn->incoming_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
queue_resume_callback(rconn->incoming_q);
queue_free(rconn->incoming_q); rconn->incoming_q = NULL;
}
if (inst->router_conns)
queue_remove_data(inst->router_conns, &rconn->ll);
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router_conn: closed remote=%016llx svc_id=%u",
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
queue_entry_free(&rconn->ll);
uasync_call_soon(inst->ua, rconn, router_close_finalize);
}
static void router_drain_send_q(struct ETCP_ROUTER_CONN* rconn) {
if (rconn->no_route || rconn->closed) return;
int sq = queue_entry_count(rconn->send_q);
if (sq > 0) DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "SEND_Q_DRAIN: svc_id=%u send_q=%d inflight=%d",
rconn->svc_id, sq, (int32_t)(rconn->tx_seq - rconn->tx_acked));
@@ -229,10 +296,126 @@ static void router_drain_send_q(struct ETCP_ROUTER_CONN* rconn) {
static void router_send_resume_cb(void* arg) {
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)arg;
if (rconn->closed) return;
rconn->send_resume_timer = NULL;
router_drain_send_q(rconn);
}
static void router_no_route_retry_cb(void* arg) {
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)arg;
if (rconn->closed) return;
rconn->no_route_timer = NULL;
struct ETCP_CONN* c = route_bgp_find_conn_for_node(rconn->inst->bgp, rconn->remote_node_id);
if (c) {
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router: route appeared for %016llx svc_id=%u, draining send_q=%d",
(unsigned long long)rconn->remote_node_id, rconn->svc_id, queue_entry_count(rconn->send_q));
rconn->no_route = 0;
router_drain_send_q(rconn);
return;
}
rconn->no_route_timer = uasync_set_timeout(rconn->inst->ua,
ROUTER_NO_ROUTE_RETRY_TB, rconn, router_no_route_retry_cb, "router_no_route");
}
// ====================================================================
// Ретрансмиты
// ====================================================================
static void router_retransmit_one(struct ETCP_ROUTER_CONN* rconn, struct ROUTER_INFLIGHT* inf) {
struct UTUN_INSTANCE* inst = rconn->inst;
size_t total_len = SVC_ROUTE_HDR_SIZE + inf->payload_len;
uint8_t* dgram = u_malloc(total_len);
if (!dgram) { rconn->c_pkts_send_err++; return; }
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)dgram;
hdr->cmd = ETCP_RT_ID_SVC_ROUTE;
hdr->dst_node_id = rconn->remote_node_id;
hdr->src_node_id = inst->node_id;
hdr->seq = inf->seq;
hdr->svc_id = rconn->svc_id;
hdr->flags = (rconn->sess_id << ROUTER_SESS_ID_SHIFT);
struct ETCP_CONN* conn = NULL;
struct ROUTE_BGP* bgp = inst->bgp;
if (bgp) {
struct NODEINFO_Q* nq = nodeinfo_find_by_id(bgp, rconn->remote_node_id);
if (nq && nq->conn_mgr_type == CONN_TYPE_INDIRECT && nq->conn_mgr_intermediariy_count > 0) {
for (uint8_t i = 0; i < nq->conn_mgr_intermediariy_count; i++) {
conn = route_bgp_find_conn_for_node(bgp, nq->conn_mgr_intermediaries[i]);
if (conn) break;
}
}
}
if (!conn) conn = route_bgp_find_conn_for_node(bgp, rconn->remote_node_id);
if (!conn) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router_retransmit: no route to %016llx svc_id=%u",
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
u_free(dgram); rconn->c_pkts_send_err++; return;
}
memcpy(dgram + SVC_ROUTE_HDR_SIZE, inf->payload, inf->payload_len);
struct ll_entry* entry = queue_entry_new(0);
if (!entry) { u_free(dgram); rconn->c_pkts_send_err++; return; }
entry->dgram = dgram;
entry->len = total_len;
rconn->last_dgram_ts = get_current_timestamp();
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "RETRANS: svc_id=%u seq=%u attempt=%d → %016llx",
rconn->svc_id, inf->seq, inf->send_count + 1, (unsigned long long)rconn->remote_node_id);
int ret = etcp_send(conn, entry);
if (ret != 0) {
queue_dgram_free(entry); queue_entry_free(entry);
rconn->c_pkts_send_err++;
} else {
inf->last_sent_tb = get_time_tb();
inf->send_count++;
rconn->c_retrans_done++;
rconn->c_pkts_sent++;
}
}
static void router_retrans_schedule(struct ETCP_ROUTER_CONN* rconn) {
if (rconn->retrans_timer) return;
uint64_t now = get_time_tb();
if (rconn->last_ack_changed_tb == 0) rconn->last_ack_changed_tb = now;
int64_t remaining = (int64_t)ROUTER_RETRANS_TIMEOUT_TB - (int64_t)(now - rconn->last_ack_changed_tb);
if (remaining <= 0) remaining = 1;
rconn->retrans_timer = uasync_set_timeout(rconn->inst->ua,
(uint32_t)remaining, rconn, router_retrans_timer_cb, "router_retrans");
}
static void router_retrans_timer_cb(void* arg) {
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)arg;
if (rconn->closed) return;
rconn->retrans_timer = NULL;
uint64_t now = get_time_tb();
uint64_t elapsed = now - rconn->last_ack_changed_tb;
if (elapsed >= ROUTER_RETRANS_TIMEOUT_TB) {
rconn->no_ack_count++;
if (rconn->no_ack_count >= ROUTER_NO_ACK_MAX_RETRANS) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: no ACK progress for %d retrans cycles, closing svc_id=%u remote=%016llx",
rconn->no_ack_count, rconn->svc_id, (unsigned long long)rconn->remote_node_id);
router_close_and_notify(rconn);
return;
}
uint32_t s = rconn->tx_acked;
while ((int32_t)(s - rconn->tx_acked) < (int32_t)(rconn->tx_seq - rconn->tx_acked)) {
struct ROUTER_INFLIGHT* inf = (struct ROUTER_INFLIGHT*)queue_find_data_by_index(rconn->inflight_q, &s);
if (!inf) { s++; continue; }
router_retransmit_one(rconn, inf);
s++;
}
rconn->last_ack_changed_tb = now;
}
if (queue_entry_count(rconn->inflight_q) > 0) {
int64_t remaining = (int64_t)ROUTER_RETRANS_TIMEOUT_TB - (int64_t)(now - rconn->last_ack_changed_tb);
if (remaining <= 0) remaining = 1;
rconn->retrans_timer = uasync_set_timeout(rconn->inst->ua,
(uint32_t)remaining, rconn, router_retrans_timer_cb, "router_retrans");
}
}
static int router_enqueue_send(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, int force) {
if (!force && queue_entry_count(rconn->send_q) >= ROUTER_MAX_SEND_Q_PACKETS) {
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router_send_q: FULL svc_id=%u count=%d — backpressure",
@@ -275,7 +458,7 @@ static void router_send_ack(struct ETCP_ROUTER_CONN* rconn) {
}
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)u_malloc(SVC_ROUTE_HDR_SIZE);
if (!hdr) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_ack: u_malloc failed"); return; }
hdr->cmd = ETCP_ID_SVC_ROUTE;
hdr->cmd = ETCP_RT_ID_SVC_ROUTE;
hdr->dst_node_id = rconn->remote_node_id;
hdr->src_node_id = inst->node_id;
hdr->seq = rconn->rx_seq;
@@ -319,6 +502,7 @@ static void router_schedule_ack(struct ETCP_ROUTER_CONN* rconn) {
static void router_ack_timer_cb(void* arg) {
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)arg;
if (rconn->closed) return;
rconn->ack_timer = NULL;
if (rconn->rx_seq != rconn->last_sent_ack_seq)
router_ack_do_send(rconn);
@@ -334,6 +518,7 @@ static void router_ack_timer_cb(void* arg) {
static void router_idle_ack_timer_cb(void* arg) {
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)arg;
if (rconn->closed) return;
rconn->idle_ack_timer = NULL;
if (rconn->rx_seq != rconn->last_sent_ack_seq)
router_ack_do_send(rconn);
@@ -381,7 +566,207 @@ static void router_try_assembly(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN
}
// ====================================================================
// etcp_router_recv_cb — обработчик ETCP_ID_SVC_ROUTE
// Очередь приёма входящего трафика (между сетью и recv_q)
// ====================================================================
static void router_incoming_q_cb(struct ll_queue* q, void* arg) {
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)arg;
if (rconn->closed) { queue_resume_callback(q); return; }
struct ll_entry* e = queue_data_get(q);
if (!e) { queue_resume_callback(q); return; }
uint32_t seq = *(uint32_t*)e->data;
const uint8_t* pl = e->dgram;
size_t pl_len = e->len;
if (queue_find_data_by_index(rconn->recv_q, &seq)) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router_incoming_q: dup in recv_q seq=%u, dropping", seq);
queue_dgram_free(e); queue_entry_free(e); queue_resume_callback(q); return;
}
struct ll_entry* rq = queue_entry_new(4);
if (!rq) { queue_dgram_free(e); queue_entry_free(e); queue_resume_callback(q); return; }
*(uint32_t*)rq->data = seq;
rq->dgram = e->dgram; rq->len = e->len; e->dgram = NULL;
queue_entry_free(e);
queue_data_put_with_index(rconn->recv_q, rq);
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(rconn->inst->bgp, rconn->remote_node_id);
if (seq == rconn->rx_seq) router_try_assembly(rconn, conn);
router_schedule_ack(rconn);
queue_resume_callback(q);
}
// ====================================================================
// Извлечённые функции для etcp_router_recv_cb
// ====================================================================
static int router_verify_signature(struct UTUN_INSTANCE* inst, struct ll_entry* entry,
struct SVC_ROUTE_HDR* hdr, size_t* pl_len) {
if (!(hdr->flags & ROUTER_FLAG_SIGNED)) return 0;
if (*pl_len < SC_SIGN_SIZE) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: SIGNED packet too short len=%zu from %016llx",
*pl_len, (unsigned long long)hdr->src_node_id);
free_entry(entry); return -1;
}
size_t actual_payload = *pl_len - SC_SIGN_SIZE;
uint8_t* sig = (uint8_t*)entry->dgram + SVC_ROUTE_HDR_SIZE + actual_payload;
struct ROUTE_BGP* bgp = inst->bgp;
struct NODEINFO_Q* nq = bgp ? nodeinfo_find_by_id(bgp, hdr->src_node_id) : NULL;
if (!nq) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: SIGNED packet from unknown node %016llx — dropping",
(unsigned long long)hdr->src_node_id);
free_entry(entry); return -1;
}
{
uint8_t zero[SC_PUBKEY_SIZE] = {0};
if (memcmp(nq->node.ed25519_public_key, zero, SC_PUBKEY_SIZE) == 0) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: no Ed25519 pubkey for node %016llx — dropping",
(unsigned long long)hdr->src_node_id);
free_entry(entry); return -1;
}
}
struct sc_stream_sign_state vfy_state;
if (sc_stream_sign_verify_init(&vfy_state, nq->node.ed25519_public_key) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router: sign_verify_init failed for node %016llx",
(unsigned long long)hdr->src_node_id);
free_entry(entry); return -1;
}
size_t signed_len = SVC_ROUTE_HDR_SIZE + actual_payload;
if (sc_stream_sign_update(&vfy_state, entry->dgram, signed_len) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router: sign_update failed for node %016llx",
(unsigned long long)hdr->src_node_id);
sc_stream_sign_cleanup(&vfy_state); free_entry(entry); return -1;
}
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++;
free_entry(entry); return -1;
}
*pl_len = actual_payload;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router: signature verified for node %016llx svc_id=%u",
(unsigned long long)hdr->src_node_id, hdr->svc_id);
return 0;
}
static void router_handle_ack(struct UTUN_INSTANCE* inst, struct ETCP_ROUTER_CONN* rconn,
uint32_t seq, uint64_t src_node_id) {
(void)src_node_id;
if (!rconn) return;
if ((int32_t)(seq - rconn->tx_acked) >= 0) {
uint32_t old_acked = rconn->tx_acked;
rconn->tx_acked = seq;
uint32_t clean_seq = old_acked;
while ((int32_t)(clean_seq - rconn->tx_acked) < 0) {
struct ROUTER_INFLIGHT* inf = (struct ROUTER_INFLIGHT*)queue_find_data_by_index(rconn->inflight_q, &clean_seq);
if (inf) {
queue_remove_data(rconn->inflight_q, &inf->ll);
if (inf->payload) u_free(inf->payload);
u_free(inf);
}
clean_seq++;
}
rconn->last_ack_changed_tb = get_time_tb();
rconn->no_ack_count = 0;
if (queue_entry_count(rconn->inflight_q) == 0 && rconn->retrans_timer) {
uasync_cancel_timeout(rconn->inst->ua, rconn->retrans_timer);
rconn->retrans_timer = NULL;
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "SEND_Q_ACKED: svc_id=%u ack=%u inflight=%d send_q=%d inflight_q=%d from %016llx",
rconn->svc_id, seq, (int32_t)(rconn->tx_seq - rconn->tx_acked),
queue_entry_count(rconn->send_q), queue_entry_count(rconn->inflight_q), (unsigned long long)src_node_id);
rconn->c_ack_recv++;
} else {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: stale ACK seq=%u tx_acked=%u from %016llx, ignoring",
seq, rconn->tx_acked, (unsigned long long)src_node_id);
rconn->c_stale_ack++;
}
rconn->last_dgram_ts = get_current_timestamp();
if (rconn->send_blocked) router_drain_send_q(rconn);
}
static void router_forward_transit(struct UTUN_INSTANCE* inst, struct ll_entry* entry,
struct SVC_ROUTE_HDR* hdr) {
struct ETCP_CONN* next = route_bgp_find_conn_for_node(inst->bgp, hdr->dst_node_id);
if (!next) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "etcp_router: no route to %016llx svc_id=%u, dropping",
(unsigned long long)hdr->dst_node_id, hdr->svc_id);
free_entry(entry); return;
}
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);
int fwd_err = etcp_send(next, entry);
if (fwd_err != 0) {
free_entry(entry);
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "etcp_router: transit etcp_send failed ret=%d", fwd_err);
}
}
static int router_check_peer_restart(struct UTUN_INSTANCE* inst, struct ETCP_ROUTER_CONN** prconn,
struct SVC_ROUTE_HDR* hdr) {
struct ETCP_ROUTER_CONN* rconn = *prconn;
uint8_t incoming_sess = (hdr->flags >> ROUTER_SESS_ID_SHIFT) & 0x3;
if (incoming_sess != rconn->peer_sess_id ||
((hdr->flags & ROUTER_FLAG_START) && rconn->rx_seq > 0)) {
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE,
"router: peer restart sess_id=%u→%u svc_id=%u from %016llx",
rconn->peer_sess_id, incoming_sess, hdr->svc_id,
(unsigned long long)hdr->src_node_id);
etcp_router_conn_restart(inst, hdr->src_node_id, hdr->svc_id);
*prconn = etcp_router_conn_get(inst, hdr->src_node_id, hdr->svc_id);
if (!*prconn) return -1;
(*prconn)->peer_sess_id = incoming_sess;
rconn = *prconn;
}
if (hdr->flags & ROUTER_FLAG_RST) {
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router: RST from %016llx svc_id=%u",
(unsigned long long)hdr->src_node_id, hdr->svc_id);
router_close_and_notify(rconn);
return -1;
}
return 0;
}
static void router_handle_data_packet(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* conn,
uint32_t seq, const uint8_t* pl, size_t pl_len) {
(void)conn;
rconn->last_dgram_ts = get_current_timestamp();
if (!rconn->peer_sync_done) { rconn->peer_sync_done = 1; rconn->rx_seq = seq; }
{// SEQ out of bounds
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++; return;
}
}
if (((int32_t)(rconn->rx_seq - seq) > 0) || queue_find_data_by_index(rconn->recv_q, &seq)) {// DUP or delta seq<=0
router_schedule_ack(rconn);// сценарий потеряного ack
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router: dup seq=%u rx_seq=%u, dropping", seq, rconn->rx_seq);
rconn->c_dup_dropped++; return;
}
struct ll_entry* qe = queue_entry_new(4);
if (!qe) return;
*(uint32_t*)qe->data = seq;
qe->dgram = u_malloc(pl_len);
if (!qe->dgram) { queue_dgram_free(qe); queue_entry_free(qe); return; }
qe->len = pl_len; memcpy(qe->dgram, pl, pl_len);
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "router: incoming_q seq=%u rx_seq=%u from %016llx svc_id=%u",
seq, rconn->rx_seq,
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
queue_data_put(rconn->incoming_q, qe);
}
// ====================================================================
// etcp_router_recv_cb — обработчик ETCP_RT_ID_SVC_ROUTE
// ====================================================================
static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
@@ -390,197 +775,36 @@ static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry)
if (!inst || entry->len < SVC_ROUTE_HDR_SIZE) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "etcp_router: invalid packet inst=%p len=%zu min=%u",
(void*)inst, entry->len, (unsigned)SVC_ROUTE_HDR_SIZE);
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
return;
free_entry(entry); return;
}
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)entry->dgram;
size_t pl_len = entry->len - SVC_ROUTE_HDR_SIZE;
uint8_t* pl = entry->dgram + SVC_ROUTE_HDR_SIZE;
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)entry->dgram;
size_t pl_len = entry->len - SVC_ROUTE_HDR_SIZE;
uint8_t* pl = entry->dgram + SVC_ROUTE_HDR_SIZE;
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "ETCP_RECV: svc_id=%u seq=%u len=%zu from %016llx",
hdr->svc_id, hdr->seq, pl_len, (unsigned long long)hdr->src_node_id);
if (hdr->dst_node_id == inst->node_id) {
// ========== Проверка подписи (если флаг ROUTER_FLAG_SIGNED) ==========
size_t actual_payload = pl_len;
if (hdr->flags & ROUTER_FLAG_SIGNED) {
if (pl_len < SC_SIGN_SIZE) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: SIGNED packet too short len=%zu from %016llx", pl_len, (unsigned long long)hdr->src_node_id);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
actual_payload = pl_len - SC_SIGN_SIZE;
uint8_t* sig = pl + actual_payload;
struct ROUTE_BGP* bgp = inst->bgp;
struct NODEINFO_Q* nq = bgp ? nodeinfo_find_by_id(bgp, hdr->src_node_id) : NULL;
if (!nq) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: SIGNED packet from unknown node %016llx — dropping", (unsigned long long)hdr->src_node_id);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
{
uint8_t zero[SC_PUBKEY_SIZE] = {0};
if (memcmp(nq->node.ed25519_public_key, zero, SC_PUBKEY_SIZE) == 0) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: no Ed25519 pubkey for node %016llx — dropping", (unsigned long long)hdr->src_node_id);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
}
struct sc_stream_sign_state vfy_state;
if (sc_stream_sign_verify_init(&vfy_state, nq->node.ed25519_public_key) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router: sign_verify_init failed for node %016llx", (unsigned long long)hdr->src_node_id);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
size_t signed_len = SVC_ROUTE_HDR_SIZE + actual_payload;
if (sc_stream_sign_update(&vfy_state, entry->dgram, signed_len) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router: sign_update failed for node %016llx", (unsigned long long)hdr->src_node_id);
sc_stream_sign_cleanup(&vfy_state); queue_dgram_free(entry); queue_entry_free(entry);
return;
}
if (sc_stream_sign_verify(&vfy_state, sig, SC_SIGN_SIZE) != SC_OK) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: signature verification FAILED for node %016llx svc_id=%u — dropping", (unsigned long long)hdr->src_node_id, hdr->svc_id);
{
struct ETCP_ROUTER_CONN* sr = router_conn_find(inst, hdr->src_node_id, hdr->svc_id);
if (sr) sr->c_sign_fail++;
}
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
pl_len = actual_payload;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router: signature verified for node %016llx svc_id=%u", (unsigned long long)hdr->src_node_id, hdr->svc_id);
}
if (hdr->dst_node_id != inst->node_id) { router_forward_transit(inst, entry, hdr); return; }
// ========== Мы — целевая нода ==========
struct ETCP_ROUTER_CONN* rconn = router_conn_find(inst, hdr->src_node_id, hdr->svc_id);
if (router_verify_signature(inst, entry, hdr, &pl_len) != 0) return;
// CLOSE или RST — pl_len==0 и флаг в seq (закрываем conn)
if (pl_len == 0 && (hdr->seq & (ROUTER_SEQ_CLOSE_FLAG | ROUTER_SEQ_RST_FLAG))) {
if (rconn) router_close_and_notify(rconn);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
struct ETCP_ROUTER_CONN* rconn = router_conn_find(inst, hdr->src_node_id, hdr->svc_id);
if (pl_len == 0) {
if (rconn) {
if ((int32_t)(hdr->seq - rconn->tx_acked) >= 0) {
rconn->tx_acked = hdr->seq;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "SEND_Q_ACKED: svc_id=%u ack=%u inflight=%d send_q=%d from %016llx",
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);
}
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
// Data-пакет — авто-создаём conn если ещё нет
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-флаг
{
uint8_t incoming_sess = (hdr->flags >> ROUTER_SESS_ID_SHIFT) & 0x3;
if (pl_len > 0 && (incoming_sess != rconn->peer_sess_id ||
((hdr->flags & ROUTER_FLAG_START) && rconn->rx_seq > 0))) {
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE,
"router: peer restart sess_id=%u→%u svc_id=%u from %016llx",
rconn->peer_sess_id, incoming_sess, hdr->svc_id,
(unsigned long long)hdr->src_node_id);
etcp_router_conn_restart(inst, hdr->src_node_id, hdr->svc_id);
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 = incoming_sess;
}
// RST-флаг — peer сигналит нарушение последовательности
if (hdr->flags & ROUTER_FLAG_RST) {
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE,
"router: RST from %016llx svc_id=%u",
(unsigned long long)hdr->src_node_id, hdr->svc_id);
router_close_and_notify(rconn);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
}
// Seq-состояние есть — reorder
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;
}
}
// Дубликат (seq уже доставлен или в recv_q)
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;
}
// Кладём в recv_q
struct ll_entry* qe = queue_entry_new(4);
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_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);
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "router: queued seq=%u rx_seq=%u recv_q=%d from %016llx svc_id=%u",
seq, rconn->rx_seq, queue_entry_count(rconn->recv_q),
(unsigned long long)hdr->src_node_id, rconn->svc_id);
queue_data_put_with_index(rconn->recv_q, qe);
if (seq == rconn->rx_seq)
router_try_assembly(rconn, conn);
router_schedule_ack(rconn);
queue_dgram_free(entry); queue_entry_free(entry);
} else {
// ========== Транзит ==========
struct ETCP_CONN* next = route_bgp_find_conn_for_node(inst->bgp, hdr->dst_node_id);
if (!next) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "etcp_router: no route to %016llx svc_id=%u, dropping",
(unsigned long long)hdr->dst_node_id, hdr->svc_id);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
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);
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);
}
if (pl_len == 0 && (hdr->flags & (ROUTER_FLAG_CLOSE | ROUTER_FLAG_RST))) {
if (rconn) router_close_and_notify(rconn);
free_entry(entry); return;
}
if (pl_len == 0) { router_handle_ack(inst, rconn, hdr->seq, hdr->src_node_id); free_entry(entry); return; }
if (!rconn) {
rconn = etcp_router_conn_get(inst, hdr->src_node_id, hdr->svc_id);
if (!rconn) { free_entry(entry); return; }
rconn->peer_sess_id = (hdr->flags >> ROUTER_SESS_ID_SHIFT) & 0x3;
}
if (router_check_peer_restart(inst, &rconn, hdr) != 0) { free_entry(entry); return; }
router_handle_data_packet(rconn, conn, hdr->seq, pl, pl_len);
free_entry(entry);
}
// ====================================================================
@@ -613,21 +837,7 @@ void etcp_router_destroy(struct UTUN_INSTANCE* inst) {
struct ll_entry* entry;
while ((entry = queue_data_get(inst->router_conns)) != NULL) {
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)entry;
router_send_close_to_service(rconn);
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; }
if (rconn->recv_q) {
struct ll_entry* f;
while ((f = queue_data_get(rconn->recv_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
queue_free(rconn->recv_q);
}
if (rconn->send_q) {
struct ll_entry* f;
while ((f = queue_data_get(rconn->send_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
queue_free(rconn->send_q);
}
queue_entry_free(entry);
router_close_and_notify(rconn);
}
queue_free(inst->router_conns);
inst->router_conns = NULL;
@@ -678,9 +888,17 @@ int etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t dst_node_id, struct ll_
struct ETCP_CONN* etcp_conn = route_bgp_find_conn_for_node(inst->bgp, dst_node_id);
if (!etcp_conn) {
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "etcp_route_send: no BGP route to %016llx svc_id=%u, dropping",
(unsigned long long)dst_node_id, svc_id);
struct ETCP_ROUTER_CONN* rconn = etcp_router_conn_get(inst, dst_node_id, svc_id);
if (!rconn) { queue_dgram_free(entry); queue_entry_free(entry); return -1; }
int eq_ret = router_enqueue_send(rconn, entry->dgram + 1, payload_len, force);
queue_dgram_free(entry); queue_entry_free(entry);
if (eq_ret == 0) {
rconn->no_route = 1;
if (!rconn->no_route_timer)
rconn->no_route_timer = uasync_set_timeout(rconn->inst->ua,
ROUTER_NO_ROUTE_RETRY_TB, rconn, router_no_route_retry_cb, "router_no_route");
return 0;
}
return -1;
}
@@ -746,6 +964,8 @@ 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; }
if (rconn->no_route_timer) { uasync_cancel_timeout(inst->ua, rconn->no_route_timer); rconn->no_route_timer = NULL; }
if (rconn->retrans_timer) { uasync_cancel_timeout(inst->ua, rconn->retrans_timer); rconn->retrans_timer = NULL; }
struct ll_entry* f;
if (rconn->send_q) {
@@ -756,9 +976,27 @@ void etcp_router_conn_restart(struct UTUN_INSTANCE* inst, uint64_t remote_node_i
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);
}
if (rconn->inflight_q) {
while ((f = queue_data_get(rconn->inflight_q)) != NULL) {
struct ROUTER_INFLIGHT* inf = (struct ROUTER_INFLIGHT*)f;
if (inf->payload) u_free(inf->payload);
u_free(inf);
}
queue_free(rconn->inflight_q);
}
if (rconn->incoming_q) {
while ((f = queue_data_get(rconn->incoming_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
queue_resume_callback(rconn->incoming_q);
queue_free(rconn->incoming_q);
}
rconn->send_q = queue_new(inst->ua, 0, 0, 0, "router_send_q");
rconn->recv_q = queue_new(inst->ua, ROUTER_RECVQ_HASH_SIZE, 0, 4, "router_recv_q");
queue_set_threshold(rconn->send_q, ROUTER_MAX_SEND_Q_PACKETS / 2, 0);
rconn->inflight_q = queue_new(inst->ua, ROUTER_INFLIGHT_HASH_SIZE, 0, 4, "router_inflight_q");
rconn->incoming_q = queue_new(inst->ua, 0, 0, 0, "router_incoming_q");
queue_set_threshold(rconn->send_q, ROUTER_MAX_SEND_Q_PACKETS - 1, 0);
queue_set_callback(rconn->incoming_q, router_incoming_q_cb, rconn);
queue_set_threshold(rconn->incoming_q, 0, 0);
rconn->incoming_data_ready = 0;
rconn->tx_seq = 0;
rconn->rx_seq = 0;
@@ -773,10 +1011,15 @@ void etcp_router_conn_restart(struct UTUN_INSTANCE* inst, uint64_t remote_node_i
rconn->c_pkts_rcvd = 0;
rconn->c_ack_sent = 0;
rconn->c_ack_recv = 0;
rconn->c_retrans_done = 0;
rconn->c_dup_dropped = 0;
rconn->c_oob_dropped = 0;
rconn->c_stale_ack = 0;
rconn->c_sign_fail = 0;
rconn->last_ack_changed_tb = 0;
rconn->no_route = 0;
rconn->no_ack_count = 0;
rconn->closed = 0;
}
void etcp_router_on_send_ready(struct UTUN_INSTANCE* inst, uint64_t node_id, uint8_t svc_id,
@@ -834,11 +1077,27 @@ struct ETCP_ROUTER_CONN* etcp_router_conn_get(struct UTUN_INSTANCE* inst,
rconn->c_oob_dropped = 0;
rconn->c_stale_ack = 0;
rconn->c_sign_fail = 0;
rconn->c_retrans_done = 0;
rconn->retrans_timer = NULL;
rconn->last_ack_changed_tb = 0;
rconn->no_route = 0;
rconn->no_route_timer = NULL;
rconn->no_ack_count = 0;
rconn->closed = 0;
rconn->close_callback = NULL;
rconn->close_callback_arg = NULL;
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");
if (!rconn->send_q) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_conn_get: queue_new(send_q) failed"); queue_free(rconn->recv_q); queue_entry_free(&rconn->ll); return NULL; }
queue_set_threshold(rconn->send_q, ROUTER_MAX_SEND_Q_PACKETS / 2, 0);
rconn->inflight_q = queue_new(inst->ua, ROUTER_INFLIGHT_HASH_SIZE, 0, 4, "router_inflight_q");
if (!rconn->inflight_q) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_conn_get: queue_new(inflight_q) failed"); queue_free(rconn->send_q); queue_free(rconn->recv_q); queue_entry_free(&rconn->ll); return NULL; }
rconn->incoming_q = queue_new(inst->ua, 0, 0, 0, "router_incoming_q");
if (!rconn->incoming_q) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_conn_get: queue_new(incoming_q) failed"); queue_free(rconn->inflight_q); queue_free(rconn->send_q); queue_free(rconn->recv_q); queue_entry_free(&rconn->ll); return NULL; }
queue_set_callback(rconn->incoming_q, router_incoming_q_cb, rconn);
queue_set_threshold(rconn->incoming_q, 0, 0);
rconn->incoming_data_ready = 0;
queue_set_threshold(rconn->send_q, ROUTER_MAX_SEND_Q_PACKETS - 1, 0);
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router_conn: new conn remote=%016llx svc_id=%u",
(unsigned long long)remote_node_id, svc_id);
queue_data_put_with_index(inst->router_conns, &rconn->ll);
@@ -876,31 +1135,31 @@ void etcp_router_conn_close(struct ETCP_ROUTER_CONN* rconn) {
router_close_and_notify(rconn);
}
void etcp_router_conn_close_async(struct ETCP_ROUTER_CONN* rconn,
void (*on_close_done)(void* arg),
void* close_arg) {
if (!rconn) return;
rconn->close_callback = on_close_done;
rconn->close_callback_arg = close_arg;
router_close_and_notify(rconn);
}
void etcp_router_conn_close_all_for_node(struct UTUN_INSTANCE* inst, uint64_t remote_node_id) {
if (!inst || !inst->router_conns) return;
struct ll_entry* entry;
while ((entry = queue_data_get(inst->router_conns)) != NULL) {
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)entry;
if (rconn->remote_node_id == remote_node_id) {
router_send_close_to_service(rconn);
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; }
if (rconn->recv_q) {
struct ll_entry* f;
while ((f = queue_data_get(rconn->recv_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
queue_free(rconn->recv_q);
}
if (rconn->send_q) {
struct ll_entry* f;
while ((f = queue_data_get(rconn->send_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
queue_free(rconn->send_q);
}
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router_conn: closed by node remote=%016llx svc_id=%u",
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
queue_entry_free(&rconn->ll);
} else {
queue_data_put(inst->router_conns, entry);
// Iterate via hash chains to avoid inf-loop from put-back in FIFO iteration
struct ll_entry* to_close[256];
int count = 0;
struct ll_queue* q = inst->router_conns;
for (uint32_t slot = 0; slot < q->hash_size && count < 256; slot++) {
struct ll_entry* entry = q->hash_table[slot];
while (entry) {
struct ll_entry* next = entry->hash_next;
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)entry;
if (rconn->remote_node_id == remote_node_id)
to_close[count++] = entry;
entry = next;
}
}
for (int i = 0; i < count; i++)
router_close_and_notify((struct ETCP_ROUTER_CONN*)to_close[i]);
}
+39 -5
View File
@@ -14,7 +14,7 @@
#pragma pack(push, 1)
struct SVC_ROUTE_HDR {
uint8_t cmd; // ETCP_ID_SVC_ROUTE (0x03)
uint8_t cmd; // ETCP_ID_SVC_ROUTE (0x03) / ETCP_RT_ID_SVC_ROUTE
uint64_t dst_node_id;
uint64_t src_node_id;
uint32_t seq; // data: tx_seq; ACK: rx_seq (ожидаемый seq)
@@ -29,6 +29,7 @@ struct SVC_ROUTE_HDR {
#define ROUTER_FLAG_START 0x80
#define ROUTER_FLAG_RST 0x40
#define ROUTER_FLAG_SIGNED 0x08 // пакет содержит Ed25519 подпись (64 байта после payload)
#define ROUTER_FLAG_CLOSE 0x02 // нормальное закрытие conn
#define ROUTER_SESS_ID_SHIFT 4
#define ROUTER_SESS_ID_MASK 0x30
@@ -53,16 +54,32 @@ struct ETCP_ROUTER_CONN {
struct ll_queue* send_q; // очередь ожидающих отправки (inflight полон)
void* send_resume_timer; // таймер возобновления отправки
uint8_t send_blocked; // 1 = inflight полон, ждём ack/таймер
uint8_t no_route; // 1 = нет BGP-маршрута, передача приостановлена
void* no_route_timer; // таймер 20ms проверки появления маршрута
uint8_t no_ack_count; // счётчик последовательных ретрансмиссий без ACK
uint8_t closed; // 1 = в процессе закрытия, таймеры игнорируют
void* close_callback; // пользовательский коллбэк завершения закрытия
void* close_callback_arg; // аргумент для close_callback
uint8_t sess_id; // наш session id (0-3)
uint8_t peer_sess_id; // последний sess_id от peer'а
uint8_t start_sent; // 0 = нужно отправить START в первом data
uint8_t peer_sync_done; // 0 = rx_seq ещё не синхронизирован с удалённым seq (авто-создание/рестарт)
// Ретрансмиты: inflight очередь
struct ll_queue* inflight_q; // хеш по seq (4 байта), непрерывный блок без SACK
void* retrans_timer; // таймер проверки застоя ACK
uint64_t last_ack_changed_tb; // когда tx_acked последний раз менялся (0.1ms)
// Очередь приёма входящего трафика (между сетью и recv_q)
struct ll_queue* incoming_q; // FIFO очередь входящих пакетов
uint8_t incoming_data_ready; // 1 = данные в incoming_q и callback был вызван
uint32_t c_pkts_sent; // успешные отправки данных
uint32_t c_pkts_send_err; // ошибки отправки
uint32_t c_pkts_rcvd; // получено и доставлено данных
uint32_t c_ack_sent; // отправлено ACK
uint32_t c_ack_recv; // получено ACK (последовательных)
uint32_t c_retrans_done; // число выполненых ретрансмитов
uint32_t c_dup_dropped; // дропнуто дубликатов seq
uint32_t c_oob_dropped; // дропнуто out-of-bounds seq
uint32_t c_stale_ack; // устаревших ACK
@@ -75,11 +92,23 @@ struct ETCP_ROUTER_CONN {
#define ROUTER_ACK_INTERVAL_TB 100 // интервал ACK: 10ms в timebase (0.1ms)
#define ROUTER_ACK_IDLE_TB 5000 // idle таймаут: 500ms
#define ROUTER_SEND_RESUME_TB 500 // retry интервал send_q: 50ms
#define ROUTER_MAX_SEND_Q_PACKETS 4 // порог backpressure на send_q
#define ROUTER_MAX_SEND_Q_PACKETS 64 // порог backpressure на send_q
// Флаги в старших битах seq (взаимоисключающие)
#define ROUTER_SEQ_CLOSE_FLAG 0x80000000 // нормальное закрытие conn
#define ROUTER_SEQ_RST_FLAG 0x40000000 // conn не найден (reset)
// Ретрансмиты
#define ROUTER_RETRANS_TIMEOUT_TB 3000 // 300ms в timebase (0.1ms)
#define ROUTER_INFLIGHT_HASH_SIZE 1024
#define ROUTER_NO_ROUTE_RETRY_TB 200 // 20ms проверка появления маршрута
#define ROUTER_NO_ACK_MAX_RETRANS 17 // 17 × 300ms ≈ 5s таймаут без ACK
// Inflight запись — копия отправленного пакета для возможного ретрансмита
struct ROUTER_INFLIGHT {
struct ll_entry ll; // индекс по seq (4 байта, offset 0)
uint32_t seq;
uint64_t last_sent_tb; // время последней отправки (0.1ms)
uint8_t send_count; // число переотправок
uint8_t* payload; // копия данных
size_t payload_len;
};
// Bindings для сервисов внутри etcp_router (аналогично ETCP_BINDINGS)
struct ETCP_ROUTER_BINDINGS {
@@ -115,6 +144,11 @@ int etcp_router_conn_send_signed(struct ETCP_ROUTER_CONN* rconn,
// Закрыть seq-подключение
void etcp_router_conn_close(struct ETCP_ROUTER_CONN* rconn);
// Асинхронное закрытие: коллбэк вызывается после освобождения памяти rconn
void etcp_router_conn_close_async(struct ETCP_ROUTER_CONN* rconn,
void (*on_close_done)(void* arg),
void* close_arg);
// Закрыть все router_conn для указанного remote_node_id (peer умер)
void etcp_router_conn_close_all_for_node(struct UTUN_INSTANCE* inst, uint64_t remote_node_id);
+7 -7
View File
@@ -20,7 +20,7 @@ static ip_str_t ip_host_to_str(uint32_t ip_host) {
// ==================== Callbacks ====================
// CLIENT: NAT TUN output → encapsulate in ETCP_ID_NAT → send to provider via etcp_router
// CLIENT: NAT TUN output → encapsulate in ETCP_RT_ID_NAT → send to provider via etcp_router
static void nat_transport_client_tun_out_cb(struct ll_queue* q, void* arg) {
struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)arg;
if (!inst) { queue_resume_callback(q); return; }
@@ -35,7 +35,7 @@ static void nat_transport_client_tun_out_cb(struct ll_queue* q, void* arg) {
uint8_t* new_dgram = u_malloc(total_len);
if (!new_dgram) { queue_dgram_free(pkt); queue_entry_free(pkt); queue_resume_callback(q); return; }
new_dgram[0] = ETCP_ID_NAT;
new_dgram[0] = ETCP_RT_ID_NAT;
memcpy(new_dgram + 1, &tr->self_node_id, 8);
memcpy(new_dgram + 9, pkt->dgram + 1, ip_len);
@@ -78,7 +78,7 @@ static void nat_transport_provider_tun_out_cb(struct ll_queue* q, void* arg) {
uint8_t* new_dgram = u_malloc(total_len);
if (!new_dgram) { queue_dgram_free(pkt); queue_entry_free(pkt); queue_resume_callback(q); return; }
new_dgram[0] = ETCP_ID_NAT;
new_dgram[0] = ETCP_RT_ID_NAT;
memcpy(new_dgram + 1, &inst->nat_tr.self_node_id, 8);
memcpy(new_dgram + 9, pkt->dgram + 1, ip_len);
@@ -98,7 +98,7 @@ static void nat_transport_provider_tun_out_cb(struct ll_queue* q, void* arg) {
}
}
// ETCP_ID_NAT receive via etcp_router: CLIENT gets response, PROVIDER gets request
// ETCP_RT_ID_NAT receive via etcp_router: CLIENT gets response, PROVIDER gets request
static void nat_transport_etcp_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (!conn || !entry || !entry->dgram || entry->len < NAT_SVC_HDR_SIZE) {
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
@@ -173,8 +173,8 @@ int nat_transport_init(struct UTUN_INSTANCE* inst) {
return -1;
}
if (etcp_router_bind(inst, ETCP_ID_NAT, nat_transport_etcp_recv_cb) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Failed to bind ETCP_ID_NAT via etcp_router");
if (etcp_router_bind(inst, ETCP_RT_ID_NAT, nat_transport_etcp_recv_cb) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Failed to bind ETCP_RT_ID_NAT via etcp_router");
tun_close(tr->nat_tun); tr->nat_tun = NULL;
eim_nat_destroy_ctx(&inst->nat);
return -1;
@@ -200,7 +200,7 @@ void nat_transport_destroy(struct UTUN_INSTANCE* inst) {
if (!inst || !inst->nat_tr.initialized) return;
struct nat_transport_ctx* tr = &inst->nat_tr;
etcp_router_unbind(inst, ETCP_ID_NAT);
etcp_router_unbind(inst, ETCP_RT_ID_NAT);
if (tr->nat_tun) { tun_close(tr->nat_tun); tr->nat_tun = NULL; }
+5 -5
View File
@@ -111,7 +111,7 @@ static void raw_read_cb(socket_t sock, void* arg) {
if (!e) return;
e->dgram = u_malloc(ICMP_PROXY_HDR_SIZE + payload_len);
if (!e->dgram) { queue_entry_free(e); return; }
e->dgram[0] = ETCP_ID_ICMP_PROXY;
e->dgram[0] = ETCP_RT_ID_ICMP_PROXY;
e->dgram[1] = ICMP_PROXY_SUBCMD_REPLY;
memcpy(e->dgram + 2, &r->client_node_id, 8);
memcpy(e->dgram + 10, &r->dst_ip, 4);
@@ -149,7 +149,7 @@ static void exit_handle_request(struct ETCP_CONN* conn, struct ll_entry* entry)
if (e) {
e->dgram = u_malloc(ICMP_PROXY_HDR_SIZE + payload_len);
if (e->dgram) {
e->dgram[0] = ETCP_ID_ICMP_PROXY;
e->dgram[0] = ETCP_RT_ID_ICMP_PROXY;
e->dgram[1] = ICMP_PROXY_SUBCMD_REPLY;
memcpy(e->dgram + 2, &client_node_id, 8);
memcpy(e->dgram + 10, &dst_ip, 4);
@@ -214,7 +214,7 @@ int icmp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
if (!e) return -1;
e->dgram = u_malloc(ICMP_PROXY_HDR_SIZE + payload_len);
if (!e->dgram) { queue_entry_free(e); return -1; }
e->dgram[0] = ETCP_ID_ICMP_PROXY;
e->dgram[0] = ETCP_RT_ID_ICMP_PROXY;
e->dgram[1] = ICMP_PROXY_SUBCMD_REQUEST;
memcpy(e->dgram + 2, &inst->node_id, 8);
memcpy(e->dgram + 10, &dst_ip, 4);
@@ -322,7 +322,7 @@ int icmp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua) {
}
}
etcp_router_bind(inst, ETCP_ID_ICMP_PROXY, icmp_proxy_recv_cb);
etcp_router_bind(inst, ETCP_RT_ID_ICMP_PROXY, icmp_proxy_recv_cb);
ctx->initialized = 1;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy initialized (exit=%d raw_sock=%d)", ctx->is_exit, (int)ctx->raw_sock);
return 0;
@@ -330,7 +330,7 @@ int icmp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua) {
void icmp_proxy_destroy(struct UTUN_INSTANCE* inst) {
if (!inst || !g_icmp_ctx) return;
etcp_router_unbind(inst, ETCP_ID_ICMP_PROXY);
etcp_router_unbind(inst, ETCP_RT_ID_ICMP_PROXY);
if (g_icmp_ctx->expire_timer) { uasync_cancel_timeout(g_icmp_ctx->ua, g_icmp_ctx->expire_timer); g_icmp_ctx->expire_timer = NULL; }
if (g_icmp_ctx->raw_sock != SOCKET_INVALID) {
if (g_icmp_ctx->raw_read_id) { uasync_remove_socket_t(g_icmp_ctx->ua, g_icmp_ctx->raw_sock); g_icmp_ctx->raw_read_id = NULL; }
+167 -34
View File
@@ -51,7 +51,7 @@ static int send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd, ui
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[0] = ETCP_RT_ID_TCP_PROXY;
e->dgram[1] = subcmd;
memcpy(e->dgram + 2, &sid, 4);
if (len > 0) memcpy(e->dgram + TCP_PROXY_HDR_SIZE, data, len);
@@ -60,7 +60,10 @@ static int send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd, ui
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);
int ret = etcp_route_send(inst, dst, e, force);
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "SOCKS send_msg subcmd=%02x sid=%08x len=%zu force=%d → ret=%d",
subcmd, sid, len, force, ret);
return ret;
}
static int send_connect(struct socks_proxy_conn* c) {
@@ -77,16 +80,19 @@ static int send_data(struct socks_proxy_conn* c, const uint8_t* data, uint16_t l
}
static void send_close(struct socks_proxy_conn* c) {
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "SOCKS send CLOSE sid=%08x", c->stream_id);
if (send_msg(c->inst, c->via_node_id, TCP_PROXY_SUBCMD_CLOSE, c->stream_id, NULL, 0, 1) < 0) c->close_pending = 1;
else { c->close_pending = 0; c->close_sent = 1; }
}
static void send_error(struct socks_proxy_conn* c) {
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "SOCKS send ERROR sid=%08x", c->stream_id);
if (send_msg(c->inst, c->via_node_id, TCP_PROXY_SUBCMD_ERROR, c->stream_id, NULL, 0, 1) < 0) c->close_pending = 1;
else { c->close_pending = 0; c->close_sent = 1; }
}
static void send_fin(struct socks_proxy_conn* c) {
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "SOCKS send FIN sid=%08x", c->stream_id);
send_msg(c->inst, c->via_node_id, TCP_PROXY_SUBCMD_FIN, c->stream_id, NULL, 0, 1);
}
@@ -110,6 +116,7 @@ static int write_to_client(struct socks_proxy_conn* c, const uint8_t* data, uint
memcpy(buf, data, len);
e->dgram = buf; e->len = len;
queue_data_put(c->tc->write_queue, e);
c->bytes_to_client += len;
return 0;
}
@@ -186,48 +193,149 @@ error: {
}
// ====================================================================
// HTTP CONNECT handshake
// HTTP CONNECT / HTTP proxy 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; }
char* line_end = memmem(c->buf, c->buf_len, "\r\n", 2);
if (!line_end) return;
uint16_t line_len = (uint16_t)((uint8_t*)end - c->buf);
uint16_t line_len = (uint16_t)((uint8_t*)line_end - c->buf);
if (line_len < 8) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: http request too short"); goto error; }
// Парсим "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);
// ===== CONNECT =====
if (strncmp(line, "CONNECT ", 8) == 0) {
char host_port[256];
if (sscanf(line, "CONNECT %255s HTTP/", host_port) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad http connect: %s", line);
goto error;
}
char* colon = strrchr(host_port, ':');
if (!colon) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: no port in %s", host_port); goto error; }
*colon = '\0'; int port = atoi(colon + 1);
if (port <= 0 || port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad port %d", port); goto error; }
struct hostent* he = gethostbyname(host_port);
if (!he || he->h_addrtype != AF_INET) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: DNS failed for %s", host_port);
uint8_t resp[] = "HTTP/1.1 502 Bad Gateway\r\n\r\n";
write_to_client(c, resp, (uint16_t)strlen((char*)resp)); tcp_conn_push_close(c->tc); return;
}
memcpy(c->dest_ip, he->h_addr, 4); c->dest_port = htons((uint16_t)port);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: HTTP CONNECT %s → %d.%d.%d.%d:%d",
host_port, c->dest_ip[0], c->dest_ip[1], c->dest_ip[2], c->dest_ip[3], port);
c->buf_len = 0;
c->state = HTTP_STATE_RELAY;
uint8_t resp[] = "HTTP/1.1 200 Connection Established\r\n\r\n";
write_to_client(c, resp, (uint16_t)strlen((char*)resp));
if (send_connect(c) < 0) { tcp_conn_push_close(c->tc); }
return;
}
// ===== Не-CONNECT: HTTP-прокси (GET, POST, PUT, HEAD, OPTIONS, ...) =====
char* hdr_end = memmem(c->buf, c->buf_len, "\r\n\r\n", 4);
if (!hdr_end) return;
uint16_t headers_len = (uint16_t)((uint8_t*)hdr_end - c->buf) + 4;
char method[16] = {0}, url[512] = {0};
if (sscanf(line, "%15s %511s", method, url) < 2) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad http request line: %s", line);
goto error;
}
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; }
if (strncmp(url, "http://", 7) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: not an absolute http URL: %s", url);
goto error;
}
struct hostent* he = gethostbyname(host_port);
char host[256];
int port = 80;
const char* host_start = url + 7;
const char* path_start = strchr(host_start, '/');
const char* col = NULL;
for (const char* p = host_start; (path_start ? p < path_start : *p); p++) {
if (*p == ':') { col = p; break; }
}
if (col && (!path_start || col < path_start)) {
size_t hlen = (size_t)(col - host_start);
if (hlen >= sizeof(host)) goto error;
memcpy(host, host_start, hlen); host[hlen] = '\0';
port = atoi(col + 1);
if (port <= 0 || port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad port %d in %s", port, url); goto error; }
} else if (path_start) {
size_t hlen = (size_t)(path_start - host_start);
if (hlen >= sizeof(host)) goto error;
memcpy(host, host_start, hlen); host[hlen] = '\0';
} else {
size_t hlen = strlen(host_start);
if (hlen >= sizeof(host)) goto error;
memcpy(host, host_start, hlen); host[hlen] = '\0';
}
if (host[0] == '\0') { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: empty host in %s", url); goto error; }
if (!path_start || *path_start == '\0') path_start = "/";
struct hostent* he = gethostbyname(host);
if (!he || he->h_addrtype != AF_INET) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: DNS failed for %s", host_port);
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: DNS failed for %s", host);
uint8_t resp[] = "HTTP/1.1 502 Bad Gateway\r\n\r\n";
write_to_client(c, resp, (uint16_t)strlen((char*)resp)); tcp_conn_push_close(c->tc); return;
}
memcpy(c->dest_ip, he->h_addr, 4); c->dest_port = htons((uint16_t)port);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: HTTP 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);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: HTTP %s %s:%d%s → %d.%d.%d.%d:%d",
method, host, port, path_start,
c->dest_ip[0], c->dest_ip[1], c->dest_ip[2], c->dest_ip[3], port);
if (send_connect(c) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: send_connect failed for %s", host);
uint8_t resp[] = "HTTP/1.1 502 Bad Gateway\r\n\r\n";
write_to_client(c, resp, (uint16_t)strlen((char*)resp)); tcp_conn_push_close(c->tc); return;
}
const char* version = strstr(line, " HTTP/");
char version_str[16] = " HTTP/1.1";
if (version) {
size_t vlen = strlen(version);
if (vlen >= sizeof(version_str)) vlen = sizeof(version_str) - 1;
memcpy(version_str, version, vlen);
version_str[vlen] = '\0';
}
uint16_t rest_off = line_len + 2;
uint16_t rest_len = headers_len - rest_off;
uint8_t hdr_buf[2048];
int hdr_n = snprintf((char*)hdr_buf, sizeof(hdr_buf), "%s %s%s\r\n", method, path_start, version_str);
if (hdr_n < 0 || (size_t)hdr_n + rest_len > sizeof(hdr_buf)) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: header reconstruction overflow hdr_n=%d rest=%u", hdr_n, rest_len);
goto error;
}
memcpy(hdr_buf + hdr_n, c->buf + rest_off, rest_len);
uint16_t hdr_chunk = (uint16_t)hdr_n + rest_len;
uint16_t extra = (headers_len < c->buf_len) ? (uint16_t)(c->buf_len - headers_len) : 0;
uint16_t total = hdr_chunk + extra;
uint8_t* pkt = u_malloc(total);
if (!pkt) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: tx pkt malloc=%u failed sid=%08x", total, c->stream_id); goto error; }
memcpy(pkt, hdr_buf, hdr_chunk);
if (extra) memcpy(pkt + hdr_chunk, c->buf + headers_len, extra);
int ret = send_data(c, pkt, total);
if (ret == 0) {
u_free(pkt);
} else {
c->tx_buf = pkt; c->tx_len = total;
etcp_router_on_send_ready(c->inst, c->via_node_id, ETCP_RT_ID_TCP_PROXY, &c->tx_waiter, tx_waiter_cb, c);
}
c->buf_len = 0;
c->state = HTTP_STATE_RELAY; // 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);
}
c->state = HTTP_STATE_RELAY;
return;
error: {
@@ -272,22 +380,38 @@ static void on_read_cb(struct ll_queue* q, void* arg) {
}
// RELAY = релей данных в ETCP
if (c->tx_buf) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: RELAY with pending tx_buf sid=%08x — drop new len=%u", c->stream_id, e->len);
memory_pool_free(c->tc->data_pool, e->dgram); queue_entry_free(e);
return;
}
int ret = send_data(c, e->dgram, e->len);
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "SOCKS PROXY SEND sid=%08x len=%u", c->stream_id, e->len);
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "SOCKS PROXY SEND sid=%08x len=%u ret=%d is_http=%d", c->stream_id, e->len, ret, c->is_http);
if (ret == 0) {
memory_pool_free(c->tc->data_pool, e->dgram); queue_entry_free(e);
queue_resume_callback(q);
} else {
c->tx_buf = u_malloc(e->len);
if (c->tx_buf) { memcpy(c->tx_buf, e->dgram, e->len); c->tx_len = e->len; }
else { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: tx_buf malloc=%u failed sid=%08x — drop", e->len, c->stream_id); }
memory_pool_free(c->tc->data_pool, e->dgram); queue_entry_free(e);
etcp_router_waiter_register(c->inst, c->via_node_id, &c->tx_waiter, tx_waiter_cb, c);
etcp_router_on_send_ready(c->inst, c->via_node_id, ETCP_RT_ID_TCP_PROXY, &c->tx_waiter, tx_waiter_cb, c);
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCKS PROXY BP: sid=%08x tx_buf=%u waiter_reg", c->stream_id, c->tx_len);
}
}
static void tx_waiter_cb(struct ll_queue* q, void* arg) {
(void)q;
struct socks_proxy_conn* c = (struct socks_proxy_conn*)arg;
if (c->rem_closed || c->close_sent) return;
queue_resume_callback(c->tc->read_queue);
if (!c->tx_buf || c->rem_closed || c->close_sent) return;
int ret = send_data(c, c->tx_buf, c->tx_len);
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCKS PROXY WAKE: sid=%08x tx_buf=%u ret=%d is_http=%d", c->stream_id, c->tx_len, ret, c->is_http);
if (ret == 0) {
u_free(c->tx_buf); c->tx_buf = NULL; c->tx_len = 0;
queue_resume_callback(c->tc->read_queue);
} else {
etcp_router_on_send_ready(c->inst, c->via_node_id, ETCP_RT_ID_TCP_PROXY, &c->tx_waiter, tx_waiter_cb, c);
}
}
// ====================================================================
@@ -311,14 +435,18 @@ static void on_fin_cb(struct tcp_conn* tc, void* arg) {
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);
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: tcp error sid=%08x err=%d state=%d fm_r=%d fm_l=%d rem_cl=%d cs=%d cp=%d "
"bytes_client=%u bytes_exit=%u wq=%d",
c->stream_id, err, c->state, tc->fin_remote, tc->fin_local, c->rem_closed, c->close_sent, c->close_pending,
c->bytes_to_client, c->bytes_from_exit, c->tc->write_queue->count);
if (!c->close_sent && !c->close_pending) send_error(c);
socks_proxy_conn_free(c);
}
static void on_closed_cb(struct tcp_conn* tc, void* arg) {
struct socks_proxy_conn* c = (struct socks_proxy_conn*)arg;
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "socks_proxy: tcp closed sid=%08x", c->stream_id);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: tcp closed sid=%08x state=%d fm_r=%d fm_l=%d rem_cl=%d bytes_client=%u bytes_exit=%u",
c->stream_id, c->state, tc->fin_remote, tc->fin_local, c->rem_closed, c->bytes_to_client, c->bytes_from_exit);
if (!c->close_sent && !c->close_pending) send_close(c);
socks_proxy_conn_free(c);
}
@@ -370,6 +498,8 @@ int socks_proxy_handle_etcp(struct socks_proxy_conn** head, int* count,
if (!c) return 0;
if (subcmd == TCP_PROXY_SUBCMD_DATA) {
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "SOCKS DATA <- sid=%08x len=%zu", stream_id, data_len);
c->bytes_from_exit += (uint32_t)data_len;
if (data_len > 0) {
if (data_len > c->tc->data_pool->object_size) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: data_len=%zu > pool_sz=%zu, dropping sid=%08x",
@@ -381,8 +511,10 @@ int socks_proxy_handle_etcp(struct socks_proxy_conn** head, int* count,
if (e && buf) {
memcpy(buf, data, data_len); e->dgram = buf; e->len = (uint16_t)data_len;
queue_data_put(c->tc->write_queue, e);
c->bytes_to_client += (uint32_t)data_len;
} else {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: handle_data alloc failed sid=%08x", stream_id);
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: handle_data alloc failed sid=%08x wq=%d bytes_in=%u bytes_out=%u",
stream_id, c->tc->write_queue->count, c->bytes_from_exit, c->bytes_to_client);
if (e) queue_entry_free(e); if (buf) memory_pool_free(c->tc->data_pool, buf);
}
}
@@ -392,7 +524,7 @@ int socks_proxy_handle_etcp(struct socks_proxy_conn** head, int* count,
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);
etcp_router_cancel_send_ready(c->inst, c->via_node_id, ETCP_RT_ID_TCP_PROXY, &c->tx_waiter);
tcp_conn_push_close(c->tc);
return 1;
}
@@ -400,7 +532,7 @@ int socks_proxy_handle_etcp(struct socks_proxy_conn** head, int* count,
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);
etcp_router_cancel_send_ready(c->inst, c->via_node_id, ETCP_RT_ID_TCP_PROXY, &c->tx_waiter);
tcp_conn_push_close(c->tc);
return 1;
}
@@ -428,6 +560,7 @@ void socks_proxy_conn_free(struct socks_proxy_conn* c) {
send_msg(c->inst, c->via_node_id, subcmd, c->stream_id, NULL, 0, 1);
}
if (head) { struct socks_proxy_conn** prev = head; while (*prev) { if (*prev == c) { *prev = c->next; if (count) (*count)--; break; } prev = &(*prev)->next; } }
if (c->tx_buf) { u_free(c->tx_buf); c->tx_buf = NULL; c->tx_len = 0; }
if (c->tc) { tcp_conn_destroy(c->tc); c->tc = NULL; }
if (c->inst) etcp_router_waiter_cancel(c->inst, c->via_node_id, &c->tx_waiter);
u_free(c);
+4
View File
@@ -45,6 +45,10 @@ struct socks_proxy_conn {
uint8_t freed;
uint8_t buf[1024];
uint16_t buf_len;
uint8_t* tx_buf; // буфер при backpressure (retry в tx_waiter_cb)
uint16_t tx_len;
uint32_t bytes_to_client; // байт записано клиенту (curl) через write_queue
uint32_t bytes_from_exit; // байт получено от exit node через ETCP DATA
struct queue_waiter_handle tx_waiter;
};
+7 -5
View File
@@ -70,7 +70,7 @@ static int tcp_proxy_client_send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, u
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_send_msg: queue_entry_new failed subcmd=%02x sid=%08x", subcmd, sid); return -1; }
e->dgram = u_malloc(TCP_PROXY_HDR_SIZE + len);
if (!e->dgram) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_send_msg: malloc(%zu) failed subcmd=%02x sid=%08x", TCP_PROXY_HDR_SIZE + len, subcmd, sid); queue_entry_free(e); return -1; }
e->dgram[0] = ETCP_ID_TCP_PROXY;
e->dgram[0] = ETCP_RT_ID_TCP_PROXY;
e->dgram[1] = subcmd;
memcpy(e->dgram + 2, &sid, 4);
if (len > 0) memcpy(e->dgram + TCP_PROXY_HDR_SIZE, data, len);
@@ -517,7 +517,7 @@ void tcp_proxy_client_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* en
struct tcp_proxy_client* proxy = inst ? inst->tcp_proxy_client : NULL;
if (!entry || !entry->dgram || entry->len < TCP_PROXY_HDR_SIZE) {
if (entry && entry->len == 9 && !conn && entry->dgram[0] == ETCP_ID_TCP_PROXY_CLIENT) {
if (entry && entry->len == 9 && !conn && entry->dgram[0] == ETCP_RT_ID_TCP_PROXY) {
uint64_t peer_id;
memcpy(&peer_id, entry->dgram + 1, 8);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY CLOSE_ALL from %016llx — clearing client conns for peer",
@@ -536,6 +536,8 @@ void tcp_proxy_client_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* en
uint8_t subcmd = entry->dgram[1];
uint32_t stream_id; memcpy(&stream_id, entry->dgram + 2, 4);
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "PROXY CLIENT RECV subcmd=%02x sid=%08x pkt_len=%u", subcmd, stream_id, entry->len);
if (proxy) {
size_t data_len = entry->len > TCP_PROXY_HDR_SIZE ? entry->len - TCP_PROXY_HDR_SIZE : 0;
// Пробуем SOCKS/HTTP first (у них приоритет — могут быть без TUN)
@@ -622,10 +624,10 @@ struct tcp_proxy_client* tcp_proxy_client_create(struct UTUN_INSTANCE* inst, str
}
if (inst) {
if (etcp_router_bind(inst, ETCP_ID_TCP_PROXY_CLIENT, tcp_proxy_client_router_recv_cb) != 0) {
if (etcp_router_bind(inst, ETCP_RT_ID_TCP_PROXY, tcp_proxy_client_router_recv_cb) != 0) {
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy client: etcp_router_bind failed");
} else {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client: etcp_router bind registered for ID=0x%02x", ETCP_ID_TCP_PROXY_CLIENT);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client: etcp_router bind registered for ID=0x%02x", ETCP_RT_ID_TCP_PROXY);
if (need_tun) { udp_proxy_init(inst, ua); icmp_proxy_init(inst, ua); }
}
}
@@ -641,7 +643,7 @@ void tcp_proxy_client_destroy(struct tcp_proxy_client* p) {
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);
if (p->inst) etcp_router_unbind(p->inst, ETCP_RT_ID_TCP_PROXY);
udp_proxy_destroy(p->inst);
icmp_proxy_destroy(p->inst);
+104 -23
View File
@@ -33,6 +33,7 @@ static void read_queue_drain_cb(struct ll_queue* q, void* arg);
static void pause_resume_cb(struct ll_queue* q, void* arg);
static void close_retry_cb(void* arg);
static void diag_timer_cb(void* arg);
static void retry_timer_cb(void* arg);
static int send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd,
uint32_t sid, const uint8_t* data, size_t len, int force);
static void send_close(struct tcp_proxy_server_conn* rc);
@@ -53,7 +54,7 @@ static int send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd,
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_server: queue_entry_new failed subcmd=%02x sid=%08x", subcmd, sid); return -1; }
e->dgram = u_malloc(TCP_PROXY_HDR_SIZE + len);
if (!e->dgram) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_server: malloc(%zu) failed", TCP_PROXY_HDR_SIZE + len); queue_entry_free(e); return -1; }
e->dgram[0] = ETCP_ID_TCP_PROXY_CLIENT;
e->dgram[0] = ETCP_RT_ID_TCP_PROXY;
e->dgram[1] = subcmd;
memcpy(e->dgram + 2, &sid, 4);
if (len > 0) memcpy(e->dgram + TCP_PROXY_HDR_SIZE, data, len);
@@ -116,19 +117,26 @@ static void send_fin(struct tcp_proxy_server_conn* rc) {
static void on_fin_cb(struct tcp_conn* tc, void* arg) {
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
if (rc->cli_closed) return;
int pend = write_pending(tc);
int pend_w = write_pending(tc);
int pend_r = (rc->tx_buf != NULL) || (tc->read_queue && tc->read_queue->head);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET,
"SOCK:DST_FIN fd=%d sid=%08x fin_l=%d pend_w=%d pend_r=%d "
"sent=%u recv=%u relayed=%u drain#=%u data#=%u "
"rq=%d(%zub) wq=%d(%zub) wbuf=%s err=%d tx_buf=%s",
(int)tc->sock, rc->stream_id, tc->fin_local, pend_w, pend_r,
rc->bytes_sent, rc->bytes_recv, rc->bytes_relayed,
rc->drain_count, rc->data_count,
tc->read_queue->count, queue_total_bytes(tc->read_queue),
tc->write_queue->count, queue_total_bytes(tc->write_queue),
tc->write_buf ? "y" : "n", tc->error, rc->tx_buf ? "y" : "n");
if (tc->fin_local) {
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x fin_local=%d — both FINs, closing",
(int)tc->sock, rc->stream_id, tc->fin_local);
send_close(rc); tcp_conn_push_close(tc);
} else if (pend) {
} else if (pend_w) {
tcp_conn_set_flushed(tc, on_flushed_cb);
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x write_pend=%d → deferred",
(int)tc->sock, rc->stream_id, pend);
} else if (pend_r) {
rc->dst_fin_deferred = 1;
} else {
send_fin(rc);
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:DST_FIN fd=%d sid=%08x → relay now",
(int)tc->sock, rc->stream_id);
}
}
@@ -154,6 +162,16 @@ static void on_error_cb(struct tcp_conn* tc, int err, void* arg) {
(void)err;
if (tc->closed) return;
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET,
"SOCK:ERR fd=%d sid=%08x err=%d fin_r=%d fin_l=%d "
"sent=%u recv=%u relayed=%u drain#=%u data#=%u "
"rq=%d wq=%d wbuf=%s conn=%d",
tc ? (int)tc->sock : -1, rc->stream_id, err,
tc ? tc->fin_remote : 0, tc ? tc->fin_local : 0,
rc->bytes_sent, rc->bytes_recv, rc->bytes_relayed,
rc->drain_count, rc->data_count,
tc ? tc->read_queue->count : 0, tc ? tc->write_queue->count : 0,
tc && tc->write_buf ? "y" : "n", tc ? tc->connected : 0);
send_error(rc);
tcp_proxy_server_conn_free(rc);
}
@@ -193,8 +211,36 @@ static void diag_timer_cb(void* arg) {
static void read_queue_drain_cb(struct ll_queue* q, void* arg) {
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
struct ll_entry* e = queue_data_get(q);
if (!e) { queue_resume_callback(q); return; }
struct ll_entry* e;
if (rc->tx_buf) {
int ret = send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_DATA, rc->stream_id, rc->tx_buf, rc->tx_len, 0);
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "TPS TXBUF RETRY: sid=%08x len=%u ret=%d", rc->stream_id, rc->tx_len, ret);
if (ret == 0) {
u_free(rc->tx_buf); rc->tx_buf = NULL; rc->tx_len = 0;
if (rc->retry_timer) { uasync_cancel_timeout(rc->ua, rc->retry_timer); rc->retry_timer = NULL; }
if (rc->dst_fin_deferred && !q->head) {
rc->dst_fin_deferred = 0;
send_fin(rc);
return;
}
} else {
etcp_router_on_send_ready(inst, rc->peer_node_id, ETCP_RT_ID_TCP_PROXY,
&rc->pause_waiter, pause_resume_cb, rc);
if (!rc->retry_timer) rc->retry_timer = uasync_set_timeout(rc->ua, 5000, rc, retry_timer_cb, "tps_retry");
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "TPS TXBUF BP AGAIN: sid=%08x waiter_reg", rc->stream_id);
return;
}
}
e = queue_data_get(q);
if (!e) {
if (rc->dst_fin_deferred) {
rc->dst_fin_deferred = 0;
send_fin(rc);
}
queue_resume_callback(q); return;
}
if (rc->cli_closed) {
do {
memory_pool_free(rc->tc->data_pool, e->dgram);
@@ -205,27 +251,55 @@ static void read_queue_drain_cb(struct ll_queue* q, void* arg) {
return;
}
int ret = send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_DATA, rc->stream_id, e->dgram, e->len, 0);
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "SOCK:RECV fd=%d sid=%08x len=%u total=%d",
(int)rc->tc->sock, rc->stream_id, e->len, rc->ctx ? rc->ctx->conn_count : 0);
memory_pool_free(rc->tc->data_pool, e->dgram);
queue_entry_free(e);
rc->bytes_relayed += e->len; rc->drain_count++;
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "TPS DRAIN #%u sid=%08x len=%u → ret=%d rq=%d",
rc->drain_count, rc->stream_id, e->len, ret, q->count);
if (ret == 0) {
memory_pool_free(rc->tc->data_pool, e->dgram); queue_entry_free(e);
queue_resume_callback(q);
} else {
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:BACKPRESSURE fd=%d sid=%08x — pausing drain",
(int)rc->tc->sock, rc->stream_id);
etcp_router_on_send_ready(inst, rc->peer_node_id, ETCP_ID_TCP_PROXY_CLIENT,
rc->tx_buf = u_malloc(e->len);
if (rc->tx_buf) { memcpy(rc->tx_buf, e->dgram, e->len); rc->tx_len = e->len; }
else { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TPS tx_buf malloc=%u failed sid=%08x — drop", e->len, rc->stream_id); }
memory_pool_free(rc->tc->data_pool, e->dgram); queue_entry_free(e);
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:BACKPRESSURE fd=%d sid=%08x — buffered %u for retry",
(int)rc->tc->sock, rc->stream_id, rc->tx_len);
etcp_router_on_send_ready(inst, rc->peer_node_id, ETCP_RT_ID_TCP_PROXY,
&rc->pause_waiter, pause_resume_cb, rc);
if (!rc->retry_timer) rc->retry_timer = uasync_set_timeout(rc->ua, 5000, rc, retry_timer_cb, "tps_retry");
}
}
static void pause_resume_cb(struct ll_queue* q, void* arg) {
(void)q;
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
if (!rc->tc || rc->tc->sock == SOCKET_INVALID) return;
if (!rc->tc || rc->tc->sock == SOCKET_INVALID || rc->cli_closed) return;
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "TPS RESUME sid=%08x tx_buf=%s rq=%d",
rc->stream_id, rc->tx_buf ? "y" : "n",
rc->tc->read_queue ? rc->tc->read_queue->count : 0);
queue_resume_callback(rc->tc->read_queue);
}
static void retry_timer_cb(void* arg) {
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
rc->retry_timer = NULL;
if (!rc->tx_buf || !rc->tc || rc->tc->sock == SOCKET_INVALID || rc->cli_closed) return;
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
int ret = send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_DATA, rc->stream_id, rc->tx_buf, rc->tx_len, 1);
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "TPS RETRY TIMER: sid=%08x len=%u force=1 ret=%d", rc->stream_id, rc->tx_len, ret);
if (ret == 0) {
u_free(rc->tx_buf); rc->tx_buf = NULL; rc->tx_len = 0;
if (rc->dst_fin_deferred && rc->tc->read_queue && !rc->tc->read_queue->head) {
rc->dst_fin_deferred = 0;
send_fin(rc);
return;
}
queue_resume_callback(rc->tc->read_queue);
} else {
rc->retry_timer = uasync_set_timeout(rc->ua, 5000, rc, retry_timer_cb, "tps_retry");
}
}
// ====================================================================
// Управление жизненным циклом коннекта
// ====================================================================
@@ -257,7 +331,9 @@ 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->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->ctx && rc->ctx->inst) etcp_router_cancel_send_ready(rc->ctx->inst, rc->peer_node_id, ETCP_RT_ID_TCP_PROXY, &rc->pause_waiter);
if (rc->tx_buf) { u_free(rc->tx_buf); rc->tx_buf = NULL; rc->tx_len = 0; }
if (rc->retry_timer) { uasync_cancel_timeout(rc->ua, rc->retry_timer); rc->retry_timer = NULL; }
if (rc->close_timer) { uasync_cancel_timeout(rc->ua, rc->close_timer); rc->close_timer = NULL; }
if (rc->diag_timer) { uasync_cancel_timeout(rc->ua, rc->diag_timer); rc->diag_timer = NULL; }
if (rc->tc) { tcp_conn_destroy(rc->tc); rc->tc = NULL; }
@@ -291,7 +367,7 @@ int tcp_proxy_server_handle_connect(struct UTUN_INSTANCE* inst, struct ll_entry*
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: socket() failed"); u_free(rc); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
ctx->conn_count++;
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:NEW fd=%d sid=%08x dest=%d.%d.%d.%d:%d total=%d",
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:NEW fd=%d sid=%08x dest=%d.%d.%d.%d:%d total=%d",
(int)sock, stream_id, dest_ip[0],dest_ip[1],dest_ip[2],dest_ip[3],ntohs(dest_port), ctx->conn_count);
socket_set_nonblocking(sock);
@@ -329,6 +405,9 @@ int tcp_proxy_server_handle_data(struct UTUN_INSTANCE* inst, struct ETCP_CONN* c
queue_dgram_free(entry); queue_entry_free(entry); return -1;
}
size_t data_len = entry->len - TCP_PROXY_HDR_SIZE;
rc->bytes_sent += (uint32_t)data_len; rc->data_count++;
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "TPS DATA #%u sid=%08x len=%zu",
rc->data_count, stream_id, data_len);
if (data_len > 0) {
if (data_len > rc->tc->data_pool->object_size) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_server: data_len=%zu > pool_sz=%zu, dropping sid=%08x",
@@ -401,7 +480,7 @@ void tcp_proxy_server_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
struct UTUN_INSTANCE* inst = conn ? conn->instance : NULL;
if (!entry || !entry->dgram || entry->len < TCP_PROXY_HDR_SIZE) {
if (entry && entry->len == 9 && !conn && entry->dgram[0] == ETCP_ID_TCP_PROXY) {
if (entry && entry->len == 9 && !conn && entry->dgram[0] == ETCP_RT_ID_TCP_PROXY) {
uint64_t peer_id;
memcpy(&peer_id, entry->dgram + 1, 8);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY CLOSE_ALL from %016llx — clearing server conns for peer",
@@ -420,6 +499,8 @@ void tcp_proxy_server_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
uint8_t subcmd = entry->dgram[1];
uint32_t stream_id; memcpy(&stream_id, entry->dgram + 2, 4);
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "PROXY SERVER RECV subcmd=%02x sid=%08x pkt_len=%u", subcmd, stream_id, entry->len);
if (subcmd == TCP_PROXY_SUBCMD_CONNECT) {
uint64_t src_node_id = conn ? conn->peer_node_id : (inst ? inst->node_id : 0);
tcp_proxy_server_handle_connect(inst, entry, stream_id, src_node_id);
@@ -455,7 +536,7 @@ int tcp_proxy_server_init(struct UTUN_INSTANCE* inst) {
ctx->inst = inst;
if (!ctx->enabled) return 0;
g_tcp_proxy_server_ctx = ctx;
etcp_router_bind(inst, ETCP_ID_TCP_PROXY, tcp_proxy_server_recv_cb);
etcp_router_bind(inst, ETCP_RT_ID_TCP_PROXY, tcp_proxy_server_recv_cb);
if (!inst->config->global.tcp_proxy_client_enabled) {
udp_proxy_init(inst, inst->ua);
icmp_proxy_init(inst, inst->ua);
+9
View File
@@ -36,9 +36,18 @@ struct tcp_proxy_server_conn {
void* close_timer; // таймер повтора CLOSE/ERROR
int close_backoff; // backoff: 50..5000 tb (5ms..500ms)
void* diag_timer; // 1-секундный таймер диагностики
uint8_t dst_fin_deferred; // FIN от destination отложен (ждём drain read_queue + tx_buf)
uint8_t freed; // 1 = уже освобождён, защита от double-free
struct queue_waiter_handle pause_waiter;
void* retry_timer; // fallback timer (500ms) for force=1 retry on stall
uint8_t* tx_buf; // буфер при backpressure (retry в pause_resume_cb)
uint16_t tx_len;
uint32_t bytes_sent; // байт записано в destination (реальный TCP)
uint32_t bytes_recv; // байт прочитано от destination
uint32_t bytes_relayed; // байт отправлено клиенту через ETCP
uint32_t drain_count; // счётчик вызовов read_queue_drain_cb
uint32_t data_count; // счётчик входящих DATA от клиента
struct UASYNC* ua;
};
+4 -4
View File
@@ -52,7 +52,7 @@ static void flow_read_cb(socket_t sock, void* arg) {
if (!e) return;
e->dgram = u_malloc(UDP_PROXY_HDR_SIZE + n);
if (!e->dgram) { queue_entry_free(e); return; }
e->dgram[0] = ETCP_ID_UDP_PROXY;
e->dgram[0] = ETCP_RT_ID_UDP_PROXY;
e->dgram[1] = UDP_PROXY_SUBCMD_DATA;
memcpy(e->dgram + 2, &f->client_node_id, 8);
// Ответ src = оригинальный dst_ip:dest_port
@@ -160,7 +160,7 @@ int udp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
if (!e) return -1;
e->dgram = u_malloc(UDP_PROXY_HDR_SIZE + payload_len);
if (!e->dgram) { queue_entry_free(e); return -1; }
e->dgram[0] = ETCP_ID_UDP_PROXY;
e->dgram[0] = ETCP_RT_ID_UDP_PROXY;
e->dgram[1] = UDP_PROXY_SUBCMD_DATA;
memcpy(e->dgram + 2, &inst->node_id, 8);
memcpy(e->dgram + 10, &src_ip, 4);
@@ -245,7 +245,7 @@ int udp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua) {
ctx->is_exit = inst->tcp_proxy_server.enabled;
g_udp_ctx = ctx;
etcp_router_bind(inst, ETCP_ID_UDP_PROXY, udp_proxy_recv_cb);
etcp_router_bind(inst, ETCP_RT_ID_UDP_PROXY, udp_proxy_recv_cb);
ctx->initialized = 1;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "udp_proxy initialized (exit=%d)", ctx->is_exit);
return 0;
@@ -253,7 +253,7 @@ int udp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua) {
void udp_proxy_destroy(struct UTUN_INSTANCE* inst) {
if (!inst || !g_udp_ctx) return;
etcp_router_unbind(inst, ETCP_ID_UDP_PROXY);
etcp_router_unbind(inst, ETCP_RT_ID_UDP_PROXY);
if (g_udp_ctx->expire_timer) { uasync_cancel_timeout(g_udp_ctx->ua, g_udp_ctx->expire_timer); g_udp_ctx->expire_timer = NULL; }
struct udp_flow* f = g_udp_ctx->flows;
while (f) { struct udp_flow* n = f->next;
+29 -8
View File
@@ -47,10 +47,24 @@ static void route_bgp_send_table_request(struct ROUTE_BGP* bgp, struct ETCP_CONN
}
}
static void route_bgp_send_table_complete(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn) {
if (!bgp || !conn) return;
struct BGP_ROUTE_REQUEST* req = u_calloc(1, sizeof(struct BGP_ROUTE_REQUEST));
if (!req) return;
req->cmd = ETCP_ID_ROUTE_ENTRY;
req->subcmd = ROUTE_SUBCMD_TABLE_COMPLETE;
struct ll_entry* e = queue_entry_new(0);
if (!e) { u_free(req); return; }
e->dgram = (uint8_t*)req;
e->len = sizeof(struct BGP_ROUTE_REQUEST);
if (etcp_send(conn, e) != 0) { u_free(req); queue_entry_free(e); }
}
static void route_bgp_add_to_senders(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn);
static bool route_bgp_should_send_to(const struct NODEINFO_Q* nq, uint64_t target_id);
static void route_bgp_send_full_table(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn);
static void route_bgp_handle_request_table(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn);
static void route_bgp_send_table_complete(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn);
static void nodeinfo_dump_log(const uint8_t* data, size_t len) {
if (!data || len < sizeof(struct BGP_NODEINFO_PACKET)) return;
@@ -103,6 +117,7 @@ static const char* bgp_subcmd_name(uint8_t subcmd) {
case ROUTE_SUBCMD_PING_RESP: return "PING_RESP";
case ROUTE_SUBCMD_NAT_INFO: return "NAT_INFO";
case ROUTE_SUBCMD_NAT_CHECK_REQ: return "NAT_CHECK_REQ";
case ROUTE_SUBCMD_TABLE_COMPLETE: return "TABLE_COMPLETE";
default: return "?";
}
}
@@ -190,6 +205,9 @@ static void route_bgp_receive_cbk(struct ETCP_CONN* from_conn, struct ll_entry*
route_bgp_handle_nat_info(bgp, from_conn, data, entry->len);
} else if (subcmd == ROUTE_SUBCMD_NAT_CHECK_REQ) {
route_bgp_handle_nat_check_req(bgp, from_conn, data, entry->len);
} else if (subcmd == ROUTE_SUBCMD_TABLE_COMPLETE) {
etcp_set_routing_exchange_state(from_conn, 3);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP initial sync complete with %s", from_conn->log_name);
}
queue_dgram_free(entry);
@@ -645,7 +663,8 @@ static struct ETCP_CONN* route_bgp_find_third_node(struct ROUTE_BGP* bgp, struct
struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)e->data;
if (item && item->conn && item->conn != exclude && item->conn->links && item->conn->links->remote_only_local==0) {
// Skip nodes with private socket type - they can't help with NAT detection
if (item->conn->links->remote_type != CFG_SERVER_TYPE_PRIVATE) return item->conn;
if (item->conn->links->remote_type != CFG_SERVER_TYPE_PRIVATE
&& item->conn->links->remote_type != CFG_SERVER_TYPE_LOCAL) return item->conn;
}
e = e->next;
}
@@ -671,8 +690,8 @@ void route_bgp_start_link_nat_check(struct ROUTE_BGP* bgp, struct ETCP_LINK* lin
if (link->nat_type == NAT_TYPE_DIRECT) return;
if (link->nat_check_status == NAT_CHECK_IN_PROGRESS) return;
// Skip NAT check for private sockets - they are not reachable from outside
if (link->conn->type == CFG_SERVER_TYPE_PRIVATE) {
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "skip nat check for private socket link");
if (link->conn->type == CFG_SERVER_TYPE_PRIVATE || link->conn->type == CFG_SERVER_TYPE_LOCAL) {
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "skip nat check for private/local socket link");
return;
}
// Find third node to send ping through
@@ -999,6 +1018,7 @@ static void route_bgp_handle_request_table(struct ROUTE_BGP* bgp, struct ETCP_CO
route_bgp_send_nodeinfo(bgp->local_node, conn);
route_bgp_send_full_table(bgp, conn);
route_bgp_add_to_senders(bgp, conn);
route_bgp_send_table_complete(bgp, conn);
}
static void route_bgp_handle_nat_info(struct ROUTE_BGP* bgp, struct ETCP_CONN* from_conn, const uint8_t* data, size_t len) {
@@ -1023,8 +1043,8 @@ static void route_bgp_handle_nat_info(struct ROUTE_BGP* bgp, struct ETCP_CONN* f
int data_changed = 0;
for (int i = 0; i < bgp->local_node->node.local_v4_sockets; i++) {
if (meta[i].id == socket_id) {
if (meta[i].type != verified_type) {
meta[i].type = verified_type;
if (meta[i].nat_type != verified_type) {
meta[i].nat_type = verified_type;
data_changed = 1;
}
break;
@@ -1079,10 +1099,11 @@ static void route_bgp_handle_nat_info(struct ROUTE_BGP* bgp, struct ETCP_CONN* f
struct ETCP_LINK* l = from_conn->links;
while (l) { l->nat_type = info->nat_type; l = l->next; }
DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT_INFO from %s: socket_id=%u type=%s ip=%s port=%u",
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT_INFO from %s: sock_id=%u type=%s ip=%s port=%u matched_sock=%s old_nat_type=%d",
from_conn->log_name, socket_id,
info->nat_type == NAT_TYPE_EIM ? "EIM" : "STRICT",
ip_to_str(info->nat_ip, AF_INET).str, (unsigned)nat_port);
info->nat_type == NAT_TYPE_EIM ? "EIM" : info->nat_type == NAT_TYPE_DIRECT ? "DIRECT" : "STRICT",
ip_to_str(info->nat_ip, AF_INET).str, (unsigned)nat_port,
es ? es->name : "none", es ? (int)es->nat_type : -1);
}
static void route_bgp_handle_nat_check_req(struct ROUTE_BGP* bgp, struct ETCP_CONN* from_conn, const uint8_t* data, size_t len) {// поиск нужного линка и запуск nat_check
+1
View File
@@ -19,6 +19,7 @@
#define ROUTE_SUBCMD_WITHDRAW 0x06 // узел стал недоступен
#define ROUTE_SUBCMD_NAT_INFO 0x09 // информация о типе NAT клиента
#define ROUTE_SUBCMD_NAT_CHECK_REQ 0x0A // запрос от клиента на проверку NAT для сокета
#define ROUTE_SUBCMD_TABLE_COMPLETE 0x0B // завершение начальной синхронизации таблицы
#define MAX_HOPS 16
#define BGP_NODES_HASH_SIZE 256
+10 -7
View File
@@ -124,14 +124,15 @@ static int conn_match_candidate_sockets(struct UTUN_INSTANCE* instance,
uint32_t if_ip = if_sin->sin_addr.s_addr;
if (addr_type == ADDR_TYPE_INTERFACE) {
// предпочитаем PRIVATE сокеты в той же /24 подсети
if (e_sock->type == CFG_SERVER_TYPE_PRIVATE && (if_ip & 0x00FFFFFF) == (target_ip & 0x00FFFFFF))
// предпочитаем PRIVATE/LOCAL сокеты в той же /24 подсети
if ((e_sock->type == CFG_SERVER_TYPE_PRIVATE || e_sock->type == CFG_SERVER_TYPE_LOCAL)
&& (if_ip & 0x00FFFFFF) == (target_ip & 0x00FFFFFF))
match = 1;
} else {
// NAT или REAL: предпочитаем DIRECT > EIM > PUBLIC
// NAT или REAL: предпочитаем DIRECT > EIM > PUBLIC/UNKNOWN
if (e_sock->nat_type == NAT_VERIFIED_DIRECT) match = 1;
else if (e_sock->nat_type == NAT_VERIFIED_EIM) match = 1;
else if (e_sock->type == CFG_SERVER_TYPE_PUBLIC) match = 1;
else if (e_sock->type == CFG_SERVER_TYPE_PUBLIC || e_sock->type == CFG_SERVER_TYPE_UNKNOWN) match = 1;
}
if (match) {
@@ -150,10 +151,12 @@ static int conn_match_candidate_sockets(struct UTUN_INSTANCE* instance,
for (int i = 0; i < found; i++) if (out_sockets[i] == e_sock) { dup = 1; break; }
if (!dup) {
if (addr_type == ADDR_TYPE_INTERFACE) {
if (e_sock->type == CFG_SERVER_TYPE_PRIVATE) out_sockets[found++] = e_sock;
if (e_sock->type == CFG_SERVER_TYPE_PRIVATE || e_sock->type == CFG_SERVER_TYPE_LOCAL)
out_sockets[found++] = e_sock;
} else {
// любой не-private
if (e_sock->type != CFG_SERVER_TYPE_PRIVATE) out_sockets[found++] = e_sock;
// любой не-private и не-local
if (e_sock->type != CFG_SERVER_TYPE_PRIVATE && e_sock->type != CFG_SERVER_TYPE_LOCAL)
out_sockets[found++] = e_sock;
}
}
e_sock = e_sock->next;
+17 -8
View File
@@ -173,7 +173,7 @@ void route_node_dump_all(struct ROUTE_BGP* bgp) {
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);
DEBUG_INFO(DEBUG_CATEGORY_BGP, " v4sock[%d]: id=%u config=%u nat=%u", i, v4sm[i].id, v4sm[i].config_type, v4sm[i].nat_type);
}
const struct NODEINFO_IPV4_ADDR* v4a;
@@ -190,7 +190,7 @@ void route_node_dump_all(struct ROUTE_BGP* bgp) {
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);
DEBUG_INFO(DEBUG_CATEGORY_BGP, " v6sock[%d]: id=%u config=%u nat=%u", i, v6sm[i].id, v6sm[i].config_type, v6sm[i].nat_type);
}
const struct NODEINFO_IPV6_ADDR* v6a;
@@ -321,7 +321,7 @@ int route_node_format_all(struct ROUTE_BGP* bgp, char* buf, size_t buf_size) {
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);
FMT_ADD(" v4sock[%d]: id=%u config=%u nat=%u\n", i, v4sm[i].id, v4sm[i].config_type, v4sm[i].nat_type);
const struct NODEINFO_IPV4_ADDR* v4a;
int v4a_cnt = get_node_v4_addrs(nq, &v4a);
@@ -336,7 +336,7 @@ int route_node_format_all(struct ROUTE_BGP* bgp, char* buf, size_t buf_size) {
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);
FMT_ADD(" v6sock[%d]: id=%u config=%u nat=%u\n", i, v6sm[i].id, v6sm[i].config_type, v6sm[i].nat_type);
const struct NODEINFO_IPV6_ADDR* v6a;
int v6a_cnt = get_node_v6_addrs(nq, &v6a);
@@ -444,6 +444,7 @@ int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BG
int sock6_count = 0, addr6_count = 0;
struct ETCP_SOCKET* e_sock = instance->etcp_sockets;
while (e_sock) {
if (e_sock->type == CFG_SERVER_TYPE_PRIVATE || e_sock->type == CFG_SERVER_TYPE_LOCAL) { e_sock = e_sock->next; continue; }
if (e_sock->local_addr.ss_family == AF_INET) {
sock_count++;
addr_count++; // INTERFACE
@@ -454,7 +455,8 @@ int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BG
}
}
if (e_sock->nat_type == NAT_VERIFIED_DIRECT ||
(e_sock->type == CFG_SERVER_TYPE_PUBLIC && e_sock->nat_type < NAT_VERIFIED_UNKNOWN)) addr_count++;
((e_sock->type == CFG_SERVER_TYPE_PUBLIC || e_sock->type == CFG_SERVER_TYPE_UNKNOWN)
&& e_sock->nat_type < NAT_VERIFIED_UNKNOWN)) addr_count++;
} else if (e_sock->local_addr.ss_family == AF_INET6) {
sock6_count++;
addr6_count++; // INTERFACE only (no NAT for IPv6)
@@ -516,9 +518,11 @@ int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BG
e_sock = instance->etcp_sockets;
int midx = 0;
while (e_sock) {
if (e_sock->type == CFG_SERVER_TYPE_PRIVATE || e_sock->type == CFG_SERVER_TYPE_LOCAL) { e_sock = e_sock->next; continue; }
if (e_sock->local_addr.ss_family == AF_INET) {
meta[midx].id = e_sock->sock_id;
meta[midx].type = e_sock->nat_type;
meta[midx].config_type = e_sock->type;
meta[midx].nat_type = e_sock->nat_type;
midx++;
}
e_sock = e_sock->next;
@@ -530,6 +534,7 @@ int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BG
int aidx = 0;
e_sock = instance->etcp_sockets;
while (e_sock) {
if (e_sock->type == CFG_SERVER_TYPE_PRIVATE || e_sock->type == CFG_SERVER_TYPE_LOCAL) { e_sock = e_sock->next; continue; }
if (e_sock->local_addr.ss_family == AF_INET) {
struct sockaddr_in* if_sin = (struct sockaddr_in*)&e_sock->interface_addr;
struct sockaddr_in* nat_sin = (struct sockaddr_in*)&e_sock->nat_addr;
@@ -550,7 +555,8 @@ int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BG
}
}
if (e_sock->nat_type == NAT_VERIFIED_DIRECT ||
(e_sock->type == CFG_SERVER_TYPE_PUBLIC && e_sock->nat_type < NAT_VERIFIED_UNKNOWN)) {
((e_sock->type == CFG_SERVER_TYPE_PUBLIC || e_sock->type == CFG_SERVER_TYPE_UNKNOWN)
&& e_sock->nat_type < NAT_VERIFIED_UNKNOWN)) {
memcpy(addrs[aidx].addr, &if_sin->sin_addr.s_addr, 4);
addrs[aidx].port = ntohs(if_sin->sin_port);
addrs[aidx].type = ADDR_TYPE_REAL;
@@ -567,9 +573,11 @@ int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BG
e_sock = instance->etcp_sockets;
int m6idx = 0;
while (e_sock) {
if (e_sock->type == CFG_SERVER_TYPE_PRIVATE || e_sock->type == CFG_SERVER_TYPE_LOCAL) { e_sock = e_sock->next; continue; }
if (e_sock->local_addr.ss_family == AF_INET6) {
meta6[m6idx].id = e_sock->sock_id;
meta6[m6idx].type = e_sock->nat_type;
meta6[m6idx].config_type = e_sock->type;
meta6[m6idx].nat_type = e_sock->nat_type;
m6idx++;
}
e_sock = e_sock->next;
@@ -581,6 +589,7 @@ int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BG
int a6idx = 0;
e_sock = instance->etcp_sockets;
while (e_sock) {
if (e_sock->type == CFG_SERVER_TYPE_PRIVATE || e_sock->type == CFG_SERVER_TYPE_LOCAL) { e_sock = e_sock->next; continue; }
if (e_sock->local_addr.ss_family == AF_INET6) {
struct sockaddr_in6* if_sin6 = (struct sockaddr_in6*)&e_sock->interface_addr;
memcpy(addrs6[a6idx].addr, &if_sin6->sin6_addr, 16);
+4 -2
View File
@@ -91,7 +91,8 @@ struct NODEINFO {
// метаданные одного сокета
struct NODEINFO_IPV4_SOCKET_META {
uint8_t id; // unique socket id (0-255)
uint8_t type; // CFG_SERVER_TYPE_*/NAT_VERIFIED_*
uint8_t config_type; // CFG_SERVER_TYPE_PUBLIC/NAT/PRIVATE
uint8_t nat_type; // NAT_TYPE_* / NAT_VERIFIED_*
} __attribute__((packed));
// одна типизированная адресная запись (плоский список)
@@ -104,7 +105,8 @@ struct NODEINFO_IPV4_ADDR {
struct NODEINFO_IPV6_SOCKET_META {
uint8_t id; // unique socket id (0-255)
uint8_t type; // CFG_SERVER_TYPE_*/NAT_VERIFIED_*
uint8_t config_type; // CFG_SERVER_TYPE_PUBLIC/NAT/PRIVATE
uint8_t nat_type; // NAT_TYPE_* / NAT_VERIFIED_*
} __attribute__((packed));
struct NODEINFO_IPV6_ADDR {
+3 -6
View File
@@ -22,9 +22,6 @@
#define IP_HDR_DST_ADDR_OFFSET 16
#define IP_HDR_MIN_SIZE 20
// ETCP packet ID for routing data
#define ETCP_ID_DATA 0x00
// Extract destination IP from IPv4 packet
// Returns 0 if not IPv4 or packet too small
static uint32_t extract_dst_ip(uint8_t* data, size_t len) {
@@ -254,8 +251,8 @@ int routing_bind(struct UTUN_INSTANCE* instance) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "routing_bind: instance is NULL");
return -1;
}
if (etcp_router_bind(instance, ETCP_ID_DATA, routing_pkt_from_etcp_cb) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "routing_bind: failed to bind ETCP_ID_DATA via etcp_router for node %016llx",
if (etcp_router_bind(instance, ETCP_RT_ID_DATA, routing_pkt_from_etcp_cb) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "routing_bind: failed to bind ETCP_RT_ID_DATA via etcp_router for node %016llx",
(unsigned long long)instance->node_id);
return -1;
}
@@ -272,7 +269,7 @@ void routing_destroy(struct UTUN_INSTANCE* instance) {
(unsigned long long)instance->node_id);
// Unbind DATA handler from etcp_router
etcp_router_unbind(instance, ETCP_ID_DATA);
etcp_router_unbind(instance, ETCP_RT_ID_DATA);
// Clean up route table if exists
if (instance->rt) {
+14
View File
@@ -11,7 +11,9 @@
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifndef _WIN32
#include <sys/socket.h>
#endif
#include "../lib/mem.h"
#ifdef _WIN32
@@ -151,6 +153,7 @@ struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config)
}
} else {
if (tun->ifname[0] == '\0') strncpy(tun->ifname, "tun_test", sizeof(tun->ifname)-1);
#ifndef _WIN32
int fds[2];
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, fds) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s test_mode socketpair failed: %s", tun->ifname, strerror(errno));
@@ -159,6 +162,11 @@ struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config)
}
tun->fd = fds[0];
tun->test_fd = fds[1];
#else
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN test_mode not supported on Windows");
u_free(tun);
return NULL;
#endif
}
tun->pool = memory_pool_init(sizeof(struct ll_entry), "tun_pool");
@@ -244,6 +252,7 @@ struct tun_if* tun_init_nat(struct UASYNC* ua, const char* ifname, const char* i
return NULL;
}
} else {
#ifndef _WIN32
int fds[2];
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, fds) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "NAT TUN %s test_mode socketpair failed: %s", tun->ifname, strerror(errno));
@@ -252,6 +261,11 @@ struct tun_if* tun_init_nat(struct UASYNC* ua, const char* ifname, const char* i
}
tun->fd = fds[0];
tun->test_fd = fds[1];
#else
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "NAT TUN test_mode not supported on Windows");
u_free(tun);
return NULL;
#endif
}
tun->pool = memory_pool_init(sizeof(struct ll_entry), "tun_pool");
+16 -2
View File
@@ -12,6 +12,7 @@
#include "etcp_connections.h"
#include "etcp.h"
#include "conn_mgr.h"
#include "stcp_server.h"
#include "control_server.h"
#include "msg_transport.h"
#include "../lib/u_async.h"
@@ -156,11 +157,14 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
// TCP proxy server (exit node, optional) — must be before tcp_proxy_client so
// tcp_proxy_client_create can overwrite the handler if it has remote mappings
#ifndef _WIN32
if (tcp_proxy_server_init(instance) != 0) {
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "Failed to initialize tcp_proxy_server (non-fatal)");
}
#endif
// TCP proxy client (from [tcp_proxy] config section)
#ifndef _WIN32
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;
@@ -183,6 +187,7 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
} else {
instance->tcp_proxy_client = NULL;
}
#endif /* _WIN32 */
return 0;
}
@@ -260,7 +265,8 @@ struct UTUN_INSTANCE* utun_instance_create_from_config(struct UASYNC* ua, struct
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to allocate UTUN_INSTANCE");
return NULL;
}
instance->etcp_connect_timeout_tb = 20000;
// Initialize using common function (config ownership transferred to instance)
if (instance_init_common(instance, ua, config) != 0) {
// Cleanup on error - caller still owns config since we failed
@@ -362,6 +368,7 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
}
// Cleanup TCP proxy client module
#ifndef _WIN32
if (instance->tcp_proxy_client) {
DEBUG_INFO(DEBUG_CATEGORY_TUN, "Destroying TCP proxy client module");
tcp_proxy_client_destroy(instance->tcp_proxy_client);
@@ -370,6 +377,7 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
// Cleanup TCP proxy server
tcp_proxy_server_destroy(instance);
#endif
// Cleanup routing module (unbinds from etcp_router before etcp_router_destroy)
routing_destroy(instance);
@@ -394,6 +402,12 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
// Cleanup firewall
fw_free(&instance->fw);
// Cleanup TCP server
if (instance->stcp_server) {
stcp_server_destroy(instance->stcp_server);
instance->stcp_server = NULL;
}
// Cleanup config
if (instance->config) {
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Freeing configuration");
@@ -504,7 +518,7 @@ int utun_instance_init(struct UTUN_INSTANCE *instance) {
if (instance->config->global.msg_transport_sock.ss_family != 0) {
int mt_ret = msg_transport_init(&instance->msg_t, instance, instance->ua,
&instance->config->global.msg_transport_sock,
8, ETCP_ID_MSG_TRANSPORT);
8, ETCP_RT_ID_MSG_TRANSPORT);
if (mt_ret != 0) {
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "Failed to initialize msg_transport, continuing without IPC transport");
instance->msg_t = NULL;
+5
View File
@@ -34,6 +34,7 @@ struct control_server;
struct msg_transport;
struct PING_CONTEXT;
struct CONN_MGR;
struct ETCP_CONNECT;
// uTun instance configuration
struct UTUN_INSTANCE {
@@ -117,6 +118,10 @@ struct UTUN_INSTANCE {
// TCP proxy server (exit node)
struct tcp_proxy_server tcp_proxy_server;
// Pending background connections (etcp_connect API)
struct ETCP_CONNECT* pending_connects;
uint32_t etcp_connect_timeout_tb; // Initial timeout in 0.1ms units, default 20000 (2s)
};
// Functions
+11 -1
View File
@@ -32,6 +32,7 @@ check_PROGRAMS = \
test_lwip_tcp \
test_u_async_performance \
test_etcp_router \
test_etcp_router_reconnect \
test_etcp_bbr \
test_etcp_ping \
test_route_ping \
@@ -47,6 +48,7 @@ check_PROGRAMS = \
test_bgp_route_exchange \
test_bgp_triangle \
test_conn_mgr \
test_etcp_connect \
test_bbr_integration \
test_intensive_memory_pool \
test_tcp_io \
@@ -74,6 +76,7 @@ ETCP_CORE_OBJS = \
$(top_builddir)/src/utun-etcp_loadbalancer.o \
$(top_builddir)/src/utun-pkt_normalizer.o \
$(top_builddir)/src/utun-etcp_api.o \
$(top_builddir)/src/utun-etcp_connect.o \
$(top_builddir)/src/utun-etcp_debug.o \
$(top_builddir)/src/utun-etcp_dump.o
@@ -193,11 +196,14 @@ test_lwip_tcp_LDADD = \
$(COMMON_LIBS)
test_etcp_router_SOURCES = test_etcp_router.c
test_etcp_router_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_etcp_router_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_etcp_router_unit_SOURCES = test_etcp_router_unit.c
test_etcp_router_unit_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_etcp_router_reconnect_SOURCES = test_etcp_router_reconnect.c
test_etcp_router_reconnect_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_tcp_proxy_server_SOURCES = test_tcp_proxy_server.c
test_tcp_proxy_server_CFLAGS = -I$(top_srcdir)/lib
test_tcp_proxy_server_LDADD = $(COMMON_LIBS)
@@ -330,6 +336,10 @@ test_conn_mgr_SOURCES = test_conn_mgr.c
test_conn_mgr_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_conn_mgr_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_etcp_connect_SOURCES = test_etcp_connect.c
test_etcp_connect_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_etcp_connect_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_bbr_integration_SOURCES = bbr_integration/test_bbr_integration.c
test_bbr_integration_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_bbr_integration_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
+2 -2
View File
@@ -200,7 +200,7 @@ static void send_burst(struct test_ctx* ctx) {
while (sent < BURST_MAX) {
struct ll_entry* e = ll_alloc_lldgram(1 + PAYLOAD_SIZE);
if (!e) break;
e->dgram[0] = ETCP_ID_DATA;
e->dgram[0] = ETCP_RT_ID_DATA;
e->len = 1 + PAYLOAD_SIZE;
if (etcp_send(conn, e) == 0) { ctx->bytes_sent += PAYLOAD_SIZE; sent++; }
else { queue_entry_free(e); break; }
@@ -359,7 +359,7 @@ int main(void) {
printf("Init sender...\n");
if (utun_instance_init(ctx.sender) < 0) { printf("ERROR: sender init failed\n"); return 1; }
etcp_bind(ctx.receiver, ETCP_ID_DATA, on_recv);
etcp_bind(ctx.receiver, ETCP_RT_ID_DATA, on_recv);
etcp_set_new_conn_cbk(ctx.receiver, NULL, &ctx);
printf("Creating dummynet on port %d ...\n", DN_PORT);
+2 -2
View File
@@ -192,7 +192,7 @@ static void send_burst(struct test_ctx* ctx) {
while (sent < 64) {
struct ll_entry* e = ll_alloc_lldgram(sizeof(uint8_t) + PAYLOAD_SIZE);
if (!e) break;
e->dgram[0] = ETCP_ID_DATA;
e->dgram[0] = ETCP_RT_ID_DATA;
e->len = 1 + PAYLOAD_SIZE;
if (etcp_send(conn, e) == 0) { ctx->bytes_sent += e->len - 1; sent++; }
else { queue_entry_free(e); break; }
@@ -328,7 +328,7 @@ int main(void) {
printf("Init sender...\n");
if (utun_instance_init(ctx.sender) < 0) { printf("Sender init failed\n"); return 1; }
etcp_bind(ctx.receiver, ETCP_ID_DATA, on_recv);
etcp_bind(ctx.receiver, ETCP_RT_ID_DATA, on_recv);
etcp_set_new_conn_cbk(ctx.receiver, NULL, &ctx);
printf("Creating dummynet...\n");
+242
View File
@@ -0,0 +1,242 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include "../lib/platform_compat.h"
#include "test_utils.h"
#ifndef _WIN32
#include <unistd.h>
#endif
#include "../src/etcp.h"
#include "../src/etcp_connections.h"
#include "../src/etcp_api.h"
#include "../src/config_parser.h"
#include "../src/config_updater.h"
#include "../src/utun_instance.h"
#include "../src/route_bgp.h"
#include "../src/route_node.h"
#include "../src/secure_channel.h"
#include "../lib/u_async.h"
#include "../lib/ll_queue.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#define TIMEOUT_TB 300000
#define POLL_MS 5
#define NID_A 0xAAAA000000000001ULL
#define NID_B 0xBBBB000000000002ULL
static struct UTUN_INSTANCE* g_a = NULL, *g_b = NULL;
static struct UASYNC* ua = NULL;
static volatile int result = 0;
static void* ttimer = NULL;
static char tdir[] = "/tmp/utun_ec_XXXXXX";
static char ca[256], cb[256];
static int pa = 0, pb = 0;
static int wf(const char* p, const char* f, ...) {
va_list ap; FILE* fp = fopen(p, "w"); if (!fp) return -1;
va_start(ap, f); vfprintf(fp, f, ap); va_end(ap); fclose(fp); return 0;
}
static char* gv(const char* p, const char* k) {
struct utun_config* c = parse_config(p); if (!c) return NULL;
char* r = (strcmp(k, "pub") == 0) ? u_strdup(c->global.my_public_key_hex) : u_strdup(c->global.my_private_key_hex);
free_config(c); return r;
}
static int lks(struct UTUN_INSTANCE* i) {
int n = 0; struct ETCP_CONN* c = i->connections;
while (c) { struct ETCP_LINK* l = c->links; while (l) { if (l->initialized && l->link_status) n++; l = l->next; } c = c->next; }
return n;
}
static void fail(const char* msg) { fprintf(stderr, "FAIL: %s\n", msg); fflush(stderr); result = 1; }
static void to_cb(void* arg) { (void)arg; fprintf(stderr, "TIMEOUT\n"); result = 1; }
static void test1(void* arg); static void test2(void* arg);
static void test3(void* arg); static void test4(void* arg);
static void test5(void* arg); static void test6(void* arg);
static void test7(void* arg); static void test8(void* arg);
static void test9(void* arg); static void test10(void* arg);
/* ----- callback state per test ----- */
static volatile int cb_type = 0, cb_conn_ok = 0, cb_count = 0;
static void connect_ccb(void* a, struct ETCP_CONN* conn, int type) { cb_type = type; cb_conn_ok = (conn != NULL); cb_count++; }
/* ----- helper: create minimal NODEINFO_Q with 1 v4 addr ----- */
static struct NODEINFO_Q* mknode(uint64_t nid, const uint8_t pubkey[32],
uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint16_t port) {
size_t extra = sizeof(struct NODEINFO_IPV4_SOCKET_META) + sizeof(struct NODEINFO_IPV4_ADDR);
size_t data_sz = sizeof(struct NODEINFO_Q) - sizeof(struct ll_entry) + extra;
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)queue_entry_new(data_sz);
if (!nq) return NULL;
struct NODEINFO* ni = &nq->node;
ni->node_id = nid; ni->ver = 0;
memcpy(ni->public_key, pubkey, SC_PUBKEY_SIZE);
ni->local_v4_sockets = 1;
ni->local_v4_addrs = 1;
uint8_t* dyn = (uint8_t*)(ni + 1);
struct NODEINFO_IPV4_SOCKET_META* meta = (struct NODEINFO_IPV4_SOCKET_META*)dyn;
meta->id = 0; meta->config_type = CFG_SERVER_TYPE_PUBLIC; meta->nat_type = NAT_TYPE_UNKNOWN;
dyn += sizeof(struct NODEINFO_IPV4_SOCKET_META);
struct NODEINFO_IPV4_ADDR* addr = (struct NODEINFO_IPV4_ADDR*)dyn;
addr->addr[0] = a; addr->addr[1] = b; addr->addr[2] = c; addr->addr[3] = d;
addr->port = port; addr->type = ADDR_TYPE_INTERFACE; addr->socket_id = 0;
return nq;
}
/* ======================== Test 1: already connected → immediate EARLY+LATE ======================== */
static void test1(void* arg) {
(void)arg; if (result) return;
if (lks(g_a) < 1 || lks(g_b) < 1) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test1, "t1"); return; }
struct NODEINFO_Q* nb = nodeinfo_find_by_id(g_a->bgp, NID_B);
if (!nb) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test1, "t1b"); return; }
fprintf(stderr, "Test 1: already connected → EARLY|LATE\n"); fflush(stderr);
cb_type = 0; cb_conn_ok = 0; cb_count = 0;
int r = etcp_connect(g_a, nb, connect_ccb, NULL, ETCP_CONNECT_EARLY | ETCP_CONNECT_LATE);
if (r != 0) { fail("etcp_connect returned error"); return; }
uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test2, "t2");
}
static void test2(void* arg) {
(void)arg; if (result) return;
if (!cb_count) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test2, "t2"); return; }
if (cb_count != 2) { fail("test1: expected 2 callbacks"); return; }
if (!cb_conn_ok) { fail("test1: conn=NULL in callback"); return; }
fprintf(stderr, " OK: %d callbacks, conn=OK\n", cb_count); fflush(stderr);
/* advance to test3 */
uasync_call_soon(ua, NULL, test3);
}
/* ======================== Test 3: unreachable node → timeout error ======================== */
static void test3(void* arg) {
(void)arg; if (result) return;
fprintf(stderr, "Test 3: unreachable node → error\n"); fflush(stderr);
struct SC_MYKEYS fk; sc_generate_keypair(&fk);
uint64_t fnid = 0xF000000000000001ULL;
struct NODEINFO_Q* fn = mknode(fnid, fk.public_key, 127,0,0,1, 49999);
if (!fn) { fail("mknode failed"); return; }
queue_data_put_with_index(g_a->bgp->nodes, &fn->ll);
g_a->etcp_connect_timeout_tb = 2000;
cb_type = -1; cb_conn_ok = -1; cb_count = 0;
int r = etcp_connect(g_a, fn, connect_ccb, NULL, ETCP_CONNECT_EARLY | ETCP_CONNECT_LATE);
if (r != 0) { fail("etcp_connect(unreachable) returned error"); return; }
uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test4, "t4");
}
static void test4(void* arg) {
(void)arg; if (result) return;
if (!cb_count) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test4, "t4"); return; }
if (cb_count != 1) { fail("test3: expected 1 callback"); return; }
if (cb_conn_ok != 0) { fail("test3: expected conn=NULL"); return; }
if (cb_type != 0) { fail("test3: expected type=0 (error)"); return; }
fprintf(stderr, " OK: error callback delivered (NULL, type=0)\n"); fflush(stderr);
uasync_call_soon(ua, NULL, test5);
}
/* ======================== Test 5: double connect to unreachable → both callbacks ======================== */
static int t5_cb_count = 0;
static void t5_ccb(void* a, struct ETCP_CONN* conn, int type) {
(void)a; (void)conn; if (type == 0) t5_cb_count++;
}
static void test5(void* arg) {
(void)arg; if (result) return;
fprintf(stderr, "Test 5: double connect to unreachable → both callbacks\n"); fflush(stderr);
struct SC_MYKEYS fk; sc_generate_keypair(&fk);
uint64_t fnid = 0xF000000000000002ULL;
struct NODEINFO_Q* fn = mknode(fnid, fk.public_key, 127,0,0,1, 49998);
if (!fn) { fail("mknode failed"); return; }
queue_data_put_with_index(g_a->bgp->nodes, &fn->ll);
g_a->etcp_connect_timeout_tb = 2000;
t5_cb_count = 0;
int r1 = etcp_connect(g_a, fn, t5_ccb, NULL, ETCP_CONNECT_EARLY | ETCP_CONNECT_LATE);
int r2 = etcp_connect(g_a, fn, t5_ccb, NULL, ETCP_CONNECT_EARLY);
if (r1 != 0 || r2 != 0) { fail("etcp_connect double call returned error"); return; }
uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test6, "t6");
}
static void test6(void* arg) {
(void)arg; if (result) return;
if (t5_cb_count < 2) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test6, "t6"); return; }
if (t5_cb_count != 2) { fail("test5: expected 2 error callbacks"); return; }
fprintf(stderr, " OK: both callbacks fired\n"); fflush(stderr);
uasync_call_soon(ua, NULL, test7);
}
/* ======================== Test 7: EARLY-only flag ======================== */
static void test7(void* arg) {
(void)arg; if (result) return;
struct NODEINFO_Q* nb = nodeinfo_find_by_id(g_a->bgp, NID_B);
if (!nb || lks(g_a) < 1) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test7, "t7"); return; }
fprintf(stderr, "Test 7: EARLY-only flag\n"); fflush(stderr);
cb_type = 0; cb_conn_ok = 0; cb_count = 0;
int r = etcp_connect(g_a, nb, connect_ccb, NULL, ETCP_CONNECT_EARLY);
if (r != 0) { fail("etcp_connect(EARLY) returned error"); return; }
uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test8, "t8");
}
static void test8(void* arg) {
(void)arg; if (result) return;
if (!cb_count) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test8, "t8"); return; }
if (cb_count != 1) { fail("test7: expected exactly 1 callback"); return; }
if (cb_type != ETCP_CONNECT_EARLY) { fail("test7: expected EARLY type"); return; }
fprintf(stderr, " OK: only EARLY fired\n"); fflush(stderr);
uasync_call_soon(ua, NULL, test9);
}
/* ======================== Test 9: LATE-only flag ======================== */
static void test9(void* arg) {
(void)arg; if (result) return;
struct NODEINFO_Q* nb = nodeinfo_find_by_id(g_a->bgp, NID_B);
if (!nb || lks(g_a) < 1) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test9, "t9"); return; }
fprintf(stderr, "Test 9: LATE-only flag\n"); fflush(stderr);
cb_type = 0; cb_conn_ok = 0; cb_count = 0;
int r = etcp_connect(g_a, nb, connect_ccb, NULL, ETCP_CONNECT_LATE);
if (r != 0) { fail("etcp_connect(LATE) returned error"); return; }
uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test10, "t10");
}
static void test10(void* arg) {
(void)arg; if (result) return;
if (!cb_count) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test10, "t10"); return; }
if (cb_count != 1) { fail("test9: expected exactly 1 callback"); return; }
if (cb_type != ETCP_CONNECT_LATE) { fail("test9: expected LATE type"); return; }
fprintf(stderr, " OK: only LATE fired\n"); fflush(stderr);
fprintf(stderr, "=== ALL PASSED ===\n"); fflush(stderr);
result = 2;
}
/* ======================== setup / cleanup / main ======================== */
static void setup(void) {
test_mkdtemp(tdir);
int base = 48000 + (getpid() % 10000); pa = base; pb = base + 1;
snprintf(ca, sizeof(ca), "%s/a.conf", tdir); snprintf(cb, sizeof(cb), "%s/b.conf", tdir);
wf(ca, "[global]\nmy_node_id=0x%llx\ntun_ip=10.99.0.1/24\ntun_ifname=tun99\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n", NID_A, pa);
wf(cb, "[global]\nmy_node_id=0x%llx\ntun_ip=10.99.0.2/24\ntun_ifname=tun98\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n", NID_B, pb);
config_ensure_keys_and_node_id(ca); config_ensure_keys_and_node_id(cb);
char *p0 = gv(ca,"pub"), *r0 = gv(ca,"priv"), *p1 = gv(cb,"pub"), *r1 = gv(cb,"priv");
wf(ca, "[global]\nmy_node_id=0x%llx\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.1/24\ntun_ifname=tun99\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[client: to_b]\nkeepalive=1\npeer_public_key=%s\nlink=s1:127.0.0.1:%d\n[allowed_keys]\nallow_all=1\n", NID_A, r0, p0, pa, p1, pb);
wf(cb, "[global]\nmy_node_id=0x%llx\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.2/24\ntun_ifname=tun98\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n", NID_B, r1, p1, pb);
u_free(p0); u_free(r0); u_free(p1); u_free(r1);
}
static void cleanup(void) { test_unlink(ca); test_unlink(cb); test_rmdir(tdir); }
int main(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN);
utun_instance_set_tun_init_enabled(0); setup();
ua = uasync_create();
g_a = utun_instance_create(ua, ca); g_b = utun_instance_create(ua, cb);
if (!g_a || !g_b) goto done;
utun_instance_init(g_a); utun_instance_init(g_b);
uasync_call_soon(ua, NULL, test1);
ttimer = uasync_set_timeout(ua, TIMEOUT_TB, NULL, to_cb, "to");
{ uint64_t start = get_time_tb();
while (!result && (int)(get_time_tb() - start) < TIMEOUT_TB + 50000) { uasync_poll(ua, POLL_MS); } }
fprintf(stderr, "final result=%d\n", result); fflush(stderr);
done:
if (ttimer) uasync_cancel_timeout(ua, ttimer);
if (g_a) { g_a->running = 0; utun_instance_destroy(g_a); }
if (g_b) { g_b->running = 0; utun_instance_destroy(g_b); }
if (ua) { uasync_destroy(ua, 0); ua = NULL; }
cleanup();
return (result == 2) ? 0 : 1;
}
+2 -2
View File
@@ -209,7 +209,7 @@ static void monitor(void* arg) {
ctx->receiver = create_instance(ctx->ua, 0x2222222222222222ULL, s_priv, s_pub);
add_server(ctx->receiver, "srv1", SRV_PORT);
utun_instance_init(ctx->receiver);
etcp_bind(ctx->receiver, ETCP_ID_DATA, on_recv);
etcp_bind(ctx->receiver, ETCP_RT_ID_DATA, on_recv);
}
ctx->phase = 4;
break;
@@ -296,7 +296,7 @@ int main(void) {
if (utun_instance_init(ctx.receiver) < 0) { printf("receiver init failed\n"); return 1; }
if (utun_instance_init(ctx.sender) < 0) { printf("sender init failed\n"); return 1; }
etcp_bind(ctx.receiver, ETCP_ID_DATA, on_recv);
etcp_bind(ctx.receiver, ETCP_RT_ID_DATA, on_recv);
printf("Creating dummynet...\n");
ctx.dn = dummynet_create(ctx.ua, "127.0.0.1", DN_PORT);
+482 -185
View File
@@ -1,8 +1,16 @@
// test_etcp_router.c — Unit test for etcp_router: bind/unbind, send, loopback, forward, reply
// test_etcp_router.c — Integration test for etcp_router with dummynet congestion
// 2 ноды в одном UASYNC, dummynet-прокси между ними.
// Фазы: baseline (без congestion) + congestion cycles (fill→push→drain→verify)
// Проверка: bitmap (нет потерь/дубликатов), per-packet payload integrity, порядок.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <inttypes.h>
#include "../lib/platform_compat.h"
#include "../lib/socket_compat.h"
#include "test_utils.h"
#ifdef _WIN32
#include <windows.h>
@@ -10,13 +18,12 @@
#else
#include <unistd.h>
#endif
#include <time.h>
#include <sys/stat.h>
#include "../src/etcp.h"
#include "../src/etcp_connections.h"
#include "../src/etcp_api.h"
#include "../src/etcp_router.h"
#include "../src/dummynet.h"
#include "../src/config_parser.h"
#include "../src/utun_instance.h"
#include "../src/routing.h"
@@ -29,58 +36,168 @@
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#define TEST_SVC_ID 0x01
#define TEST_TIMEOUT_MS 5000
#define TOTAL_PACKETS 20
#define MAX_PAYLOAD 200
// ======================== Configuration ========================
#define TEST_SVC_ID 0x01
#define BASELINE_PACKETS 20
#define PACKETS_PER_CYCLE 20
#define CONGESTION_CYCLES 5
#define BITMAP_SIZE 2000 // с запасом (baseline + cycles * (fill+push))
#define LOW_BW_KBPS 80 // ~55 pkt/s @ ~180 bytes — медленнее чем можем слать
#define DUMMY_QUEUE_SIZE 300 // чтобы dummynet не дропал
#define MAX_PAYLOAD 200
#define CONNECT_TIMEOUT_MS 10000
#define TEST_TIMEOUT_MS 120000
#define DRAIN_TIMEOUT_MS 30000 // per-cycle drain timeout
// ======================== Globals ========================
static char temp_dir[] = "/tmp/utun_test_XXXXXX";
static char server_conf[256], client_conf[256];
static char server_conf[512], client_conf[512];
static struct UTUN_INSTANCE* srv = NULL;
static struct UTUN_INSTANCE* cli = NULL;
static struct UASYNC* ua = NULL;
static struct dummynet* dn = NULL;
static int g_ok = 0;
static int g_test_done = 0;
static int g_phase = 0; // 0=wait_conn, 1=fwd, 2=reply
static int g_dn_port = 0, g_srv_port = 0, g_cli_port = 0;
static const uint64_t server_node_id = 0x1111111111111111ULL;
static const uint64_t client_node_id = 0x2222222222222222ULL;
// Bitmap: 0=not received, 1=received
static uint8_t rcvd_bitmap[BITMAP_SIZE];
// Corruption/dup tracking
static int g_fail = 0; // 0=ok, 1=corrupt, 2=dup, 3=out_of_range, 4=seq_mismatch
static uint32_t g_fail_seq = 0;
static uint32_t g_fail_detail = 0;
// Counters
static uint32_t g_total_sent = 0; // total successfully sent (etcp_route_send==0)
static uint32_t g_total_rcvd = 0; // total received by server handler
static uint32_t g_total_drop = 0; // total etcp_route_send returned -1
// Expected next seq for in-order delivery check
static uint32_t g_expected_seq = 0;
// State machine
enum {
ST_WAIT_CONN,
ST_BASELINE_SEND,
ST_BASELINE_WAIT,
ST_CYCLE_FILL,
ST_CYCLE_PUSH,
ST_CYCLE_DRAIN,
ST_FINAL
};
static int g_state = ST_WAIT_CONN;
static int g_cycle = 0;
static uint32_t g_cycle_start_seq = 0;
static uint32_t g_cycle_sent = 0; // packets sent in current cycle (fill+push)
static uint32_t g_cycle_push_ok = 0; // successful pushes in PUSH phase
static int g_backpressure_seen = 0; // saw etcp_route_send=-1 in FILL
// Timing
static struct timespec t_phase_start;
static double t_connect_ms = 0, t_baseline_ms = 0;
static double t_cycle_fill_ms[CONGESTION_CYCLES];
static double t_cycle_push_ms[CONGESTION_CYCLES];
static double t_cycle_drain_ms[CONGESTION_CYCLES];
static uint32_t g_cycle_fill_count[CONGESTION_CYCLES];
static uint32_t g_cycle_drop_count[CONGESTION_CYCLES];
// Monitor
static void* g_mon_id = NULL;
static int g_done = 0; // 0=running, 1=pass, -1=fail
// Server config — node 0x1111...
static const char* srv_cfg =
"[global]\n"
"my_node_id=0x1111111111111111\n"
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"tun_ip=10.99.0.1/24\n"
"tun_ifname=tun99\n"
"[server: s1]\n"
"addr=127.0.0.1:9041\n"
"type=public\n"
"[allowed_keys]\n"
"allow_all=1\n";
// ======================== Timing helpers ========================
static void tic(struct timespec* t) {
clock_gettime(CLOCK_MONOTONIC, t);
}
// Client config — node 0x2222...
static const char* cli_cfg =
"[global]\n"
"my_node_id=0x2222222222222222\n"
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
"tun_ip=10.99.0.2/24\n"
"tun_ifname=tun98\n"
"[server: s1]\n"
"addr=127.0.0.1:9042\n"
"type=public\n"
"[client: c1]\n"
"keepalive=1\n"
"peer_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"link=s1:127.0.0.1:9041\n";
static double toc_ms(const struct timespec* start) {
struct timespec end;
clock_gettime(CLOCK_MONOTONIC, &end);
return (end.tv_sec - start->tv_sec) * 1000.0 + (end.tv_nsec - start->tv_nsec) / 1000000.0;
}
// ======================== Test state ========================
static int fwd_sent = 0, fwd_rcvd = 0;
static int reply_sent = 0, reply_rcvd = 0;
static uint8_t expected_data[MAX_PAYLOAD];
static uint64_t server_node_id = 0x1111111111111111ULL;
static uint64_t client_node_id = 0x2222222222222222ULL;
// ======================== Port allocation ========================
static int alloc_consecutive_ports(int* base_port) {
for (int attempt = 0; attempt < 200; attempt++) {
socket_t s = socket_create_udp(AF_INET);
if (s == SOCKET_INVALID) 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");
a.sin_port = 0; // auto-assign
if (bind(s, (struct sockaddr*)&a, sizeof(a)) != 0) { socket_close_wrapper(s); continue; }
socklen_t alen = sizeof(a);
getsockname(s, (struct sockaddr*)&a, &alen);
int port = ntohs(a.sin_port);
socket_close_wrapper(s);
// Need port-1, port, port+1 all free
int ok = 1;
for (int p = port - 1; p <= port + 1 && ok; p++) {
if (p < 1024) { ok = 0; break; }
socket_t ts = socket_create_udp(AF_INET);
if (ts == SOCKET_INVALID) { ok = 0; break; }
struct sockaddr_in ta;
memset(&ta, 0, sizeof(ta));
ta.sin_family = AF_INET;
ta.sin_addr.s_addr = inet_addr("127.0.0.1");
ta.sin_port = htons((uint16_t)p);
if (bind(ts, (struct sockaddr*)&ta, sizeof(ta)) != 0) ok = 0;
socket_close_wrapper(ts);
}
if (ok) { *base_port = port; return 0; }
}
return -1;
}
// ======================== Config generation ========================
static const char* srv_priv = "38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68";
static const char* srv_pub = "ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a";
static const char* cli_priv = "704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f";
static const char* cli_pub = "b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01";
static void write_configs(void) {
// Server: listens on g_srv_port
snprintf(server_conf, sizeof(server_conf), "%s/server.conf", temp_dir);
FILE* f = fopen(server_conf, "w");
if (!f) { fprintf(stderr, "fopen server fail\n"); exit(1); }
fprintf(f,
"[global]\n"
"my_node_id=0x1111111111111111\n"
"my_private_key=%s\n"
"my_public_key=%s\n"
"tun_ip=10.99.0.1/24\n"
"tun_ifname=tun99\n"
"[server: s1]\n"
"addr=127.0.0.1:%d\n"
"type=public\n"
"[allowed_keys]\n"
"allow_all=1\n",
srv_priv, srv_pub, g_srv_port);
fclose(f);
// Client: listens on g_cli_port, connects to dummynet on g_dn_port
snprintf(client_conf, sizeof(client_conf), "%s/client.conf", temp_dir);
f = fopen(client_conf, "w");
if (!f) { fprintf(stderr, "fopen client fail\n"); exit(1); }
fprintf(f,
"[global]\n"
"my_node_id=0x2222222222222222\n"
"my_private_key=%s\n"
"my_public_key=%s\n"
"tun_ip=10.99.0.2/24\n"
"tun_ifname=tun98\n"
"[server: s1]\n"
"addr=127.0.0.1:%d\n"
"type=public\n"
"[client: c1]\n"
"keepalive=1\n"
"peer_public_key=%s\n"
"link=s1:127.0.0.1:%d\n",
cli_priv, cli_pub, g_cli_port, srv_pub, g_dn_port);
fclose(f);
}
// ======================== Helpers ========================
static int conn_established(struct UTUN_INSTANCE* inst) {
@@ -89,7 +206,8 @@ static int conn_established(struct UTUN_INSTANCE* inst) {
for (struct ETCP_LINK* l = c->links; l; l = l->next) {
if (l->initialized && c->crypto_ctx.initialized) {
int ok = 0;
for (int i = 0; i < SC_SESSION_KEY_SIZE; i++) if (c->crypto_ctx.session_key[i] != 0) { ok = 1; break; }
for (int i = 0; i < SC_SESSION_KEY_SIZE; i++)
if (c->crypto_ctx.session_key[i] != 0) { ok = 1; break; }
if (ok) return 1;
}
}
@@ -97,57 +215,38 @@ static int conn_established(struct UTUN_INSTANCE* inst) {
return 0;
}
static struct ETCP_CONN* first_conn(struct UTUN_INSTANCE* inst) {
return inst ? inst->connections : NULL;
static void gen_payload(uint32_t seq, uint8_t* buf, int len) {
for (int i = 0; i < len; i++)
buf[i] = (uint8_t)((i ^ seq ^ 0xAA) & 0xFF);
}
// Build and send one router packet. Returns 0 on success, -1 on failure.
static int send_one_pkt(uint32_t seq, int data_len) {
if (data_len > MAX_PAYLOAD) data_len = MAX_PAYLOAD;
uint8_t buf[10 + MAX_PAYLOAD];
buf[0] = TEST_SVC_ID;
buf[1] = 0x01; // DATA subcmd
memcpy(buf + 2, &seq, 4);
memcpy(buf + 6, &data_len, 4);
gen_payload(seq, buf + 10, data_len);
struct ll_entry* e = queue_entry_new(0);
if (!e) return -1;
e->dgram = u_malloc(10 + data_len);
if (!e->dgram) { queue_entry_free(e); return -1; }
memcpy(e->dgram, buf, 10 + data_len);
e->len = 10 + data_len;
return etcp_route_send(cli, server_node_id, e, 0);
}
// ======================== Server handler ========================
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_dgram_free(entry); queue_entry_free(entry); }
return;
}
uint8_t subcmd = entry->dgram[1];
uint32_t seq = 0; memcpy(&seq, entry->dgram + 2, 4);
uint32_t data_len = 0; memcpy(&data_len, entry->dgram + 6, 4);
uint8_t* payload = entry->dgram + 10;
if (subcmd == 0x01) { // DATA
if (seq != (uint32_t)fwd_rcvd) {
printf("[FAIL] server: seq mismatch expected=%u got=%u\n", (uint32_t)fwd_rcvd, seq);
g_test_done = -1;
} else if (data_len > 0 && memcmp(payload, expected_data, data_len) != 0) {
printf("[FAIL] server: data mismatch at seq=%u\n", seq);
g_test_done = -1;
} else {
fwd_rcvd++;
}
queue_dgram_free(entry); queue_entry_free(entry);
// Send reply back
if (!g_test_done && reply_sent < TOTAL_PACKETS) {
uint8_t buf[10 + MAX_PAYLOAD];
buf[0] = TEST_SVC_ID;
buf[1] = 0x02; // REPLY subcmd
uint32_t rseq = reply_sent;
memcpy(buf + 2, &rseq, 4);
memcpy(buf + 6, &data_len, 4);
memcpy(buf + 10, expected_data, data_len);
struct ll_entry* re = queue_entry_new(0);
if (re) { re->dgram = u_malloc(10 + data_len); memcpy(re->dgram, buf, 10 + data_len); re->len = 10 + data_len;
etcp_route_send(srv, client_node_id, re, 0); reply_sent++; }
}
} else {
queue_dgram_free(entry); queue_entry_free(entry);
}
}
// ======================== Client handler ========================
static void cli_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
(void)conn;
if (!entry || !entry->dgram || entry->len < 10) {
printf("[FAIL] srv_handler: bad entry len=%zu\n", entry ? entry->len : 0);
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
g_fail = 4; g_done = -1;
return;
}
uint8_t subcmd = entry->dgram[1];
@@ -155,24 +254,64 @@ static void cli_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
uint32_t data_len = 0; memcpy(&data_len, entry->dgram + 6, 4);
uint8_t* payload = entry->dgram + 10;
if (subcmd == 0x02) { // REPLY
if (seq != (uint32_t)reply_rcvd) {
printf("[FAIL] client: reply seq mismatch expected=%u got=%u\n", (uint32_t)reply_rcvd, seq);
g_test_done = -1;
} else if (data_len > 0 && memcmp(payload, expected_data, data_len) != 0) {
printf("[FAIL] client: reply data mismatch at seq=%u\n", seq);
g_test_done = -1;
} else {
reply_rcvd++;
if (subcmd != 0x01) {
printf("[FAIL] srv_handler: unexpected subcmd=%u seq=%u\n", subcmd, seq);
queue_dgram_free(entry); queue_entry_free(entry);
g_fail = 4; g_done = -1;
return;
}
// Check seq in range
if (seq >= BITMAP_SIZE) {
printf("[FAIL] srv_handler: seq=%u out of bitmap range\n", seq);
queue_dgram_free(entry); queue_entry_free(entry);
g_fail = 3; g_fail_seq = seq; g_done = -1;
return;
}
// Check in-order delivery (router should guarantee this)
if (seq != g_expected_seq) {
printf("[FAIL] srv_handler: seq mismatch expected=%u got=%u (loss or reorder)\n",
g_expected_seq, seq);
queue_dgram_free(entry); queue_entry_free(entry);
g_fail = 4; g_fail_seq = seq; g_fail_detail = g_expected_seq;
g_done = -1;
return;
}
// Check payload integrity
if (data_len > 0 && data_len <= MAX_PAYLOAD) {
uint8_t expected[MAX_PAYLOAD];
gen_payload(seq, expected, data_len);
if (memcmp(payload, expected, data_len) != 0) {
printf("[FAIL] srv_handler: data corrupt at seq=%u\n", seq);
queue_dgram_free(entry); queue_entry_free(entry);
g_fail = 1; g_fail_seq = seq;
g_done = -1;
return;
}
}
queue_dgram_free(entry); queue_entry_free(entry);
// Check duplicate
if (rcvd_bitmap[seq] != 0) {
printf("[FAIL] srv_handler: duplicate seq=%u\n", seq);
queue_dgram_free(entry); queue_entry_free(entry);
g_fail = 2; g_fail_seq = seq;
g_done = -1;
return;
}
rcvd_bitmap[seq] = 1;
g_total_rcvd++;
g_expected_seq++;
queue_dgram_free(entry);
queue_entry_free(entry);
}
// ======================== Loopback test (no ETCP) ========================
static int loop_rcvd = 0;
static int loop_ok = 0;
static void loop_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
static int loop_rcvd = 0, loop_ok = 0;
static void loop_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
(void)conn;
if (entry && entry->dgram && entry->len >= 6) {
loop_rcvd++;
@@ -187,7 +326,7 @@ static int test_loopback(void) {
uint8_t data[6] = { 0xF0, 0xAA, 0xBB, 0xCC, 0x00, 0x00 };
struct ll_entry* e = queue_entry_new(0);
e->dgram = u_malloc(6); memcpy(e->dgram, data, 6); e->len = 6;
etcp_route_send(srv, srv->node_id, e, 0); // loopback
etcp_route_send(srv, srv->node_id, e, 0);
etcp_router_unbind(srv, 0xF0);
if (loop_rcvd != 1 || !loop_ok) {
printf("[FAIL] loopback: rcvd=%d ok=%d\n", loop_rcvd, loop_ok);
@@ -197,99 +336,267 @@ static int test_loopback(void) {
return 0;
}
// ======================== Test API bind/unbind ========================
static int test_api(void) {
if (etcp_router_bind(srv, 0xEE, loop_handler) != 0) { printf("[FAIL] bind\n"); return 1; }
if (etcp_router_bind(srv, 0xEE, loop_handler) != 0) { printf("[FAIL] rebind (overwrite)\n"); etcp_router_unbind(srv, 0xEE); return 1; }
if (etcp_router_unbind(srv, 0xEE) != 0) { printf("[FAIL] unbind\n"); return 1; }
if (etcp_router_unbind(srv, 0xEE) == 0) { printf("[FAIL] double unbind should fail\n"); return 1; }
// Invalid args:
if (etcp_router_bind(NULL, 0, NULL) == 0) { printf("[FAIL] bind null\n"); return 1; }
if (etcp_router_bind(srv, 0xEE, NULL) == 0) { printf("[FAIL] bind null cb\n"); return 1; }
printf(" api bind/unbind: OK\n");
// ======================== Dummynet setup ========================
static int setup_dummynet(void) {
dn = dummynet_create(ua, "127.0.0.1", (uint16_t)g_dn_port);
if (!dn) { printf("[FAIL] dummynet_create on port %d\n", g_dn_port); return -1; }
// Forward: dummynet → server
dummynet_set_direction(dn, DUMMYNET_FORWARD, 0, 0, 0, DUMMY_QUEUE_SIZE, 0,
"127.0.0.1", (uint16_t)g_srv_port);
// Backward: dummynet → client
dummynet_set_direction(dn, DUMMYNET_BACKWARD, 0, 0, 0, DUMMY_QUEUE_SIZE, 0,
"127.0.0.1", (uint16_t)g_cli_port);
return 0;
}
// ======================== Monitor & sender ========================
static uint64_t dedup_bgp_log = 0;
static void set_bw(uint32_t kbps) {
dummynet_set_direction(dn, DUMMYNET_FORWARD, 0, 0, kbps, DUMMY_QUEUE_SIZE, 0,
"127.0.0.1", (uint16_t)g_srv_port);
dummynet_set_direction(dn, DUMMYNET_BACKWARD, 0, 0, kbps, DUMMY_QUEUE_SIZE, 0,
"127.0.0.1", (uint16_t)g_cli_port);
}
// ======================== Monitor ========================
static int g_conn_ok = 0;
static int g_conn_delay = 0;
static uint32_t g_baseline_sent = 0;
static double g_cycle_drain_start_ms = 0;
static void monitor(void* arg) {
(void)arg;
if (g_test_done) { g_mon_id = NULL; return; }
if (g_done) { g_mon_id = NULL; return; }
static int conn_ok = 0, conn_delay = 0;
if (!conn_ok) {
// Phase: wait for connection
if (g_state == ST_WAIT_CONN) {
if (conn_established(srv) && conn_established(cli)) {
conn_delay++;
if (conn_delay < 40) { g_mon_id = uasync_set_timeout(ua, 10, NULL, monitor, "mon"); return; }
conn_ok = 1;
printf(" connections established\n");
// Run API tests + loopback
if (test_api() != 0) { g_test_done = -1; return; }
if (test_loopback() != 0) { g_test_done = -1; return; }
// Bind handlers
g_conn_delay++;
if (g_conn_delay < 50) { g_mon_id = uasync_set_timeout(ua, 10, NULL, monitor, "mon"); return; }
g_conn_ok = 1;
t_connect_ms = toc_ms(&t_phase_start);
printf("Phase 1 — Connect: %7.1f ms\n", t_connect_ms);
// Run loopback test
if (test_loopback() != 0) { g_done = -1; return; }
// Bind data handler
etcp_router_bind(srv, TEST_SVC_ID, srv_handler);
etcp_router_bind(cli, TEST_SVC_ID, cli_handler);
// Generate shared random data
for (int i = 0; i < MAX_PAYLOAD; i++) expected_data[i] = (uint8_t)(rand() & 0xFF);
g_phase = 1;
printf(" sending %d packets client→server...\n", TOTAL_PACKETS);
fflush(stdout);
// Start baseline
g_state = ST_BASELINE_SEND;
tic(&t_phase_start);
printf("Phase 2 — Baseline (%d packets, no congestion):\n", BASELINE_PACKETS);
}
g_mon_id = uasync_set_timeout(ua, 10, NULL, monitor, "mon");
return;
}
// Phase 1: send forward
if (g_phase == 1 && fwd_sent < TOTAL_PACKETS) {
int data_len = 16 + (fwd_sent % 32);
uint8_t buf[10 + MAX_PAYLOAD];
buf[0] = TEST_SVC_ID;
buf[1] = 0x01; // DATA subcmd
uint32_t seq = fwd_sent;
memcpy(buf + 2, &seq, 4);
memcpy(buf + 6, &data_len, 4);
memcpy(buf + 10, expected_data, data_len);
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_dgram_free(e); queue_entry_free(e); }
// Phase: baseline send (all at once, no congestion)
if (g_state == ST_BASELINE_SEND) {
while (g_baseline_sent < BASELINE_PACKETS) {
int data_len = 16 + (g_baseline_sent % 32);
if (send_one_pkt(g_total_sent, data_len) == 0) {
g_total_sent++;
g_baseline_sent++;
} else {
printf("[FAIL] baseline: send failed at seq=%u\n", g_total_sent);
g_done = -1;
return;
}
}
g_state = ST_BASELINE_WAIT;
g_mon_id = uasync_set_timeout(ua, 10, NULL, monitor, "mon");
return;
}
if (g_phase == 1 && fwd_sent >= TOTAL_PACKETS && fwd_rcvd >= TOTAL_PACKETS && reply_sent >= TOTAL_PACKETS) {
g_phase = 2;
printf(" forward phase done: sent=%d rcvd=%d reply_sent=%d\n", fwd_sent, fwd_rcvd, reply_sent);
// Phase: baseline wait
if (g_state == ST_BASELINE_WAIT) {
if (g_total_rcvd >= BASELINE_PACKETS) {
t_baseline_ms = toc_ms(&t_phase_start);
printf(" baseline: %7.1f ms sent=%u rcvd=%u\n", t_baseline_ms, g_baseline_sent, g_total_rcvd);
// Start congestion cycles
g_cycle = 0;
g_state = ST_CYCLE_FILL;
tic(&t_phase_start);
printf("Phase 3 — Congestion cycles (%d cycles, %d pkt/cycle, bw=%u kbps):\n",
CONGESTION_CYCLES, PACKETS_PER_CYCLE, LOW_BW_KBPS);
}
g_mon_id = uasync_set_timeout(ua, 10, NULL, monitor, "mon");
return;
}
if (g_phase == 2 && reply_rcvd >= TOTAL_PACKETS) {
printf(" reply phase done: rcvd=%d\n", reply_rcvd);
g_test_done = 1;
// Phase: cycle fill — send until backpressure
if (g_state == ST_CYCLE_FILL) {
set_bw(LOW_BW_KBPS);
g_cycle_start_seq = g_total_sent;
g_cycle_sent = 0;
g_backpressure_seen = 0;
g_state = ST_CYCLE_FILL + 100; // sub-state: actively filling
tic(&t_phase_start);
g_mon_id = uasync_set_timeout(ua, 1, NULL, monitor, "mon");
return;
}
if (g_state == ST_CYCLE_FILL + 100) {
// Send one packet per tick until backpressure
int data_len = 32 + (g_total_sent % 64);
int ret = send_one_pkt(g_total_sent, data_len);
if (ret == 0) {
g_total_sent++;
g_cycle_sent++;
} else {
// Backpressure hit — inflight+send_q full
g_backpressure_seen = 1;
t_cycle_fill_ms[g_cycle] = toc_ms(&t_phase_start);
g_cycle_fill_count[g_cycle] = g_cycle_sent;
printf(" cycle %d fill: %7.1f ms sent=%u (backpressure)\n",
g_cycle + 1, t_cycle_fill_ms[g_cycle], g_cycle_sent);
// Transition to push phase
g_state = ST_CYCLE_PUSH;
g_cycle_push_ok = 0;
g_cycle_drop_count[g_cycle] = 0;
tic(&t_phase_start);
}
g_mon_id = uasync_set_timeout(ua, 1, NULL, monitor, "mon");
return;
}
// Phase: cycle push — push 20 more packets through congestion
if (g_state == ST_CYCLE_PUSH) {
if (g_cycle_push_ok >= PACKETS_PER_CYCLE) {
t_cycle_push_ms[g_cycle] = toc_ms(&t_phase_start);
printf(" cycle %d push: %7.1f ms pushed=%u drops=%u\n",
g_cycle + 1, t_cycle_push_ms[g_cycle], g_cycle_push_ok,
g_cycle_drop_count[g_cycle]);
// Release shaper
set_bw(0);
g_state = ST_CYCLE_DRAIN;
tic(&t_phase_start);
g_cycle_drain_start_ms = toc_ms(&t_phase_start);
g_mon_id = uasync_set_timeout(ua, 10, NULL, monitor, "mon");
return;
}
// Try to send one packet
int data_len = 32 + (g_total_sent % 64);
int ret = send_one_pkt(g_total_sent, data_len);
if (ret == 0) {
g_total_sent++;
g_cycle_sent++;
g_cycle_push_ok++;
} else {
g_total_drop++;
g_cycle_drop_count[g_cycle]++;
}
g_mon_id = uasync_set_timeout(ua, 500, NULL, monitor, "mon"); // 50ms = ROUTER_SEND_RESUME_TB
return;
}
// Phase: cycle drain — wait for all sent packets to arrive
if (g_state == ST_CYCLE_DRAIN) {
uint32_t cycle_target = g_cycle_start_seq + g_cycle_sent;
if (g_total_rcvd >= cycle_target) {
t_cycle_drain_ms[g_cycle] = toc_ms(&t_phase_start);
printf(" cycle %d drain: %7.1f ms rcvd=%u/%u\n",
g_cycle + 1, t_cycle_drain_ms[g_cycle], g_total_rcvd, cycle_target);
g_cycle++;
if (g_cycle >= CONGESTION_CYCLES) {
g_state = ST_FINAL;
} else {
g_state = ST_CYCLE_FILL;
}
}
g_mon_id = uasync_set_timeout(ua, 10, NULL, monitor, "mon");
return;
}
// Phase: final verification
if (g_state == ST_FINAL) {
printf("Phase 4 — Final verify:\n");
// Scan bitmap for gaps
int gaps = 0;
uint32_t first_gap = 0, last_gap = 0;
for (uint32_t i = 0; i < g_total_sent; i++) {
if (rcvd_bitmap[i] == 0) {
if (gaps == 0) first_gap = i;
last_gap = i;
gaps++;
}
}
// Dummynet stats
const struct dummynet_stats* sf = dummynet_get_stats(dn, DUMMYNET_FORWARD);
const struct dummynet_stats* sb = dummynet_get_stats(dn, DUMMYNET_BACKWARD);
printf(" total: sent=%u rcvd=%u drops=%u\n", g_total_sent, g_total_rcvd, g_total_drop);
printf(" bitmap: gaps=%d (first=%u last=%u)\n", gaps, first_gap, last_gap);
printf(" dummynet fwd: recv=%" PRIu64 " sent=%" PRIu64 " dropped=%" PRIu64 " lost=%" PRIu64 " qmax=%u\n",
sf->recv, sf->sent, sf->dropped, sf->lost, sf->queue_max);
printf(" dummynet bwd: recv=%" PRIu64 " sent=%" PRIu64 " dropped=%" PRIu64 " lost=%" PRIu64 " qmax=%u\n",
sb->recv, sb->sent, sb->dropped, sb->lost, sb->queue_max);
// Router stats
struct ETCP_ROUTER_CONN* rconn = etcp_router_conn_get(cli, server_node_id, TEST_SVC_ID);
if (rconn) {
printf(" router: tx_seq=%u rx_seq=%u tx_acked=%u c_pkts_sent=%u c_pkts_rcvd=%u c_dup_dropped=%u c_oob_dropped=%u\n",
rconn->tx_seq, rconn->rx_seq, rconn->tx_acked,
rconn->c_pkts_sent, rconn->c_pkts_rcvd,
rconn->c_dup_dropped, rconn->c_oob_dropped);
}
// Per-cycle summary
printf(" per-cycle:\n");
double total_fill = 0, total_push = 0, total_drain = 0;
for (int i = 0; i < CONGESTION_CYCLES; i++) {
printf(" cycle %d: fill=%6.1fms push=%6.1fms drain=%7.1fms fill_pkt=%u push_drops=%u\n",
i + 1, t_cycle_fill_ms[i], t_cycle_push_ms[i], t_cycle_drain_ms[i],
g_cycle_fill_count[i], g_cycle_drop_count[i]);
total_fill += t_cycle_fill_ms[i];
total_push += t_cycle_push_ms[i];
total_drain += t_cycle_drain_ms[i];
}
printf(" totals: fill=%6.1fms push=%6.1fms drain=%7.1fms\n", total_fill, total_push, total_drain);
// Verdict
if (g_total_sent != g_total_rcvd) {
printf("[FAIL] sent(%u) != rcvd(%u): %u packets lost\n", g_total_sent, g_total_rcvd, g_total_sent - g_total_rcvd);
g_done = -1;
} else if (gaps > 0) {
printf("[FAIL] %d gaps in bitmap (losses)\n", gaps);
g_done = -1;
} else {
printf("[PASS] test_etcp_router — %u packets, 0 loss, 0 dup, 0 corrupt\n", g_total_sent);
g_done = 1;
}
g_mon_id = NULL;
return;
}
g_mon_id = uasync_set_timeout(ua, 10, NULL, monitor, "mon");
}
static void timeout(void* arg) {
// ======================== Timeout ========================
static void timeout_cb(void* arg) {
(void)arg;
if (!g_test_done) { printf("[FAIL] timeout: sent=%d rcvd=%d reply_sent=%d reply_rcvd=%d\n",
fwd_sent, fwd_rcvd, reply_sent, reply_rcvd);
g_test_done = -1; }
if (!g_done) {
printf("[FAIL] TIMEOUT: state=%d cycle=%d sent=%u rcvd=%u drops=%u\n",
g_state, g_cycle, g_total_sent, g_total_rcvd, g_total_drop);
g_done = -1;
}
if (g_mon_id) { uasync_cancel_timeout(ua, g_mon_id); g_mon_id = NULL; }
}
// ======================== Main ========================
int main(void) {
if (test_mkdtemp(temp_dir) != 0) { fprintf(stderr, "mkdtemp fail\n"); return 1; }
snprintf(server_conf, sizeof(server_conf), "%s/server.conf", temp_dir);
snprintf(client_conf, sizeof(client_conf), "%s/client.conf", temp_dir);
FILE* f = fopen(server_conf, "w");
if (!f) { fprintf(stderr, "fopen fail\n"); test_rmdir(temp_dir); return 1; }
fprintf(f, "%s", srv_cfg); fclose(f);
f = fopen(client_conf, "w");
if (!f) { fprintf(stderr, "fopen fail\n"); test_unlink(server_conf); test_rmdir(temp_dir); return 1; }
fprintf(f, "%s", cli_cfg); fclose(f);
// Allocate consecutive ports
int base_port;
if (alloc_consecutive_ports(&base_port) != 0) {
printf("[FAIL] cannot allocate consecutive ports\n");
test_rmdir(temp_dir);
return 1;
}
g_dn_port = base_port;
g_srv_port = base_port + 1;
g_cli_port = base_port - 1;
printf("Ports: dummynet=%d server=%d client=%d\n", g_dn_port, g_srv_port, g_cli_port);
printf("=== test_etcp_router ===\n");
write_configs();
printf("=== test_etcp_router (with dummynet congestion) ===\n");
debug_config_init();
debug_set_level(DEBUG_LEVEL_ERROR);
@@ -297,6 +604,7 @@ int main(void) {
utun_instance_set_tun_init_enabled(0);
srand((unsigned)time(NULL));
tic(&t_phase_start);
ua = uasync_create();
if (!ua) { printf("[FAIL] uasync_create\n"); goto done; }
@@ -306,35 +614,24 @@ int main(void) {
cli = utun_instance_create(ua, client_conf);
if (!cli || utun_instance_init(cli) < 0) { printf("[FAIL] client create\n"); goto done; }
g_mon_id = uasync_set_timeout(ua, 100, NULL, monitor, "mon");
void* to_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS * 10, NULL, timeout, "to");
// Create dummynet between client and server
if (setup_dummynet() != 0) { printf("[FAIL] dummynet setup\n"); goto done; }
while (!g_test_done) uasync_poll(ua, 100);
g_mon_id = uasync_set_timeout(ua, 100, NULL, monitor, "mon");
void* to_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS * 10, NULL, timeout_cb, "to");
while (!g_done) uasync_poll(ua, 10);
if (to_id) uasync_cancel_timeout(ua, to_id);
if (g_mon_id) uasync_cancel_timeout(ua, g_mon_id);
if (g_test_done == 1) {
// Check all statistics
if (fwd_sent == TOTAL_PACKETS && fwd_rcvd == TOTAL_PACKETS &&
reply_sent == TOTAL_PACKETS && reply_rcvd == TOTAL_PACKETS) {
printf("[PASS] test_etcp_router — %d packets forwarded, %d replied\n", fwd_rcvd, reply_rcvd);
g_ok = 1;
} else {
printf("[FAIL] incomplete: fwd_sent=%d fwd_rcvd=%d reply_sent=%d reply_rcvd=%d\n",
fwd_sent, fwd_rcvd, reply_sent, reply_rcvd);
}
} else {
printf("[FAIL] test did not complete\n");
}
etcp_router_unbind(srv, TEST_SVC_ID);
etcp_router_unbind(cli, TEST_SVC_ID);
done:
if (dn) dummynet_destroy(dn);
if (srv) { srv->running = 0; utun_instance_destroy(srv); }
if (cli) { cli->running = 0; utun_instance_destroy(cli); }
if (ua) uasync_destroy(ua, 0);
test_unlink(server_conf); test_unlink(client_conf); test_rmdir(temp_dir);
return g_ok ? 0 : 1;
return g_done == 1 ? 0 : 1;
}
+287
View File
@@ -0,0 +1,287 @@
// test_etcp_router_reconnect.c — ETCP router test: a-b-c, b restarts
// Топология: a [server] ←—[клиент b]—→ c [server], один UASYNC
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "../lib/platform_compat.h"
#include "test_utils.h"
#ifdef _WIN32
#include <windows.h>
#include <direct.h>
#include <process.h>
#define getpid _getpid
#else
#include <unistd.h>
#endif
#include "../src/etcp.h"
#include "../src/etcp_connections.h"
#include "../src/etcp_router.h"
#include "../src/etcp_api.h"
#include "../src/route_bgp.h"
#include "../src/route_node.h"
#include "../src/config_parser.h"
#include "../src/utun_instance.h"
#include "../src/routing.h"
#include "../src/tun_if.h"
#include "../src/secure_channel.h"
#include "../lib/u_async.h"
#include "../lib/ll_queue.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#define TEST_SVC_ID 0x10
#define TEST_TIMEOUT_MS 60000
#define PHASE_PACKETS 500
#define MAX_PAYLOAD 1500
#define MIN_PAYLOAD 20
#define MAX_BURST 20
#define MIN_BURST 2
#define POLL_TB 50
static struct UTUN_INSTANCE* g_a = NULL;
static struct UTUN_INSTANCE* g_b = NULL;
static struct UTUN_INSTANCE* g_c = NULL;
static struct UASYNC* ua = NULL;
static char temp_dir[] = "/tmp/utun_test_XXXXXX";
static char a_conf[512], b_conf[512], c_conf[512];
static int a_port, b_port_a, b_port_c, c_port;
static const uint64_t node_a = 0xAAAAAAAA00000001ULL;
static const uint64_t node_b = 0xBBBBBBBB00000001ULL;
static const uint64_t node_c = 0xCCCCCCCC00000001ULL;
static struct SC_MYKEYS keys_a, keys_b, keys_c;
static char privhex_a[65], privhex_b[65], privhex_c[65], pubhex_a[65], pubhex_b[65], pubhex_c[65];
enum { ST_INIT, ST_PHASE1_SEND, ST_PHASE1_WAIT, ST_B_KILL, ST_B_RESTART, ST_PHASE3_SEND, ST_PHASE3_WAIT, ST_DONE };
static int g_state = ST_INIT;
static int g_test_ok = 0;
static int g_fail_code = 0;
static uint32_t g_total_sent_phase1 = 0, g_total_sent_phase3 = 0;
static uint32_t g_phase_sent = 0, g_seq = 0;
static int g_tick_counter = 0;
static uint32_t g_rcvd_total = 0, g_expected_seq = 0;
static int g_bgp_ready_mask = 0;
static int g_loop_cnt = 0;
static void hex_encode(const uint8_t* bin, int len, char* out) {
static const char hex[] = "0123456789abcdef";
for (int i = 0; i < len; i++) { out[i*2]=hex[bin[i]>>4]; out[i*2+1]=hex[bin[i]&0xF]; }
out[len*2]=0;
}
static void gen_payload(uint32_t seq, uint8_t* buf, int len) {
for (int i=0;i<len;i++) buf[i]=(uint8_t)((i^seq^0xA5)&0xFF);
}
static struct NODEINFO_Q* mknode(uint64_t nid, const uint8_t pubkey[32],
uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint16_t port) {
size_t extra = sizeof(struct NODEINFO_IPV4_SOCKET_META) + sizeof(struct NODEINFO_IPV4_ADDR);
size_t data_sz = sizeof(struct NODEINFO_Q) - sizeof(struct ll_entry) + extra;
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)queue_entry_new(data_sz);
if (!nq) return NULL;
struct NODEINFO* ni = &nq->node;
ni->node_id = nid; ni->ver = 0;
memcpy(ni->public_key, pubkey, SC_PUBKEY_SIZE);
ni->local_v4_sockets = 1;
ni->local_v4_addrs = 1;
uint8_t* dyn = (uint8_t*)(ni + 1);
struct NODEINFO_IPV4_SOCKET_META* meta = (struct NODEINFO_IPV4_SOCKET_META*)dyn;
meta->id = 0; meta->config_type = CFG_SERVER_TYPE_PUBLIC; meta->nat_type = NAT_TYPE_UNKNOWN;
dyn += sizeof(struct NODEINFO_IPV4_SOCKET_META);
struct NODEINFO_IPV4_ADDR* addr = (struct NODEINFO_IPV4_ADDR*)dyn;
addr->addr[0] = a; addr->addr[1] = b; addr->addr[2] = c; addr->addr[3] = d;
addr->port = port; addr->type = ADDR_TYPE_INTERFACE; addr->socket_id = 0;
return nq;
}
static int create_temp_configs(void) {
if (test_mkdtemp(temp_dir) != 0) { fprintf(stderr, "mkdtemp fail\n"); return -1; }
int base = 42000+(getpid()%15000);
a_port=base; b_port_a=base+1; b_port_c=base+2; c_port=base+3;
if (sc_generate_keypair(&keys_a)!=SC_OK||sc_generate_keypair(&keys_b)!=SC_OK||sc_generate_keypair(&keys_c)!=SC_OK) { fprintf(stderr,"keygen fail\n"); return -1; }
hex_encode(keys_a.private_key,32,privhex_a); hex_encode(keys_a.public_key,32,pubhex_a);
hex_encode(keys_b.private_key,32,privhex_b); hex_encode(keys_b.public_key,32,pubhex_b);
hex_encode(keys_c.private_key,32,privhex_c); hex_encode(keys_c.public_key,32,pubhex_c);
snprintf(a_conf,sizeof(a_conf),"%s/a.conf",temp_dir);
FILE* f=fopen(a_conf,"w"); if(!f){perror("a_conf");return -1;}
fprintf(f,"[global]\nmy_node_id=0x%016llX\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.1/24\ntun_ifname=tun99\n\n"
"[server:hub_a]\naddr=127.0.0.1:%d\ntype=public\n\n[allowed_keys]\nallow_all=1\n",(unsigned long long)node_a,privhex_a,pubhex_a,a_port);
fclose(f);
snprintf(b_conf,sizeof(b_conf),"%s/b.conf",temp_dir);
f=fopen(b_conf,"w"); if(!f){perror("b_conf");return -1;}
fprintf(f,"[global]\nmy_node_id=0x%016llX\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.2/24\ntun_ifname=tun98\n\n"
"[server:s_a]\naddr=127.0.0.1:%d\ntype=public\n\n[server:s_c]\naddr=127.0.0.1:%d\ntype=public\n",
(unsigned long long)node_b,privhex_b,pubhex_b,b_port_a,b_port_c);
fclose(f);
snprintf(c_conf,sizeof(c_conf),"%s/c.conf",temp_dir);
f=fopen(c_conf,"w"); if(!f){perror("c_conf");return -1;}
fprintf(f,"[global]\nmy_node_id=0x%016llX\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.3/24\ntun_ifname=tun97\n\n"
"[server:hub_c]\naddr=127.0.0.1:%d\ntype=public\n\n[allowed_keys]\nallow_all=1\n",(unsigned long long)node_c,privhex_c,pubhex_c,c_port);
fclose(f);
return 0;
}
static void cleanup(void) { test_unlink(a_conf); test_unlink(b_conf); test_unlink(c_conf); test_rmdir(temp_dir); }
static void recv_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (!entry || !entry->dgram) { fprintf(stderr, "RECV: null entry\n"); g_test_ok=-1; g_fail_code=10; return; }
if (!conn && entry->len == 9) { queue_dgram_free(entry); queue_entry_free(entry); return; }
if (entry->len < 7) { fprintf(stderr, "RECV: short entry len=%u\n", (unsigned)entry->len); g_test_ok=-1; g_fail_code=10; queue_dgram_free(entry); queue_entry_free(entry); return; }
uint32_t seq=0; memcpy(&seq,entry->dgram+1,4);
uint16_t sz=0; memcpy(&sz, entry->dgram+5,2);
if (entry->len != (size_t)(7+sz)) { fprintf(stderr, "FAIL: size mismatch seq=%u\n", seq); g_test_ok=-1; g_fail_code=11; queue_dgram_free(entry); queue_entry_free(entry); return; }
if (seq != g_expected_seq) { fprintf(stderr, "FAIL: seq broken seq=%u expected=%u\n", seq, g_expected_seq); g_test_ok=-1; g_fail_code=12; queue_dgram_free(entry); queue_entry_free(entry); return; }
uint8_t exp[MAX_PAYLOAD]; gen_payload(seq, exp, sz);
if (memcmp(entry->dgram+7, exp, sz) != 0) { fprintf(stderr, "FAIL: corrupt seq=%u\n", seq); g_test_ok=-1; g_fail_code=13; queue_dgram_free(entry); queue_entry_free(entry); return; }
g_expected_seq++; g_rcvd_total++;
queue_dgram_free(entry); queue_entry_free(entry);
}
static int send_one_pkt(void) {
int len = MIN_PAYLOAD + (rand()%(MAX_PAYLOAD-MIN_PAYLOAD+1));
uint8_t* buf = u_malloc(7+len); if(!buf) return -1;
buf[0]=TEST_SVC_ID; memcpy(buf+1,&g_seq,4);
uint16_t s16=(uint16_t)len; memcpy(buf+5,&s16,2);
gen_payload(g_seq, buf+7, len);
struct ll_entry* e=queue_entry_new(0); if(!e){u_free(buf);return -1;}
e->dgram=buf; e->len=7+len;
int ret = etcp_route_send(g_a, node_c, e, 0);
if (ret == 0) g_seq++;
return ret;
}
static void connect_bgp_ready_cb(void* arg, struct ETCP_CONN* conn, int type) {
(void)conn;
if (type == ETCP_CONNECT_BGP_READY) {
int id = *(int*)arg; g_bgp_ready_mask |= (1 << id);
}
}
static const char* state_name(int s) {
static const char* n[]={"INIT","P1SEND","P1WAIT","B_KILL","B_RESTART","P3SEND","P3WAIT","DONE"}; return s<8?n[s]:"?";
}
static void send_burst(void) {
int b = MIN_BURST+(rand()%(MAX_BURST-MIN_BURST+1));
if (b>(int)(PHASE_PACKETS-g_phase_sent)) b=PHASE_PACKETS-g_phase_sent;
for (int i=0;i<b;i++) { if (send_one_pkt()==0) g_phase_sent++; else break; }
}
static void state_step(void) {
switch (g_state) {
case ST_INIT:
if (g_bgp_ready_mask == 3) {
struct ETCP_CONN* rc = route_bgp_find_conn_for_node(g_a->bgp, node_c);
if (rc) g_state=ST_PHASE1_SEND;
}
break;
case ST_PHASE1_SEND:
if (g_phase_sent < PHASE_PACKETS && g_tick_counter <= 0) {
send_burst(); g_tick_counter = 1; g_total_sent_phase1 = g_phase_sent;
}
break;
case ST_PHASE1_WAIT:
if (g_rcvd_total >= g_total_sent_phase1) g_state=ST_B_KILL;
break;
case ST_B_KILL:
g_b->running=0; utun_instance_destroy(g_b); g_b=NULL;
{ struct ETCP_CONN *c,*n;
for(c=g_a->connections;c;c=n){n=c->next;if(c->peer_node_id==node_b)etcp_connection_close(c);}
for(c=g_c->connections;c;c=n){n=c->next;if(c->peer_node_id==node_b)etcp_connection_close(c);} }
g_phase_sent=0; g_bgp_ready_mask=0; g_state=ST_B_RESTART;
break;
case ST_B_RESTART:
if (!g_b) {
g_b=utun_instance_create(ua,b_conf);
if (!g_b||utun_instance_init(g_b)<0) { fprintf(stderr,"FAIL: b restart\n"); g_test_ok=-1; g_fail_code=20; return; }
g_b->etcp_connect_timeout_tb = 300000;
struct NODEINFO_Q* nq_a = mknode(node_a, keys_a.public_key, 127,0,0,1, (uint16_t)a_port);
struct NODEINFO_Q* nq_c = mknode(node_c, keys_c.public_key, 127,0,0,1, (uint16_t)c_port);
if (!nq_a || !nq_c) { fprintf(stderr,"FAIL: mknode\n"); g_test_ok=-1; return; }
static int cc_a=0,cc_c=1;
etcp_connect(g_b,nq_a,connect_bgp_ready_cb,&cc_a,ETCP_CONNECT_BGP_READY);
etcp_connect(g_b,nq_c,connect_bgp_ready_cb,&cc_c,ETCP_CONNECT_BGP_READY);
}
if (g_bgp_ready_mask == 3) {
struct ETCP_CONN* rc = route_bgp_find_conn_for_node(g_a->bgp, node_c);
if (rc) { g_phase_sent=0; g_state=ST_PHASE3_SEND; }
}
break;
case ST_PHASE3_SEND:
if (g_phase_sent < PHASE_PACKETS && g_tick_counter <= 0) {
send_burst(); g_tick_counter = 1; g_total_sent_phase3 = g_phase_sent;
}
break;
case ST_PHASE3_WAIT:
if (g_rcvd_total >= g_total_sent_phase1 + g_total_sent_phase3) g_state=ST_DONE;
break;
case ST_DONE:
if (g_rcvd_total == g_total_sent_phase1+g_total_sent_phase3 && g_rcvd_total>0) g_test_ok=1;
else { fprintf(stderr, "FAIL: sent=%u rcvd=%u\n", g_total_sent_phase1+g_total_sent_phase3, g_rcvd_total); g_test_ok=-1; g_fail_code=30; }
return;
}
if (g_tick_counter>0) g_tick_counter--;
}
static void timeout_cb(void* arg) {
(void)arg;
fprintf(stderr, "TIMEOUT: state=%s sent_p1=%u sent_p3=%u rcvd=%u\n", state_name(g_state), g_total_sent_phase1, g_total_sent_phase3, g_rcvd_total);
g_test_ok=-1; g_fail_code=99;
}
int main(void) {
srand((unsigned)time(NULL));
if (create_temp_configs()!=0) return 1;
printf("=== ETCP Router Reconnect Test ===\nPorts: a=%d b_a=%d b_c=%d c=%d\n", a_port, b_port_a, b_port_c, c_port);
debug_config_init();
debug_set_level(DEBUG_LEVEL_ERROR);
utun_instance_set_tun_init_enabled(0);
ua=uasync_create(); if(!ua){cleanup();return 1;}
g_a=utun_instance_create(ua,a_conf); if(!g_a||utun_instance_init(g_a)<0){fprintf(stderr,"FAIL: a\n");goto fail;}
g_c=utun_instance_create(ua,c_conf); if(!g_c||utun_instance_init(g_c)<0){fprintf(stderr,"FAIL: c\n");goto fail;}
g_b=utun_instance_create(ua,b_conf); if(!g_b||utun_instance_init(g_b)<0){fprintf(stderr,"FAIL: b\n");goto fail;}
etcp_router_bind(g_c, TEST_SVC_ID, recv_handler);
struct NODEINFO_Q* nq_a = mknode(node_a, keys_a.public_key, 127,0,0,1, (uint16_t)a_port);
struct NODEINFO_Q* nq_c = mknode(node_c, keys_c.public_key, 127,0,0,1, (uint16_t)c_port);
if (!nq_a || !nq_c) { fprintf(stderr,"FAIL: mknode\n"); goto fail; }
g_b->etcp_connect_timeout_tb = 300000;
{ static int cc_a=0,cc_c=1;
etcp_connect(g_b,nq_a,connect_bgp_ready_cb,&cc_a,ETCP_CONNECT_BGP_READY);
etcp_connect(g_b,nq_c,connect_bgp_ready_cb,&cc_c,ETCP_CONNECT_BGP_READY); }
void* to_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS*10, NULL, timeout_cb, "to");
int max_loops = TEST_TIMEOUT_MS * 10 / POLL_TB;
while (!g_test_ok && g_loop_cnt < max_loops) {
uasync_poll(ua, POLL_TB);
state_step();
if (g_state==ST_PHASE1_SEND && g_phase_sent>=PHASE_PACKETS) g_state=ST_PHASE1_WAIT;
else if (g_state==ST_PHASE3_SEND && g_phase_sent>=PHASE_PACKETS) g_state=ST_PHASE3_WAIT;
g_loop_cnt++;
}
uasync_cancel_timeout(ua,to_id);
if(g_a){g_a->running=0;utun_instance_destroy(g_a);}
if(g_b){g_b->running=0;utun_instance_destroy(g_b);}
if(g_c){g_c->running=0;utun_instance_destroy(g_c);}
if(ua) uasync_destroy(ua,0);
cleanup();
if(g_test_ok==1){printf("=== TEST PASSED ===\n");return 0;}
printf("=== TEST FAILED: code=%d ===\n",g_fail_code);
return 1;
fail:
if(g_a){g_a->running=0;utun_instance_destroy(g_a);}
if(g_b){g_b->running=0;utun_instance_destroy(g_b);}
if(g_c){g_c->running=0;utun_instance_destroy(g_c);}
if(ua) uasync_destroy(ua,0);
cleanup();
return 1;
}
+7 -7
View File
@@ -64,7 +64,7 @@ static void inject(etcp_recv_fn recv_cb, struct UTUN_INSTANCE* inst,
const uint8_t* pl, size_t pl_len, int is_ack, uint8_t flags) {
struct SVC_ROUTE_HDR hdr;
memset(&hdr, 0, sizeof(hdr));
hdr.cmd = ETCP_ID_SVC_ROUTE;
hdr.cmd = ETCP_RT_ID_SVC_ROUTE;
hdr.dst_node_id = inst->node_id;
hdr.src_node_id = src;
hdr.seq = seq;
@@ -98,7 +98,7 @@ static void inject(etcp_recv_fn recv_cb, struct UTUN_INSTANCE* inst,
fake_conn.instance = &inst; \
TESTASSERT(etcp_router_init(&inst) == 0); \
TESTASSERT(etcp_router_bind(&inst, TEST_SVC_ID, test_handler) == 0); \
recv_cb = inst.api_bindings.callbacks[ETCP_ID_SVC_ROUTE]; \
recv_cb = inst.api_bindings.callbacks[ETCP_RT_ID_SVC_ROUTE]; \
TESTASSERT(recv_cb != NULL); \
} while(0)
@@ -179,7 +179,7 @@ static void inject_signed(etcp_recv_fn recv_cb, struct UTUN_INSTANCE* inst,
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)dgram;
memset(hdr, 0, SVC_ROUTE_HDR_SIZE);
hdr->cmd = ETCP_ID_SVC_ROUTE;
hdr->cmd = ETCP_RT_ID_SVC_ROUTE;
hdr->dst_node_id = inst->node_id;
hdr->src_node_id = src;
hdr->seq = seq;
@@ -221,11 +221,11 @@ static int test_init_destroy(void) {
if (etcp_router_init(&inst) != 0) FAIL("init");
if (inst.router_conns == NULL) FAIL("router_conns NULL after init");
if (inst.api_bindings.callbacks[ETCP_ID_SVC_ROUTE] == NULL) FAIL("recv_cb not bound");
if (inst.api_bindings.callbacks[ETCP_RT_ID_SVC_ROUTE] == NULL) FAIL("recv_cb not bound");
etcp_router_destroy(&inst);
if (inst.router_conns != NULL) FAIL("router_conns not NULL after destroy");
if (inst.api_bindings.callbacks[ETCP_ID_SVC_ROUTE] != NULL) FAIL("recv_cb still bound");
if (inst.api_bindings.callbacks[ETCP_RT_ID_SVC_ROUTE] != NULL) FAIL("recv_cb still bound");
uasync_destroy(ua, 0);
PASS();
@@ -752,7 +752,7 @@ static int test_server_reinit(void) {
memset(&inst.router_bindings, 0, sizeof(inst.router_bindings));
if (etcp_router_init(&inst) != 0) FAIL("reinit failed");
etcp_router_bind(&inst, TEST_SVC_ID, test_handler);
recv_cb = inst.api_bindings.callbacks[ETCP_ID_SVC_ROUTE];
recv_cb = inst.api_bindings.callbacks[ETCP_RT_ID_SVC_ROUTE];
if (!recv_cb) FAIL("recv_cb not bound");
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(&inst, TEST_REMOTE_NODE, TEST_SVC_ID);
@@ -853,7 +853,7 @@ static int test_sign_too_short(void) {
rx_reset(0xF5);
struct SVC_ROUTE_HDR hdr;
memset(&hdr, 0, sizeof(hdr));
hdr.cmd = ETCP_ID_SVC_ROUTE;
hdr.cmd = ETCP_RT_ID_SVC_ROUTE;
hdr.dst_node_id = inst.node_id;
hdr.src_node_id = SIGNER_NODE;
hdr.seq = 0;
+1 -1
View File
@@ -123,7 +123,7 @@ static void monitor(void* arg) {
if (cli_ok && exit_ok) {
g_test_phase = 1;
// Override client handler for test verification
etcp_router_bind(cli, ETCP_ID_ICMP_PROXY, cli_recv_cb);
etcp_router_bind(cli, ETCP_RT_ID_ICMP_PROXY, cli_recv_cb);
for (int i = 0; i < ICMP_PAYLOAD_SIZE; i++) send_buf[i] = (uint8_t)(rand() & 0xFF);
icmp_proxy_send_to_exit(cli, exit_node_id,
inet_addr("127.0.0.1"), inet_addr("127.0.0.100"), test_echo_id, test_echo_seq, send_buf, ICMP_PAYLOAD_SIZE);
+3 -3
View File
@@ -171,10 +171,10 @@ static int verify_ipv6_local_nodeinfo(const char* name, struct UTUN_INSTANCE* in
int mc = get_node_v6_sockets_meta(inst->bgp->local_node, &meta);
if (mc != expect_socks || !meta) { printf("FAIL [%s]: v6 sockets meta retrieval failed\n", name); return 0; }
for (int i = 0; i < mc; i++) {
if (meta[i].type != CFG_SERVER_TYPE_PUBLIC) {
printf("FAIL [%s]: v6 socket meta[%d] type=%d expected=%d\n", name, i, meta[i].type, CFG_SERVER_TYPE_PUBLIC); return 0;
if (meta[i].config_type != CFG_SERVER_TYPE_PUBLIC) {
printf("FAIL [%s]: v6 socket meta[%d] config_type=%d expected=%d\n", name, i, meta[i].config_type, CFG_SERVER_TYPE_PUBLIC); return 0;
}
printf("PASS [%s]: v6 socket meta[%d] id=%d type=%d\n", name, i, meta[i].id, meta[i].type);
printf("PASS [%s]: v6 socket meta[%d] id=%d config=%d nat=%d\n", name, i, meta[i].id, meta[i].config_type, meta[i].nat_type);
}
// Verify IPv6 addresses
+7 -7
View File
@@ -261,9 +261,9 @@ int main(void) {
// Verify C1 socket has config type before NAT detection
{
struct ETCP_SOCKET* s = inst_c1->etcp_sockets;
if (!s || s->nat_type != CFG_SERVER_TYPE_NAT) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: C1 socket nat_type=%d, expected CFG_SERVER_TYPE_NAT(%d) before detection",
s ? (int)s->nat_type : -1, CFG_SERVER_TYPE_NAT);
if (!s || s->type != CFG_SERVER_TYPE_NAT) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: C1 socket type=%d, expected CFG_SERVER_TYPE_NAT(%d) before detection",
s ? (int)s->type : -1, CFG_SERVER_TYPE_NAT);
goto cleanup;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "C1 socket has config NAT type before detection");
@@ -367,9 +367,9 @@ int main(void) {
int found = 0;
for (int i = 0; i < meta_count; i++) {
if (meta[i].id == sock_c1->sock_id) {
if (meta[i].type != NAT_VERIFIED_EIM) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: local_node socket meta not updated on C1: type=%u expected=%u",
meta[i].type, NAT_VERIFIED_EIM);
if (meta[i].nat_type != NAT_VERIFIED_EIM) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: local_node socket meta not updated on C1: nat_type=%u expected=%u",
meta[i].nat_type, NAT_VERIFIED_EIM);
goto cleanup;
}
found = 1;
@@ -413,7 +413,7 @@ int main(void) {
const struct NODEINFO_IPV4_SOCKET_META* meta = NULL;
int meta_count = get_node_v4_sockets_meta(node_c1_on_c2, &meta);
for (int i = 0; i < meta_count; i++) {
if (meta[i].type == NAT_VERIFIED_EIM) {
if (meta[i].nat_type == NAT_VERIFIED_EIM) {
c2_verified_nat = 1;
break;
}
+5 -5
View File
@@ -285,12 +285,12 @@ static int test_provider_egress(void) {
}
DEBUG_INFO(DEBUG_CATEGORY_NAT, "Client conn peer_node_id=0x%016llx", (unsigned long long)client_conn->peer_node_id);
// Build ETCP_ID_NAT packet via etcp_route_send (new format: svc_id + src_node_id + ip_data)
// Build ETCP_RT_ID_NAT packet via etcp_route_send (new format: svc_id + src_node_id + ip_data)
size_t ip_len;
uint8_t* raw_ip = build_udp_pkt(0x0A0000FE, 40000, 0x08080808, 53, &ip_len);
size_t total = NAT_SVC_HDR_SIZE + ip_len;
uint8_t* dgram = u_malloc(total);
dgram[0] = ETCP_ID_NAT;
dgram[0] = ETCP_RT_ID_NAT;
memcpy(dgram + 1, &inst_client->nat_tr.self_node_id, 8);
memcpy(dgram + 9, raw_ip, ip_len);
free(raw_ip);
@@ -306,7 +306,7 @@ static int test_provider_egress(void) {
queue_entry_free(entry);
return 0;
}
DEBUG_INFO(DEBUG_CATEGORY_NAT, "ETCP_ID_NAT sent from client to provider via etcp_router");
DEBUG_INFO(DEBUG_CATEGORY_NAT, "ETCP_RT_ID_NAT sent from client to provider via etcp_router");
// Poll to let provider process
int cycles = 0;
@@ -455,12 +455,12 @@ static int test_full_roundtrip(void) {
memset(&inst_provider->nat.table[p], 0, sizeof(struct eim_nat_entry));
inst_provider->nat.next_port = inst_provider->nat.port_start;
// === Step 1: Send ETCP_ID_NAT from client to provider via etcp_router (egress) ===
// === Step 1: Send ETCP_RT_ID_NAT from client to provider via etcp_router (egress) ===
size_t ip_len;
uint8_t* raw_ip = build_udp_pkt(0x0A0000CD, 44444, 0x08080808, 80, &ip_len);
size_t total = NAT_SVC_HDR_SIZE + ip_len;
uint8_t* dgram = u_malloc(total);
dgram[0] = ETCP_ID_NAT;
dgram[0] = ETCP_RT_ID_NAT;
memcpy(dgram + 1, &inst_client->nat_tr.self_node_id, 8);
memcpy(dgram + 9, raw_ip, ip_len);
free(raw_ip);
+278 -46
View File
@@ -1,5 +1,7 @@
// test_socks_http_proxy.c — SOCKS5 + HTTP CONNECT proxy integration test
// 2 workers (1 SOCKS + 1 HTTP), 25 reqs each, files up to 100KB
// test_socks_http_proxy.c — Comprehensive SOCKS5 + HTTP proxy integration test
// 4 workers: socks_ipv4, socks_domain, http_proxy, https_connect
// Files: 1KB, 100KB, 1MB, 10MB — deterministic content, byte-by-byte verification
// HTTP + HTTPS (self-signed cert) + POST/echo + HEAD + OPTIONS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -28,25 +30,28 @@
#include "../src/routing.h"
#include "../src/tun_if.h"
#define TIMEOUT_MS 120000
#define WORKERS 2
#define REQS_PER_WORKER 25
#define TIMEOUT_MS 300000
#define WORKERS 4
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" };
enum { W_SOCKS_IPV4, W_SOCKS_DOMAIN, W_HTTP_PROXY, W_HTTPS_CONNECT };
static const int file_sizes[] = { 1024, 100*1024, 1024*1024, 10*1024*1024 };
static const char* file_names[] = { "f_1k.bin", "f_100k.bin", "f_1m.bin", "f_10m.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_http_pid = 0, g_https_pid = 0;
static pid_t g_workers[WORKERS];
static int g_http_port = 0, g_cli_port = 0, g_srv_port = 0;
static pid_t g_workers_orig[WORKERS];
static int g_http_port = 0, g_https_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 char g_cert[512], g_key[512];
static void* g_mon_id = NULL;
static int g_file_seed = 0;
static int alloc_port(void) {
int s = socket(AF_INET, SOCK_STREAM, 0);
@@ -62,17 +67,49 @@ static int alloc_port(void) {
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); }
if (!f) { fprintf(stderr, "gen_file: fopen(%s) failed\n", path); return; }
uint8_t buf[4096];
for (int off = 0; off < size; off += (int)sizeof(buf)) {
int chunk = size - off; if (chunk > (int)sizeof(buf)) chunk = (int)sizeof(buf);
for (int i = 0; i < chunk; i++) buf[i] = (uint8_t)(((off + i) ^ seed) & 0xFF);
fwrite(buf, 1, (size_t)chunk, f);
}
fclose(f);
}
static int start_http_server(int port, const char* dir) {
char script[512]; snprintf(script, sizeof(script), "%s/_srv.py", dir);
FILE* sf = fopen(script, "w");
if (!sf) return -1;
fprintf(sf,
"import sys,os,ssl,threading\n"
"from http.server import HTTPServer,SimpleHTTPRequestHandler\n"
"class H(SimpleHTTPRequestHandler):\n"
" def do_POST(self):\n"
" if self.path=='/echo':\n"
" n=int(self.headers.get('Content-Length',0))\n"
" b=self.rfile.read(n)\n"
" self.send_response(200)\n"
" self.send_header('Content-Type','application/octet-stream')\n"
" self.send_header('Content-Length',str(len(b)))\n"
" self.end_headers();self.wfile.write(b)\n"
" else:self.send_response(405);self.end_headers()\n"
" def do_OPTIONS(self):\n"
" self.send_response(200)\n"
" self.send_header('Allow','GET,HEAD,POST,OPTIONS')\n"
" self.send_header('Content-Length','0')\n"
" self.end_headers()\n"
"port=int(sys.argv[1])\n"
"os.chdir(sys.argv[2])\n"
"HTTPServer(('127.0.0.1',port),H).serve_forever()\n"
);
fclose(sf);
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);
execlp("python3", "python3", script, port_str, dir, (char*)NULL);
_exit(1);
}
g_http_pid = pid;
@@ -80,6 +117,66 @@ static int start_http_server(int port, const char* dir) {
return 0;
}
static int start_https_server(int port, const char* dir, const char* cert, const char* key) {
char script[512]; snprintf(script, sizeof(script), "%s/_srv_https.py", dir);
FILE* sf = fopen(script, "w");
if (!sf) return -1;
fprintf(sf,
"import sys,os,ssl,threading,traceback\n"
"from http.server import HTTPServer,SimpleHTTPRequestHandler\n"
"class H(SimpleHTTPRequestHandler):\n"
" def do_GET(self):\n"
" try:\n super().do_GET()\n"
" except Exception as e:\n"
" sys.stderr.write(f'HTTPS_ERR GET {self.path}: {e}\\n{traceback.format_exc()}\\n')\n"
" def do_POST(self):\n"
" if self.path=='/echo':\n"
" n=int(self.headers.get('Content-Length',0))\n"
" b=self.rfile.read(n)\n"
" self.send_response(200)\n"
" self.send_header('Content-Type','application/octet-stream')\n"
" self.send_header('Content-Length',str(len(b)))\n"
" self.end_headers();self.wfile.write(b)\n"
" else:self.send_response(405);self.end_headers()\n"
" def do_OPTIONS(self):\n"
" self.send_response(200)\n"
" self.send_header('Allow','GET,HEAD,POST,OPTIONS')\n"
" self.send_header('Content-Length','0')\n"
" self.end_headers()\n"
"os.chdir(sys.argv[3])\n"
"ctx=ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)\n"
"ctx.load_cert_chain(sys.argv[1],sys.argv[2])\n"
"httpd=HTTPServer(('127.0.0.1',int(sys.argv[4])),H)\n"
"httpd.socket=ctx.wrap_socket(httpd.socket,server_side=True)\n"
"httpd.timeout=None\n"
"httpd.serve_forever()\n"
);
fclose(sf);
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", script, cert, key, dir, port_str, (char*)NULL);
_exit(1);
}
g_https_pid = pid;
usleep(300000);
return 0;
}
static int gen_self_signed_cert(const char* tmpdir, char* cert_out, size_t cert_sz, char* key_out, size_t key_sz) {
snprintf(cert_out, cert_sz, "%s/cert.pem", tmpdir);
snprintf(key_out, key_sz, "%s/key.pem", tmpdir);
char cmd[512];
snprintf(cmd, sizeof(cmd),
"openssl req -x509 -newkey rsa:2048 -keyout %s -out %s -days 1 -nodes -subj '/CN=127.0.0.1' 2>/dev/null",
key_out, cert_out);
int rc = system(cmd);
if (rc != 0) { fprintf(stderr, "[FAIL] openssl cert generation\n"); return -1; }
return 0;
}
static char* make_cfg_server(void) {
static char buf[1024];
snprintf(buf, sizeof(buf),
@@ -122,30 +219,105 @@ static int conn_ready(struct UTUN_INSTANCE* inst) {
return 0;
}
static int worker_main(const char* type, const char* proxy, const char* url_base, const char* tmpdir) {
static int worker_main(int wtype, const char* socks_proxy, const char* http_proxy,
const char* url_http, const char* url_https, const char* tmpdir) {
const char* type_names[] = { "socks_ipv4", "socks_domain", "http_proxy", "https_connect" };
const char* tname = type_names[wtype];
char prof_path[512]; snprintf(prof_path, sizeof(prof_path), "%s/.prof_%d", tmpdir, getpid());
FILE* prof = fopen(prof_path, "w");
if (prof) fprintf(prof, "%s\n", tname);
struct timespec t1, t2;
double times_ms[num_files];
for (int fi = 0; fi < num_files; fi++) {
int count = file_counts[fi];
char expected[512];
char expected[512], out[512], url[512], cmd[2048];
snprintf(expected, sizeof(expected), "%s/%s", tmpdir, file_names[fi]);
snprintf(out, sizeof(out), "%s/out_%d_%d_%s", tmpdir, getpid(), fi, 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);
const char* base = (wtype == W_HTTPS_CONNECT) ? url_https : url_http;
snprintf(url, sizeof(url), "%s/%s", base, file_names[fi]);
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);
switch (wtype) {
case W_SOCKS_IPV4:
snprintf(cmd, sizeof(cmd), "curl -s --connect-timeout 15 --max-time 60 --socks5 %s -o %s %s 2>/dev/null",
socks_proxy, out, url);
break;
case W_SOCKS_DOMAIN:
snprintf(cmd, sizeof(cmd), "curl -s --connect-timeout 15 --max-time 60 --socks5-hostname %s -o %s %s 2>/dev/null",
socks_proxy, out, url);
break;
case W_HTTP_PROXY:
snprintf(cmd, sizeof(cmd), "curl -s --connect-timeout 15 --max-time 60 -x http://%s -o %s %s 2>/dev/null",
http_proxy, out, url);
break;
case W_HTTPS_CONNECT:
snprintf(cmd, sizeof(cmd), "curl -s --connect-timeout 15 --max-time 60 --proxytunnel -x %s -k -o %s %s 2>/dev/null",
http_proxy, out, url);
break;
}
clock_gettime(CLOCK_MONOTONIC, &t1);
int rc = system(cmd);
clock_gettime(CLOCK_MONOTONIC, &t2);
if (rc != 0) { fprintf(stderr, "[FAIL] %s %s curl exit=%d\n", tname, file_names[fi], WEXITSTATUS(rc)); if (prof) fclose(prof); 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] %s %s byte mismatch\n", tname, file_names[fi]);
char sizecmd[1024]; snprintf(sizecmd, sizeof(sizecmd), "wc -c <%s", out);
FILE* pf = popen(sizecmd, "r"); if (pf) { int sz = 0; fscanf(pf, "%d", &sz); pclose(pf);
fprintf(stderr, " downloaded: %d bytes, expected: %d bytes\n", sz, file_sizes[fi]); }
unlink(out); if (prof) fclose(prof); return 1;
}
unlink(out);
double elapsed_ms = (t2.tv_sec - t1.tv_sec) * 1000.0 + (t2.tv_nsec - t1.tv_nsec) / 1e6;
double mbps = (file_sizes[fi] / (1024.0 * 1024.0)) / (elapsed_ms / 1000.0);
times_ms[fi] = elapsed_ms;
if (prof) fprintf(prof, "%s %d %.0f %.1f\n", file_names[fi], file_sizes[fi], elapsed_ms, mbps);
}
{
double tot_ms = 0; int tot_bytes = 0;
for (int fi = 0; fi < num_files; fi++) { tot_ms += times_ms[fi]; tot_bytes += file_sizes[fi]; }
double avg_mbps = (tot_bytes / (1024.0 * 1024.0)) / (tot_ms / 1000.0);
if (prof) { fprintf(prof, "TOTAL %d %.0f %.1f\n", tot_bytes, tot_ms, avg_mbps); fclose(prof); prof = NULL; }
}
if (wtype == W_HTTP_PROXY) {
char cmd[2048], out[512], f1[512];
int seed = (int)getpid() ^ 0x55;
snprintf(f1, sizeof(f1), "%s/post_%d.bin", tmpdir, getpid());
gen_file(f1, 256, seed);
snprintf(out, sizeof(out), "%s/post_out_%d.bin", tmpdir, getpid());
snprintf(cmd, sizeof(cmd),
"curl -s --connect-timeout 10 --max-time 30 -x http://%s --data-binary @%s -o %s %s/echo 2>/dev/null",
http_proxy, f1, out, url_http);
if (system(cmd) != 0) { fprintf(stderr, "[FAIL] %s POST/echo curl\n", tname); unlink(f1); return 1; }
snprintf(cmd, sizeof(cmd), "cmp -s %s %s", out, f1);
if (system(cmd) != 0) { fprintf(stderr, "[FAIL] %s POST/echo cmp\n", tname); unlink(f1); unlink(out); return 1; }
unlink(f1); unlink(out);
snprintf(cmd, sizeof(cmd),
"curl -s -I --connect-timeout 15 --max-time 30 -x http://%s %s/f_1k.bin 2>/dev/null | head -1 | grep -q '200 OK'",
http_proxy, url_http);
if (system(cmd) != 0) { fprintf(stderr, "[FAIL] %s HEAD 200\n", tname); return 1; }
snprintf(out, sizeof(out), "%s/opt_%d.txt", tmpdir, getpid());
snprintf(cmd, sizeof(cmd),
"curl -s -i --connect-timeout 15 --max-time 30 -x http://%s -X OPTIONS -o %s %s/f_1k.bin 2>/dev/null",
http_proxy, out, url_http);
if (system(cmd) != 0) { fprintf(stderr, "[FAIL] %s OPTIONS curl\n", tname); return 1; }
snprintf(cmd, sizeof(cmd), "grep -q 'Allow:' %s", out);
if (system(cmd) != 0) { fprintf(stderr, "[FAIL] %s OPTIONS Allow\n", tname); unlink(out); return 1; }
unlink(out);
}
return 0;
}
@@ -157,18 +329,39 @@ static void monitor(void* arg) {
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);
char url_http[128]; snprintf(url_http, sizeof(url_http), "http://127.0.0.1:%d", g_http_port);
char url_https[128]; snprintf(url_https, sizeof(url_https), "https://127.0.0.1:%d", g_https_port);
char socks[64]; snprintf(socks, sizeof(socks), "127.0.0.1:%d", g_socks_port);
char hproxy[64]; snprintf(hproxy, sizeof(hproxy), "127.0.0.1:%d", g_http_proxy_port);
// Для socks_domain worker'а: используем localhost вместо 127.0.0.1 чтобы тестировать atyp=3 (DNS через SOCKS)
char url_http_local[128];
snprintf(url_http_local, sizeof(url_http_local), "http://localhost:%d", g_http_port);
struct {
int type;
const char* socks;
const char* hproxy;
const char* url_h;
const char* url_s;
} wcfg[WORKERS] = {
{ W_SOCKS_IPV4, socks, NULL, url_http, NULL },
{ W_SOCKS_DOMAIN,socks, NULL, url_http_local, NULL },
{ W_HTTP_PROXY, NULL, hproxy, url_http, NULL },
{ W_HTTPS_CONNECT,NULL, hproxy, NULL, url_https },
};
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);
int rc = worker_main(wcfg[i].type, wcfg[i].socks, wcfg[i].hproxy,
wcfg[i].url_h, wcfg[i].url_s, g_tmpdir);
_exit(rc);
}
g_workers[i] = pid;
g_workers_orig[i] = pid;
}
}
}
@@ -185,45 +378,78 @@ static void monitor(void* arg) {
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; }
printf("\n=== PROFILING ===\n");
printf("%-14s | %-9s %-9s %-9s %-9s | %-11s\n",
"Worker", "1KB", "100KB", "1MB", "10MB", "Avg MB/s");
printf("%s\n", "-----------------------------------------------------------------");
for (int i = 0; i < WORKERS; i++) {
if (g_workers[i] > 0) continue;
char pp[512]; snprintf(pp, sizeof(pp), "%s/.prof_%d", g_tmpdir, (int)((long)g_workers_orig[i]));
FILE* prf = fopen(pp, "r");
if (!prf) continue;
char tname[64] = "?"; { char buf[128]; if (fgets(buf, sizeof(buf), prf)) sscanf(buf, "%63s", tname); }
double speeds[4] = {0}; char fn[64];
for (int fi = 0; fi < num_files; fi++) {
double mbps; char buf[128];
if (fgets(buf, sizeof(buf), prf)) {
int sz2; double em2; int n = sscanf(buf, "%63s %d %lf %lf", fn, &sz2, &em2, &mbps);
if (n >= 4) speeds[fi] = mbps;
}
}
double total_mbps = 0; { char buf[128]; while (fgets(buf, sizeof(buf), prf)) {
int tb; double tm, tm2; if (sscanf(buf, "TOTAL %d %lf %lf", &tb, &tm, &tm2) == 3) { total_mbps = tm2; break; }
}}
fclose(prf);
printf("%-14s | %5.1f MB/s %5.1f MB/s %5.1f MB/s %5.1f MB/s | %5.1f MB/s\n",
tname, speeds[0], speeds[1], speeds[2], speeds[3], total_mbps);
}
printf("\n"); fflush(stdout);
}
}
if (!g_done) g_mon_id = uasync_set_timeout(g_ua, 500, NULL, monitor, "mon");
if (!g_done) g_mon_id = uasync_set_timeout(g_ua, 1000, NULL, monitor, "mon");
}
static void test_timeout(void* arg) {
(void)arg;
if (!g_done) { printf("[FAIL] timeout\n"); g_done = -1; }
if (!g_done) { printf("[FAIL] timeout (%ds)\n", TIMEOUT_MS/1000); 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_http_port = alloc_port(); g_https_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) {
if (g_http_port < 0 || g_https_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);
printf(" ports: http=%d https=%d socks=%d http_proxy=%d\n", g_http_port, g_https_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();
if (gen_self_signed_cert(g_tmpdir, g_cert, sizeof(g_cert), g_key, sizeof(g_key)) < 0) goto cleanup;
g_file_seed = (int)time(NULL) ^ (int)getpid();
int total_mb = 0;
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);
gen_file(path, file_sizes[fi], g_file_seed + fi);
total_mb += file_sizes[fi];
}
printf(" test files: 1k, 10k, 50k, 100k\n"); fflush(stdout);
printf(" test files: 1KB, 100KB, 1MB, 10MB (total %.1fMB)\n", total_mb / (1024.0*1024.0)); fflush(stdout);
if (start_http_server(g_http_port, g_tmpdir) < 0) { printf("[FAIL] start http server\n"); goto cleanup; }
if (start_https_server(g_https_port, g_tmpdir, g_cert, g_key) < 0) { printf("[FAIL] start https server\n"); goto cleanup; }
printf(" HTTP/HTTPS servers started\n");
g_ua = uasync_create();
if (!g_ua) { printf("[FAIL] uasync_create\n"); goto cleanup; }
@@ -242,13 +468,19 @@ int main(void) {
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");
if (g_ok) {
int total_files = WORKERS * num_files;
printf("[PASS] test_socks_http_proxy — %d workers × %d files (%.1fMB each) = %d downloads verified byte-by-byte\n",
WORKERS, num_files, total_mb / (1024.0*1024.0), total_files);
} 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_https_pid > 0) { kill(g_https_pid, SIGKILL); waitpid(g_https_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);
+1 -1
View File
@@ -127,7 +127,7 @@ static void monitor(void* arg) {
if (cli_ok && exit_ok) {
g_test_phase = 1;
// Bind client handler for UDP replies (overrides what tcp_proxy_client_create set, for test verification)
etcp_router_bind(cli, ETCP_ID_UDP_PROXY, cli_recv_cb);
etcp_router_bind(cli, ETCP_RT_ID_UDP_PROXY, cli_recv_cb);
// Send UDP_REQUEST
for (int i = 0; i < PAYLOAD_SIZE; i++) send_buf[i] = (uint8_t)(rand() & 0xFF);
udp_proxy_send_to_exit(cli, exit_node_id,
+1 -1
View File
@@ -6,4 +6,4 @@ del /Q *.exe 2>nul
del /Q build.log 2>nul
echo Clean complete!
pause
build.bat
+25 -12
View File
@@ -231,20 +231,20 @@ static void CreateControls(struct etcpmon_app* app) {
app->hListSockets = CreateWindowExA(WS_EX_CLIENTEDGE, "LISTBOX", "",
WS_CHILD | WS_VISIBLE | LBS_NOTIFY | WS_VSCROLL | LBS_NOINTEGRALHEIGHT,
x + 320, y + 20, 450, 120, hWnd, (HMENU)IDC_LIST_SOCKETS, hInst, NULL);
x + 320, y + 20, 550, 120, hWnd, (HMENU)IDC_LIST_SOCKETS, hInst, NULL);
/* Action input */
CreateWindowExA(0, "STATIC", "Action:",
WS_CHILD | WS_VISIBLE,
x + 780, y, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
x + 880, y, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
app->hEditAction = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "",
WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL,
x + 780, y + 20, 140, 22, hWnd, (HMENU)IDC_EDIT_ACTION, hInst, NULL);
x + 880, y + 20, 110, 22, hWnd, (HMENU)IDC_EDIT_ACTION, hInst, NULL);
app->hBtnAction = CreateWindowExA(0, "BUTTON", "Send",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
x + 780, y + 44, 60, 24, hWnd, (HMENU)IDC_BTN_ACTION, hInst, NULL);
x + 880, y + 44, 60, 24, hWnd, (HMENU)IDC_BTN_ACTION, hInst, NULL);
/* Graph area */
y += 140;
@@ -1530,10 +1530,22 @@ void etcpmon_gui_update_conn_list(struct etcpmon_app* app,
}
}
static const char* config_type_to_string(uint8_t config_type) {
switch (config_type) {
case CFG_SERVER_TYPE_PUBLIC: return "PUBLIC";
case CFG_SERVER_TYPE_NAT: return "NAT";
case CFG_SERVER_TYPE_PRIVATE: return "Private";
case CFG_SERVER_TYPE_LOCAL: return "Local";
case CFG_SERVER_TYPE_UNKNOWN: return "Unknown";
default: return "?";
}
}
static const char* nat_type_to_string(uint8_t nat_type) {
switch (nat_type) {
case NAT_TYPE_EIM: return "EIM";
case NAT_TYPE_STRICT: return "Strict";
case NAT_TYPE_DIRECT: return "Direct";
case NAT_VERIFIED_EIM: return "Verified-EIM";
case NAT_VERIFIED_STRICT: return "Verified-Strict";
case NAT_VERIFIED_DIRECT: return "Verified-Direct";
@@ -1554,22 +1566,23 @@ void etcpmon_gui_update_socket_list(struct etcpmon_app* app,
if (list[i].name[0] == '\0') continue;
char display[256];
const char* cfg_str = config_type_to_string(list[i].config_type);
const char* nat_str = nat_type_to_string(list[i].nat_type);
if (list[i].nat_ip[0] != '\0') {
if (list[i].is_ipv6) {
snprintf(display, sizeof(display), "[%s]:%d | NAT: %s (%s:%d)",
list[i].ip, list[i].port, nat_str, list[i].nat_ip, list[i].nat_port);
snprintf(display, sizeof(display), "[%s]:%d | Type: %s | NAT: %s (%s:%d)",
list[i].ip, list[i].port, cfg_str, nat_str, list[i].nat_ip, list[i].nat_port);
} else {
snprintf(display, sizeof(display), "%s:%d | NAT: %s (%s:%d)",
list[i].ip, list[i].port, nat_str, list[i].nat_ip, list[i].nat_port);
snprintf(display, sizeof(display), "%s:%d | Type: %s | NAT: %s (%s:%d)",
list[i].ip, list[i].port, cfg_str, nat_str, list[i].nat_ip, list[i].nat_port);
}
} else {
if (list[i].is_ipv6) {
snprintf(display, sizeof(display), "[%s]:%d | NAT: %s",
list[i].ip, list[i].port, nat_str);
snprintf(display, sizeof(display), "[%s]:%d | Type: %s | NAT: %s",
list[i].ip, list[i].port, cfg_str, nat_str);
} else {
snprintf(display, sizeof(display), "%s:%d | NAT: %s",
list[i].ip, list[i].port, nat_str);
snprintf(display, sizeof(display), "%s:%d | Type: %s | NAT: %s",
list[i].ip, list[i].port, cfg_str, nat_str);
}
}
SendMessageA(app->hListSockets, LB_ADDSTRING, 0, (LPARAM)display);
+10 -1
View File
@@ -27,10 +27,18 @@ extern "C" {
#define NAT_TYPE_UNKNOWN 0
#define NAT_TYPE_EIM 1 /* Endpoint-Independent Mapping */
#define NAT_TYPE_STRICT 2 /* Address/Restricted or Symmetric */
#define NAT_TYPE_DIRECT 3 /* Real public IP */
#define NAT_VERIFIED_UNKNOWN 4
#define NAT_VERIFIED_EIM 5 /* EIM NAT */
#define NAT_VERIFIED_STRICT 6 /* strict NAT */
#define NAT_VERIFIED_DIRECT 7
/* Server config types (mirrored from config_parser.h) */
#define CFG_SERVER_TYPE_UNKNOWN 0
#define CFG_SERVER_TYPE_PUBLIC 1
#define CFG_SERVER_TYPE_NAT 2
#define CFG_SERVER_TYPE_PRIVATE 3
#define CFG_SERVER_TYPE_LOCAL 4
#define ETCPMON_MAX_MSG_SIZE 32768
#define ETCPMON_MAX_CONN_NAME 32
#define ETCPMON_MAX_CONNECTIONS 250
@@ -126,7 +134,8 @@ struct etcpmon_socket_info {
char ip[46]; /* Null-terminated IP address string */
uint16_t port; /* Port number (host byte order) */
uint8_t is_ipv6; /* 1 = IPv6, 0 = IPv4 */
uint8_t nat_type; /* NAT type (NAT_TYPE_*, NAT_VERIFIED_*) */
uint8_t config_type; /* CFG_SERVER_TYPE_PUBLIC/NAT/PRIVATE */
uint8_t nat_type; /* NAT_TYPE_* / NAT_VERIFIED_* */
char nat_ip[46]; /* Null-terminated NAT IP address string */
uint16_t nat_port; /* NAT port (host byte order) */
};