Author SHA1 Message Date
Evgeny 487cb8718e fix: preserve newlines in config_updater insert/update — no extra newlines on key replacement, add leading nl when inserting at end without trailing nl 2026-07-02 17:37:17 +03:00
Evgeny 42d96f118d fix: swap queue_entry_free/queue_dgram_free order in 27 places — prevents UAF when dgram_free accesses freed entry
uasync: replace raw socket_node* handle with packed index — fixes UAF after socket_array realloc
tcp_io: defer u_free in tcp_conn_destroy via uasync_call_soon — prevents callback-chain UAF
tcp_io: guard read_cb/write_cb with NULL queue checks after deferred destroy
tcp_io: save read_queue to local before queue_data_put + NULL guard after
route_connectivity: linked-list probe_ctx — cancel all parallel probes before nq free
2026-07-02 17:14:38 +03:00
Evgeny 8962f58ea4 uasync: replace raw socket_node pointer with index-based handle — fix UAF after realloc 2026-07-02 17:08:24 +03:00
Evgeny 1f541d6432 tcp_io: save read_queue to local var before queue_data_put, add NULL guard after 2026-07-02 16:38:31 +03:00
Evgeny e66156eb49 tcp_io: deferred free in tcp_conn_destroy to prevent UAF in callback chain 2026-07-02 16:26:43 +03:00
Evgeny 2cd38988e6 AGENTS: add ASAN build instructions 2026-07-02 15:49:10 +03:00
Evgeny d58b532741 gitignore: add utun.pid 2026-07-02 15:48:05 +03:00
Evgeny 4716e54069 build: add --asan flag to build.sh, tcp_io: null sock/queues in destroy + guard read_cb/write_cb 2026-07-02 15:47:54 +03:00
Evgeny aa73e9d8cb route_connectivity: fix UAF in conn_probe_finish — break ctx->nq on cancel_node, always free ctx in finish 2026-07-02 15:02:11 +03:00
Evgeny 993c265125 control_server: add idle timeout (30s) on client socket + large output queue warning 2026-07-02 14:17:42 +03:00
Evgeny 5ca0bbf9e7 tcp_io: place write_monitor=0 before queue_resume_callback to prevent busy-loop when waiter_defer=1 2026-07-02 13:50:43 +03:00
Evgeny 0ef7ee597c tcp_io: fix write_cb busy-loop with deferred callback — turn off write_monitor after flush when waiter_defer=1 2026-07-02 13:43:19 +03:00
Evgeny 7d460ca156 fix: ASAN-detected UAFs and buffer overflows across 6 modules
- etcp_connections: fix link leak & UAF in etcp_socket_remove — remove_link inside etcp_link_close shifts array, skip NULL set and i++
- stcp_server: fix UAF in stcp_conn_process_recv — defer free via uasync_call_soon after stcp_conn_do_close
- etcp: save pkt_len before queue_data_put to output_queue (callback may free entry synchronously)
- socks_proxy: add pool bounds check before memcpy, UINT16_MAX truncation guard, freed flag
- tcp_proxy_server: reorder cleanup (cancel waiters before tcp_conn_destroy), UINT16_MAX guard
- test_route6_lib: increase nodes[] array to STRESS_NODES + STRESS_OPS
2026-07-02 13:35:22 +03:00
Evgeny b738b2ff1b etcp_router: add u_check guards at send_q/recv_q entry points to detect memory corruption 2026-07-02 12:57:21 +03:00
Evgeny cc5289e37c etcp_router: don't reset start_sent in reactive restart — prevents START-flag cascade on multi-hop restart detection 2026-07-02 12:28:31 +03:00
Evgeny 0c5f25c96d etcp_router: fix restart cascade — sync peer_sess_id on auto-create, remove sess_id increment on reactive restart 2026-07-02 12:23:12 +03:00
Evgeny b53758bdbe etcp_router: cancel timers in conn_restart, reset last_ack_sent_tb — fix use-after-free SIGSEGV 2026-07-02 11:34:57 +03:00
Evgeny f5b1c3e789 etcp_router: fix OOB drops after restart — add peer_sync_done to sync rx_seq with first incoming seq 2026-07-02 11:12:14 +03:00
Evgeny 0edd21e335 etcpmon: lift Router to y=1181 (tight to Queues), shrink window to 1400 2026-07-02 10:33:41 +03:00
Evgeny e0c38676e7 etcpmon: compress Queues h 300->280, lift Router y 1260->1240 2026-07-02 10:31:44 +03:00
Evgeny fefd2bcc52 etcpmon: fix layout — restore Debug Levels y=485, lift Links to y=710, compress Queues h=300 y=900 2026-07-02 10:29:58 +03:00
Evgeny 8427e18c98 control_server: fix infinite loop in router_conns aggregation — use read-only head->next traversal instead of get/put 2026-07-02 10:23:06 +03:00
Evgeny 7329fa65f1 etcp_router: add congestion counters, fix tx_seq rollback on etcp_send failure, add router metrics to etcpmon GUI
- etcp_router.h: add 9 per-conn counters (pkts_sent/err/rcvd, ack_sent/recv/stale, dup/oob/sign_fail)
- etcp_router.c: increment counters at 13 event points; fix tx_seq rollback on etcp_send failure (was causing phantom inflight=256 deadlock); add DEBUG_ERROR on all 5 ignored etcp_send returns; fix router_ack_do_send to update timestamps only on success
- etcpmon_protocol.h: add etcpmon_router_metrics (14 aggregated fields)
- control_server.c: iterate router_conns to fill aggregated router metrics
- etcpmon_gui: new 'Router Congestion' block with queue depths and all counters, Debug Levels shifted down, window 1300->1440
- test_etcp_router_unit: fix test_ack_timer for correct no-BGP-route behavior
2026-07-02 09:49:10 +03:00
Evgeny de8cce521a all 51 tests pass: fd=0 protection + clean socks_http_proxy test
- socket_compat: never close fd=0
- socks_http_proxy: rewritten cleanly, 2 workers, 25 reqs each, up to 100KB
- uasync_socket_race: dup2-based fd=0 reservation
- u_async: removed DIAG spam
- tcp_io: removed debug fprintf

Result: 51/51 tests pass.
2026-07-02 00:58:47 +03:00
Evgeny f06ebd2ab4 fix: protect fd 0 from close in socket_close_wrapper + dup2 reserve in tests
- socket_close_wrapper: never close sock==0 (prevents fd 0 reuse cycle)
- test_uasync_socket_race: dup2-based fd 0 reservation, pass 800/800
- tcp_io: SOCKET_INVALID before on_error (previous commit)
- Plus earlier fixes: uasync gen counter, ll_queue call_soon_id, pkt_normalizer

Result: test_uasync_socket_race PASSES, 50/51 tests pass
2026-07-02 00:50:36 +03:00
Evgeny a97a38d867 fix: set tc->sock=SOCKET_INVALID before on_error in handle_error
В одном epoll_wait batch три события для fd: EPOLLERR (освобождает tc),
EPOLLIN (read_cb), EPOLLOUT (write_cb). read_cb/write_cb guard проверяет
tc->sock==SOCKET_INVALID, но sock обнулялся ПОСЛЕ on_error.
Перенос sock=SOCKET_INVALID до on_error защищает от use-after-free.

Доказано: без фикса segfault на tcp_io.c:374 (write_cb → tc->connected).
С фиксом тест работает без крашей.
2026-07-02 00:43:37 +03:00
Evgeny aff3b2d00f fix: always remove socket from epoll in tcp_conn_handle_error
tcp_conn_handle_error гвардил uasync_remove_socket_t по tc->socket_id,
пропуская повторный вызов. В одном epoll_wait batch два события
(EPOLLERR + EPOLLOUT) на один fd: первое делает remove (socket_id=NULL),
второе проходит guard и dispatch'ит write_cb на освобождённом tc.

Фикс: всегда вызывать uasync_remove_socket_t + epoll_ctl(DEL),
независимо от socket_id.
2026-07-02 00:16:05 +03:00
Evgeny ccdbab95a8 fix: use-after-free in pkt_normalizer + ll_queue waiter cancel + uasync gen counter for epoll fd-reuse
- pkt_normalizer: save recvpart->len before queue_data_put (may free synchronously)
- ll_queue: save uasync_call_soon handle in queue_waiter_handle, cancel on free
- uasync: generation counter in epoll data.u64 to skip stale events after fd reuse
- uasync: keep fd_to_index mapping after socket_array_remove for inactive nodes
- test: test_uasync_socket_race — 4 children × 200 iterations, currently flaky
2026-07-01 23:51:31 +03:00
Evgeny a01e2f0899 tun_enabled config + socks/http stress test (50 reqs, 2 workers)
- config_parser: tun_enabled в [global] (по умолчанию yes), не дефолтить
  tun_ifname если TUN не нужен
- utun_instance: уважать tun_enabled, чистые дефолты tcp_proxy TUN
- socks_proxy: фикс дэдлока — reply сразу после CONNECT, без ожидания DATA
- utun.conf.sample: документирован tun_enabled, tun_ifname
- test_socks_http_proxy: 50 запросов (SOCKS+HTTP), 2 воркера параллельно,
  файлы 1k..100k, сверка через cmp, python http.server + curl
2026-07-01 22:14:37 +03:00
Evgeny dfdec06a17 build.sh: add missing ./configure step after autoreconf 2026-07-01 21:19:21 +03:00
Evgeny 393a641e18 socks/http proxy client: SOCKS5 + HTTP CONNECT через ETCP
Добавлен модуль socks_proxy.c/h — SOCKS5 и HTTP CONNECT proxy на клиенте.
Использует tcp_io для локальных TCP соединений, туннелит трафик через
существующий ETCP-протокол (ETCP_ID_TCP_PROXY/ETCP_ID_TCP_PROXY_CLIENT).

Конфиг [tcp_proxy_client]:
- socks_enabled/socks_addr — включить SOCKS5 сервер на указанном IP:PORT
- http_proxy_enabled/http_proxy_addr — включить HTTP CONNECT сервер
- Можно включать TUN, SOCKS и HTTP прокси одновременно или раздельно

Весь трефик идёт через exit node (tcp_proxy_server), менять exit не пришлось.
2026-07-01 20:33:11 +03:00
Evgeny 53d41d5c62 fix: use-after-free in route table — add route_delete before u_free in route_bgp_update_my_nodeinfo and route_bgp_process_nodeinfo, relax routes_overlap to allow hierarchical routes
Root cause: when bgp->local_node was reallocated (changed=1), old route
entries remained with stale v_node_info pointing to freed memory. The freed
memory got reused by a BGP peer's NODEINFO_Q, causing route_lookup to return
wrong node_id (b3b2b1b0afaeadac instead of local), which then failed at
etcp_route_send with 'no BGP route' and dropped packets to 10.23.2.1.

Changes:
- route_node.c: route_delete(old) before u_free + route_insert(new) after alloc
- utun_instance.c: remove duplicate route_insert (now atomic inside function)
- route_bgp.c: route_delete before freeing old peer NODEINFO_Q
- route_lib.c: routes_overlap now rejects only exact prefix+network duplicates
- tests/Makefile.am: add route_lib.o to test_route6_lib
2026-07-01 18:53:23 +03:00
Evgeny c60ba6eaec tun: tun_init_nat always uses TUN_MTU_DEFAULT (1500), ignore ETCP mtu from config 2026-07-01 17:57:36 +03:00
Evgeny c5123e4baf bgp: prevent sending NODEINFO to self + own NODEINFO early return
- route_bgp_process_nodeinfo: skip own node_id early, don't add to bgp->nodes
- broadcast loop: skip sending NODEINFO to connection where peer == node_id
- route_bgp_should_send_to: don't send node's NODEINFO to itself
- tests: lower debug level to ERROR, fix keepalive adaptive config
2026-07-01 17:49:27 +03:00
Evgeny 928c2fe057 tun: always use TUN_MTU_DEFAULT (1500) for TUN interface, ignore ETCP mtu from config 2026-07-01 17:35:53 +03:00
Evgeny b5b6875bc5 bgp: skip own NODEINFO in route_bgp_process_nodeinfo
When own NODEINFO is reflected back from a peer, do not add it
to bgp->nodes. Only update bgp->local_node which is sent separately.
This prevents route duplication and the 'local node in learned list'
inconsistency.
2026-07-01 14:48:38 +03:00
Evgeny 7c953c1929 bgp: add test_bgp_triangle + keepalive_adaptive config option
- keepalive_adaptive=0 disables adaptive keepalive period growth,
  keeping it fixed at 200ms for faster timeout detection in tests
- test_bgp_triangle: 5-node triangle topology test (6 phases):
  validates alternative path recording (fix#1), cascading node
  removal on full isolation, and full recovery
- config generation via utun_instance_create_from_str() with
  pre-generated keys — no temp files needed
2026-07-01 14:22:59 +03:00
Evgeny 1c09ad2e5a bgp: fix node removal when alternative paths exist
- route_bgp_process_nodeinfo: record alternative paths even when NODEINFO
  version hasn't changed (fixes nodes disappearing on link down despite
  having other active links)
- route_bgp_process_withdraw: only remove node when all paths gone (ret==1)
- route_bgp_remove_conn: broadcast withdraw for each removed node with
  correct wd_source
2026-07-01 13:37:35 +03:00
jjj 3284dffaaf 1 2026-07-01 13:02:32 +03:00
Evgeny c893285d71 action popup: use SW_RESTORE + SetForegroundWindow to bring hidden window to front 2026-07-01 12:46:19 +03:00
Evgeny 75028f278a action popup: re-show window on every action result if it was hidden 2026-07-01 12:42:30 +03:00
Evgeny 7fc1b5419f action popup: use ClearType Consolas 10pt font instead of ANSI_FIXED_FONT 2026-07-01 12:27:32 +03:00
Evgeny dee9889423 action popup: convert \n -> \r\n for Windows EDIT control line breaks 2026-07-01 12:24:48 +03:00
Evgeny 35871891db action nodes: send BGP node dump as text response to GUI, display in popup
- route_node_format_all(): format all BGP nodes to snprintf buffer
- control_server: 'nodes' action sends ETCPMON_RSP_ACTION_RESULT (0x89) with text
- etcpmon_protocol.h: new response type + struct, increase MAX_MSG_SIZE to 32K
- etcpmon_client: on_action_result callback, send_action via send_request with seq_id
- etcpmon_gui: ETCPMonActionResult popup window class with EDIT multiline,
  on_action_result creates popup (same size as main) or appends text + scrolls
2026-07-01 12:19:50 +03:00
Evgeny 373e4b33c7 route_node_dump_all: dump all BGP nodes to log via debug INFO
Add route_node_dump_all() in route_node.c that walks bgp->nodes list
and outputs per-node info: node_id, name, version, pubkeys, IPv4/IPv6
addrs/sockets/subnets, paths with hop list, connectivity state, conn_mgr.
Wire 'nodes' action in control_server to call this function.
2026-06-30 23:33:45 +03:00
Evgeny 090e2f28e4 1 2026-06-30 19:18:03 +03:00
Evgeny a2c7f1e6cb fix etcp: infinite loop in feed_dgram_to_asm due to uint16_t overflow in new_cap doubling 2026-06-30 16:21:43 +03:00
Evgeny 21da640ebe route_bgp: find_conn_for_node prefers live links_up path, fallback to best any 2026-06-30 14:59:26 +03:00
57 changed files with 3337 additions and 885 deletions

No files matched your search

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