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

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

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

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

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

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

No files matched your search

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